fix(go): repair migration wire contracts and runtime lifecycle; document incomplete port
This commit is contained in:
1 parent
e43a310617
commit
32e47014b5
35 files changed
+16826
-438
No files matched your search
@@ -0,0 +1,193 @@
|
||||
package gameserver
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"math"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"ananta-ps/internal/config"
|
||||
"ananta-ps/pkg/protocol"
|
||||
)
|
||||
|
||||
func TestWeatherAndTimeUseActualClientContracts(t *testing.T) {
|
||||
srv := New(&config.Config{}, "")
|
||||
conn := &recordingConn{}
|
||||
srv.sessions["test"] = &Session{conn: conn}
|
||||
if err := srv.SetWorldWeather(3); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
f, err := protocol.ReadFrame(conn, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
pkt := protocol.ParseRpcPacket(f.Payload)
|
||||
if pkt.MethodID != 68712141 || !bytes.Equal(pkt.Body, []byte{3, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0}) {
|
||||
t.Fatalf("weather contract: %+v", pkt)
|
||||
}
|
||||
if err := srv.SetWorldTimeOfDay(12.5); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
f, err = protocol.ReadFrame(conn, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
pkt = protocol.ParseRpcPacket(f.Payload)
|
||||
if pkt.MethodID != 68337673 || len(pkt.Body) != 22 || binary.LittleEndian.Uint32(pkt.Body[4:8]) != 45000 || pkt.Body[8] != 1 {
|
||||
t.Fatalf("time contract: %+v", pkt)
|
||||
}
|
||||
for _, hour := range []float32{-1, 24, float32(math.NaN()), float32(math.Inf(1))} {
|
||||
if err := srv.SetWorldTimeOfDay(hour); err == nil {
|
||||
t.Fatalf("invalid hour: %v", hour)
|
||||
}
|
||||
}
|
||||
if conn.Len() != 0 {
|
||||
t.Fatal("invalid time emitted a packet")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdminRejectsInvalidTeleport(t *testing.T) {
|
||||
srv := New(&config.Config{}, "")
|
||||
conn := &recordingConn{}
|
||||
srv.sessions["test"] = &Session{conn: conn, posX: 123}
|
||||
for _, cmd := range []string{"tp garbage 0 0", "tp NaN 0 0", "tp 0 +Inf 0"} {
|
||||
if _, err := srv.GetAdminBridge().ExecuteCommand(cmd); err == nil {
|
||||
t.Fatalf("accepted %s", cmd)
|
||||
}
|
||||
}
|
||||
if conn.Len() != 0 || srv.sessions["test"].posX != 123 {
|
||||
t.Fatal("invalid command changed position")
|
||||
}
|
||||
}
|
||||
|
||||
type recordingConn struct{ bytes.Buffer }
|
||||
|
||||
func (c *recordingConn) Close() error { return nil }
|
||||
func (c *recordingConn) LocalAddr() net.Addr { return &net.TCPAddr{} }
|
||||
func (c *recordingConn) RemoteAddr() net.Addr { return &net.TCPAddr{} }
|
||||
func (c *recordingConn) SetDeadline(time.Time) error { return nil }
|
||||
func (c *recordingConn) SetReadDeadline(time.Time) error { return nil }
|
||||
func (c *recordingConn) SetWriteDeadline(time.Time) error { return nil }
|
||||
|
||||
func TestDispatchContractFallbackAndPacketKinds(t *testing.T) {
|
||||
srv := New(&config.Config{}, "")
|
||||
conn := &recordingConn{}
|
||||
sess := &Session{conn: conn}
|
||||
// An unimplemented RPC returning bool needs one byte, not an empty OK.
|
||||
if err := srv.dispatch(sess, &protocol.RpcPacket{Kind: protocol.PacketKindInvoke, MethodID: 67709054, InvokeID: 12}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
f, err := protocol.ReadFrame(conn, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
pkt := protocol.ParseRpcPacket(f.Payload)
|
||||
if pkt.Kind != protocol.PacketKindReturn || pkt.InvokeID != 12 || !bytes.Equal(pkt.Body, []byte{0}) {
|
||||
t.Fatalf("bad bool fallback: %+v", pkt)
|
||||
}
|
||||
for _, kind := range []protocol.RpcPacketKind{protocol.PacketKindReturn, protocol.PacketKindUnknown} {
|
||||
if err := srv.dispatch(sess, &protocol.RpcPacket{Kind: kind, MethodID: protocol.MethodAskSwitchSpirit}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if conn.Len() != 0 {
|
||||
t.Fatal("dispatched a return or malformed packet as a request")
|
||||
}
|
||||
}
|
||||
if err := sess.ReturnEmptyOk(0, protocol.MethodLoginGame); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if conn.Len() != 0 {
|
||||
t.Fatal("unsolicited return for a notification")
|
||||
}
|
||||
// Fabricated old AskMoveStart collided with a real client ID; it must not move state.
|
||||
if err := srv.dispatch(sess, &protocol.RpcPacket{Kind: protocol.PacketKindNotify, MethodID: 68412852}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if conn.Len() != 0 {
|
||||
t.Fatal("invented route still active")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHangupAndActualMovementContract(t *testing.T) {
|
||||
srv := New(&config.Config{}, "")
|
||||
sess := &Session{conn: &recordingConn{}, unitId: 9}
|
||||
for _, tc := range []struct {
|
||||
id uint32
|
||||
paused bool
|
||||
}{{63842476, true}, {63910160, false}} {
|
||||
if err := srv.dispatch(sess, &protocol.RpcPacket{Kind: protocol.PacketKindInvoke, MethodID: tc.id, InvokeID: 1}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if sess.hangup.Load() != tc.paused {
|
||||
t.Fatal("hangup state not updated")
|
||||
}
|
||||
}
|
||||
body := protocol.NewUxWriter().U8(0xff).Int7(1).U64(9).F32(1).F32(2).F32(3).F32(0).F32(90).F32(0).Bytes()
|
||||
if err := srv.dispatch(sess, &protocol.RpcPacket{Kind: protocol.PacketKindNotify, MethodID: 67543012, Body: body}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if sess.posX != 1 || sess.posY != 2 || sess.posZ != 3 || sess.facing != 90 {
|
||||
t.Fatal("actual movement contract not applied")
|
||||
}
|
||||
if err := srv.dispatch(sess, &protocol.RpcPacket{Kind: protocol.PacketKindNotify, MethodID: 67543012, Body: body[:15]}); err == nil {
|
||||
t.Fatal("truncated position accepted")
|
||||
}
|
||||
if sess.posX != 1 || sess.posY != 2 {
|
||||
t.Fatal("partial position update")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTeleportIncludesNullableTail(t *testing.T) {
|
||||
conn := &recordingConn{}
|
||||
sess := &Session{conn: conn, unitId: 9}
|
||||
if err := sess.Teleport(1, 2, 3, 90); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
f, err := protocol.ReadFrame(conn, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
pkt := protocol.ParseRpcPacket(f.Payload)
|
||||
if pkt.MethodID != 68030312 || len(pkt.Body) != 29 || !bytes.Equal(pkt.Body[24:], []byte{0, 0, 6, 0, 0}) {
|
||||
t.Fatalf("invalid position contract: %+v", pkt)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStopClosesIdleHandshakeConnections(t *testing.T) {
|
||||
srv := New(&config.Config{Network: config.NetworkConfig{BindHost: "127.0.0.1"}}, "")
|
||||
if err := srv.Start(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
conn, err := net.DialTimeout("tcp", srv.ln.Addr().String(), time.Second)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer conn.Close()
|
||||
// Wait for the server to own the socket before stopping.
|
||||
deadline := time.Now().Add(time.Second)
|
||||
for {
|
||||
srv.mu.RLock()
|
||||
n := len(srv.connections)
|
||||
srv.mu.RUnlock()
|
||||
if n == 1 {
|
||||
break
|
||||
}
|
||||
if time.Now().After(deadline) {
|
||||
t.Fatal("connection not accepted")
|
||||
}
|
||||
time.Sleep(time.Millisecond)
|
||||
}
|
||||
done := make(chan struct{})
|
||||
go func() { srv.Stop(); close(done) }()
|
||||
select {
|
||||
case <-done:
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("Stop blocked on idle handshake")
|
||||
}
|
||||
conn.SetReadDeadline(time.Now().Add(time.Second))
|
||||
if _, err := conn.Read(make([]byte, 1)); err == nil {
|
||||
t.Fatal("idle connection survived Stop")
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user