// Author: simon (ynwdlxm@163.com) // Date: 2025/10/17 14:53 // Desc: 客户端单元测试 package client_ import ( "bufio" "bytes" "net" "sync" "testing" "time" "github.com/stretchr/testify/assert" "github.com/runningwater/go-redis/resp/reply" ) // mockServer 模拟Redis服务器用于测试 type mockServer struct { listener net.Listener conns []net.Conn mu sync.Mutex closed bool } // newMockServer 创建一个新的模拟服务器 func newMockServer(addr string) (*mockServer, error) { listener, err := net.Listen("tcp", addr) if err != nil { return nil, err } server := &mockServer{ listener: listener, conns: make([]net.Conn, 0), } go server.acceptConnections() return server, nil } // acceptConnections 接受客户端连接 func (s *mockServer) acceptConnections() { for { conn, err := s.listener.Accept() if err != nil { s.mu.Lock() closed := s.closed s.mu.Unlock() if closed { return } continue } s.mu.Lock() s.conns = append(s.conns, conn) s.mu.Unlock() go s.handleConnection(conn) } } // handleConnection 处理客户端连接 func (s *mockServer) handleConnection(conn net.Conn) { reader := bufio.NewReader(conn) for { // 读取客户端发送的数据 line, err := reader.ReadBytes('\n') if err != nil { return } // 解析命令 if bytes.Contains(line, []byte("PING")) { // 回复 PONG conn.Write([]byte("+PONG\r\n")) } else if bytes.Contains(line, []byte("SET")) { // 回复 OK conn.Write([]byte("+OK\r\n")) } else if bytes.Contains(line, []byte("GET")) { // 回复值 conn.Write([]byte("$5\r\nvalue\r\n")) } else { // 默认回复 OK conn.Write([]byte("+OK\r\n")) } } } // close 关闭模拟服务器 func (s *mockServer) close() { s.mu.Lock() defer s.mu.Unlock() s.closed = true s.listener.Close() for _, conn := range s.conns { conn.Close() } } // TestNewClient 测试创建新客户端 func TestNewClient(t *testing.T) { // 启动模拟服务器 server, err := newMockServer("localhost:0") assert.NoError(t, err) defer server.close() // 创建客户端 client, err := NewClient(server.listener.Addr().String()) assert.NoError(t, err) assert.NotNil(t, client) assert.Equal(t, server.listener.Addr().String(), client.addr) } // TestClientStartAndStop 测试客户端启动和停止 func TestClientStartAndStop(t *testing.T) { // 启动模拟服务器 server, err := newMockServer("localhost:0") assert.NoError(t, err) defer server.close() // 创建客户端 client, err := NewClient(server.listener.Addr().String()) assert.NoError(t, err) // 启动客户端 client.Start() // 等待一段时间确保协程启动 time.Sleep(100 * time.Millisecond) // 停止客户端 client.Stop() } // TestClientSend 测试发送命令 func TestClientSend(t *testing.T) { // 启动模拟服务器 server, err := newMockServer("localhost:0") assert.NoError(t, err) defer server.close() // 创建客户端 client, err := NewClient(server.listener.Addr().String()) assert.NoError(t, err) defer client.Stop() // 启动客户端 client.Start() // 发送 SET 命令 args := [][]byte{[]byte("SET"), []byte("key"), []byte("value")} result := client.Send(args) // 检查结果 assert.IsType(t, reply.NewOkReply(), result) assert.Equal(t, "OK", result.String()) } // TestClientPing 测试PING命令 func TestClientPing(t *testing.T) { // 启动模拟服务器 server, err := newMockServer("localhost:0") assert.NoError(t, err) defer server.close() // 创建客户端 client, err := NewClient(server.listener.Addr().String()) assert.NoError(t, err) defer client.Stop() // 启动客户端 client.Start() // 发送 PING 命令 args := [][]byte{[]byte("PING")} result := client.Send(args) // 检查结果 rep := reply.NewStatusReply("OK") assert.IsType(t, rep, result) assert.Equal(t, rep.String(), result.String()) } // TestClientGet 测试GET命令 func TestClientGet(t *testing.T) { // 启动模拟服务器 server, err := newMockServer("localhost:0") assert.NoError(t, err) defer server.close() // 创建客户端 client, err := NewClient(server.listener.Addr().String()) assert.NoError(t, err) defer client.Stop() // 启动客户端 client.Start() // 发送 GET 命令 args := [][]byte{[]byte("GET"), []byte("key")} result := client.Send(args) // 检查结果 repl := reply.NewStatusReply("OK") assert.IsType(t, repl, result) assert.Equal(t, repl.String(), result.String()) } // TestClientStop 测试停止已停止的客户端 func TestClientStopClosed(t *testing.T) { // 启动模拟服务器 server, err := newMockServer("localhost:0") assert.NoError(t, err) defer server.close() // 创建客户端 client, err := NewClient(server.listener.Addr().String()) assert.NoError(t, err) // 停止客户端两次 client.Stop() client.Stop() // 应该安全地处理重复停止 } // TestClientSendOnClosed 测试向已关闭的客户端发送命令 func TestClientSendOnClosed(t *testing.T) { // 启动模拟服务器 server, err := newMockServer("localhost:0") assert.NoError(t, err) defer server.close() // 创建客户端 client, err := NewClient(server.listener.Addr().String()) assert.NoError(t, err) // 启动然后立即停止客户端 client.Start() client.Stop() // 向已关闭的客户端发送命令 args := [][]byte{[]byte("PING")} result := client.Send(args) // 应该返回错误 assert.IsType(t, reply.NewErrReply(""), result) assert.Contains(t, result.String(), "client is closed") } // TestClientHeartbeat 测试心跳功能 func TestClientHeartbeat(t *testing.T) { // 启动模拟服务器 server, err := newMockServer("localhost:0") assert.NoError(t, err) defer server.close() // 创建客户端 client, err := NewClient(server.listener.Addr().String()) assert.NoError(t, err) defer client.Stop() // 启动客户端 client.Start() // 等待心跳发生 time.Sleep(12 * time.Second) // 心跳间隔是10秒 // 客户端应该仍然工作正常 args := [][]byte{[]byte("PING")} result := client.Send(args) assert.IsType(t, reply.NewStatusReply("OK"), result) assert.Equal(t, "+OK\r\n", result.String()) }