Skip to content

Commit

Permalink
Added more tests for SET command (#1118)
Browse files Browse the repository at this point in the history
  • Loading branch information
psrvere authored Oct 17, 2024
1 parent e377e33 commit 373e868
Show file tree
Hide file tree
Showing 3 changed files with 131 additions and 30 deletions.
93 changes: 74 additions & 19 deletions integration_tests/commands/async/set_test.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package async

import (
"fmt"
"net"
"strconv"
"sync"
"testing"
"time"

"gotest.tools/v3/assert"
"github.com/stretchr/testify/assert"
)

type TestCase struct {
Expand Down Expand Up @@ -38,12 +41,11 @@ func TestSet(t *testing.T) {

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// deleteTestKeys([]string{"k"}, store)
FireCommand(conn, "DEL k")

for i, cmd := range tc.commands {
result := FireCommand(conn, cmd)
assert.DeepEqual(t, tc.expected[i], result)
assert.Equal(t, tc.expected[i], result)
}
})
}
Expand Down Expand Up @@ -75,6 +77,11 @@ func TestSetWithOptions(t *testing.T) {
commands: []string{"DEL k", "SET k v XX", "GET k"},
expected: []interface{}{int64(0), "(nil)", "(nil)"},
},
{
name: "XX on existing key",
commands: []string{"SET k v1", "SET k v2 XX", "GET k"},
expected: []interface{}{"OK", "OK", "v2"},
},
{
name: "NX on non-existing key",
commands: []string{"DEL k", "SET k v NX", "GET k"},
Expand All @@ -100,21 +107,11 @@ func TestSetWithOptions(t *testing.T) {
commands: []string{"SET k2 v2 PXAT 123123", "GET k2"},
expected: []interface{}{"OK", "(nil)"},
},
{
name: "XX on existing key",
commands: []string{"SET k v1", "SET k v2 XX", "GET k"},
expected: []interface{}{"OK", "OK", "v2"},
},
{
name: "Multiple XX operations",
commands: []string{"SET k v1", "SET k v2 XX", "SET k v3 XX", "GET k"},
expected: []interface{}{"OK", "OK", "OK", "v3"},
},
{
name: "EX option",
commands: []string{"SET k v EX 1", "GET k", "SLEEP 2", "GET k"},
expected: []interface{}{"OK", "v", "OK", "(nil)"},
},
{
name: "XX option",
commands: []string{"SET k v XX EX 1", "GET k", "SLEEP 2", "GET k", "SET k v XX EX 1", "GET k"},
Expand All @@ -124,7 +121,6 @@ func TestSetWithOptions(t *testing.T) {

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// deleteTestKeys([]string{"k", "k1", "k2"}, store)
FireCommand(conn, "DEL k")
FireCommand(conn, "DEL k1")
FireCommand(conn, "DEL k2")
Expand All @@ -144,21 +140,19 @@ func TestSetWithExat(t *testing.T) {

t.Run("SET with EXAT",
func(t *testing.T) {
// deleteTestKeys([]string{"k"}, store)
FireCommand(conn, "DEL k")
assert.Equal(t, "OK", FireCommand(conn, "SET k v EXAT "+Etime), "Value mismatch for cmd SET k v EXAT "+Etime)
assert.Equal(t, "v", FireCommand(conn, "GET k"), "Value mismatch for cmd GET k")
assert.Assert(t, FireCommand(conn, "TTL k").(int64) <= 5, "Value mismatch for cmd TTL k")
assert.True(t, FireCommand(conn, "TTL k").(int64) <= 5, "Value mismatch for cmd TTL k")
time.Sleep(3 * time.Second)
assert.Assert(t, FireCommand(conn, "TTL k").(int64) <= 3, "Value mismatch for cmd TTL k")
assert.True(t, FireCommand(conn, "TTL k").(int64) <= 3, "Value mismatch for cmd TTL k")
time.Sleep(3 * time.Second)
assert.Equal(t, "(nil)", FireCommand(conn, "GET k"), "Value mismatch for cmd GET k")
assert.Equal(t, int64(-2), FireCommand(conn, "TTL k"), "Value mismatch for cmd TTL k")
})

t.Run("SET with invalid EXAT expires key immediately",
func(t *testing.T) {
// deleteTestKeys([]string{"k"}, store)
FireCommand(conn, "DEL k")
assert.Equal(t, "OK", FireCommand(conn, "SET k v EXAT "+BadTime), "Value mismatch for cmd SET k v EXAT "+BadTime)
assert.Equal(t, "(nil)", FireCommand(conn, "GET k"), "Value mismatch for cmd GET k")
Expand All @@ -167,7 +161,6 @@ func TestSetWithExat(t *testing.T) {

t.Run("SET with EXAT and PXAT returns syntax error",
func(t *testing.T) {
// deleteTestKeys([]string{"k"}, store)
FireCommand(conn, "DEL k")
assert.Equal(t, "ERR syntax error", FireCommand(conn, "SET k v PXAT "+Etime+" EXAT "+Etime), "Value mismatch for cmd SET k v PXAT "+Etime+" EXAT "+Etime)
assert.Equal(t, "(nil)", FireCommand(conn, "GET k"), "Value mismatch for cmd GET k")
Expand Down Expand Up @@ -198,3 +191,65 @@ func TestWithKeepTTLFlag(t *testing.T) {

assert.Equal(t, out, FireCommand(conn, cmd), "Value mismatch for cmd %s\n.", cmd)
}

type reqSet struct {
key string
value int64
}
type respSet struct {
client net.Conn
response int64
}

func TestConcurrentSetCommands(t *testing.T) {
numOfClients := 10 // parallel connections

requests := make(map[net.Conn]reqSet, numOfClients)
// create requests from different clients
for i := 0; i < numOfClients; i++ {
client := getLocalConnection()
req := reqSet{
key: fmt.Sprintf("k%v", i),
value: int64(i),
}
requests[client] = req
}

// set and get value
var wg sync.WaitGroup
respChan := make(chan respSet, numOfClients)
for client, req := range requests {
wg.Add(1)
go executeSetAndGet(t, client, req, respChan, &wg)
}
wg.Wait()
close(respChan)

// verify responses
assert.Equal(t, numOfClients, len(respChan))
for resp := range respChan {
got := resp.response
want := requests[resp.client].value
assert.Equal(t, want, got, "LocalAdds: %v, got: %v, want: %v", resp.client.LocalAddr(), want, got)
}
}

func executeSetAndGet(t *testing.T, client net.Conn, req reqSet, respChan chan respSet, wg *sync.WaitGroup) {
defer wg.Done()
defer client.Close()

resp := FireCommand(client, fmt.Sprintf("SET %v %v", req.key, req.value))
assert.Equal(t, "OK", resp, "setting value failed")

resp = FireCommand(client, fmt.Sprintf("GET %v", req.key))
assert.NotNil(t, resp, "received nil value in GET") // value should not be nil i.e. not set

response, ok := resp.(int64)
assert.True(t, ok, "typecasting failed. LocalAddr: %v, response: %v, ok: %v", client.LocalAddr(), response, ok)

rs := respSet{
client: client,
response: response,
}
respChan <- rs
}
53 changes: 49 additions & 4 deletions internal/eval/eval_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,11 +203,36 @@ func testEvalSET(t *testing.T, store *dstore.Store) {
input: []string{"KEY", "VAL", Ex, "2"},
migratedOutput: EvalResponse{Result: clientio.OK, Error: nil},
},
{
name: "key val pair and invalid negative EX",
input: []string{"KEY", "VAL", Ex, "-2"},
migratedOutput: EvalResponse{Result: nil, Error: errors.New("ERR invalid expire time in 'set' command")},
},
{
name: "key val pair and invalid float EX",
input: []string{"KEY", "VAL", Ex, "2.0"},
migratedOutput: EvalResponse{Result: nil, Error: errors.New("ERR value is not an integer or out of range")},
},
{
name: "key val pair and invalid out of range int EX",
input: []string{"KEY", "VAL", Ex, "9223372036854775807"},
migratedOutput: EvalResponse{Result: nil, Error: errors.New("ERR invalid expire time in 'set' command")},
},
{
name: "key val pair and invalid greater than max duration EX",
input: []string{"KEY", "VAL", Ex, "9223372036854775"},
migratedOutput: EvalResponse{Result: nil, Error: errors.New("ERR invalid expire time in 'set' command")},
},
{
name: "key val pair and invalid EX",
input: []string{"KEY", "VAL", Ex, "invalid_expiry_val"},
migratedOutput: EvalResponse{Result: nil, Error: errors.New("ERR value is not an integer or out of range")},
},
{
name: "key val pair and PX no val",
input: []string{"KEY", "VAL", Px},
migratedOutput: EvalResponse{Result: nil, Error: errors.New("ERR syntax error")},
},
{
name: "key val pair and valid PX",
input: []string{"KEY", "VAL", Px, "2000"},
Expand All @@ -218,6 +243,26 @@ func testEvalSET(t *testing.T, store *dstore.Store) {
input: []string{"KEY", "VAL", Px, "invalid_expiry_val"},
migratedOutput: EvalResponse{Result: nil, Error: errors.New("ERR value is not an integer or out of range")},
},
{
name: "key val pair and invalid negative PX",
input: []string{"KEY", "VAL", Px, "-2"},
migratedOutput: EvalResponse{Result: nil, Error: errors.New("ERR invalid expire time in 'set' command")},
},
{
name: "key val pair and invalid float PX",
input: []string{"KEY", "VAL", Px, "2.0"},
migratedOutput: EvalResponse{Result: nil, Error: errors.New("ERR value is not an integer or out of range")},
},
{
name: "key val pair and invalid out of range int PX",
input: []string{"KEY", "VAL", Px, "9223372036854775807"},
migratedOutput: EvalResponse{Result: nil, Error: errors.New("ERR invalid expire time in 'set' command")},
},
{
name: "key val pair and invalid greater than max duration PX",
input: []string{"KEY", "VAL", Px, "9223372036854775"},
migratedOutput: EvalResponse{Result: nil, Error: errors.New("ERR invalid expire time in 'set' command")},
},
{
name: "key val pair and both EX and PX",
input: []string{"KEY", "VAL", Ex, "2", Px, "2000"},
Expand Down Expand Up @@ -5361,18 +5406,18 @@ func testEvalAPPEND(t *testing.T, store *dstore.Store) {
input: []string{"bitKey", "val"},
output: diceerrors.NewErrWithFormattedMessage(diceerrors.WrongTypeErr),
},
"append value with leading zeros":{
"append value with leading zeros": {
setup: func() {
store.Del("key_with_leading_zeros")
},
input: []string{"key_with_leading_zeros", "0043"},
input: []string{"key_with_leading_zeros", "0043"},
output: clientio.Encode(4, false), // The length of "0043" is 4
validator: func(output []byte){
validator: func(output []byte) {
obj := store.Get("key_with_leading_zeros")
_, enc := object.ExtractTypeEncoding(obj)
if enc != object.ObjEncodingRaw {
t.Errorf("expected encoding to be Raw for string with leading zeros")
}
}
if obj.Value.(string) != "0043" {
t.Errorf("expected value to be '0043', got %v", obj.Value)
}
Expand Down
15 changes: 8 additions & 7 deletions internal/eval/store_eval.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import (
"strconv"
"strings"

"github.com/bytedance/sonic"
"github.com/axiomhq/hyperloglog"
"github.com/bytedance/sonic"
"github.com/dicedb/dice/internal/clientio"
diceerrors "github.com/dicedb/dice/internal/errors"
"github.com/dicedb/dice/internal/eval/sortedset"
Expand All @@ -22,9 +22,10 @@ import (
//
// EX or ex which will set the expiry time(in secs) for the key
// PX or px which will set the expiry time(in milliseconds) for the key
// EXAT or exat which will set the specified Unix time at which the key will expire, in seconds (a positive integer).
// PXAT or PX which will the specified Unix time at which the key will expire, in milliseconds (a positive integer).
// XX orr xx which will only set the key if it already exists.
// EXAT or exat which will set the specified Unix time at which the key will expire, in seconds (a positive integer)
// PXAT or PX which will the specified Unix time at which the key will expire, in milliseconds (a positive integer)
// XX or xx which will only set the key if it already exists
// NX or nx which will only set the key if it doesn not already exist
//
// Returns encoded error response if at least a <key, value> pair is not part of args
// Returns encoded error response if expiry time value in not integer
Expand Down Expand Up @@ -556,7 +557,7 @@ func evalJSONCLEAR(args []string, store *dstore.Store) *EvalResponse {
obj.Value = jsonData
return &EvalResponse{
Result: countClear,
Error: nil,
Error: nil,
}
}

Expand Down Expand Up @@ -718,7 +719,7 @@ func evalJSONSTRLEN(args []string, store *dstore.Store) *EvalResponse {
}
return &EvalResponse{
Result: strLenResults,
Error: nil,
Error: nil,
}
}

Expand Down Expand Up @@ -849,7 +850,7 @@ func evalJSONOBJLEN(args []string, store *dstore.Store) *EvalResponse {
}
return &EvalResponse{
Result: objectLen,
Error: nil,
Error: nil,
}
}

Expand Down

0 comments on commit 373e868

Please sign in to comment.