cloud-game/pkg/com/net_test.go
Sergey Stepanov b3f677d32f Use for range
2024-04-22 18:10:35 +03:00

225 lines
5.4 KiB
Go

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
}