package com import ( "encoding/json" "fmt" "math/rand/v2" "net" "net/http" "net/url" "sync" "testing" "time" "github.com/giongto35/cloud-game/v3/pkg/logger" "github.com/giongto35/cloud-game/v3/pkg/network/websocket" ) type TestIn struct { Id Uid T uint8 Payload json.RawMessage } func (i TestIn) GetId() Uid { return i.Id } func (i TestIn) GetType() uint8 { return i.T } func (i TestIn) GetPayload() []byte { return i.Payload } type TestOut struct { Id string T uint8 Payload any } func (o *TestOut) SetId(s string) { o.Id = s } func (o *TestOut) SetType(u uint8) { o.T = u } func (o *TestOut) SetPayload(a any) { o.Payload = a } func (o *TestOut) SetGetId(stringer fmt.Stringer) { o.Id = stringer.String() } func (o *TestOut) GetPayload() any { return o.Payload } func TestWebsocket(t *testing.T) { testCases := []struct { name string test func(t *testing.T) }{ {"If WebSocket implementation is OK in general", testWebsocket}, } for _, tc := range testCases { t.Run(tc.name, tc.test) } } func testWebsocket(t *testing.T) { port, err := getFreePort() if err != nil { t.Logf("couldn't get any free port") t.Skip() } addr := fmt.Sprintf(":%v", port) server := newServer(addr, t) client := newClient(t, url.URL{Scheme: "ws", Host: "localhost" + addr, Path: "/ws"}) clDone := client.ProcessPackets(func(in TestIn) error { return nil }) if server.conn == nil { t.Fatalf("couldn't make new socket") } calls := []struct { packet TestOut concurrent bool value any }{ {packet: TestOut{T: 10, Payload: "test"}, value: "test", concurrent: true}, {packet: TestOut{T: 10, Payload: "test2"}, value: "test2"}, {packet: TestOut{T: 11, Payload: "test3"}, value: "test3"}, {packet: TestOut{T: 99, Payload: ""}, value: ""}, {packet: TestOut{T: 0}}, {packet: TestOut{T: 12, Payload: 123}, value: 123}, {packet: TestOut{T: 10, Payload: false}, value: false}, {packet: TestOut{T: 10, Payload: true}, value: true}, {packet: TestOut{T: 11, Payload: []string{"test", "test", "test"}}, value: []string{"test", "test", "test"}}, {packet: TestOut{T: 22, Payload: []string{}}, value: []string{}}, } const n = 42 var wait sync.WaitGroup wait.Add(n * len(calls)) // test for _, call := range calls { if call.concurrent { for range n { packet := call.packet go func() { defer wait.Done() time.Sleep(time.Duration(rand.IntN(200-100)+100) * time.Millisecond) vv, err := client.rpc.Call(client.sock.conn, &packet) err = checkCall(vv, err, call.value) if err != nil { t.Errorf("%v", err) return } }() } } else { for range n { packet := call.packet vv, err := client.rpc.Call(client.sock.conn, &packet) err = checkCall(vv, err, call.value) if err != nil { wait.Done() t.Fatalf("%v", err) } else { wait.Done() } } } } wait.Wait() client.sock.conn.Close() client.rpc.Cleanup() <-clDone server.conn.Close() <-server.done } func newClient(t *testing.T, addr url.URL) *SocketClient[uint8, TestIn, TestOut, *TestOut] { connector := Client{} conn, err := connector.Connect(addr) if err != nil { t.Fatalf("error: couldn't connect to %v because of %v", addr.String(), err) } rpc := new(RPC[uint8, TestIn]) rpc.calls = Map[Uid, *request]{m: make(map[Uid]*request, 10)} return &SocketClient[uint8, TestIn, TestOut, *TestOut]{sock: conn, log: logger.Default(), rpc: rpc} } func checkCall(v []byte, err error, need any) error { if err != nil { return err } var value any if v != nil { if err = json.Unmarshal(v, &value); err != nil { return fmt.Errorf("can't unmarshal %v", v) } } nice := true // cast values after default unmarshal switch value.(type) { default: nice = value == need case bool: nice = value == need.(bool) case float64: nice = value == float64(need.(int)) case string: nice = value == need.(string) case []any: // let's assume that's strings vv := value.([]any) for i := 0; i < len(need.([]string)); i++ { if vv[i].(string) != need.([]string)[i] { nice = false break } } case map[string]any: // ??? } if !nice { return fmt.Errorf("expected %v, but got %v", need, v) } return nil } type serverHandler struct { conn *websocket.Connection // ws server reference made dynamically on HTTP request done chan struct{} } func (s *serverHandler) serve(t *testing.T) func(w http.ResponseWriter, r *http.Request) { connector := Server{} return func(w http.ResponseWriter, r *http.Request) { sock, err := connector.Server.Connect(w, r, nil) if err != nil { t.Fatalf("couldn't init socket server") } s.conn = sock s.conn.SetMessageHandler(func(m []byte, err error) { s.conn.Write(m) }) // echo s.done = s.conn.Listen() } } func newServer(addr string, t *testing.T) *serverHandler { var wg sync.WaitGroup handler := serverHandler{} http.HandleFunc("/ws", handler.serve(t)) wg.Add(1) go func() { wg.Done() if err := http.ListenAndServe(addr, nil); err != nil { t.Errorf("no server, %v", err) return } }() wg.Wait() return &handler } func getFreePort() (port int, err error) { var a *net.TCPAddr var l *net.TCPListener if a, err = net.ResolveTCPAddr("tcp", ":0"); err == nil { if l, err = net.ListenTCP("tcp", a); err == nil { defer func() { _ = l.Close() }() return l.Addr().(*net.TCPAddr).Port, nil } } return }