Skip to content

Commit b2e2743

Browse files
committed
refactor: streamline testStreamsXreadBlockMaxID function and introduce StreamAssertion for response validation
1 parent 024884c commit b2e2743

File tree

3 files changed

+117
-61
lines changed

3 files changed

+117
-61
lines changed

internal/resp/decoder/decode_array.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,11 @@ func decodeArray(reader *bytes.Reader) (resp_value.Value, error) {
3131
}
3232
}
3333

34-
if length < 0 {
34+
if length == -1 {
35+
return resp_value.NewNilValue(), nil
36+
}
37+
38+
if length < -1 {
3539
// Ensure error points to the correct byte
3640
reader.Seek(int64(offsetBeforeLength), io.SeekStart)
3741

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package resp_assertions
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"reflect"
7+
8+
resp_value "github.com/codecrafters-io/redis-tester/internal/resp/value"
9+
)
10+
11+
type StreamAssertion struct {
12+
ExpectedValue [][]interface{}
13+
}
14+
15+
func NewStreamAssertion(expectedValue [][]interface{}) RESPAssertion {
16+
return StreamAssertion{ExpectedValue: expectedValue}
17+
}
18+
19+
func (a StreamAssertion) Run(value resp_value.Value) error {
20+
if value.Type != resp_value.ARRAY {
21+
return fmt.Errorf("Expected array, got %s", value.Type)
22+
}
23+
24+
var convertToSlice func(v resp_value.Value) interface{}
25+
convertToSlice = func(v resp_value.Value) interface{} {
26+
switch v.Type {
27+
case resp_value.BULK_STRING:
28+
return v.String()
29+
case resp_value.ARRAY:
30+
result := make([]interface{}, len(v.Array()))
31+
for i, elem := range v.Array() {
32+
result[i] = convertToSlice(elem)
33+
}
34+
return result
35+
default:
36+
return v.String()
37+
}
38+
}
39+
actual := convertToSlice(value).([]interface{})
40+
41+
expected := make([]interface{}, len(a.ExpectedValue))
42+
for i, v := range a.ExpectedValue {
43+
expected[i] = v
44+
}
45+
46+
if !reflect.DeepEqual(expected, actual) {
47+
expectedJSON, err := json.MarshalIndent(expected, "", " ")
48+
if err != nil {
49+
return err
50+
}
51+
actualJSON, err := json.MarshalIndent(actual, "", " ")
52+
if err != nil {
53+
return err
54+
}
55+
return fmt.Errorf("Expected:\n%s\nGot:\n%s", expectedJSON, actualJSON)
56+
}
57+
58+
return nil
59+
}
Lines changed: 53 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,16 @@
11
package internal
22

33
import (
4-
"encoding/json"
5-
"fmt"
6-
"reflect"
74
"strconv"
8-
"strings"
95
"time"
106

7+
"github.com/codecrafters-io/redis-tester/internal/instrumented_resp_connection"
118
"github.com/codecrafters-io/redis-tester/internal/redis_executable"
9+
"github.com/codecrafters-io/redis-tester/internal/resp_assertions"
10+
"github.com/codecrafters-io/redis-tester/internal/test_cases"
1211

1312
testerutils_random "github.com/codecrafters-io/tester-utils/random"
1413
"github.com/codecrafters-io/tester-utils/test_case_harness"
15-
"github.com/go-redis/redis"
1614
)
1715

1816
func testStreamsXreadBlockMaxID(stageHarness *test_case_harness.TestCaseHarness) error {
@@ -22,93 +20,88 @@ func testStreamsXreadBlockMaxID(stageHarness *test_case_harness.TestCaseHarness)
2220
}
2321

2422
logger := stageHarness.Logger
25-
client := NewRedisClient("localhost:6379")
23+
24+
client1, err := instrumented_resp_connection.NewFromAddr(logger, "localhost:6379", "client-1")
25+
if err != nil {
26+
logFriendlyError(logger, err)
27+
return err
28+
}
29+
defer client1.Close()
2630

2731
randomKey := testerutils_random.RandomWord()
2832
randomInt := testerutils_random.RandomInt(1, 100)
2933

30-
xaddTest := &XADDTest{
31-
streamKey: randomKey,
32-
id: "0-1",
33-
values: map[string]interface{}{"temperature": randomInt},
34-
expectedResponse: "0-1",
34+
xaddCommandTestCase := &test_cases.SendCommandTestCase{
35+
Command: "XADD",
36+
Args: []string{randomKey, "0-1", "temperature", strconv.Itoa(randomInt)},
37+
Assertion: resp_assertions.NewStringAssertion("0-1"),
38+
ShouldSkipUnreadDataCheck: true,
3539
}
3640

37-
err := xaddTest.Run(client, logger)
38-
39-
if err != nil {
41+
if err := xaddCommandTestCase.Run(client1, logger); err != nil {
4042
return err
4143
}
4244

43-
respChan := make(chan *[]redis.XStream, 1)
45+
waitChan := make(chan bool, 1)
46+
randomInt = testerutils_random.RandomInt(1, 100)
4447

4548
go func() error {
46-
logger.Infof("$ redis-cli xread block %d streams %v", 0, strings.Join([]string{randomKey, "0-1"}, " "))
47-
48-
resp, err := client.XRead(&redis.XReadArgs{
49-
Streams: []string{randomKey, "$"},
50-
Block: 0,
51-
}).Result()
49+
expectedValue := [][]interface{}{
50+
{
51+
randomKey,
52+
[]interface{}{
53+
[]interface{}{"0-2", []interface{}{"temperature", strconv.Itoa(randomInt)}},
54+
},
55+
},
56+
}
57+
assertion := resp_assertions.NewStreamAssertion(expectedValue)
58+
xreadCommandTestCase := &test_cases.SendCommandTestCase{
59+
Command: "XREAD",
60+
Args: []string{"block", "0", "streams", randomKey, "$"},
61+
Assertion: assertion,
62+
ShouldSkipUnreadDataCheck: true,
63+
}
5264

53-
if err != nil {
54-
logger.Errorf("Error: %q", err)
65+
if err := xreadCommandTestCase.Run(client1, logger); err != nil {
66+
logger.Errorf("Error: %v", err)
5567
return err
5668
}
5769

58-
respChan <- &resp
59-
70+
waitChan <- true
6071
return nil
6172
}()
6273

6374
time.Sleep(1000 * time.Millisecond)
6475

65-
xaddTest = &XADDTest{
66-
streamKey: randomKey,
67-
id: "0-2",
68-
values: map[string]interface{}{"temperature": randomInt},
69-
expectedResponse: "0-2",
70-
}
71-
72-
err = xaddTest.Run(client, logger)
73-
76+
client2, err := instrumented_resp_connection.NewFromAddr(logger, "localhost:6379", "client-2")
7477
if err != nil {
78+
logFriendlyError(logger, err)
7579
return err
7680
}
81+
defer client2.Close()
7782

78-
resp := <-respChan
79-
80-
expectedResp := &[]redis.XStream{
81-
{
82-
Stream: randomKey,
83-
Messages: []redis.XMessage{
84-
{
85-
ID: "0-2",
86-
Values: map[string]interface{}{"temperature": strconv.Itoa(randomInt)},
87-
},
88-
},
89-
},
83+
xaddCommandTestCase = &test_cases.SendCommandTestCase{
84+
Command: "XADD",
85+
Args: []string{randomKey, "0-2", "temperature", strconv.Itoa(randomInt)},
86+
Assertion: resp_assertions.NewStringAssertion("0-2"),
87+
ShouldSkipUnreadDataCheck: true,
9088
}
9189

92-
expectedRespJSON, err := json.MarshalIndent(expectedResp, "", " ")
93-
94-
if err != nil {
95-
logFriendlyError(logger, err)
90+
if err := xaddCommandTestCase.Run(client2, logger); err != nil {
9691
return err
9792
}
9893

99-
respJSON, err := json.MarshalIndent(resp, "", " ")
94+
<-waitChan
10095

101-
if err != nil {
102-
logFriendlyError(logger, err)
103-
return err
96+
xreadCommandTestCase := &test_cases.SendCommandTestCase{
97+
Command: "XREAD",
98+
Args: []string{"block", "1000", "streams", randomKey, "$"},
99+
Assertion: resp_assertions.NewNilAssertion(),
100+
ShouldSkipUnreadDataCheck: false,
104101
}
105102

106-
if !reflect.DeepEqual(resp, expectedResp) {
107-
logger.Infof("Received response: \"%v\"", string(respJSON))
108-
return fmt.Errorf("Expected %v, got %v", string(expectedRespJSON), string(respJSON))
109-
} else {
110-
logger.Successf("Received response: \"%v\"", string(respJSON))
103+
if err := xreadCommandTestCase.Run(client1, logger); err != nil {
104+
return err
111105
}
112-
113106
return nil
114107
}

0 commit comments

Comments
 (0)