Skip to content

Commit

Permalink
update tests
Browse files Browse the repository at this point in the history
Signed-off-by: TJ Zhang <tj.zhang@improving.com>
  • Loading branch information
TJ Zhang committed Dec 10, 2024
1 parent 1099130 commit af06931
Show file tree
Hide file tree
Showing 6 changed files with 224 additions and 17 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@
* Core: Improve retry logic and update unmaintained dependencies for Rust lint CI ([#2673](https://github.com/valkey-io/valkey-glide/pull/2643))
* Core: Release the read lock while creating connections in `refresh_connections` ([#2630](https://github.com/valkey-io/valkey-glide/issues/2630))
* Core: SlotMap refactor - Added NodesMap, Update the slot map upon MOVED errors ([#2682](https://github.com/valkey-io/valkey-glide/issues/2682))
* Go: Add `SScan` and `SMove` ([#2789](https://github.com/valkey-io/valkey-glide/issues/2789))

#### Breaking Changes

Expand Down
12 changes: 6 additions & 6 deletions go/api/base_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -626,27 +626,27 @@ func (client *baseClient) SPop(key string) (Result[string], error) {
return handleStringResponse(result)
}

func (client *baseClient) SScan(key string, cursor string) (Result[string], []Result[string], error) {
func (client *baseClient) SScan(key string, cursor string) (string, []string, error) {
result, err := client.executeCommand(C.SScan, []string{key, cursor})
if err != nil {
return CreateNilStringResult(), nil, err
return "", nil, err
}
return handleScanResponse(result)
}

func (client *baseClient) SScanWithOption(
key string,
cursor string,
options BaseScanOptions,
) (Result[string], []Result[string], error) {
options *BaseScanOptions,
) (string, []string, error) {
optionArgs, err := options.toArgs()
if err != nil {
return CreateNilStringResult(), nil, err
return "", nil, err
}

result, err := client.executeCommand(C.SScan, append([]string{key, cursor}, optionArgs...))
if err != nil {
return CreateNilStringResult(), nil, err
return "", nil, err
}
return handleScanResponse(result)
}
Expand Down
14 changes: 14 additions & 0 deletions go/api/command_options.go
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,20 @@ type BaseScanOptions struct {
count int64
}

func NewBaseScanOptionsBuilder() *BaseScanOptions {
return &BaseScanOptions{}
}

func (scanOptions *BaseScanOptions) SetMatch(m string) *BaseScanOptions {
scanOptions.match = m
return scanOptions
}

func (scanOptions *BaseScanOptions) SetCount(c int64) *BaseScanOptions {
scanOptions.count = c
return scanOptions
}

func (opts *BaseScanOptions) toArgs() ([]string, error) {
args := []string{}
var err error
Expand Down
24 changes: 20 additions & 4 deletions go/api/response_handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -361,17 +361,33 @@ type ScanResult struct {

func handleScanResponse(
response *C.struct_CommandResponse,
) (Result[string], []Result[string], error) {
) (string, []string, error) {
defer C.free_command_response(response)

slice, err := parseArray(response)
if err != nil {
return CreateNilStringResult(), nil, err
return "", nil, err
}

if arr, ok := slice.([]interface{}); ok {
return arr[0].(Result[string]), arr[1].([]Result[string]), nil
resCollection, err := convertToStrings(arr[1].([]interface{}))
if err != nil {
return "", nil, err
}
return arr[0].(string), resCollection, nil
}

return CreateNilStringResult(), nil, err
return "", nil, err
}

func convertToStrings(input []interface{}) ([]string, error) {
result := make([]string, len(input))
for i, v := range input {
str, ok := v.(string)
if !ok {
return nil, fmt.Errorf("element at index %d is not a string: %v", i, v)
}
result[i] = str
}
return result, nil
}
4 changes: 2 additions & 2 deletions go/api/set_commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ type SetCommands interface {
// Example:
//
// [valkey.io]: https://valkey.io/commands/sscan/
SScan(key string, cursor string) (Result[string], []Result[string], error)
SScan(key string, cursor string) (string, []string, error)

// Iterates incrementally over a set.
//
Expand All @@ -314,7 +314,7 @@ type SetCommands interface {
// Example:
//
// [valkey.io]: https://valkey.io/commands/sscan/
SScanWithOption(key string, cursor string, options BaseScanOptions) (Result[string], []Result[string], error)
SScanWithOption(key string, cursor string, options *BaseScanOptions) (string, []string, error)

// Moves `member` from the set at `source` to the set at `destination`, removing it from the source set.
// Creates a new destination set if needed. The operation is atomic.
Expand Down
186 changes: 181 additions & 5 deletions go/integTest/shared_commands_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package integTest

import (
"math"
"strconv"
"time"

"github.com/google/uuid"
Expand Down Expand Up @@ -1691,9 +1692,9 @@ func (suite *GlideTestSuite) TestSMove() {
suite.runWithDefaultClients(func(client api.BaseClient) {
key1 := "{key}-1-" + uuid.NewString()
key2 := "{key}-2-" + uuid.NewString()
// key3 := "{key}-3-" + uuid.NewString()
// stringKey := "{key}-4-" + uuid.NewString()
// nonExistingKey := "{key}-5-" + uuid.NewString()
key3 := "{key}-3-" + uuid.NewString()
stringKey := "{key}-4-" + uuid.NewString()
nonExistingKey := "{key}-5-" + uuid.NewString()
memberArray1 := []string{"1", "2", "3"}
memberArray2 := []string{"2", "3"}
t := suite.T()
Expand Down Expand Up @@ -1731,18 +1732,193 @@ func (suite *GlideTestSuite) TestSMove() {

res7, err := client.SMembers(key1)
assert.NoError(t, err)
assert.Len(t, res4, 2)
assert.Len(t, res7, 2)
assert.Contains(t, res7, api.CreateStringResult("2"))
assert.Contains(t, res7, api.CreateStringResult("3"))

res8, err := client.SMembers(key2)
assert.NoError(t, err)
assert.Len(t, res4, 2)
assert.Len(t, res8, 2)
assert.Contains(t, res8, api.CreateStringResult("1"))
assert.Contains(t, res8, api.CreateStringResult("3"))

// attempt to move from a non-existing key
res9, err := client.SMove(nonExistingKey, key1, "4")
assert.NoError(t, err)
assert.False(t, res9.Value())

res10, err := client.SMembers(key1)
assert.NoError(t, err)
assert.Len(t, res10, 2)
assert.Contains(t, res10, api.CreateStringResult("2"))
assert.Contains(t, res10, api.CreateStringResult("3"))

// move to a new set
res11, err := client.SMove(key1, key3, "2")
assert.NoError(t, err)
assert.True(t, res11.Value())

res12, err := client.SMembers(key1)
assert.NoError(t, err)
assert.Len(t, res12, 1)
assert.Contains(t, res12, api.CreateStringResult("3"))

res13, err := client.SMembers(key3)
assert.NoError(t, err)
assert.Len(t, res13, 1)
assert.Contains(t, res13, api.CreateStringResult("2"))

// attempt to move a missing element
res14, err := client.SMove(key1, key3, "42")
assert.NoError(t, err)
assert.False(t, res14.Value())

res12, err = client.SMembers(key1)
assert.NoError(t, err)
assert.Len(t, res12, 1)
assert.Contains(t, res12, api.CreateStringResult("3"))

res13, err = client.SMembers(key3)
assert.NoError(t, err)
assert.Len(t, res13, 1)
assert.Contains(t, res13, api.CreateStringResult("2"))

// moving missing element to missing key
res15, err := client.SMove(key1, nonExistingKey, "42")
assert.NoError(t, err)
assert.False(t, res15.Value())

res12, err = client.SMembers(key1)
assert.NoError(t, err)
assert.Len(t, res12, 1)
assert.Contains(t, res12, api.CreateStringResult("3"))

// key exists but is not contain a set
_, err = client.Set(stringKey, "value")
assert.NoError(t, err)

_, err = client.SMove(stringKey, key1, "_")
assert.NotNil(suite.T(), err)
assert.IsType(suite.T(), &api.RequestError{}, err)
})
}

func (suite *GlideTestSuite) TestSScan() {
suite.runWithDefaultClients(func(client api.BaseClient) {
key1 := "{key}-1-" + uuid.NewString()
key2 := "{key}-2-" + uuid.NewString()
initialCursor := "0"
defaultCount := 10
// use large dataset to force an iterative cursor.
numMembers := make([]string, 50000)
charMembers := []string{"a", "b", "c", "d", "e"}
t := suite.T()

// populate the dataset slice
for i := 0; i < 50000; i++ {
numMembers[i] = strconv.Itoa(i)
}

// empty set
resCursor, resCollection, err := client.SScan(key1, initialCursor)
assert.NoError(t, err)
assert.Equal(t, initialCursor, resCursor)
assert.Empty(t, resCollection)

// negative cursor
if suite.serverVersion < "8.0.0" {
resCursor, resCollection, err = client.SScan(key1, "-1")
assert.NoError(t, err)
assert.Equal(t, initialCursor, resCursor)
assert.Empty(t, resCollection)
} else {
_, _, err = client.SScan(key1, "-1")
assert.NotNil(suite.T(), err)
assert.IsType(suite.T(), &api.RequestError{}, err)
}

// result contains the whole set
res, err := client.SAdd(key1, charMembers)
assert.NoError(t, err)
assert.Equal(t, int64(len(charMembers)), res.Value())
resCursor, resCollection, err = client.SScan(key1, initialCursor)
assert.NoError(t, err)
assert.Equal(t, initialCursor, resCursor)
assert.Equal(t, len(charMembers), len(resCollection))
assert.True(t, isSubset(resCollection, charMembers))

opts := api.NewBaseScanOptionsBuilder().SetMatch("a")
resCursor, resCollection, err = client.SScanWithOption(key1, initialCursor, opts)
assert.NoError(t, err)
assert.Equal(t, initialCursor, resCursor)
assert.True(t, isSubset(resCollection, []string{"a"}))

// result contains a subset of the key
res, err = client.SAdd(key1, numMembers)
assert.NoError(t, err)
assert.Equal(t, int64(50000), res.Value())
resCursor, resCollection, err = client.SScan(key1, "0")
assert.NoError(t, err)

// 0 is returned for the cursor of the last iteration
for resCursor != "0" {
nextCursor, nextCol, err := client.SScan(key1, resCursor)
assert.NoError(t, err)
assert.NotEqual(t, nextCursor, resCursor)
assert.False(t, isSubset(resCollection, nextCol))
resCollection = append(resCollection, nextCol...)
resCursor = nextCursor
}
assert.NotEmpty(t, resCollection)
assert.True(t, isSubset(numMembers, resCollection))
assert.True(t, isSubset(charMembers, resCollection))

// test match pattern
opts = api.NewBaseScanOptionsBuilder().SetMatch("*")
resCursor, resCollection, err = client.SScanWithOption(key1, initialCursor, opts)
assert.NoError(t, err)
assert.NotEqual(t, initialCursor, resCursor)
assert.GreaterOrEqual(t, len(resCollection), defaultCount)

// test count
opts = api.NewBaseScanOptionsBuilder().SetCount(20)
resCursor, resCollection, err = client.SScanWithOption(key1, initialCursor, opts)
assert.NoError(t, err)
assert.NotEqual(t, initialCursor, resCursor)
assert.GreaterOrEqual(t, len(resCollection), 20)

// test count with match, returns a non-empty array
opts = api.NewBaseScanOptionsBuilder().SetMatch("1*").SetCount(20)
resCursor, resCollection, err = client.SScanWithOption(key1, initialCursor, opts)
assert.NoError(t, err)
assert.NotEqual(t, initialCursor, resCursor)
assert.GreaterOrEqual(t, len(resCollection), 0)

// exceptions
// non-set key
_, err = client.Set(key2, "test")
assert.NoError(t, err)

_, _, err = client.SScan(key2, initialCursor)
assert.NotNil(suite.T(), err)
assert.IsType(suite.T(), &api.RequestError{}, err)
})
}

// check if sliceA is a subset of sliceB
func isSubset(sliceA, sliceB []string) bool {
setB := make(map[string]struct{})
for _, v := range sliceB {
setB[v] = struct{}{}
}
for _, v := range sliceA {
if _, found := setB[v]; !found {
return false
}
}
return true
}

func (suite *GlideTestSuite) TestLRange() {
suite.runWithDefaultClients(func(client api.BaseClient) {
list := []string{"value4", "value3", "value2", "value1"}
Expand Down

0 comments on commit af06931

Please sign in to comment.