Skip to content

Commit

Permalink
Correct tests and add them to CI
Browse files Browse the repository at this point in the history
  • Loading branch information
slytomcat committed May 1, 2024
1 parent 4f0f7b0 commit 1011f59
Show file tree
Hide file tree
Showing 10 changed files with 206 additions and 116 deletions.
30 changes: 29 additions & 1 deletion .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,36 @@ jobs:
name: build_artifacts
path: |
ws
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: './go.mod'
- name: Test ws
run: go test -v -coverprofile cover.out .
- name: Format ws coverage
run: go tool cover -html=cover.out -o coverage.html
- name: Test server
run: go test -v -coverprofile cover_server.out ./server
- name: Format ws coverage
run: go tool cover -html=cover_server.out -o coverage_server.html
- name: Test ws
run: go test -v -coverprofile cover_echo-server.out ./echo-server
- name: Format ws coverage
run: go tool cover -html=cover_echo-server.out -o coverage_echo-server.html
- name: Upload coverage to Artifacts
uses: actions/upload-artifact@v4
with:
name: coverage_artifacts
path: |
coverage.html
coverage_server.html
coverage_echo-server.html
push:
needs: build
needs: [build, test]
if: github.ref == 'refs/heads/master'
runs-on: ubuntu-latest
steps:
Expand Down
6 changes: 0 additions & 6 deletions build.sh
Original file line number Diff line number Diff line change
@@ -1,9 +1,3 @@
#!/bin/bash
CGO_ENABLED=0 go build -buildvcs=false -trimpath -ldflags="-s -w -X main.version=$(git branch --show-current)-$(git rev-parse --short HEAD)" .
upx -qqq --best ws

cd echo-server/
CGO_ENABLED=0 go build -buildvcs=false -trimpath -ldflags="-s -w -X main.version=$(git branch --show-current)-$(git rev-parse --short HEAD)" .
upx -qqq --best echo-server


4 changes: 2 additions & 2 deletions connection.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ func connect(url string, rl *readline.Instance) []error {
return nil
})
ws.SetPongHandler(func(appData string) error {
fmt.Fprint(rl.Stdout(), ctSprintf("%s < pong\n", getPrefix()))
fmt.Fprint(rl.Stdout(), ctSprintf("%s < pong: %s\n", getPrefix(), appData))
return nil
})
}
Expand Down Expand Up @@ -149,7 +149,7 @@ func (s *Session) pingHandler() {
return
}
if options.pingPong {
fmt.Fprint(s.rl.Stdout(), ctSprintf("%s > ping\n", getPrefix()))
fmt.Fprint(s.rl.Stdout(), ctSprintf("%s > ping: \n", getPrefix()))
}
}
}
Expand Down
61 changes: 34 additions & 27 deletions connection_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,18 @@ func TestSession(t *testing.T) {
errLock: sync.Mutex{},
}
sent := "test message"
typed := "typed"
binary := "binary"
unknown := "unknown"
toBeFiltered := "must be filtered"
// test sendMsg
options.timestamp = true
defer func() { options.timestamp = false }()
err = s.sendMsg(sent)
require.NoError(t, err)
require.Eventually(t, func() bool { return len(srv.Received) > 0 }, 30*time.Millisecond, 3*time.Millisecond)
require.Eventually(t, func() bool { return len(srv.Received) > 0 }, 100*time.Millisecond, 3*time.Millisecond)
require.Equal(t, sent, <-srv.Received)
// test typing
typed := "typed"
_, err = rl.WriteStdin([]byte(typed + "\n"))
require.NoError(t, err)
go func() {
Expand All @@ -74,66 +77,70 @@ func TestSession(t *testing.T) {
// binary as text
options.binAsText = true
defer func() { options.binAsText = false }()
srv.ToSend <- "binary"
srv.ToSend <- binary
require.Eventually(t, func() bool { return len(srv.ToSend) == 0 }, 20*time.Millisecond, 2*time.Millisecond)
// filtered
toBeFiltered := "must be filtered"
options.filter = regexp.MustCompile("^.*not filtered.*$")
defer func() { options.filter = nil }()
require.False(t, options.filter.MatchString(toBeFiltered))
srv.ToSend <- toBeFiltered
require.Eventually(t, func() bool { return len(srv.ToSend) == 0 }, 20*time.Millisecond, 2*time.Millisecond)
// unknown mode
atomic.StoreInt64(&srv.Mode, 0)
srv.ToSend <- "unknown"
srv.ToSend <- unknown
require.Eventually(t, func() bool { return len(srv.ToSend) == 0 }, 20*time.Millisecond, 2*time.Millisecond)
time.Sleep(20 * time.Millisecond)
cancel()
outW.Close()
output, err := io.ReadAll(outR)
out := string(output)
require.NoError(t, err)
require.Contains(t, out, " > test message")
require.Contains(t, out, " > typed")
require.Contains(t, out, " < test message")
require.Contains(t, out, " < \n00000000 74 65 73 74 20 6d 65 73 73 61 67 65 |test message|")
require.Contains(t, out, " < binary")
require.Contains(t, out, " > "+sent)
require.Contains(t, out, " > "+typed)
require.Contains(t, out, " < "+sent)
require.Contains(t, out, " < \n00000000 74 65 73 74 20 6d 65 73 73 61 67 65 |"+sent+"|")
require.Contains(t, out, " < "+binary)
require.NotContains(t, out, toBeFiltered)
require.NotContains(t, out, "unknown")
require.NotContains(t, out, unknown)
// t.Log(out)
}

func TestPingPong(t *testing.T) {
srv := newMockServer(2 * time.Millisecond)
defer srv.Close()
srv := newMockServer(5 * time.Millisecond)
options.pingPong = true
options.pingInterval = 2 * time.Millisecond
options.pingInterval = 5 * time.Millisecond
outR, outW, _ := os.Pipe()
errs := make(chan []error, 1)
rl, err := readline.NewEx(&readline.Config{Prompt: "> ", Stdout: outW, UniqueEditLine: true})
require.NoError(t, err)
// the only way I found to keep redline working for a while
go func() {
for i := 0; i < 400; i++ {
_, err = rl.WriteStdin([]byte("typed"))
}
inR, inW, _ := os.Pipe()
defer func() {
inW.Close()
outW.Close()
options.pingPong = false
options.pingInterval = 0
srv.Close()
}()
rl.Write([]byte("typed"))
errs := make(chan []error, 1)
// substitute FuncMakeRaw and FuncExitRaw to empty func to use open pipe as Stdin
// switching to raw file descriptor 0 will cause immediately closure of rl due to EOF
success := func() error { return nil }
rl, err := readline.NewEx(&readline.Config{Prompt: "> ", Stdin: inR, Stdout: outW, FuncMakeRaw: success, FuncExitRaw: success})
require.NoError(t, err)
go func() {
errs <- connect(mockURL, rl)
}()
time.Sleep(200 * time.Millisecond)
time.Sleep(20 * time.Millisecond)
require.Eventually(t, func() bool { return session != nil }, 100*time.Millisecond, 2*time.Millisecond)
session.cancel()
require.Eventually(t, func() bool { return len(errs) > 0 }, 20*time.Millisecond, 2*time.Millisecond)
inW.Close()
outW.Close()
require.Eventually(t, func() bool { return len(errs) > 0 }, 20*time.Millisecond, 2*time.Millisecond)
output, err := io.ReadAll(outR)
out := string(output)
// t.Log(out)
require.NoError(t, err)
require.Contains(t, out, "> ping")
require.Contains(t, out, "< pong")
require.Contains(t, out, "< ping:")
require.Contains(t, out, "> pong:")
require.Contains(t, out, "< ping")
require.Contains(t, out, "> pong")
}

func TestInitMsg(t *testing.T) {
Expand Down
113 changes: 61 additions & 52 deletions echo-server/echo-server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,58 +18,13 @@ import (
func TestEchoServer(t *testing.T) {
envName := fmt.Sprintf("BE_%s", t.Name())
if os.Getenv(envName) == "1" {
go DoMain([]string{""})
time.Sleep(30 * time.Millisecond)
dialer := websocket.Dialer{}
conn, _, err := dialer.Dial(defaultUrl, nil)
require.NoError(t, err)
testCases := []struct {
name string
toSend string
toReceive []string
}{
{
name: "echo success",
toSend: `{"type":"echo", "payload":"Hello world!"}`,
toReceive: []string{
`{"type":"echo","payload":"Hello world!"}`,
},
},
{
name: "broadcast success",
toSend: `{"type":"broadcast", "payload":"Hello world!"}`,
toReceive: []string{
`{"type":"broadcast","payload":"Hello world!"}`,
`{"type":"broadcastResult","payload":"Hello world!","listenerCount":1}`,
},
},
{
name: "wrong message type",
toSend: `{"type":"wrong", "payload":"Hello world!"}`,
toReceive: []string{
`{"type":"error","payload":"unknown type"}`,
},
},
{
name: "incorrect json",
toSend: `}`,
toReceive: []string{
`{"type":"error","payload":"message parsing error: invalid character '}' looking for beginning of value"}`,
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
err := conn.WriteMessage(websocket.TextMessage, []byte(tc.toSend))
assert.NoError(t, err)
for _, r := range tc.toReceive {
mType, received, err := conn.ReadMessage()
assert.NoError(t, err)
assert.Equal(t, websocket.TextMessage, mType)
assert.Equal(t, string(r), string(received))
}
})
}
// conn, _, err := dialer.Dial(defaultUrl, nil)
// if err == nil {
// fmt.Println("unexpected connection")
// os.Exit(2)
// }
go DoMain([]string{"", defaultUrl})
time.Sleep(500 * time.Millisecond)
// server.TryCloseNormally(conn, "tests finish")
//require.NoError(t, srv.Close())
syscall.Kill(syscall.Getpid(), syscall.SIGINT)
Expand All @@ -87,6 +42,60 @@ func TestEchoServer(t *testing.T) {
require.NoError(t, err)
cmd.Env = append(os.Environ(), envName+"=1")
require.NoError(t, cmd.Start())
time.Sleep(50 * time.Millisecond)
dialer := websocket.Dialer{}
conn, _, err := dialer.Dial(defaultUrl, nil)
if err != nil {
panic(err)
}
testCases := []struct {
name string
toSend string
toReceive []string
}{
{
name: "echo success",
toSend: `{"type":"echo", "payload":"Hello world!"}`,
toReceive: []string{
`{"type":"echo","payload":"Hello world!"}`,
},
},
{
name: "broadcast success",
toSend: `{"type":"broadcast", "payload":"Hello world!"}`,
toReceive: []string{
`{"type":"broadcast","payload":"Hello world!"}`,
`{"type":"broadcastResult","payload":"Hello world!","listenerCount":1}`,
},
},
{
name: "wrong message type",
toSend: `{"type":"wrong", "payload":"Hello world!"}`,
toReceive: []string{
`{"type":"error","payload":"unknown type"}`,
},
},
{
name: "incorrect json",
toSend: `}`,
toReceive: []string{
`{"type":"error","payload":"message parsing error: invalid character '}' looking for beginning of value"}`,
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
err := conn.WriteMessage(websocket.TextMessage, []byte(tc.toSend))
assert.NoError(t, err)
for _, r := range tc.toReceive {
mType, received, err := conn.ReadMessage()
assert.NoError(t, err)
assert.Equal(t, websocket.TextMessage, mType)
assert.Equal(t, string(r), string(received))
}
})
}

out, _ := io.ReadAll(r)
output := string(out)
err = cmd.Wait()
Expand Down
21 changes: 2 additions & 19 deletions main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,30 +9,11 @@ import (
"testing"
"time"

"github.com/gorilla/websocket"
"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestMockServer(t *testing.T) {
s := newMockServer(0)
defer s.Close()
conn := newMockConn()
defer TryCloseNormally(conn, "test finished")
sent := "test"
// test client -> server message
require.NoError(t, conn.WriteMessage(websocket.TextMessage, []byte(sent)))
require.Eventually(t, func() bool { return len(s.Received) > 0 }, 20*time.Millisecond, 2*time.Millisecond)
require.Equal(t, sent, <-s.Received)
// test server -> client message
s.ToSend <- sent
require.NoError(t, conn.SetReadDeadline(time.Now().Add(20*time.Millisecond)))
_, data, err := conn.ReadMessage()
require.NoError(t, err)
require.Equal(t, sent, string(data))
}

func TestWSinitMsg(t *testing.T) {
s := newMockServer(0)
defer s.Close()
Expand All @@ -54,6 +35,8 @@ func TestWSconnectFail(t *testing.T) {
envName := fmt.Sprintf("BE_%s", t.Name())
if os.Getenv(envName) == "1" {
root(&cobra.Command{}, []string{"wss://127.0.0.1:8080"})
time.Sleep(300 * time.Millisecond)
session.cancel()
return
}
args := []string{"-test.run=" + t.Name()}
Expand Down
18 changes: 13 additions & 5 deletions mockServer.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,22 +29,23 @@ func newMockServer(interval time.Duration) *mockServer {
Close: func() error {
cancel()
s.Shutdown(ctx)
s.Close()
return nil
},
Received: received,
ToSend: toSend,
Mode: websocket.TextMessage,
}
s.WSHandleFunc(u.Path, func(conn *websocket.Conn) {
defer conn.Close()
if interval != 0 {
go func() {
ticker := time.NewTicker(interval)
defer ticker.Stop()
for {
select {
case <-ticker.C:
if err := conn.WriteControl(websocket.PingMessage, []byte{}, time.Now().Add(20*time.Millisecond)); err != nil {
return
}
conn.WriteControl(websocket.PingMessage, []byte{}, time.Now().Add(20*time.Millisecond))
case <-ctx.Done():
return
}
Expand All @@ -70,8 +71,15 @@ func newMockServer(interval time.Duration) *mockServer {
}
}
})
go s.ListenAndServe()
time.Sleep(50 * time.Millisecond)
errCh := make(chan error, 1)
go func() {
errCh <- s.ListenAndServe()
}()
select {
case err := <-errCh:
panic(err)
case <-time.After(50 * time.Millisecond):
}
return m
}

Expand Down
Loading

0 comments on commit 1011f59

Please sign in to comment.