From af27c2167a5cf019cedb49ad3e605647c6e42c91 Mon Sep 17 00:00:00 2001 From: Ankush Chavan Date: Wed, 6 May 2026 17:28:11 +0530 Subject: [PATCH 1/2] test(resp): add unit tests for PerformRequest and ReplyCommand Signed-off-by: Ankush Chavan --- resp/reply_test.go | 42 ++++++++++++++++++ resp/request_test.go | 102 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 144 insertions(+) create mode 100644 resp/reply_test.go create mode 100644 resp/request_test.go diff --git a/resp/reply_test.go b/resp/reply_test.go new file mode 100644 index 0000000..1895d39 --- /dev/null +++ b/resp/reply_test.go @@ -0,0 +1,42 @@ +package resp + +import ( + "io" + "net" + "testing" +) + +func TestReplyCommand_WritesBytes(t *testing.T) { + server, client := net.Pipe() + defer server.Close() + + ch := make(chan []byte, 1) + go func() { + b, _ := io.ReadAll(client) + ch <- b + }() + + reply := []byte("+PONG\r\n") + ReplyCommand(reply, server) + server.Close() + + got := <-ch + if string(got) != string(reply) { + t.Errorf("got %q, want %q", got, reply) + } + client.Close() +} + +func TestReplyCommand_ClosedConn(t *testing.T) { + server, client := net.Pipe() + client.Close() + + // Should not panic even when the connection is closed + defer func() { + if r := recover(); r != nil { + t.Errorf("ReplyCommand panicked on closed connection: %v", r) + } + }() + ReplyCommand([]byte("+PONG\r\n"), server) + server.Close() +} diff --git a/resp/request_test.go b/resp/request_test.go new file mode 100644 index 0000000..639fe4d --- /dev/null +++ b/resp/request_test.go @@ -0,0 +1,102 @@ +package resp + +import ( + "io" + "net" + "testing" +) + +// newPipe returns a connected pair of net.Conn. server is passed to the function +// under test; client is used to read back what was written. +func newPipe(t *testing.T) (server, client net.Conn) { + t.Helper() + server, client = net.Pipe() + t.Cleanup(func() { + server.Close() + client.Close() + }) + return +} + +// readReply reads from the client side of the pipe in a goroutine and returns +// the bytes via a channel. The server side must be closed (or the write must +// complete) before this unblocks. +func readReply(client net.Conn) <-chan []byte { + ch := make(chan []byte, 1) + go func() { + b, _ := io.ReadAll(client) + ch <- b + }() + return ch +} + +func TestPerformRequest_ValidPing(t *testing.T) { + server, client := newPipe(t) + ch := readReply(client) + + PerformRequest([]byte("*1\r\n$4\r\nPING\r\n"), server) + server.Close() + + got := string(<-ch) + want := "+PONG\r\n" + if got != want { + t.Errorf("got %q, want %q", got, want) + } +} + +func TestPerformRequest_ValidEcho(t *testing.T) { + server, client := newPipe(t) + ch := readReply(client) + + PerformRequest([]byte("*2\r\n$4\r\nECHO\r\n$5\r\nhello\r\n"), server) + server.Close() + + got := string(<-ch) + want := "$5\r\nhello\r\n" + if got != want { + t.Errorf("got %q, want %q", got, want) + } +} + +func TestPerformRequest_InvalidRESP(t *testing.T) { + server, client := newPipe(t) + ch := readReply(client) + + // Unsupported RESP data type prefix + PerformRequest([]byte("%1\r\n$4\r\nPING\r\n"), server) + server.Close() + + got := string(<-ch) + want := "-invalid request\r\n" + if got != want { + t.Errorf("got %q, want %q", got, want) + } +} + +func TestPerformRequest_UnknownCommand(t *testing.T) { + server, client := newPipe(t) + ch := readReply(client) + + PerformRequest([]byte("*1\r\n$3\r\nFOO\r\n"), server) + server.Close() + + got := string(<-ch) + want := "-unsupported command\r\n" + if got != want { + t.Errorf("got %q, want %q", got, want) + } +} + +func TestPerformRequest_EmptyBuffer(t *testing.T) { + server, client := newPipe(t) + ch := readReply(client) + + // nil buffer — ParseCommand returns zero-value Cmd with empty Name, nothing written + PerformRequest(nil, server) + server.Close() + + got := string(<-ch) + if got != "" { + t.Errorf("expected no reply for empty buffer, got %q", got) + } +} From 262901daed46835c97bc9feb3299b6f1e80a183a Mon Sep 17 00:00:00 2001 From: Ankush Chavan Date: Wed, 6 May 2026 21:28:31 +0530 Subject: [PATCH 2/2] refactor(test): convert resp tests to table-driven style Signed-off-by: Ankush Chavan --- resp/reply_test.go | 44 ++++++++++++++-------- resp/request_test.go | 90 ++++++++++++-------------------------------- 2 files changed, 53 insertions(+), 81 deletions(-) diff --git a/resp/reply_test.go b/resp/reply_test.go index 1895d39..8f80d0d 100644 --- a/resp/reply_test.go +++ b/resp/reply_test.go @@ -6,32 +6,44 @@ import ( "testing" ) -func TestReplyCommand_WritesBytes(t *testing.T) { - server, client := net.Pipe() - defer server.Close() +// TestReplyCommand tests the ReplyCommand function for +// all possible valid and invalid inputs +func TestReplyCommand(t *testing.T) { + tests := []struct { + reply []byte + want string + }{ + {[]byte("+PONG\r\n"), "+PONG\r\n"}, + {[]byte("+OK\r\n"), "+OK\r\n"}, + {[]byte("$5\r\nhello\r\n"), "$5\r\nhello\r\n"}, + {[]byte("-error message\r\n"), "-error message\r\n"}, + } + for _, test := range tests { + server, client := net.Pipe() - ch := make(chan []byte, 1) - go func() { - b, _ := io.ReadAll(client) - ch <- b - }() + ch := make(chan []byte, 1) + go func() { + b, _ := io.ReadAll(client) + ch <- b + }() - reply := []byte("+PONG\r\n") - ReplyCommand(reply, server) - server.Close() + ReplyCommand(test.reply, server) + server.Close() - got := <-ch - if string(got) != string(reply) { - t.Errorf("got %q, want %q", got, reply) + got := string(<-ch) + if got != test.want { + t.Errorf("ReplyCommand(%q) = %q; want %q", test.reply, got, test.want) + } + client.Close() } - client.Close() } +// TestReplyCommand_ClosedConn tests that ReplyCommand does not panic +// when the connection is already closed func TestReplyCommand_ClosedConn(t *testing.T) { server, client := net.Pipe() client.Close() - // Should not panic even when the connection is closed defer func() { if r := recover(); r != nil { t.Errorf("ReplyCommand panicked on closed connection: %v", r) diff --git a/resp/request_test.go b/resp/request_test.go index 639fe4d..b49b175 100644 --- a/resp/request_test.go +++ b/resp/request_test.go @@ -30,73 +30,33 @@ func readReply(client net.Conn) <-chan []byte { return ch } -func TestPerformRequest_ValidPing(t *testing.T) { - server, client := newPipe(t) - ch := readReply(client) - - PerformRequest([]byte("*1\r\n$4\r\nPING\r\n"), server) - server.Close() - - got := string(<-ch) - want := "+PONG\r\n" - if got != want { - t.Errorf("got %q, want %q", got, want) - } -} - -func TestPerformRequest_ValidEcho(t *testing.T) { - server, client := newPipe(t) - ch := readReply(client) - - PerformRequest([]byte("*2\r\n$4\r\nECHO\r\n$5\r\nhello\r\n"), server) - server.Close() - - got := string(<-ch) - want := "$5\r\nhello\r\n" - if got != want { - t.Errorf("got %q, want %q", got, want) - } -} - -func TestPerformRequest_InvalidRESP(t *testing.T) { - server, client := newPipe(t) - ch := readReply(client) - - // Unsupported RESP data type prefix - PerformRequest([]byte("%1\r\n$4\r\nPING\r\n"), server) - server.Close() - - got := string(<-ch) - want := "-invalid request\r\n" - if got != want { - t.Errorf("got %q, want %q", got, want) - } -} - -func TestPerformRequest_UnknownCommand(t *testing.T) { - server, client := newPipe(t) - ch := readReply(client) - - PerformRequest([]byte("*1\r\n$3\r\nFOO\r\n"), server) - server.Close() - - got := string(<-ch) - want := "-unsupported command\r\n" - if got != want { - t.Errorf("got %q, want %q", got, want) +// TestPerformRequest tests the PerformRequest function for +// all possible valid and invalid inputs +func TestPerformRequest(t *testing.T) { + tests := []struct { + input []byte + want string + }{ + // Valid commands + {[]byte("*1\r\n$4\r\nPING\r\n"), "+PONG\r\n"}, + {[]byte("*2\r\n$4\r\nECHO\r\n$5\r\nhello\r\n"), "$5\r\nhello\r\n"}, + // Invalid RESP prefix + {[]byte("%1\r\n$4\r\nPING\r\n"), "-invalid request\r\n"}, + // Unknown command + {[]byte("*1\r\n$3\r\nFOO\r\n"), "-unsupported command\r\n"}, + // nil buffer — ParseCommand returns zero-value Cmd with empty Name, nothing written + {nil, ""}, } -} + for _, test := range tests { + server, client := newPipe(t) + ch := readReply(client) -func TestPerformRequest_EmptyBuffer(t *testing.T) { - server, client := newPipe(t) - ch := readReply(client) - - // nil buffer — ParseCommand returns zero-value Cmd with empty Name, nothing written - PerformRequest(nil, server) - server.Close() + PerformRequest(test.input, server) + server.Close() - got := string(<-ch) - if got != "" { - t.Errorf("expected no reply for empty buffer, got %q", got) + got := string(<-ch) + if got != test.want { + t.Errorf("PerformRequest(%q) = %q; want %q", test.input, got, test.want) + } } }