127 lines
3.5 KiB
Go
127 lines
3.5 KiB
Go
package netserver
|
||
|
||
import (
|
||
"context"
|
||
"net"
|
||
"testing"
|
||
"time"
|
||
|
||
"mc/internal/logx"
|
||
"mc/internal/netproto"
|
||
)
|
||
|
||
// testHandler 记录回调的测试处理器。
|
||
type testHandler struct {
|
||
logins chan netproto.Handshake
|
||
moves chan netproto.PlayerMove
|
||
connects chan uint64
|
||
}
|
||
|
||
func newTestHandler() *testHandler {
|
||
return &testHandler{
|
||
logins: make(chan netproto.Handshake, 8),
|
||
moves: make(chan netproto.PlayerMove, 8),
|
||
connects: make(chan uint64, 8),
|
||
}
|
||
}
|
||
|
||
func (h *testHandler) OnConnect(c *Conn) { h.connects <- c.ID() }
|
||
func (h *testHandler) OnHandshake(c *Conn, hs netproto.Handshake) uint8 {
|
||
h.logins <- hs
|
||
return 0
|
||
}
|
||
func (h *testHandler) OnMove(c *Conn, m netproto.PlayerMove) { h.moves <- m }
|
||
func (h *testHandler) OnDisconnect(c *Conn) {}
|
||
|
||
// startServer 启动测试服务器。
|
||
func startServer(t *testing.T) (*Server, *testHandler, context.CancelFunc) {
|
||
t.Helper()
|
||
log, err := logx.New("", logx.LevelDebug) // 仅 stderr(避免临时文件句柄)
|
||
if err != nil {
|
||
t.Fatalf("创建日志失败: %v", err)
|
||
}
|
||
h := newTestHandler()
|
||
s := New("127.0.0.1:0", log, h)
|
||
ctx, cancel := context.WithCancel(context.Background())
|
||
if err := s.Listen(ctx); err != nil {
|
||
t.Fatalf("监听失败: %v", err)
|
||
}
|
||
t.Cleanup(func() {
|
||
cancel()
|
||
time.Sleep(20 * time.Millisecond)
|
||
})
|
||
return s, h, cancel
|
||
}
|
||
|
||
// dial 建立客户端连接。
|
||
func dial(t *testing.T, addr string) net.Conn {
|
||
t.Helper()
|
||
c, err := net.DialTimeout("tcp", addr, 3*time.Second)
|
||
if err != nil {
|
||
t.Fatalf("拨号失败: %v", err)
|
||
}
|
||
t.Cleanup(func() { _ = c.Close() })
|
||
return c
|
||
}
|
||
|
||
// TestHandshakeFlow 握手 → 登录响应 → 心跳回显全链路(网络协议.md §4)。
|
||
func TestHandshakeFlow(t *testing.T) {
|
||
s, h, _ := startServer(t)
|
||
nc := dial(t, s.Addr())
|
||
|
||
// 发送握手
|
||
hs := netproto.Handshake{ProtocolVersion: 1, ClientID: "测试客户端"}
|
||
_, err := nc.Write(netproto.EncodeFrame(netproto.Frame{MsgID: netproto.MsgHandshake, Payload: hs.Encode()}))
|
||
if err != nil {
|
||
t.Fatalf("发送握手失败: %v", err)
|
||
}
|
||
select {
|
||
case got := <-h.logins:
|
||
if got != hs {
|
||
t.Fatalf("握手内容不一致: %+v", got)
|
||
}
|
||
case <-time.After(3 * time.Second):
|
||
t.Fatal("未收到握手回调")
|
||
}
|
||
|
||
// 读登录响应帧
|
||
_ = nc.SetReadDeadline(time.Now().Add(3 * time.Second))
|
||
buf := make([]byte, 256)
|
||
n, err := nc.Read(buf)
|
||
if err != nil {
|
||
t.Fatalf("读登录响应失败: %v", err)
|
||
}
|
||
f, _, err := netproto.DecodeFrame(buf[:n])
|
||
if err != nil || f.MsgID != netproto.MsgLoginResponse {
|
||
t.Fatalf("登录响应异常: %+v err=%v", f, err)
|
||
}
|
||
if len(f.Payload) != 1 || f.Payload[0] != 0 {
|
||
t.Fatalf("登录结果应为 0(成功),实际 %v", f.Payload)
|
||
}
|
||
|
||
// 心跳回显
|
||
_, _ = nc.Write(netproto.EncodeFrame(netproto.Frame{MsgID: netproto.MsgHeartbeat, Payload: []byte{7}}))
|
||
n, _ = nc.Read(buf)
|
||
f, _, _ = netproto.DecodeFrame(buf[:n])
|
||
if f.MsgID != netproto.MsgHeartbeat || len(f.Payload) != 1 || f.Payload[0] != 7 {
|
||
t.Fatalf("心跳回显异常: %+v", f)
|
||
}
|
||
}
|
||
|
||
// TestMoveBroadcast 移动消息分发。
|
||
func TestMoveBroadcast(t *testing.T) {
|
||
s, h, _ := startServer(t)
|
||
nc := dial(t, s.Addr())
|
||
m := netproto.PlayerMove{X: 1.5, Y: 64, Z: -2, Yaw: 0.5, Pitch: 0, OnGround: true}
|
||
_, _ = nc.Write(netproto.EncodeFrame(netproto.Frame{MsgID: netproto.MsgPlayerMove, Payload: m.Encode()}))
|
||
select {
|
||
case got := <-h.moves:
|
||
if got != m {
|
||
t.Fatalf("移动数据不一致: %+v", got)
|
||
}
|
||
case <-time.After(3 * time.Second):
|
||
t.Fatal("未收到移动回调")
|
||
}
|
||
_ = s.Count() // 连接计数可用
|
||
}
|