Repository: fppt/jedis-mock
Pull request #46 (2019-05-28 21:12:10)

SSCAN support
Hey, I added support for sscan, in the process I modified a bit the base scan to in future make easier to implement other "scan like" commands


Patch:
diff --git a/src/main/java/com/github/fppt/jedismock/operations/OperationFactory.java b/src/main/java/com/github/fppt/jedismock/operations/OperationFactory.java
index f71b2f4..0759e07 100644
--- a/src/main/java/com/github/fppt/jedismock/operations/OperationFactory.java
+++ b/src/main/java/com/github/fppt/jedismock/operations/OperationFactory.java
@@ -59,6 +59,7 @@ public class OperationFactory {
         TRANSACTIONAL_OPERATIONS.put("keys", RO_keys::new);
         TRANSACTIONAL_OPERATIONS.put("sadd", RO_sadd::new);
         TRANSACTIONAL_OPERATIONS.put("scan", RO_scan::new);
+        TRANSACTIONAL_OPERATIONS.put("sscan", RO_sscan::new);
         TRANSACTIONAL_OPERATIONS.put("spop", RO_spop::new);
         TRANSACTIONAL_OPERATIONS.put("srem", RO_srem::new);
         TRANSACTIONAL_OPERATIONS.put("scard", RO_scard::new);
diff --git a/src/main/java/com/github/fppt/jedismock/operations/RO_scan.java b/src/main/java/com/github/fppt/jedismock/operations/RO_scan.java
index 9d6a695..a8f1b66 100644
--- a/src/main/java/com/github/fppt/jedismock/operations/RO_scan.java
+++ b/src/main/java/com/github/fppt/jedismock/operations/RO_scan.java
@@ -21,32 +21,34 @@ class RO_scan extends AbstractRedisOperation {
     private static final String MATCH = "match";
     private static final String COUNT = "count";
 
+    protected Slice cursorSlice;
+    protected int size;
+
     RO_scan(RedisBase base, List<Slice> params) {
         super(base, params);
     }
 
-    Slice response() {
+    @Override
+    void doOptionalWork() {
+        this.cursorSlice = params().get(0);
+        this.size = base().keys().size();
+    }
 
-        Slice cursorSlice = params().get(0);
+    Slice response() {
         long cursor = cursorSlice != null ? convertToLong(cursorSlice.toString()) : CURSOR_START;
 
         String match = extractParameter(params(), MATCH).map(Slice::toString).orElse("*");
         long count = extractParameter(params(), COUNT).map(s -> convertToLong(s.toString())).orElse(DEFAULT_COUNT);
 
         String regex = Utils.createRegexFromGlob(match);
-        List<Slice> matchingKeys = base().keys().stream()
-                .skip(cursor)
-                .limit(count)
-                .filter(x -> x.toString().matches(regex))
-                .map(Response::bulkString)
-                .collect(Collectors.toList());
+        List<Slice> matchingValues = getMatchingValues(regex, cursor, count);
 
         cursor = cursor + count;
-        if (cursor >= base().keys().size()) {
+        if (cursor >= size) {
             cursor = CURSOR_START;
         }
 
-        List<Slice> response = Lists.newArrayList(Response.bulkString(Slice.create(String.valueOf(cursor))), Response.array(matchingKeys));
+        List<Slice> response = Lists.newArrayList(Response.bulkString(Slice.create(String.valueOf(cursor))), Response.array(matchingValues));
         return Response.array(response);
     }
 
@@ -59,4 +61,13 @@ class RO_scan extends AbstractRedisOperation {
         }
         return Optional.empty();
     }
+
+    protected List<Slice> getMatchingValues(String regex, long cursor, long count) {
+        return base().keys().stream()
+                .skip(cursor)
+                .limit(count)
+                .filter(x -> x.toString().matches(regex))
+                .map(Response::bulkString)
+                .collect(Collectors.toList());
+    }
 }
diff --git a/src/main/java/com/github/fppt/jedismock/operations/RO_sscan.java b/src/main/java/com/github/fppt/jedismock/operations/RO_sscan.java
new file mode 100644
index 0000000..d756716
--- /dev/null
+++ b/src/main/java/com/github/fppt/jedismock/operations/RO_sscan.java
@@ -0,0 +1,37 @@
+package com.github.fppt.jedismock.operations;
+
+import com.github.fppt.jedismock.server.Response;
+import com.github.fppt.jedismock.server.Slice;
+import com.github.fppt.jedismock.storage.RedisBase;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+class RO_sscan extends RO_scan {
+
+    RO_sscan(RedisBase base, List<Slice> params) {
+        super(base, params);
+    }
+
+    private Slice keySlice;
+
+    @Override
+    void doOptionalWork() {
+        this.keySlice = params().get(0);
+        this.cursorSlice = params().get(1);
+        this.size = base().keys().size();
+    }
+
+    @Override
+    protected List<Slice> getMatchingValues(String regex, long cursor, long count) {
+        Set<Slice> set = getDataFromBase(keySlice, new HashSet<>());
+        this.size = set.size();
+        return set.stream().skip(cursor)
+                .limit(count)
+                .filter(x -> x.toString().matches(regex))
+                .map(Response::bulkString)
+                .collect(Collectors.toList());
+    }
+}


Test patch:
diff --git a/src/test/java/com/github/fppt/jedismock/comparisontests/SimpleOperationsTest.java b/src/test/java/com/github/fppt/jedismock/comparisontests/SimpleOperationsTest.java
index f3babc4..79d251e 100644
--- a/src/test/java/com/github/fppt/jedismock/comparisontests/SimpleOperationsTest.java
+++ b/src/test/java/com/github/fppt/jedismock/comparisontests/SimpleOperationsTest.java
@@ -47,14 +47,14 @@ public class SimpleOperationsTest extends ComparisonBase {
     }
 
     @Theory
-    public void whenUsingRpop_EnsureTheLastElementPushedIsReturned(Jedis jedis){
+    public void whenUsingRpop_EnsureTheLastElementPushedIsReturned(Jedis jedis) {
         String key = "Another key";
         jedis.rpush(key, "1", "2", "3");
         assertEquals(jedis.rpop(key), "3");
     }
 
     @Theory
-    public void whenUsingRpoplpush_CorrectResultsAreReturned(Jedis jedis){
+    public void whenUsingRpoplpush_CorrectResultsAreReturned(Jedis jedis) {
         String list1key = "list 1";
         String list2key = "list 2";
 
@@ -91,7 +91,7 @@ public class SimpleOperationsTest extends ComparisonBase {
     }
 
     @Theory
-    public void whenUsingFlushall_EnsureEverythingIsDeleted(Jedis jedis){
+    public void whenUsingFlushall_EnsureEverythingIsDeleted(Jedis jedis) {
         String key = "my-super-special-key";
         String value = "my-not-so-special-value";
 
@@ -103,7 +103,7 @@ public class SimpleOperationsTest extends ComparisonBase {
     }
 
     @Theory
-    public void whenUsingFlushdb_EnsureEverythingIsDeleted(Jedis jedis){
+    public void whenUsingFlushdb_EnsureEverythingIsDeleted(Jedis jedis) {
         String key = "my-super-special-key";
         String value = "my-not-so-special-value";
 
@@ -115,7 +115,7 @@ public class SimpleOperationsTest extends ComparisonBase {
     }
 
     @Theory
-    public void whenUsingLrem_EnsureDeletionsWorkAsExpected(Jedis jedis){
+    public void whenUsingLrem_EnsureDeletionsWorkAsExpected(Jedis jedis) {
         String key = "my-super-special-sexy-key";
         String hello = "hello";
         String foo = "foo";
@@ -142,7 +142,7 @@ public class SimpleOperationsTest extends ComparisonBase {
     }
 
     @Theory
-    public void whenUsingQuit_EnsureTheConnectionIsClosed(Jedis jedis){
+    public void whenUsingQuit_EnsureTheConnectionIsClosed(Jedis jedis) {
         //Create a new connection
         Client client = jedis.getClient();
         Jedis newJedis = new Jedis(client.getHost(), client.getPort());
@@ -157,7 +157,7 @@ public class SimpleOperationsTest extends ComparisonBase {
     @Theory
     public void whenConcurrentlyIncrementingAndDecrementingCount_EnsureFinalCountIsCorrect(Jedis jedis) throws ExecutionException, InterruptedException {
         String key = "my-count-tracker";
-        int [] count = new int[]{1, 5, 6, 2, -9, -2, 10, 11, 5, -2, -2};
+        int[] count = new int[]{1, 5, 6, 2, -9, -2, 10, 11, 5, -2, -2};
 
         jedis.set(key, "0");
         assertEquals(0, Integer.parseInt(jedis.get(key)));
@@ -183,12 +183,12 @@ public class SimpleOperationsTest extends ComparisonBase {
     }
 
     @Theory
-    public void whenPinging_Pong(Jedis jedis){
+    public void whenPinging_Pong(Jedis jedis) {
         assertEquals("PONG", jedis.ping());
     }
 
     @Theory
-    public void whenGettingKeys_EnsureCorrectKeysAreReturned(Jedis jedis){
+    public void whenGettingKeys_EnsureCorrectKeysAreReturned(Jedis jedis) {
         jedis.flushAll();
         jedis.mset("one", "1", "two", "2", "three", "3", "four", "4");
 
@@ -209,7 +209,7 @@ public class SimpleOperationsTest extends ComparisonBase {
     }
 
     @Theory
-    public void whenAddingToASet_EnsureTheSetIsUpdated(Jedis jedis){
+    public void whenAddingToASet_EnsureTheSetIsUpdated(Jedis jedis) {
         String key = "my-set-key";
         Set<String> mySet = new HashSet<>(Arrays.asList("a", "b", "c", "d"));
 
@@ -228,7 +228,7 @@ public class SimpleOperationsTest extends ComparisonBase {
     }
 
     @Theory
-    public void whenAddingToASet_ensureCountIsUpdated(Jedis jedis){
+    public void whenAddingToASet_ensureCountIsUpdated(Jedis jedis) {
         String key = "my-counted-set-key";
         Set<String> mySet = new HashSet<>(Arrays.asList("d", "e", "f"));
 
@@ -240,13 +240,13 @@ public class SimpleOperationsTest extends ComparisonBase {
     }
 
     @Theory
-    public void whenCalledForNonExistentSet_ensureScardReturnsZero(Jedis jedis){
+    public void whenCalledForNonExistentSet_ensureScardReturnsZero(Jedis jedis) {
         String key = "non-existent";
         assertEquals(0, jedis.scard(key).intValue());
     }
 
     @Theory
-    public void whenRemovingFromASet_EnsureTheSetIsUpdated(Jedis jedis){
+    public void whenRemovingFromASet_EnsureTheSetIsUpdated(Jedis jedis) {
         String key = "my-set-key";
         Set<String> mySet = new HashSet<>(Arrays.asList("a", "b", "c", "d"));
 
@@ -265,7 +265,7 @@ public class SimpleOperationsTest extends ComparisonBase {
     }
 
     @Theory
-    public void whenPoppingFromASet_EnsureTheSetIsUpdated(Jedis jedis){
+    public void whenPoppingFromASet_EnsureTheSetIsUpdated(Jedis jedis) {
 
         String key = "my-set-key-spop";
         Set<String> mySet = new HashSet<>(Arrays.asList("a", "b", "c", "d"));
@@ -276,18 +276,18 @@ public class SimpleOperationsTest extends ComparisonBase {
         String poppedValue;
         do {
             poppedValue = jedis.spop(key);
-            if(poppedValue != null) assertTrue("Popped value not in set", mySet.contains(poppedValue));
+            if (poppedValue != null) assertTrue("Popped value not in set", mySet.contains(poppedValue));
         } while (poppedValue != null);
     }
 
     @Theory
-    public void whenHSettingOnTheSameKeys_EnsureReturnTypeIs1WhenKeysAreNew(Jedis jedis){
+    public void whenHSettingOnTheSameKeys_EnsureReturnTypeIs1WhenKeysAreNew(Jedis jedis) {
         assertEquals(new Long(1L), jedis.hset(HASH, FIELD_1, VALUE_1));
         assertEquals(new Long(0L), jedis.hset(HASH, FIELD_1, VALUE_1));
     }
 
     @Theory
-    public void whenHSettingAndHGetting_EnsureValuesAreSetAndRetreived(Jedis jedis){
+    public void whenHSettingAndHGetting_EnsureValuesAreSetAndRetreived(Jedis jedis) {
         String field = "my-field";
         String hash = "my-hash";
         String value = "my-value";
@@ -298,7 +298,7 @@ public class SimpleOperationsTest extends ComparisonBase {
     }
 
     @Theory
-    public void whenHDeleting_EnsureValuesAreRemoved(Jedis jedis){
+    public void whenHDeleting_EnsureValuesAreRemoved(Jedis jedis) {
         String field = "my-field-2";
         String hash = "my-hash-2";
         String value = "my-value-2";
@@ -311,7 +311,7 @@ public class SimpleOperationsTest extends ComparisonBase {
     }
 
     @Theory
-    public void whenHGetAll_EnsureAllKeysAndValuesReturned(Jedis jedis){
+    public void whenHGetAll_EnsureAllKeysAndValuesReturned(Jedis jedis) {
         jedis.hset(HASH, FIELD_1, VALUE_1);
         jedis.hset(HASH, FIELD_2, VALUE_2);
 
@@ -336,7 +336,7 @@ public class SimpleOperationsTest extends ComparisonBase {
     }
 
     @Theory
-    public void whenUsingHsinter_EnsureSetIntersectionIsReturned(Jedis jedis){
+    public void whenUsingHsinter_EnsureSetIntersectionIsReturned(Jedis jedis) {
         String key1 = "my-set-key-1";
         Set<String> mySet1 = new HashSet<>(Arrays.asList("a", "b", "c", "d"));
         String key2 = "my-set-key-2";
@@ -360,7 +360,7 @@ public class SimpleOperationsTest extends ComparisonBase {
     }
 
     @Theory
-    public void whenUsingHMget_EnsureAllValuesReturnedForEachField(Jedis jedis){
+    public void whenUsingHMget_EnsureAllValuesReturnedForEachField(Jedis jedis) {
         jedis.hset(HASH, FIELD_1, VALUE_1);
         jedis.hset(HASH, FIELD_2, VALUE_2);
         jedis.hset(HASH, FIELD_3, VALUE_3);
@@ -376,7 +376,7 @@ public class SimpleOperationsTest extends ComparisonBase {
     }
 
     @Theory
-    public void whenUsingHMset_EnsureAllValuesAreSetForEachField(Jedis jedis){
+    public void whenUsingHMset_EnsureAllValuesAreSetForEachField(Jedis jedis) {
         Map<String, String> map = new HashMap<>();
         map.put(FIELD_1, VALUE_1);
         map.put(FIELD_2, VALUE_2);
@@ -392,7 +392,7 @@ public class SimpleOperationsTest extends ComparisonBase {
     }
 
     @Theory
-    public void whenUsingHsetnx_EnsureValueIsOnlyPutIfOtherValueDoesNotExist(Jedis jedis){
+    public void whenUsingHsetnx_EnsureValueIsOnlyPutIfOtherValueDoesNotExist(Jedis jedis) {
         assertNull(jedis.hget(HASH, FIELD_3));
         jedis.hsetnx(HASH, FIELD_3, VALUE_1);
         assertEquals(VALUE_1, jedis.hget(HASH, FIELD_3));
@@ -401,12 +401,12 @@ public class SimpleOperationsTest extends ComparisonBase {
     }
 
     @Theory
-    public void whenGettingInfo_EnsureSomeDateIsReturned(Jedis jedis){
+    public void whenGettingInfo_EnsureSomeDateIsReturned(Jedis jedis) {
         assertNotNull(jedis.info());
     }
 
     @Theory
-    public void whenCreatingKeys_existsValuesUpdated(Jedis jedis){
+    public void whenCreatingKeys_existsValuesUpdated(Jedis jedis) {
         jedis.set("foo", "bar");
         assertTrue(jedis.exists("foo"));
 
@@ -417,7 +417,7 @@ public class SimpleOperationsTest extends ComparisonBase {
     }
 
     @Theory
-    public void deletionRemovesKeys(Jedis jedis){
+    public void deletionRemovesKeys(Jedis jedis) {
         String key1 = "hey_toremove";
         String key2 = "hmap_toremove";
         jedis.set(key1, "value");
@@ -492,6 +492,88 @@ public class SimpleOperationsTest extends ComparisonBase {
         assertNotEquals(ScanParams.SCAN_POINTER_START, result.getStringCursor());
     }
 
+    @Theory
+    public void sscanReturnsAllValues(Jedis jedis) {
+
+        jedis.flushDB();
+
+        String key = "sscankey";
+        jedis.del(key);
+        String[] values = new String[20];
+        for (int i = 0; i < 20; i++) {
+            values[i] = (21 - i) + "_value_" + i;
+        }
+        jedis.sadd(key, values);
+
+        ScanResult<String> result = jedis.sscan(key, ScanParams.SCAN_POINTER_START, new ScanParams().count(30));
+
+        assertEquals(20, result.getResult().size());
+        assertTrue(result.getResult().contains(values[1]));
+    }
+
+    @Theory
+    public void sscanReturnsPartialSet(Jedis jedis) {
+
+        jedis.flushDB();
+
+        String key = "sscankey";
+        jedis.del(key);
+        String[] values = new String[20];
+        for (int i = 0; i < 20; i++) {
+            values[i] = (21 - i) + "_value_" + i;
+        }
+        jedis.sadd(key, values);
+
+        ScanResult<String> result = jedis.sscan(key, ScanParams.SCAN_POINTER_START, new ScanParams().count(13));
+        assertNotEquals(ScanParams.SCAN_POINTER_START, result.getStringCursor());
+    }
+
+    @Theory
+    public void sscanReturnsMatchingSet(Jedis jedis) {
+
+        jedis.flushDB();
+
+        String key = "sscankey";
+        jedis.del(key);
+        String[] values = new String[9];
+        for (int i = 0; i < 9; i++) {
+            values[i] = (21 - i) + "_value_" + i;
+        }
+        jedis.sadd(key, values);
+
+        ScanResult<String> result = jedis.sscan(key, ScanParams.SCAN_POINTER_START, new ScanParams().match("21_value_0"));
+
+        assertEquals(ScanParams.SCAN_POINTER_START, result.getStringCursor());
+        assertEquals(1, result.getResult().size());
+        assertTrue(result.getResult().contains(values[0]));
+    }
+
+    @Theory
+    public void sscanIterates(Jedis jedis) {
+
+        jedis.flushDB();
+
+        String key = "sscankey";
+        jedis.del(key);
+        String[] values = new String[45];
+        for (int i = 0; i < 45; i++) {
+            values[i] = (45 - i) + "_value_" + i;
+        }
+        jedis.sadd(key, values);
+        String cursor = null;
+
+        int iterations = 0;
+        while (cursor == null || !cursor.equals(ScanParams.SCAN_POINTER_START)) {
+            if (cursor == null) {
+                cursor = ScanParams.SCAN_POINTER_START;
+            }
+            ScanResult<String> result = jedis.sscan(key, cursor);
+            cursor = result.getStringCursor();
+            iterations++;
+        }
+        assertEquals(5, iterations);
+    }
+
     @Theory
     public void zaddAddsKey(Jedis jedis) {