Skip to content

Commit

Permalink
Merge pull request #141 from codecrafters-io/CC-1057-rdb-ref
Browse files Browse the repository at this point in the history
CC-1057: Refactor RDB Pesrsistence stages to use our RESP framework.
  • Loading branch information
ryan-gang committed May 6, 2024
2 parents 66914d5 + 5eea301 commit 61229ad
Show file tree
Hide file tree
Showing 15 changed files with 524 additions and 346 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
dist

# Tests generate this
internal/dump.rdb
**.rdb

vendor/
.history/
Expand Down
15 changes: 10 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,18 @@ build:
go build -o dist/main.out ./cmd/tester

test:
go test -p 1 -v ./internal/...
go test -count=1 -p 1 -v ./internal/...

test_with_redis: build
CODECRAFTERS_SUBMISSION_DIR=./internal/test_helpers/pass_all \
CODECRAFTERS_TEST_CASES_JSON="[{\"slug\":\"repl-master-cmd-prop\",\"tester_log_prefix\":\"replication-11\",\"title\":\"Replication Stage\"}]" \
CODECRAFTERS_TEST_CASES_JSON="[{\"slug\":\"repl-wait\",\"tester_log_prefix\":\"stage-118\",\"title\":\"Stage #118: WAIT Command\"}]" \
dist/main.out


test_tmp: build
cd /Users/ryang/Developer/byox/build-your-own-redis && \
CODECRAFTERS_SUBMISSION_DIR=/Users/ryang/Developer/byox/build-your-own-redis \
CODECRAFTERS_TEST_CASES_JSON="[{\"slug\":\"init\",\"tester_log_prefix\":\"stage-1\",\"title\":\"Stage #1: Bind to a port\"},{\"slug\":\"ping-pong\",\"tester_log_prefix\":\"stage-2\",\"title\":\"Stage #2: Respond to PING\"},{\"slug\":\"ping-pong-multiple\",\"tester_log_prefix\":\"stage-3\",\"title\":\"Stage #3: Respond to multiple PINGs\"},{\"slug\":\"concurrent-clients\",\"tester_log_prefix\":\"stage-4\",\"title\":\"Stage #4: Handle concurrent clients\"},{\"slug\":\"echo\",\"tester_log_prefix\":\"stage-5\",\"title\":\"Stage #5: Implement the ECHO command\"},{\"slug\":\"set_get\",\"tester_log_prefix\":\"stage-6\",\"title\":\"Stage #6: Implement the SET \u0026 GET commands\"},{\"slug\":\"expiry\",\"tester_log_prefix\":\"stage-7\",\"title\":\"Stage #7: Expiry\"},{\"slug\":\"repl-custom-port\",\"tester_log_prefix\":\"stage-101\",\"title\":\"Stage #101: Replication - Custom Port\"}, {\"slug\":\"repl-info\",\"tester_log_prefix\":\"stage-102\",\"title\":\"Stage #102: Replication - Info on Master\"},{\"slug\":\"repl-info-replica\",\"tester_log_prefix\":\"stage-103\",\"title\":\"Stage #103: Replication - Info on Replica\"}, {\"slug\":\"repl-id\",\"tester_log_prefix\":\"stage-104\",\"title\":\"Stage #104: Replication - Replication ID and Offset\"}, {\"slug\":\"repl-replica-ping\",\"tester_log_prefix\":\"stage-105\",\"title\":\"Stage #105: Replication - Handshake 1\"},{\"slug\":\"repl-replica-replconf\",\"tester_log_prefix\":\"stage-106\",\"title\":\"Stage #106: Replication - Handshake 2\"},{\"slug\":\"repl-replica-psync\",\"tester_log_prefix\":\"stage-107\",\"title\":\"Stage #107: Replication - Handshake 3\"},{\"slug\":\"repl-master-replconf\",\"tester_log_prefix\":\"stage-108\",\"title\":\"Stage #108: Replication - REPLCONF\"},{\"slug\":\"repl-master-psync\",\"tester_log_prefix\":\"stage-109\",\"title\":\"Stage #109: Replication - PSYNC\"},{\"slug\":\"repl-master-psync-rdb\",\"tester_log_prefix\":\"stage-110\",\"title\":\"Stage #110: Replication - PSYNC w RDB file\"},{\"slug\":\"repl-master-cmd-prop\",\"tester_log_prefix\":\"stage-111\",\"title\":\"Stage #111: Command Propagation\"},{\"slug\":\"repl-multiple-replicas\",\"tester_log_prefix\":\"stage-112\",\"title\":\"Stage #112: Command Propagation to multiple Replicas\"},{\"slug\":\"repl-cmd-processing\",\"tester_log_prefix\":\"stage-113\",\"title\":\"Stage #113: Command Processing\"},{\"slug\":\"repl-replica-getack\",\"tester_log_prefix\":\"stage-114\",\"title\":\"Stage #114: GetAck with 0 offset\"},{\"slug\":\"repl-replica-getack-nonzero\",\"tester_log_prefix\":\"stage-115\",\"title\":\"Stage #115: GetAck with non-0 offset\"},{\"slug\":\"repl-wait-zero-replicas\",\"tester_log_prefix\":\"stage-116\",\"title\":\"Stage #116: WAIT with 0 replicas\"},{\"slug\":\"repl-wait-zero-offset\",\"tester_log_prefix\":\"stage-117\",\"title\":\"Stage #117: WAIT with 0 offset\"},{\"slug\":\"repl-wait\",\"tester_log_prefix\":\"stage-118\",\"title\":\"Stage #118: WAIT Command\"},{\"slug\":\"rdb-config\",\"tester_log_prefix\":\"stage-201\",\"title\":\"Stage #1: RDB Config\"}, {\"slug\":\"rdb-read-key\",\"tester_log_prefix\":\"stage-202\",\"title\":\"Stage #2: RDB Read Key\"}, {\"slug\":\"rdb-read-string-value\",\"tester_log_prefix\":\"stage-203\",\"title\":\"Stage #3: RDB String Value\"}, {\"slug\":\"rdb-read-multiple-keys\",\"tester_log_prefix\":\"stage-204\",\"title\":\"Stage #4: RDB Read Multiple Keys\"}, {\"slug\":\"rdb-read-multiple-string-values\",\"tester_log_prefix\":\"stage-205\",\"title\":\"Stage #5: RDB Read Multiple String Values\"}, {\"slug\":\"rdb-read-value-with-expiry\",\"tester_log_prefix\":\"stage-206\",\"title\":\"Stage #6: RDB Read Value With Expiry\"}]" \
CODECRAFTERS_TEST_CASES_JSON="[{\"slug\":\"rdb-config\",\"tester_log_prefix\":\"stage-201\",\"title\":\"Stage #1: RDB Config\"}, {\"slug\":\"rdb-read-key\",\"tester_log_prefix\":\"stage-202\",\"title\":\"Stage #2: RDB Read Key\"}, {\"slug\":\"rdb-read-string-value\",\"tester_log_prefix\":\"stage-203\",\"title\":\"Stage #3: RDB String Value\"}, {\"slug\":\"rdb-read-multiple-keys\",\"tester_log_prefix\":\"stage-204\",\"title\":\"Stage #4: RDB Read Multiple Keys\"}, {\"slug\":\"rdb-read-multiple-string-values\",\"tester_log_prefix\":\"stage-205\",\"title\":\"Stage #5: RDB Read Multiple String Values\"}, {\"slug\":\"rdb-read-value-with-expiry\",\"tester_log_prefix\":\"stage-206\",\"title\":\"Stage #6: RDB Read Value With Expiry\"}]" \
$(shell pwd)/dist/main.out

copy_course_file:
Expand All @@ -43,5 +43,10 @@ update_tester_utils:

test_all_with_redis: build
CODECRAFTERS_SUBMISSION_DIR=./internal/test_helpers/pass_all \
CODECRAFTERS_TEST_CASES_JSON="[{\"slug\":\"init\",\"tester_log_prefix\":\"stage-1\",\"title\":\"Stage #1: Bind to a port\"},{\"slug\":\"ping-pong\",\"tester_log_prefix\":\"stage-2\",\"title\":\"Stage #2: Respond to PING\"},{\"slug\":\"ping-pong-multiple\",\"tester_log_prefix\":\"stage-3\",\"title\":\"Stage #3: Respond to multiple PINGs\"},{\"slug\":\"concurrent-clients\",\"tester_log_prefix\":\"stage-4\",\"title\":\"Stage #4: Handle concurrent clients\"},{\"slug\":\"echo\",\"tester_log_prefix\":\"stage-5\",\"title\":\"Stage #5: Implement the ECHO command\"},{\"slug\":\"set_get\",\"tester_log_prefix\":\"stage-6\",\"title\":\"Stage #6: Implement the SET \u0026 GET commands\"},{\"slug\":\"expiry\",\"tester_log_prefix\":\"stage-7\",\"title\":\"Stage #7: Expiry\"},{\"slug\":\"repl-custom-port\",\"tester_log_prefix\":\"stage-101\",\"title\":\"Stage #101: Replication - Custom Port\"}, {\"slug\":\"repl-info\",\"tester_log_prefix\":\"stage-102\",\"title\":\"Stage #102: Replication - Info on Master\"},{\"slug\":\"repl-info-replica\",\"tester_log_prefix\":\"stage-103\",\"title\":\"Stage #103: Replication - Info on Replica\"}, {\"slug\":\"repl-id\",\"tester_log_prefix\":\"stage-104\",\"title\":\"Stage #104: Replication - Replication ID and Offset\"}, {\"slug\":\"repl-replica-ping\",\"tester_log_prefix\":\"stage-105\",\"title\":\"Stage #105: Replication - Handshake 1\"},{\"slug\":\"repl-replica-replconf\",\"tester_log_prefix\":\"stage-106\",\"title\":\"Stage #106: Replication - Handshake 2\"},{\"slug\":\"repl-replica-psync\",\"tester_log_prefix\":\"stage-107\",\"title\":\"Stage #107: Replication - Handshake 3\"},{\"slug\":\"repl-master-replconf\",\"tester_log_prefix\":\"stage-108\",\"title\":\"Stage #108: Replication - REPLCONF\"},{\"slug\":\"repl-master-psync\",\"tester_log_prefix\":\"stage-109\",\"title\":\"Stage #109: Replication - PSYNC\"},{\"slug\":\"repl-master-psync-rdb\",\"tester_log_prefix\":\"stage-110\",\"title\":\"Stage #110: Replication - PSYNC w RDB file\"},{\"slug\":\"repl-master-cmd-prop\",\"tester_log_prefix\":\"stage-111\",\"title\":\"Stage #111: Command Propagation\"},{\"slug\":\"repl-multiple-replicas\",\"tester_log_prefix\":\"stage-112\",\"title\":\"Stage #112: Command Propagation to multiple Replicas\"},{\"slug\":\"repl-cmd-processing\",\"tester_log_prefix\":\"stage-113\",\"title\":\"Stage #113: Command Processing\"},{\"slug\":\"repl-replica-getack\",\"tester_log_prefix\":\"stage-114\",\"title\":\"Stage #114: GetAck with 0 offset\"},{\"slug\":\"repl-replica-getack-nonzero\",\"tester_log_prefix\":\"stage-115\",\"title\":\"Stage #115: GetAck with non-0 offset\"},{\"slug\":\"repl-wait-zero-replicas\",\"tester_log_prefix\":\"stage-116\",\"title\":\"Stage #116: WAIT with 0 replicas\"},{\"slug\":\"repl-wait-zero-offset\",\"tester_log_prefix\":\"stage-117\",\"title\":\"Stage #117: WAIT with 0 offset\"},{\"slug\":\"repl-wait\",\"tester_log_prefix\":\"stage-118\",\"title\":\"Stage #118: WAIT Command\"},{\"slug\":\"rdb-config\",\"tester_log_prefix\":\"stage-201\",\"title\":\"Stage #1: RDB Config\"}, {\"slug\":\"rdb-read-key\",\"tester_log_prefix\":\"stage-202\",\"title\":\"Stage #2: RDB Read Key\"}, {\"slug\":\"rdb-read-string-value\",\"tester_log_prefix\":\"stage-203\",\"title\":\"Stage #3: RDB String Value\"}, {\"slug\":\"rdb-read-multiple-keys\",\"tester_log_prefix\":\"stage-204\",\"title\":\"Stage #4: RDB Read Multiple Keys\"}, {\"slug\":\"rdb-read-multiple-string-values\",\"tester_log_prefix\":\"stage-205\",\"title\":\"Stage #5: RDB Read Multiple String Values\"}, {\"slug\":\"rdb-read-value-with-expiry\",\"tester_log_prefix\":\"stage-206\",\"title\":\"Stage #6: RDB Read Value With Expiry\"}]" \
dist/main.out
CODECRAFTERS_TEST_CASES_JSON="[{\"slug\":\"init\",\"tester_log_prefix\":\"stage-1\",\"title\":\"Stage #1: Bind to a port\"},{\"slug\":\"ping-pong\",\"tester_log_prefix\":\"stage-2\",\"title\":\"Stage #2: Respond to PING\"},{\"slug\":\"ping-pong-multiple\",\"tester_log_prefix\":\"stage-3\",\"title\":\"Stage #3: Respond to multiple PINGs\"},{\"slug\":\"concurrent-clients\",\"tester_log_prefix\":\"stage-4\",\"title\":\"Stage #4: Handle concurrent clients\"},{\"slug\":\"echo\",\"tester_log_prefix\":\"stage-5\",\"title\":\"Stage #5: Implement the ECHO command\"},{\"slug\":\"set_get\",\"tester_log_prefix\":\"stage-6\",\"title\":\"Stage #6: Implement the SET \u0026 GET commands\"},{\"slug\":\"expiry\",\"tester_log_prefix\":\"stage-7\",\"title\":\"Stage #7: Expiry\"},{\"slug\":\"repl-custom-port\",\"tester_log_prefix\":\"stage-101\",\"title\":\"Stage #101: Replication - Custom Port\"}, {\"slug\":\"repl-info\",\"tester_log_prefix\":\"stage-102\",\"title\":\"Stage #102: Replication - Info on Master\"},{\"slug\":\"repl-info-replica\",\"tester_log_prefix\":\"stage-103\",\"title\":\"Stage #103: Replication - Info on Replica\"}, {\"slug\":\"repl-id\",\"tester_log_prefix\":\"stage-104\",\"title\":\"Stage #104: Replication - Replication ID and Offset\"}, {\"slug\":\"repl-replica-ping\",\"tester_log_prefix\":\"stage-105\",\"title\":\"Stage #105: Replication - Handshake 1\"},{\"slug\":\"repl-replica-replconf\",\"tester_log_prefix\":\"stage-106\",\"title\":\"Stage #106: Replication - Handshake 2\"},{\"slug\":\"repl-replica-psync\",\"tester_log_prefix\":\"stage-107\",\"title\":\"Stage #107: Replication - Handshake 3\"},{\"slug\":\"repl-master-replconf\",\"tester_log_prefix\":\"stage-108\",\"title\":\"Stage #108: Replication - REPLCONF\"},{\"slug\":\"repl-master-psync\",\"tester_log_prefix\":\"stage-109\",\"title\":\"Stage #109: Replication - PSYNC\"},{\"slug\":\"repl-master-psync-rdb\",\"tester_log_prefix\":\"stage-110\",\"title\":\"Stage #110: Replication - PSYNC w RDB file\"},{\"slug\":\"repl-master-cmd-prop\",\"tester_log_prefix\":\"stage-111\",\"title\":\"Stage #111: Command Propagation\"},{\"slug\":\"repl-multiple-replicas\",\"tester_log_prefix\":\"stage-112\",\"title\":\"Stage #112: Command Propagation to multiple Replicas\"},{\"slug\":\"repl-cmd-processing\",\"tester_log_prefix\":\"stage-113\",\"title\":\"Stage #113: Command Processing\"},{\"slug\":\"repl-replica-getack\",\"tester_log_prefix\":\"stage-114\",\"title\":\"Stage #114: GetAck with 0 offset\"},{\"slug\":\"repl-replica-getack-nonzero\",\"tester_log_prefix\":\"stage-115\",\"title\":\"Stage #115: GetAck with non-0 offset\"},{\"slug\":\"repl-wait-zero-replicas\",\"tester_log_prefix\":\"stage-116\",\"title\":\"Stage #116: WAIT with 0 replicas\"},{\"slug\":\"repl-wait-zero-offset\",\"tester_log_prefix\":\"stage-117\",\"title\":\"Stage #117: WAIT with 0 offset\"},{\"slug\":\"repl-wait\",\"tester_log_prefix\":\"stage-118\",\"title\":\"Stage #118: WAIT Command\"}]" \
dist/main.out

test_rdb_with_redis: build
CODECRAFTERS_SUBMISSION_DIR=./internal/test_helpers/pass_all \
CODECRAFTERS_TEST_CASES_JSON="[{\"slug\":\"rdb-config\",\"tester_log_prefix\":\"stage-201\",\"title\":\"Stage #1: RDB Config\"}, {\"slug\":\"rdb-read-key\",\"tester_log_prefix\":\"stage-202\",\"title\":\"Stage #2: RDB Read Key\"}, {\"slug\":\"rdb-read-string-value\",\"tester_log_prefix\":\"stage-203\",\"title\":\"Stage #3: RDB String Value\"}, {\"slug\":\"rdb-read-multiple-keys\",\"tester_log_prefix\":\"stage-204\",\"title\":\"Stage #4: RDB Read Multiple Keys\"}, {\"slug\":\"rdb-read-multiple-string-values\",\"tester_log_prefix\":\"stage-205\",\"title\":\"Stage #5: RDB Read Multiple String Values\"}, {\"slug\":\"rdb-read-value-with-expiry\",\"tester_log_prefix\":\"stage-206\",\"title\":\"Stage #6: RDB Read Value With Expiry\"}]" \
dist/main.out
2 changes: 1 addition & 1 deletion internal/rdb_file_creator.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ type RDBFileCreator struct {
StageHarness *test_case_harness.TestCaseHarness
}

func NewRDBFileCreator(stageHarness *test_case_harness.TestCaseHarness) (*RDBFileCreator, error) {
func NewRDBFileCreator() (*RDBFileCreator, error) {
tmpDir, err := os.MkdirTemp("", "rdbfiles")
if err != nil {
return &RDBFileCreator{}, err
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,17 @@ import (
resp_value "github.com/codecrafters-io/redis-tester/internal/resp/value"
)

type StringArrayAssertion struct {
// OrderedStringArrayAssertion : Order of the actual and expected values matters.
// We don't alter the ordering.
type OrderedStringArrayAssertion struct {
ExpectedValue []string
}

func NewStringArrayAssertion(expectedValue []string) RESPAssertion {
return StringArrayAssertion{ExpectedValue: expectedValue}
func NewOrderedStringArrayAssertion(expectedValue []string) RESPAssertion {
return OrderedStringArrayAssertion{ExpectedValue: expectedValue}
}

func (a StringArrayAssertion) Run(value resp_value.Value) error {
func (a OrderedStringArrayAssertion) Run(value resp_value.Value) error {
if value.Type != resp_value.ARRAY {
return fmt.Errorf("Expected an array, got %s", value.Type)
}
Expand Down
54 changes: 54 additions & 0 deletions internal/resp_assertions/unordered_string_array_assertion.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package resp_assertions

import (
"encoding/json"
"fmt"
"sort"

resp_value "github.com/codecrafters-io/redis-tester/internal/resp/value"
)

// UnorderedStringArrayAssertion : Order of the actual and expected values doesn't matter.
// We sort the expected and actual values before comparing them.
type UnorderedStringArrayAssertion struct {
ExpectedValue []string
}

func NewUnorderedStringArrayAssertion(expectedValue []string) RESPAssertion {
return UnorderedStringArrayAssertion{ExpectedValue: expectedValue}
}

func (a UnorderedStringArrayAssertion) Run(value resp_value.Value) error {
if value.Type != resp_value.ARRAY {
return fmt.Errorf("Expected an array, got %s", value.Type)
}

if len(value.Array()) != len(a.ExpectedValue) {
return fmt.Errorf("Expected %d elements in array, got %d (%s)", len(a.ExpectedValue), len(value.Array()), value.FormattedString())
}

actualElementStringArray := make([]string, len(value.Array()))
for i := range value.Array() {
actualElement := value.Array()[i]
if actualElement.Type != resp_value.BULK_STRING && actualElement.Type != resp_value.SIMPLE_STRING {
return fmt.Errorf("Expected element #%d to be a string, got %s", i+1, actualElement.Type)
}
actualElementStringArray[i] = value.Array()[i].String()
}

expectedValueArrayForPrinting, _ := json.Marshal(a.ExpectedValue)
expectedValueStringArray := make([]string, len(a.ExpectedValue))
copy(expectedValueStringArray, a.ExpectedValue)
sort.Strings(actualElementStringArray)
sort.Strings(expectedValueStringArray)

for i, expectedValue := range expectedValueStringArray {
actualElement := actualElementStringArray[i]

if actualElement != expectedValue {
return fmt.Errorf("Expected: %v (in any order), got %v", string(expectedValueArrayForPrinting), value.FormattedString())
}
}

return nil
}
1 change: 1 addition & 0 deletions internal/stages_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ func normalizeTesterOutput(testerOutput []byte) []byte {
"xadd_id": {regexp.MustCompile(`\d{13}-\d+`)},
"rdb_bytes": {regexp.MustCompile(`"\$[0-9]+\\r\\nREDIS.*"`)},
"info_replication_bytes": {regexp.MustCompile(`"\$[0-9]+\\r\\n# Replication\\r\\n[^"]+"`)},
"rdb_keys": {regexp.MustCompile(`\[stage-11\] .*Received .*`)},
}

for replacement, regexes := range replacements {
Expand Down
Loading

0 comments on commit 61229ad

Please sign in to comment.