|
|
@@ -0,0 +1,278 @@
|
|
|
+// 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())
|
|
|
+}
|