| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279 |
- // 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())
- }
|