DonalEvans commented on a change in pull request #7278:
URL: https://github.com/apache/geode/pull/7278#discussion_r793918352



##########
File path: 
geode-for-redis/src/integrationTest/java/org/apache/geode/redis/internal/commands/executor/set/SScanIntegrationTest.java
##########
@@ -37,21 +36,29 @@ public int getPort() {
   }
 
   @Test
-  public void 
givenDifferentCursorThanSpecifiedByPreviousSscan_returnsAllMembers() {
-    List<byte[]> memberList = new ArrayList<>();
-    for (int i = 0; i < 10; i++) {
-      jedis.sadd("a", String.valueOf(i));
-      memberList.add(String.valueOf(i).getBytes());
-    }
-
-    ScanParams scanParams = new ScanParams();
-    scanParams.count(5);
-    ScanResult<byte[]> result =
-        jedis.sscan("a".getBytes(), "0".getBytes(), scanParams);
-    assertThat(result.isCompleteIteration()).isFalse();
-
-    result = jedis.sscan("a".getBytes(), "100".getBytes());
-
-    
assertThat(result.getResult()).containsExactlyInAnyOrderElementsOf(memberList);
+  public void givenCursorGreaterThanUnsignedLongCapacity_returnsCursorError() {

Review comment:
       The test names in this class are inaccurate, as we're testing CURSOR 
values related to *signed* Long max values, not unsigned. A better naming 
format would be "LongMaxValue" or "LongMinValue" rather than 
"UnsignedLongCapacity".

##########
File path: 
geode-for-redis/src/integrationTest/java/org/apache/geode/redis/internal/commands/executor/set/SScanIntegrationTest.java
##########
@@ -37,21 +36,29 @@ public int getPort() {
   }
 
   @Test
-  public void 
givenDifferentCursorThanSpecifiedByPreviousSscan_returnsAllMembers() {
-    List<byte[]> memberList = new ArrayList<>();
-    for (int i = 0; i < 10; i++) {
-      jedis.sadd("a", String.valueOf(i));
-      memberList.add(String.valueOf(i).getBytes());
-    }
-
-    ScanParams scanParams = new ScanParams();
-    scanParams.count(5);
-    ScanResult<byte[]> result =
-        jedis.sscan("a".getBytes(), "0".getBytes(), scanParams);
-    assertThat(result.isCompleteIteration()).isFalse();
-
-    result = jedis.sscan("a".getBytes(), "100".getBytes());
-
-    
assertThat(result.getResult()).containsExactlyInAnyOrderElementsOf(memberList);
+  public void givenCursorGreaterThanUnsignedLongCapacity_returnsCursorError() {
+    assertThatThrownBy(
+        () -> jedis.sscan(KEY, SIGNED_LONG_MAX.add(BigInteger.ONE).toString()))
+            .hasMessageContaining(ERROR_CURSOR);
+  }
+
+  @Test
+  public void 
givenNegativeCursorGreaterThanUnsignedLongCapacity_returnsCursorError() {
+    assertThatThrownBy(
+        () -> jedis.sscan(KEY, 
SIGNED_LONG_MIN.add(BigInteger.valueOf(-1)).toString()))
+            .hasMessageContaining(ERROR_CURSOR);
+  }
+
+  @Test
+  public void givenCursorEqualToUnsignedLongCapacity_doesNotError() {
+    jedis.sadd(KEY, "1");
+    assertThatNoException()
+        .isThrownBy(() -> jedis.sscan(KEY, 
SIGNED_LONG_MAX.subtract(BigInteger.ONE).toString()));
+  }
+
+  @Test
+  public void 
givenNegativeCursorEqualToUnsignedLongCapacity_returnsCursorError() {
+    jedis.sadd(KEY, "1");
+    assertThatNoException().isThrownBy(() -> jedis.sscan(KEY, 
SIGNED_LONG_MAX.toString()));

Review comment:
       These tests should be asserting that CURSOR values equal to 
`Long.MAX_VALUE` (not that value -1) and `Long.MIN_VALUE` do not error, and 
should be named accordingly.

##########
File path: 
geode-for-redis/src/integrationTest/java/org/apache/geode/redis/internal/commands/executor/set/AbstractSScanIntegrationTest.java
##########
@@ -54,295 +74,397 @@ public void tearDown() {
   }
 
   @Test
-  public void givenNoKeyArgument_returnsWrongNumberOfArgumentsError() {
-    assertThatThrownBy(() -> jedis.sendCommand("key", Protocol.Command.SSCAN))
-        .hasMessageContaining("ERR wrong number of arguments for 'sscan' 
command");
+  public void givenLessThanTwoArguments_returnsWrongNumberOfArgumentsError() {
+    assertAtLeastNArgs(jedis, Protocol.Command.SSCAN, 2);
+  }
+
+  @Test
+  public void givenNonexistentKey_returnsEmptyArray() {
+    ScanResult<String> result = jedis.sscan("nonexistent", ZERO_CURSOR);
+
+    assertThat(result.isCompleteIteration()).isTrue();
+    assertThat(result.getResult()).isEmpty();
+  }
+
+  @Test
+  public void 
givenNonexistentKeyAndIncorrectOptionalArguments_returnsEmptyArray() {
+    result = sendCustomSscanCommand("nonexistentKey", "nonexistentKey", 
ZERO_CURSOR, "ANY");
+    assertThat(result.getResult()).isEmpty();
   }
 
   @Test
-  public void givenNoCursorArgument_returnsWrongNumberOfArgumentsError() {
-    assertThatThrownBy(() -> jedis.sendCommand("key", Protocol.Command.SSCAN, 
"key"))
+  public void givenIncorrectOptionalArgumentAndKeyExists_returnsSyntaxError() {
+    assertThatThrownBy(() -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, 
KEY))
         .hasMessageContaining("ERR wrong number of arguments for 'sscan' 
command");
   }

Review comment:
       This test should be deleted, because the behaviour in the test name does 
not match the behaviour in the test, and both behaviours are already covered by 
the "givenIncorrectOptionalArgumentsAndKeyExists_returnsSyntaxError" and 
"givenLessThanTwoArguments_returnsWrongNumberOfArgumentsError" tests 
respectively.

##########
File path: 
geode-for-redis/src/integrationTest/java/org/apache/geode/redis/internal/commands/executor/set/AbstractSScanIntegrationTest.java
##########
@@ -54,295 +74,397 @@ public void tearDown() {
   }
 
   @Test
-  public void givenNoKeyArgument_returnsWrongNumberOfArgumentsError() {
-    assertThatThrownBy(() -> jedis.sendCommand("key", Protocol.Command.SSCAN))
-        .hasMessageContaining("ERR wrong number of arguments for 'sscan' 
command");
+  public void givenLessThanTwoArguments_returnsWrongNumberOfArgumentsError() {
+    assertAtLeastNArgs(jedis, Protocol.Command.SSCAN, 2);
+  }
+
+  @Test
+  public void givenNonexistentKey_returnsEmptyArray() {
+    ScanResult<String> result = jedis.sscan("nonexistent", ZERO_CURSOR);
+
+    assertThat(result.isCompleteIteration()).isTrue();
+    assertThat(result.getResult()).isEmpty();
+  }
+
+  @Test
+  public void 
givenNonexistentKeyAndIncorrectOptionalArguments_returnsEmptyArray() {
+    result = sendCustomSscanCommand("nonexistentKey", "nonexistentKey", 
ZERO_CURSOR, "ANY");
+    assertThat(result.getResult()).isEmpty();
   }
 
   @Test
-  public void givenNoCursorArgument_returnsWrongNumberOfArgumentsError() {
-    assertThatThrownBy(() -> jedis.sendCommand("key", Protocol.Command.SSCAN, 
"key"))
+  public void givenIncorrectOptionalArgumentAndKeyExists_returnsSyntaxError() {
+    assertThatThrownBy(() -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, 
KEY))
         .hasMessageContaining("ERR wrong number of arguments for 'sscan' 
command");
   }
 
   @Test
-  public void givenArgumentsAreNotOddAndKeyExists_returnsSyntaxError() {
-    jedis.sadd("a", "1");
-    assertThatThrownBy(() -> jedis.sendCommand("a", Protocol.Command.SSCAN, 
"a", "0", "a*"))
+  public void givenIncorrectOptionalArgumentsAndKeyExists_returnsSyntaxError() 
{
+    jedis.sadd(KEY, "1");
+    assertThatThrownBy(() -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, 
KEY, ZERO_CURSOR, "a*"))
         .hasMessageContaining(ERROR_SYNTAX);
   }
 
   @Test
-  @SuppressWarnings("unchecked")
-  public void givenArgumentsAreNotOddAndKeyDoesNotExist_returnsEmptyArray() {
-    List<Object> result =
-        (List<Object>) jedis.sendCommand("key!", Protocol.Command.SSCAN, 
"key!", "0", "a*");
+  public void 
givenMatchArgumentWithoutPatternOnExistingKey_returnsSyntaxError() {
+    jedis.sadd(KEY, "1");
+    assertThatThrownBy(
+        () -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, KEY, ZERO_CURSOR, 
"MATCH"))
+            .hasMessageContaining(ERROR_SYNTAX);
+  }
 
-    assertThat((byte[]) result.get(0)).isEqualTo("0".getBytes());
-    assertThat((List<Object>) result.get(1)).isEmpty();
+  @Test
+  public void 
givenCountArgumentWithoutNumberOnExistingKey_returnsSyntaxError() {
+    jedis.sadd(KEY, "1");
+    assertThatThrownBy(
+        () -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, KEY, ZERO_CURSOR, 
"COUNT"))
+            .hasMessageContaining(ERROR_SYNTAX);
   }
 
   @Test
   public void givenMatchOrCountKeywordNotSpecified_returnsSyntaxError() {

Review comment:
       This test name is slightly misleading, as it's perfectly fine to not 
specify MATCH or COUNT when performing SSCAN. What this test is actually doing 
is checking the behaviour when an additional argument is given, but that 
argument is not MATCH or COUNT. A better name would therefore be something like 
"givenAdditionalArgumentNotEqualToMatchOrCount_returnsSyntaxError"

##########
File path: 
geode-for-redis/src/integrationTest/java/org/apache/geode/redis/internal/commands/executor/set/AbstractSScanIntegrationTest.java
##########
@@ -54,295 +74,397 @@ public void tearDown() {
   }
 
   @Test
-  public void givenNoKeyArgument_returnsWrongNumberOfArgumentsError() {
-    assertThatThrownBy(() -> jedis.sendCommand("key", Protocol.Command.SSCAN))
-        .hasMessageContaining("ERR wrong number of arguments for 'sscan' 
command");
+  public void givenLessThanTwoArguments_returnsWrongNumberOfArgumentsError() {
+    assertAtLeastNArgs(jedis, Protocol.Command.SSCAN, 2);
+  }
+
+  @Test
+  public void givenNonexistentKey_returnsEmptyArray() {
+    ScanResult<String> result = jedis.sscan("nonexistent", ZERO_CURSOR);
+
+    assertThat(result.isCompleteIteration()).isTrue();
+    assertThat(result.getResult()).isEmpty();
+  }
+
+  @Test
+  public void 
givenNonexistentKeyAndIncorrectOptionalArguments_returnsEmptyArray() {
+    result = sendCustomSscanCommand("nonexistentKey", "nonexistentKey", 
ZERO_CURSOR, "ANY");
+    assertThat(result.getResult()).isEmpty();
   }
 
   @Test
-  public void givenNoCursorArgument_returnsWrongNumberOfArgumentsError() {
-    assertThatThrownBy(() -> jedis.sendCommand("key", Protocol.Command.SSCAN, 
"key"))
+  public void givenIncorrectOptionalArgumentAndKeyExists_returnsSyntaxError() {
+    assertThatThrownBy(() -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, 
KEY))
         .hasMessageContaining("ERR wrong number of arguments for 'sscan' 
command");
   }
 
   @Test
-  public void givenArgumentsAreNotOddAndKeyExists_returnsSyntaxError() {
-    jedis.sadd("a", "1");
-    assertThatThrownBy(() -> jedis.sendCommand("a", Protocol.Command.SSCAN, 
"a", "0", "a*"))
+  public void givenIncorrectOptionalArgumentsAndKeyExists_returnsSyntaxError() 
{
+    jedis.sadd(KEY, "1");
+    assertThatThrownBy(() -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, 
KEY, ZERO_CURSOR, "a*"))
         .hasMessageContaining(ERROR_SYNTAX);
   }
 
   @Test
-  @SuppressWarnings("unchecked")
-  public void givenArgumentsAreNotOddAndKeyDoesNotExist_returnsEmptyArray() {
-    List<Object> result =
-        (List<Object>) jedis.sendCommand("key!", Protocol.Command.SSCAN, 
"key!", "0", "a*");
+  public void 
givenMatchArgumentWithoutPatternOnExistingKey_returnsSyntaxError() {
+    jedis.sadd(KEY, "1");
+    assertThatThrownBy(
+        () -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, KEY, ZERO_CURSOR, 
"MATCH"))
+            .hasMessageContaining(ERROR_SYNTAX);
+  }
 
-    assertThat((byte[]) result.get(0)).isEqualTo("0".getBytes());
-    assertThat((List<Object>) result.get(1)).isEmpty();
+  @Test
+  public void 
givenCountArgumentWithoutNumberOnExistingKey_returnsSyntaxError() {
+    jedis.sadd(KEY, "1");
+    assertThatThrownBy(
+        () -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, KEY, ZERO_CURSOR, 
"COUNT"))
+            .hasMessageContaining(ERROR_SYNTAX);
   }
 
   @Test
   public void givenMatchOrCountKeywordNotSpecified_returnsSyntaxError() {
-    jedis.sadd("a", "1");
-    assertThatThrownBy(() -> jedis.sendCommand("a", Protocol.Command.SSCAN, 
"a", "0", "a*", "1"))
-        .hasMessageContaining(ERROR_SYNTAX);
+    jedis.sadd(KEY, "1");
+    assertThatThrownBy(
+        () -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, KEY, ZERO_CURSOR, 
"a*", "1"))
+            .hasMessageContaining(ERROR_SYNTAX);
   }
 
   @Test
   public void 
givenCount_whenCountParameterIsNotAnInteger_returnsNotIntegerError() {
-    jedis.sadd("a", "1");
+    jedis.sadd(KEY, "1");
     assertThatThrownBy(
-        () -> jedis.sendCommand("a", Protocol.Command.SSCAN, "a", "0", 
"COUNT", "MATCH"))
+        () -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, KEY, ZERO_CURSOR, 
"COUNT", "MATCH"))
             .hasMessageContaining(ERROR_NOT_INTEGER);
   }
 
   @Test
   public void 
givenMultipleCounts_whenAnyCountParameterIsNotAnInteger_returnsNotIntegerError()
 {
-    jedis.sadd("a", "1");
-    assertThatThrownBy(() -> jedis.sendCommand("a", Protocol.Command.SSCAN, 
"a", "0", "COUNT", "2",
-        "COUNT", "sjlfs", "COUNT", "1"))
-            .hasMessageContaining(ERROR_NOT_INTEGER);
+    jedis.sadd(KEY, "1");
+    assertThatThrownBy(
+        () -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, KEY, ZERO_CURSOR, 
"COUNT", "12",
+            "COUNT", "sjlfs", "COUNT", "1"))
+                .hasMessageContaining(ERROR_NOT_INTEGER);
   }
 
   @Test
   public void 
givenMultipleCounts_whenAnyCountParameterIsLessThanOne_returnsSyntaxError() {
-    jedis.sadd("a", "1");
-    assertThatThrownBy(() -> jedis.sendCommand("a", Protocol.Command.SSCAN, 
"a", "0", "COUNT", "2",
-        "COUNT", "0", "COUNT", "1"))
-            .hasMessageContaining(ERROR_SYNTAX);
+    jedis.sadd(KEY, "1");
+    assertThatThrownBy(
+        () -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, KEY, ZERO_CURSOR, 
"COUNT", "12",
+            "COUNT", "0", "COUNT", "1"))
+                .hasMessageContaining(ERROR_SYNTAX);
   }
 
   @Test
   public void givenCount_whenCountParameterIsZero_returnsSyntaxError() {
-    jedis.sadd("a", "1");
-
-    assertThatThrownBy(() -> jedis.sendCommand("a", Protocol.Command.SSCAN, 
"a", "0", "COUNT", "0"))
-        .hasMessageContaining(ERROR_SYNTAX);
+    jedis.sadd(KEY, "1");
+    assertThatThrownBy(
+        () -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, KEY, ZERO_CURSOR, 
"COUNT", "0"))
+            .hasMessageContaining(ERROR_SYNTAX);
   }
 
   @Test
   public void givenCount_whenCountParameterIsNegative_returnsSyntaxError() {
-    jedis.sadd("a", "1");
-
+    jedis.sadd(KEY, "1");
     assertThatThrownBy(
-        () -> jedis.sendCommand("a", Protocol.Command.SSCAN, "a", "0", 
"COUNT", "-37"))
+        () -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, KEY, ZERO_CURSOR, 
"COUNT", "-37"))
             .hasMessageContaining(ERROR_SYNTAX);
   }
 
   @Test
   public void givenKeyIsNotASet_returnsWrongTypeError() {
-    jedis.hset("a", "b", "1");
-
+    jedis.hset(KEY, "b", FIELD_ONE);
     assertThatThrownBy(
-        () -> jedis.sendCommand("a", Protocol.Command.SSCAN, "a", "0", 
"COUNT", "-37"))
+        () -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, KEY, ZERO_CURSOR, 
"COUNT", "-37"))
             .hasMessageContaining(ERROR_WRONG_TYPE);
   }
 
   @Test
   public void 
givenKeyIsNotASet_andCursorIsNotAnInteger_returnsInvalidCursorError() {
-    jedis.hset("a", "b", "1");
-
-    assertThatThrownBy(() -> jedis.sendCommand("a", Protocol.Command.SSCAN, 
"a", "sjfls"))
+    jedis.hset(KEY, "b", FIELD_ONE);
+    assertThatThrownBy(() -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, 
KEY, "not-int"))
         .hasMessageContaining(ERROR_CURSOR);
   }
 
   @Test
   public void 
givenNonexistentKey_andCursorIsNotInteger_returnsInvalidCursorError() {
     assertThatThrownBy(
-        () -> jedis.sendCommand("notReal", Protocol.Command.SSCAN, "notReal", 
"notReal", "sjfls"))
+        () -> jedis.sendCommand("notReal", Protocol.Command.SSCAN, "notReal", 
"notReal", "not-int"))
             .hasMessageContaining(ERROR_CURSOR);
   }
 
   @Test
   public void 
givenExistentSetKey_andCursorIsNotAnInteger_returnsInvalidCursorError() {
-    jedis.set("a", "b");
-
-    assertThatThrownBy(() -> jedis.sendCommand("a", Protocol.Command.SSCAN, 
"a", "sjfls"))
+    jedis.set(KEY, "b");
+    assertThatThrownBy(() -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, 
KEY, "not-int"))
         .hasMessageContaining(ERROR_CURSOR);
   }
 
   @Test
-  public void givenNonexistentKey_returnsEmptyArray() {
-    ScanResult<String> result = jedis.sscan("nonexistent", "0");
-
-    assertThat(result.isCompleteIteration()).isTrue();
-    assertThat(result.getResult()).isEmpty();
+  public void givenNegativeCursor_doesNotError() {
+    initializeThousandMemberSet();
+    assertThatNoException().isThrownBy(() -> jedis.sscan(KEY, "-1"));
   }
 
   @Test
   public void givenSetWithOneMember_returnsMember() {
-    jedis.sadd("a", "1");
-    ScanResult<String> result = jedis.sscan("a", "0");
+    jedis.sadd(KEY, "1");
+
+    ScanResult<String> result = jedis.sscan(KEY, ZERO_CURSOR);
 
     assertThat(result.isCompleteIteration()).isTrue();
-    assertThat(result.getResult()).containsExactly("1");
+    assertThat(result.getResult()).containsOnly(FIELD_ONE);
   }
 
   @Test
-  public void givenSetWithMultipleMembers_returnsAllMembers() {
-    jedis.sadd("a", "1", "2", "3");
-    ScanResult<String> result = jedis.sscan("a", "0");
+  public void givenSetWithMultipleMembers_returnsSubsetOfMembers() {
+    final Set<String> initialMemberData = initializeThousandMemberSet();
 
-    assertThat(result.isCompleteIteration()).isTrue();
-    assertThat(result.getResult()).containsExactlyInAnyOrder("1", "2", "3");
+    ScanResult<String> result = jedis.sscan(KEY, ZERO_CURSOR);
+
+    assertThat(result.getResult()).isSubsetOf(initialMemberData);
   }
 
   @Test
   public void givenCount_returnsAllMembersWithoutDuplicates() {
-    jedis.sadd("a", "1", "2", "3");
-
+    Set<byte[]> initialTotalSet = initializeThousandMemberByteSet();
+    int count = 10;
     ScanParams scanParams = new ScanParams();
-    scanParams.count(1);
-    String cursor = "0";
-    ScanResult<byte[]> result;
-    List<byte[]> allMembersFromScan = new ArrayList<>();
+    scanParams.count(10);
 
-    do {
-      result = jedis.sscan("a".getBytes(), cursor.getBytes(), scanParams);
-      allMembersFromScan.addAll(result.getResult());
-      cursor = result.getCursor();
-    } while (!result.isCompleteIteration());
+    result = jedis.sscan(KEY.getBytes(), ZERO_CURSOR.getBytes(), scanParams);
 
-    assertThat(allMembersFromScan).containsExactlyInAnyOrder("1".getBytes(),
-        "2".getBytes(),
-        "3".getBytes());
+    assertThat(result.getResult().size()).isGreaterThanOrEqualTo(count);
+    assertThat(result.getResult()).isSubsetOf(initialTotalSet);
   }
 
   @Test
-  @SuppressWarnings("unchecked")
   public void givenMultipleCounts_returnsAllEntriesWithoutDuplicates() {
-    jedis.sadd("a", "1", "12", "3");
-
-    List<Object> result;
-
+    Set<byte[]> initialMemberData = initializeThousandMemberByteSet();
     List<byte[]> allEntries = new ArrayList<>();
-    String cursor = "0";
+    String cursor = ZERO_CURSOR;
 
     do {
-      result =
-          (List<Object>) jedis.sendCommand("a", Protocol.Command.SSCAN, "a", 
cursor, "COUNT", "2",
-              "COUNT", "1");
-      allEntries.addAll((List<byte[]>) result.get(1));
-      cursor = new String((byte[]) result.get(0));
-    } while (!Arrays.equals((byte[]) result.get(0), "0".getBytes()));
+      result = sendCustomSscanCommand(KEY, KEY, cursor, "COUNT", "1", "COUNT", 
"2");
+      cursor = result.getCursor();
+      allEntries.addAll(result.getResult());
+    } while (!cursor.equals(ZERO_CURSOR));
 
-    assertThat((byte[]) result.get(0)).isEqualTo("0".getBytes());
-    assertThat(allEntries).containsExactlyInAnyOrder("1".getBytes(),
-        "12".getBytes(),
-        "3".getBytes());
+    
assertThat(allEntries).containsExactlyInAnyOrderElementsOf(initialMemberData);
   }
 
   @Test
-  public void givenMatch_returnsAllMatchingMembersWithoutDuplicates() {
-    jedis.sadd("a", "1", "12", "3");
-
+  public void givenSetWithThreeEntriesAndMatch_returnsOnlyMatchingElements() {
+    jedis.sadd(KEY, FIELD_ONE, FIELD_TWO, FIELD_THREE);
     ScanParams scanParams = new ScanParams();
     scanParams.match("1*");
 
-    ScanResult<byte[]> result =
-        jedis.sscan("a".getBytes(), "0".getBytes(), scanParams);
+    result = jedis.sscan(KEY.getBytes(), ZERO_CURSOR.getBytes(), scanParams);
 
     assertThat(result.isCompleteIteration()).isTrue();
-    assertThat(result.getResult()).containsExactlyInAnyOrder("1".getBytes(),
-        "12".getBytes());
+    assertThat(result.getResult()).containsOnly(FIELD_ONE.getBytes(),
+        FIELD_TWO.getBytes());
   }
 
   @Test
-  @SuppressWarnings("unchecked")
-  public void givenMultipleMatches_returnsMembersMatchingLastMatchParameter() {
-    jedis.sadd("a", "1", "12", "3");
+  public void 
givenSetWithThreeEntriesAndMultipleMatchArguments_returnsOnlyElementsMatchingLastMatchArgument()
 {
+    jedis.sadd(KEY, FIELD_ONE, FIELD_TWO, FIELD_THREE);
 
-    List<Object> result = (List<Object>) jedis.sendCommand("a", 
Protocol.Command.SSCAN, "a", "0",
-        "MATCH", "3*", "MATCH", "1*");
+    result = sendCustomSscanCommand(KEY, KEY, ZERO_CURSOR, "MATCH", "3*", 
"MATCH", "1*");
 
-    assertThat((byte[]) result.get(0)).isEqualTo("0".getBytes());
-    assertThat((List<byte[]>) 
result.get(1)).containsExactlyInAnyOrder("1".getBytes(),
-        "12".getBytes());
+    assertThat(result.getCursor()).isEqualTo(ZERO_CURSOR);
+    assertThat(result.getResult()).containsOnly(FIELD_ONE.getBytes(),
+        FIELD_TWO.getBytes());
   }
 
   @Test
-  public void givenMatchAndCount_returnsAllMembersWithoutDuplicates() {
-    jedis.sadd("a", "1", "12", "3");
-
+  public void 
givenSetWithThreeMembersAndMatchAndCount_returnsAllMatchingMembers() {
+    jedis.sadd(KEY, FIELD_ONE, FIELD_TWO, FIELD_THREE);
     ScanParams scanParams = new ScanParams();
     scanParams.count(1);
     scanParams.match("1*");
-    ScanResult<byte[]> result;
     List<byte[]> allMembersFromScan = new ArrayList<>();
-    String cursor = "0";
+    String cursor = ZERO_CURSOR;
 
     do {
-      result = jedis.sscan("a".getBytes(), cursor.getBytes(), scanParams);
+      result = jedis.sscan(KEY.getBytes(), cursor.getBytes(), scanParams);
       allMembersFromScan.addAll(result.getResult());
       cursor = result.getCursor();
     } while (!result.isCompleteIteration());
 
-    assertThat(allMembersFromScan).containsExactlyInAnyOrder("1".getBytes(),
-        "12".getBytes());
+    assertThat(allMembersFromScan).containsOnly(FIELD_ONE.getBytes(),
+        FIELD_TWO.getBytes());
   }
 
   @Test
-  @SuppressWarnings("unchecked")
-  public void 
givenMultipleCountsAndMatches_returnsAllEntriesWithoutDuplicates() {
-    jedis.sadd("a", "1", "12", "3");
-
-    List<Object> result;
+  public void 
givenSetWithThreeMembersAndMultipleMatchAndCountArguments_returnsAllMatchingMembers()
 {

Review comment:
       This test should be modified in a similar way to the one above, but with 
different COUNT values:
   ```
     @Test
     public void givenMultipleCountAndMatch_usesLastSpecified() {
       Set<byte[]> initialMemberData = initializeThousandMemberByteSet();
   
       // Choose a large COUNT to ensure that some matching members are returned
       // There are 111 matching members in the set 0..999
       result = sendCustomSscanCommand(KEY, KEY, ZERO_CURSOR,
           "COUNT", "20",
           "MATCH", "1*",
           "COUNT", "950",
           "MATCH", "9*");
   
       List<byte[]> returnedMembers = this.result.getResult();
       // We know that we must have found at least 61 matching members, given 
the size of COUNT and the
       // number of matching members in the set
       assertThat(returnedMembers.size()).isGreaterThanOrEqualTo(61);
       assertThat(returnedMembers).isSubsetOf(initialMemberData);
       assertThat(returnedMembers).allSatisfy(bytes -> assertThat(new 
String(bytes)).startsWith("9"));
     }
   ```

##########
File path: 
geode-for-redis/src/integrationTest/java/org/apache/geode/redis/internal/commands/executor/set/AbstractSScanIntegrationTest.java
##########
@@ -14,37 +14,57 @@
  */
 package org.apache.geode.redis.internal.commands.executor.set;
 
+import static 
org.apache.geode.redis.RedisCommandArgumentsTestHelper.assertAtLeastNArgs;
 import static org.apache.geode.redis.internal.RedisConstants.ERROR_CURSOR;
 import static org.apache.geode.redis.internal.RedisConstants.ERROR_NOT_INTEGER;
 import static org.apache.geode.redis.internal.RedisConstants.ERROR_SYNTAX;
 import static org.apache.geode.redis.internal.RedisConstants.ERROR_WRONG_TYPE;
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatNoException;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 
+import java.math.BigInteger;
 import java.util.ArrayList;
-import java.util.Arrays;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import redis.clients.jedis.HostAndPort;
+import redis.clients.jedis.Jedis;
 import redis.clients.jedis.JedisCluster;
 import redis.clients.jedis.Protocol;
 import redis.clients.jedis.ScanParams;
 import redis.clients.jedis.ScanResult;
 
+import org.apache.geode.redis.ConcurrentLoopingThreads;
 import org.apache.geode.redis.RedisIntegrationTest;
+import org.apache.geode.redis.internal.data.KeyHashUtil;
 import org.apache.geode.test.awaitility.GeodeAwaitility;
+import org.apache.geode.test.dunit.rules.RedisClusterStartupRule;
 
 public abstract class AbstractSScanIntegrationTest implements 
RedisIntegrationTest {
   protected JedisCluster jedis;
-  private static final int REDIS_CLIENT_TIMEOUT =
-      Math.toIntExact(GeodeAwaitility.getTimeout().toMillis());
+  protected ScanResult<byte[]> result;
+  public static final String KEY = "key";
+  public static final int SLOT_FOR_KEY = 
KeyHashUtil.slotForKey(KEY.getBytes());
+  public static final String ZERO_CURSOR = "0";
+  public static final BigInteger SIGNED_LONG_MAX = new 
BigInteger(Long.toString(Long.MAX_VALUE));
+  public static final BigInteger SIGNED_LONG_MIN = new 
BigInteger(Long.toString(Long.MIN_VALUE));
+
+  public static final String FIELD_ONE = "1";
+  public static final String FIELD_TWO = "12";
+  public static final String FIELD_THREE = "3";
+
+  public static final String BASE_FIELD = "baseField_";

Review comment:
       Since Redis sets deal with members rather than fields and values, could 
these constants be renamed "MEMBER" instead of "FIELD" please?

##########
File path: 
geode-for-redis/src/integrationTest/java/org/apache/geode/redis/internal/commands/executor/set/AbstractSScanIntegrationTest.java
##########
@@ -14,37 +14,57 @@
  */
 package org.apache.geode.redis.internal.commands.executor.set;
 
+import static 
org.apache.geode.redis.RedisCommandArgumentsTestHelper.assertAtLeastNArgs;
 import static org.apache.geode.redis.internal.RedisConstants.ERROR_CURSOR;
 import static org.apache.geode.redis.internal.RedisConstants.ERROR_NOT_INTEGER;
 import static org.apache.geode.redis.internal.RedisConstants.ERROR_SYNTAX;
 import static org.apache.geode.redis.internal.RedisConstants.ERROR_WRONG_TYPE;
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatNoException;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 
+import java.math.BigInteger;
 import java.util.ArrayList;
-import java.util.Arrays;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import redis.clients.jedis.HostAndPort;
+import redis.clients.jedis.Jedis;
 import redis.clients.jedis.JedisCluster;
 import redis.clients.jedis.Protocol;
 import redis.clients.jedis.ScanParams;
 import redis.clients.jedis.ScanResult;
 
+import org.apache.geode.redis.ConcurrentLoopingThreads;
 import org.apache.geode.redis.RedisIntegrationTest;
+import org.apache.geode.redis.internal.data.KeyHashUtil;
 import org.apache.geode.test.awaitility.GeodeAwaitility;
+import org.apache.geode.test.dunit.rules.RedisClusterStartupRule;
 
 public abstract class AbstractSScanIntegrationTest implements 
RedisIntegrationTest {
   protected JedisCluster jedis;
-  private static final int REDIS_CLIENT_TIMEOUT =
-      Math.toIntExact(GeodeAwaitility.getTimeout().toMillis());
+  protected ScanResult<byte[]> result;

Review comment:
       Rather than extracting this to a field and having to reset it in the 
`setUp()` method, it would be better to leave it as an instance variable in the 
tests that use it.

##########
File path: 
geode-for-redis/src/integrationTest/java/org/apache/geode/redis/internal/commands/executor/set/SScanIntegrationTest.java
##########
@@ -37,21 +36,29 @@ public int getPort() {
   }
 
   @Test
-  public void 
givenDifferentCursorThanSpecifiedByPreviousSscan_returnsAllMembers() {
-    List<byte[]> memberList = new ArrayList<>();
-    for (int i = 0; i < 10; i++) {
-      jedis.sadd("a", String.valueOf(i));
-      memberList.add(String.valueOf(i).getBytes());
-    }
-
-    ScanParams scanParams = new ScanParams();
-    scanParams.count(5);
-    ScanResult<byte[]> result =
-        jedis.sscan("a".getBytes(), "0".getBytes(), scanParams);
-    assertThat(result.isCompleteIteration()).isFalse();
-
-    result = jedis.sscan("a".getBytes(), "100".getBytes());
-
-    
assertThat(result.getResult()).containsExactlyInAnyOrderElementsOf(memberList);
+  public void givenCursorGreaterThanUnsignedLongCapacity_returnsCursorError() {
+    assertThatThrownBy(
+        () -> jedis.sscan(KEY, SIGNED_LONG_MAX.add(BigInteger.ONE).toString()))
+            .hasMessageContaining(ERROR_CURSOR);
+  }
+
+  @Test
+  public void 
givenNegativeCursorGreaterThanUnsignedLongCapacity_returnsCursorError() {
+    assertThatThrownBy(
+        () -> jedis.sscan(KEY, 
SIGNED_LONG_MIN.add(BigInteger.valueOf(-1)).toString()))

Review comment:
       For consistency with the test above, could this be 
`SIGNED_LONG_MIN.subtract(BigInteger.ONE)`?

##########
File path: 
geode-for-redis/src/integrationTest/java/org/apache/geode/redis/internal/commands/executor/set/AbstractSScanIntegrationTest.java
##########
@@ -54,295 +74,397 @@ public void tearDown() {
   }
 
   @Test
-  public void givenNoKeyArgument_returnsWrongNumberOfArgumentsError() {
-    assertThatThrownBy(() -> jedis.sendCommand("key", Protocol.Command.SSCAN))
-        .hasMessageContaining("ERR wrong number of arguments for 'sscan' 
command");
+  public void givenLessThanTwoArguments_returnsWrongNumberOfArgumentsError() {
+    assertAtLeastNArgs(jedis, Protocol.Command.SSCAN, 2);
+  }
+
+  @Test
+  public void givenNonexistentKey_returnsEmptyArray() {
+    ScanResult<String> result = jedis.sscan("nonexistent", ZERO_CURSOR);
+
+    assertThat(result.isCompleteIteration()).isTrue();
+    assertThat(result.getResult()).isEmpty();
+  }
+
+  @Test
+  public void 
givenNonexistentKeyAndIncorrectOptionalArguments_returnsEmptyArray() {
+    result = sendCustomSscanCommand("nonexistentKey", "nonexistentKey", 
ZERO_CURSOR, "ANY");
+    assertThat(result.getResult()).isEmpty();
   }
 
   @Test
-  public void givenNoCursorArgument_returnsWrongNumberOfArgumentsError() {
-    assertThatThrownBy(() -> jedis.sendCommand("key", Protocol.Command.SSCAN, 
"key"))
+  public void givenIncorrectOptionalArgumentAndKeyExists_returnsSyntaxError() {
+    assertThatThrownBy(() -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, 
KEY))
         .hasMessageContaining("ERR wrong number of arguments for 'sscan' 
command");
   }
 
   @Test
-  public void givenArgumentsAreNotOddAndKeyExists_returnsSyntaxError() {
-    jedis.sadd("a", "1");
-    assertThatThrownBy(() -> jedis.sendCommand("a", Protocol.Command.SSCAN, 
"a", "0", "a*"))
+  public void givenIncorrectOptionalArgumentsAndKeyExists_returnsSyntaxError() 
{
+    jedis.sadd(KEY, "1");
+    assertThatThrownBy(() -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, 
KEY, ZERO_CURSOR, "a*"))
         .hasMessageContaining(ERROR_SYNTAX);
   }
 
   @Test
-  @SuppressWarnings("unchecked")
-  public void givenArgumentsAreNotOddAndKeyDoesNotExist_returnsEmptyArray() {
-    List<Object> result =
-        (List<Object>) jedis.sendCommand("key!", Protocol.Command.SSCAN, 
"key!", "0", "a*");
+  public void 
givenMatchArgumentWithoutPatternOnExistingKey_returnsSyntaxError() {
+    jedis.sadd(KEY, "1");
+    assertThatThrownBy(
+        () -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, KEY, ZERO_CURSOR, 
"MATCH"))
+            .hasMessageContaining(ERROR_SYNTAX);
+  }
 
-    assertThat((byte[]) result.get(0)).isEqualTo("0".getBytes());
-    assertThat((List<Object>) result.get(1)).isEmpty();
+  @Test
+  public void 
givenCountArgumentWithoutNumberOnExistingKey_returnsSyntaxError() {
+    jedis.sadd(KEY, "1");
+    assertThatThrownBy(
+        () -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, KEY, ZERO_CURSOR, 
"COUNT"))
+            .hasMessageContaining(ERROR_SYNTAX);
   }
 
   @Test
   public void givenMatchOrCountKeywordNotSpecified_returnsSyntaxError() {
-    jedis.sadd("a", "1");
-    assertThatThrownBy(() -> jedis.sendCommand("a", Protocol.Command.SSCAN, 
"a", "0", "a*", "1"))
-        .hasMessageContaining(ERROR_SYNTAX);
+    jedis.sadd(KEY, "1");
+    assertThatThrownBy(
+        () -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, KEY, ZERO_CURSOR, 
"a*", "1"))
+            .hasMessageContaining(ERROR_SYNTAX);
   }
 
   @Test
   public void 
givenCount_whenCountParameterIsNotAnInteger_returnsNotIntegerError() {
-    jedis.sadd("a", "1");
+    jedis.sadd(KEY, "1");
     assertThatThrownBy(
-        () -> jedis.sendCommand("a", Protocol.Command.SSCAN, "a", "0", 
"COUNT", "MATCH"))
+        () -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, KEY, ZERO_CURSOR, 
"COUNT", "MATCH"))
             .hasMessageContaining(ERROR_NOT_INTEGER);
   }
 
   @Test
   public void 
givenMultipleCounts_whenAnyCountParameterIsNotAnInteger_returnsNotIntegerError()
 {
-    jedis.sadd("a", "1");
-    assertThatThrownBy(() -> jedis.sendCommand("a", Protocol.Command.SSCAN, 
"a", "0", "COUNT", "2",
-        "COUNT", "sjlfs", "COUNT", "1"))
-            .hasMessageContaining(ERROR_NOT_INTEGER);
+    jedis.sadd(KEY, "1");
+    assertThatThrownBy(
+        () -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, KEY, ZERO_CURSOR, 
"COUNT", "12",
+            "COUNT", "sjlfs", "COUNT", "1"))
+                .hasMessageContaining(ERROR_NOT_INTEGER);
   }
 
   @Test
   public void 
givenMultipleCounts_whenAnyCountParameterIsLessThanOne_returnsSyntaxError() {
-    jedis.sadd("a", "1");
-    assertThatThrownBy(() -> jedis.sendCommand("a", Protocol.Command.SSCAN, 
"a", "0", "COUNT", "2",
-        "COUNT", "0", "COUNT", "1"))
-            .hasMessageContaining(ERROR_SYNTAX);
+    jedis.sadd(KEY, "1");
+    assertThatThrownBy(
+        () -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, KEY, ZERO_CURSOR, 
"COUNT", "12",
+            "COUNT", "0", "COUNT", "1"))
+                .hasMessageContaining(ERROR_SYNTAX);
   }
 
   @Test
   public void givenCount_whenCountParameterIsZero_returnsSyntaxError() {
-    jedis.sadd("a", "1");
-
-    assertThatThrownBy(() -> jedis.sendCommand("a", Protocol.Command.SSCAN, 
"a", "0", "COUNT", "0"))
-        .hasMessageContaining(ERROR_SYNTAX);
+    jedis.sadd(KEY, "1");
+    assertThatThrownBy(
+        () -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, KEY, ZERO_CURSOR, 
"COUNT", "0"))
+            .hasMessageContaining(ERROR_SYNTAX);
   }
 
   @Test
   public void givenCount_whenCountParameterIsNegative_returnsSyntaxError() {
-    jedis.sadd("a", "1");
-
+    jedis.sadd(KEY, "1");
     assertThatThrownBy(
-        () -> jedis.sendCommand("a", Protocol.Command.SSCAN, "a", "0", 
"COUNT", "-37"))
+        () -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, KEY, ZERO_CURSOR, 
"COUNT", "-37"))
             .hasMessageContaining(ERROR_SYNTAX);
   }
 
   @Test
   public void givenKeyIsNotASet_returnsWrongTypeError() {

Review comment:
       This test should probably be named 
"givenKeyIsNotASetAndCountIsNegative_returnsWrongTypeError". It would also be 
good to add another test before this one where the key is not a set but COUNT 
is not specified named "givenKeyIsNotASet_returnsWrongTypeError" just to cover 
all our bases.

##########
File path: 
geode-for-redis/src/integrationTest/java/org/apache/geode/redis/internal/commands/executor/set/AbstractSScanIntegrationTest.java
##########
@@ -54,295 +74,397 @@ public void tearDown() {
   }
 
   @Test
-  public void givenNoKeyArgument_returnsWrongNumberOfArgumentsError() {
-    assertThatThrownBy(() -> jedis.sendCommand("key", Protocol.Command.SSCAN))
-        .hasMessageContaining("ERR wrong number of arguments for 'sscan' 
command");
+  public void givenLessThanTwoArguments_returnsWrongNumberOfArgumentsError() {
+    assertAtLeastNArgs(jedis, Protocol.Command.SSCAN, 2);
+  }
+
+  @Test
+  public void givenNonexistentKey_returnsEmptyArray() {
+    ScanResult<String> result = jedis.sscan("nonexistent", ZERO_CURSOR);
+
+    assertThat(result.isCompleteIteration()).isTrue();
+    assertThat(result.getResult()).isEmpty();
+  }
+
+  @Test
+  public void 
givenNonexistentKeyAndIncorrectOptionalArguments_returnsEmptyArray() {
+    result = sendCustomSscanCommand("nonexistentKey", "nonexistentKey", 
ZERO_CURSOR, "ANY");
+    assertThat(result.getResult()).isEmpty();
   }
 
   @Test
-  public void givenNoCursorArgument_returnsWrongNumberOfArgumentsError() {
-    assertThatThrownBy(() -> jedis.sendCommand("key", Protocol.Command.SSCAN, 
"key"))
+  public void givenIncorrectOptionalArgumentAndKeyExists_returnsSyntaxError() {
+    assertThatThrownBy(() -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, 
KEY))
         .hasMessageContaining("ERR wrong number of arguments for 'sscan' 
command");
   }
 
   @Test
-  public void givenArgumentsAreNotOddAndKeyExists_returnsSyntaxError() {
-    jedis.sadd("a", "1");
-    assertThatThrownBy(() -> jedis.sendCommand("a", Protocol.Command.SSCAN, 
"a", "0", "a*"))
+  public void givenIncorrectOptionalArgumentsAndKeyExists_returnsSyntaxError() 
{
+    jedis.sadd(KEY, "1");
+    assertThatThrownBy(() -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, 
KEY, ZERO_CURSOR, "a*"))
         .hasMessageContaining(ERROR_SYNTAX);
   }
 
   @Test
-  @SuppressWarnings("unchecked")
-  public void givenArgumentsAreNotOddAndKeyDoesNotExist_returnsEmptyArray() {
-    List<Object> result =
-        (List<Object>) jedis.sendCommand("key!", Protocol.Command.SSCAN, 
"key!", "0", "a*");
+  public void 
givenMatchArgumentWithoutPatternOnExistingKey_returnsSyntaxError() {
+    jedis.sadd(KEY, "1");
+    assertThatThrownBy(
+        () -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, KEY, ZERO_CURSOR, 
"MATCH"))
+            .hasMessageContaining(ERROR_SYNTAX);
+  }
 
-    assertThat((byte[]) result.get(0)).isEqualTo("0".getBytes());
-    assertThat((List<Object>) result.get(1)).isEmpty();
+  @Test
+  public void 
givenCountArgumentWithoutNumberOnExistingKey_returnsSyntaxError() {
+    jedis.sadd(KEY, "1");
+    assertThatThrownBy(
+        () -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, KEY, ZERO_CURSOR, 
"COUNT"))
+            .hasMessageContaining(ERROR_SYNTAX);
   }
 
   @Test
   public void givenMatchOrCountKeywordNotSpecified_returnsSyntaxError() {
-    jedis.sadd("a", "1");
-    assertThatThrownBy(() -> jedis.sendCommand("a", Protocol.Command.SSCAN, 
"a", "0", "a*", "1"))
-        .hasMessageContaining(ERROR_SYNTAX);
+    jedis.sadd(KEY, "1");
+    assertThatThrownBy(
+        () -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, KEY, ZERO_CURSOR, 
"a*", "1"))
+            .hasMessageContaining(ERROR_SYNTAX);
   }
 
   @Test
   public void 
givenCount_whenCountParameterIsNotAnInteger_returnsNotIntegerError() {
-    jedis.sadd("a", "1");
+    jedis.sadd(KEY, "1");
     assertThatThrownBy(
-        () -> jedis.sendCommand("a", Protocol.Command.SSCAN, "a", "0", 
"COUNT", "MATCH"))
+        () -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, KEY, ZERO_CURSOR, 
"COUNT", "MATCH"))
             .hasMessageContaining(ERROR_NOT_INTEGER);
   }
 
   @Test
   public void 
givenMultipleCounts_whenAnyCountParameterIsNotAnInteger_returnsNotIntegerError()
 {
-    jedis.sadd("a", "1");
-    assertThatThrownBy(() -> jedis.sendCommand("a", Protocol.Command.SSCAN, 
"a", "0", "COUNT", "2",
-        "COUNT", "sjlfs", "COUNT", "1"))
-            .hasMessageContaining(ERROR_NOT_INTEGER);
+    jedis.sadd(KEY, "1");
+    assertThatThrownBy(
+        () -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, KEY, ZERO_CURSOR, 
"COUNT", "12",
+            "COUNT", "sjlfs", "COUNT", "1"))
+                .hasMessageContaining(ERROR_NOT_INTEGER);
   }
 
   @Test
   public void 
givenMultipleCounts_whenAnyCountParameterIsLessThanOne_returnsSyntaxError() {
-    jedis.sadd("a", "1");
-    assertThatThrownBy(() -> jedis.sendCommand("a", Protocol.Command.SSCAN, 
"a", "0", "COUNT", "2",
-        "COUNT", "0", "COUNT", "1"))
-            .hasMessageContaining(ERROR_SYNTAX);
+    jedis.sadd(KEY, "1");
+    assertThatThrownBy(
+        () -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, KEY, ZERO_CURSOR, 
"COUNT", "12",
+            "COUNT", "0", "COUNT", "1"))
+                .hasMessageContaining(ERROR_SYNTAX);
   }
 
   @Test
   public void givenCount_whenCountParameterIsZero_returnsSyntaxError() {
-    jedis.sadd("a", "1");
-
-    assertThatThrownBy(() -> jedis.sendCommand("a", Protocol.Command.SSCAN, 
"a", "0", "COUNT", "0"))
-        .hasMessageContaining(ERROR_SYNTAX);
+    jedis.sadd(KEY, "1");
+    assertThatThrownBy(
+        () -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, KEY, ZERO_CURSOR, 
"COUNT", "0"))
+            .hasMessageContaining(ERROR_SYNTAX);
   }
 
   @Test
   public void givenCount_whenCountParameterIsNegative_returnsSyntaxError() {
-    jedis.sadd("a", "1");
-
+    jedis.sadd(KEY, "1");
     assertThatThrownBy(
-        () -> jedis.sendCommand("a", Protocol.Command.SSCAN, "a", "0", 
"COUNT", "-37"))
+        () -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, KEY, ZERO_CURSOR, 
"COUNT", "-37"))
             .hasMessageContaining(ERROR_SYNTAX);
   }
 
   @Test
   public void givenKeyIsNotASet_returnsWrongTypeError() {
-    jedis.hset("a", "b", "1");
-
+    jedis.hset(KEY, "b", FIELD_ONE);
     assertThatThrownBy(
-        () -> jedis.sendCommand("a", Protocol.Command.SSCAN, "a", "0", 
"COUNT", "-37"))
+        () -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, KEY, ZERO_CURSOR, 
"COUNT", "-37"))
             .hasMessageContaining(ERROR_WRONG_TYPE);
   }
 
   @Test
   public void 
givenKeyIsNotASet_andCursorIsNotAnInteger_returnsInvalidCursorError() {
-    jedis.hset("a", "b", "1");
-
-    assertThatThrownBy(() -> jedis.sendCommand("a", Protocol.Command.SSCAN, 
"a", "sjfls"))
+    jedis.hset(KEY, "b", FIELD_ONE);
+    assertThatThrownBy(() -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, 
KEY, "not-int"))
         .hasMessageContaining(ERROR_CURSOR);
   }
 
   @Test
   public void 
givenNonexistentKey_andCursorIsNotInteger_returnsInvalidCursorError() {
     assertThatThrownBy(
-        () -> jedis.sendCommand("notReal", Protocol.Command.SSCAN, "notReal", 
"notReal", "sjfls"))
+        () -> jedis.sendCommand("notReal", Protocol.Command.SSCAN, "notReal", 
"notReal", "not-int"))
             .hasMessageContaining(ERROR_CURSOR);
   }
 
   @Test
   public void 
givenExistentSetKey_andCursorIsNotAnInteger_returnsInvalidCursorError() {
-    jedis.set("a", "b");
-
-    assertThatThrownBy(() -> jedis.sendCommand("a", Protocol.Command.SSCAN, 
"a", "sjfls"))
+    jedis.set(KEY, "b");
+    assertThatThrownBy(() -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, 
KEY, "not-int"))
         .hasMessageContaining(ERROR_CURSOR);
   }
 
   @Test
-  public void givenNonexistentKey_returnsEmptyArray() {
-    ScanResult<String> result = jedis.sscan("nonexistent", "0");
-
-    assertThat(result.isCompleteIteration()).isTrue();
-    assertThat(result.getResult()).isEmpty();
+  public void givenNegativeCursor_doesNotError() {
+    initializeThousandMemberSet();
+    assertThatNoException().isThrownBy(() -> jedis.sscan(KEY, "-1"));
   }
 
   @Test
   public void givenSetWithOneMember_returnsMember() {
-    jedis.sadd("a", "1");
-    ScanResult<String> result = jedis.sscan("a", "0");
+    jedis.sadd(KEY, "1");
+
+    ScanResult<String> result = jedis.sscan(KEY, ZERO_CURSOR);
 
     assertThat(result.isCompleteIteration()).isTrue();
-    assertThat(result.getResult()).containsExactly("1");
+    assertThat(result.getResult()).containsOnly(FIELD_ONE);
   }
 
   @Test
-  public void givenSetWithMultipleMembers_returnsAllMembers() {
-    jedis.sadd("a", "1", "2", "3");
-    ScanResult<String> result = jedis.sscan("a", "0");
+  public void givenSetWithMultipleMembers_returnsSubsetOfMembers() {
+    final Set<String> initialMemberData = initializeThousandMemberSet();
 
-    assertThat(result.isCompleteIteration()).isTrue();
-    assertThat(result.getResult()).containsExactlyInAnyOrder("1", "2", "3");
+    ScanResult<String> result = jedis.sscan(KEY, ZERO_CURSOR);
+
+    assertThat(result.getResult()).isSubsetOf(initialMemberData);
   }
 
   @Test
   public void givenCount_returnsAllMembersWithoutDuplicates() {
-    jedis.sadd("a", "1", "2", "3");
-
+    Set<byte[]> initialTotalSet = initializeThousandMemberByteSet();
+    int count = 10;
     ScanParams scanParams = new ScanParams();
-    scanParams.count(1);
-    String cursor = "0";
-    ScanResult<byte[]> result;
-    List<byte[]> allMembersFromScan = new ArrayList<>();
+    scanParams.count(10);
 
-    do {
-      result = jedis.sscan("a".getBytes(), cursor.getBytes(), scanParams);
-      allMembersFromScan.addAll(result.getResult());
-      cursor = result.getCursor();
-    } while (!result.isCompleteIteration());
+    result = jedis.sscan(KEY.getBytes(), ZERO_CURSOR.getBytes(), scanParams);
 
-    assertThat(allMembersFromScan).containsExactlyInAnyOrder("1".getBytes(),
-        "2".getBytes(),
-        "3".getBytes());
+    assertThat(result.getResult().size()).isGreaterThanOrEqualTo(count);
+    assertThat(result.getResult()).isSubsetOf(initialTotalSet);
   }
 
   @Test
-  @SuppressWarnings("unchecked")
   public void givenMultipleCounts_returnsAllEntriesWithoutDuplicates() {
-    jedis.sadd("a", "1", "12", "3");
-
-    List<Object> result;
-
+    Set<byte[]> initialMemberData = initializeThousandMemberByteSet();
     List<byte[]> allEntries = new ArrayList<>();
-    String cursor = "0";
+    String cursor = ZERO_CURSOR;
 
     do {
-      result =
-          (List<Object>) jedis.sendCommand("a", Protocol.Command.SSCAN, "a", 
cursor, "COUNT", "2",
-              "COUNT", "1");
-      allEntries.addAll((List<byte[]>) result.get(1));
-      cursor = new String((byte[]) result.get(0));
-    } while (!Arrays.equals((byte[]) result.get(0), "0".getBytes()));
+      result = sendCustomSscanCommand(KEY, KEY, cursor, "COUNT", "1", "COUNT", 
"2");
+      cursor = result.getCursor();
+      allEntries.addAll(result.getResult());
+    } while (!cursor.equals(ZERO_CURSOR));
 
-    assertThat((byte[]) result.get(0)).isEqualTo("0".getBytes());
-    assertThat(allEntries).containsExactlyInAnyOrder("1".getBytes(),
-        "12".getBytes(),
-        "3".getBytes());
+    
assertThat(allEntries).containsExactlyInAnyOrderElementsOf(initialMemberData);
   }
 
   @Test
-  public void givenMatch_returnsAllMatchingMembersWithoutDuplicates() {
-    jedis.sadd("a", "1", "12", "3");
-
+  public void givenSetWithThreeEntriesAndMatch_returnsOnlyMatchingElements() {
+    jedis.sadd(KEY, FIELD_ONE, FIELD_TWO, FIELD_THREE);
     ScanParams scanParams = new ScanParams();
     scanParams.match("1*");
 
-    ScanResult<byte[]> result =
-        jedis.sscan("a".getBytes(), "0".getBytes(), scanParams);
+    result = jedis.sscan(KEY.getBytes(), ZERO_CURSOR.getBytes(), scanParams);
 
     assertThat(result.isCompleteIteration()).isTrue();
-    assertThat(result.getResult()).containsExactlyInAnyOrder("1".getBytes(),
-        "12".getBytes());
+    assertThat(result.getResult()).containsOnly(FIELD_ONE.getBytes(),
+        FIELD_TWO.getBytes());
   }
 
   @Test
-  @SuppressWarnings("unchecked")
-  public void givenMultipleMatches_returnsMembersMatchingLastMatchParameter() {
-    jedis.sadd("a", "1", "12", "3");
+  public void 
givenSetWithThreeEntriesAndMultipleMatchArguments_returnsOnlyElementsMatchingLastMatchArgument()
 {
+    jedis.sadd(KEY, FIELD_ONE, FIELD_TWO, FIELD_THREE);
 
-    List<Object> result = (List<Object>) jedis.sendCommand("a", 
Protocol.Command.SSCAN, "a", "0",
-        "MATCH", "3*", "MATCH", "1*");
+    result = sendCustomSscanCommand(KEY, KEY, ZERO_CURSOR, "MATCH", "3*", 
"MATCH", "1*");
 
-    assertThat((byte[]) result.get(0)).isEqualTo("0".getBytes());
-    assertThat((List<byte[]>) 
result.get(1)).containsExactlyInAnyOrder("1".getBytes(),
-        "12".getBytes());
+    assertThat(result.getCursor()).isEqualTo(ZERO_CURSOR);
+    assertThat(result.getResult()).containsOnly(FIELD_ONE.getBytes(),
+        FIELD_TWO.getBytes());
   }
 
   @Test
-  public void givenMatchAndCount_returnsAllMembersWithoutDuplicates() {
-    jedis.sadd("a", "1", "12", "3");
-
+  public void 
givenSetWithThreeMembersAndMatchAndCount_returnsAllMatchingMembers() {

Review comment:
       Tests using both MATCH and COUNT are tricky. The way SSCAN works, first 
COUNT members are scanned, then the matching members (if any) from that list 
are returned, which means that it's entirely possible that 0 members are 
returned by a single SSCAN even if COUNT is not small and there are matching 
members in the set.
   
   As such, it's difficult to know what to assert in a test for both COUNT and 
MATCH that's actually meaningful, as the number of members returned could be 
anything between 0 and every matching member (depending on hash collisions 
resulting in more than COUNT members being scanned in a single scan). I think 
the best bet would be to have a test that shows that a very large COUNT results 
in almost all of the matching members in the set being returned, to show that 
we didn't use the default value of COUNT, and that we filtered the scanned 
elements by MATCH:
   ```
     @Test
     public void givenLargeCountAndMatch_returnsOnlyMatchingMembers() {
       Set<byte[]> initialMemberData = initializeThousandMemberByteSet();
   
       ScanParams scanParams = new ScanParams();
       // There are 111 matching members in the set 0..999
       scanParams.match("9*");
       // Choose a large COUNT to ensure that some matching members are returned
       scanParams.count(950);
       result = jedis.sscan(KEY.getBytes(), ZERO_CURSOR.getBytes(), scanParams);
   
       List<byte[]> returnedMembers = this.result.getResult();
       // We know that we must have found at least 61 matching members, given 
the size of COUNT and the
       // number of matching members in the set
       assertThat(returnedMembers.size()).isGreaterThanOrEqualTo(61);
       assertThat(returnedMembers).isSubsetOf(initialMemberData);
       assertThat(returnedMembers).allSatisfy(bytes -> assertThat(new 
String(bytes)).startsWith("9"));
     }
   ```

##########
File path: 
geode-for-redis/src/integrationTest/java/org/apache/geode/redis/internal/commands/executor/set/AbstractSScanIntegrationTest.java
##########
@@ -54,295 +74,397 @@ public void tearDown() {
   }
 
   @Test
-  public void givenNoKeyArgument_returnsWrongNumberOfArgumentsError() {
-    assertThatThrownBy(() -> jedis.sendCommand("key", Protocol.Command.SSCAN))
-        .hasMessageContaining("ERR wrong number of arguments for 'sscan' 
command");
+  public void givenLessThanTwoArguments_returnsWrongNumberOfArgumentsError() {
+    assertAtLeastNArgs(jedis, Protocol.Command.SSCAN, 2);
+  }
+
+  @Test
+  public void givenNonexistentKey_returnsEmptyArray() {
+    ScanResult<String> result = jedis.sscan("nonexistent", ZERO_CURSOR);
+
+    assertThat(result.isCompleteIteration()).isTrue();
+    assertThat(result.getResult()).isEmpty();
+  }
+
+  @Test
+  public void 
givenNonexistentKeyAndIncorrectOptionalArguments_returnsEmptyArray() {
+    result = sendCustomSscanCommand("nonexistentKey", "nonexistentKey", 
ZERO_CURSOR, "ANY");
+    assertThat(result.getResult()).isEmpty();
   }
 
   @Test
-  public void givenNoCursorArgument_returnsWrongNumberOfArgumentsError() {
-    assertThatThrownBy(() -> jedis.sendCommand("key", Protocol.Command.SSCAN, 
"key"))
+  public void givenIncorrectOptionalArgumentAndKeyExists_returnsSyntaxError() {
+    assertThatThrownBy(() -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, 
KEY))
         .hasMessageContaining("ERR wrong number of arguments for 'sscan' 
command");
   }
 
   @Test
-  public void givenArgumentsAreNotOddAndKeyExists_returnsSyntaxError() {
-    jedis.sadd("a", "1");
-    assertThatThrownBy(() -> jedis.sendCommand("a", Protocol.Command.SSCAN, 
"a", "0", "a*"))
+  public void givenIncorrectOptionalArgumentsAndKeyExists_returnsSyntaxError() 
{
+    jedis.sadd(KEY, "1");
+    assertThatThrownBy(() -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, 
KEY, ZERO_CURSOR, "a*"))
         .hasMessageContaining(ERROR_SYNTAX);
   }
 
   @Test
-  @SuppressWarnings("unchecked")
-  public void givenArgumentsAreNotOddAndKeyDoesNotExist_returnsEmptyArray() {
-    List<Object> result =
-        (List<Object>) jedis.sendCommand("key!", Protocol.Command.SSCAN, 
"key!", "0", "a*");
+  public void 
givenMatchArgumentWithoutPatternOnExistingKey_returnsSyntaxError() {
+    jedis.sadd(KEY, "1");
+    assertThatThrownBy(
+        () -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, KEY, ZERO_CURSOR, 
"MATCH"))
+            .hasMessageContaining(ERROR_SYNTAX);
+  }
 
-    assertThat((byte[]) result.get(0)).isEqualTo("0".getBytes());
-    assertThat((List<Object>) result.get(1)).isEmpty();
+  @Test
+  public void 
givenCountArgumentWithoutNumberOnExistingKey_returnsSyntaxError() {
+    jedis.sadd(KEY, "1");
+    assertThatThrownBy(
+        () -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, KEY, ZERO_CURSOR, 
"COUNT"))
+            .hasMessageContaining(ERROR_SYNTAX);
   }
 
   @Test
   public void givenMatchOrCountKeywordNotSpecified_returnsSyntaxError() {
-    jedis.sadd("a", "1");
-    assertThatThrownBy(() -> jedis.sendCommand("a", Protocol.Command.SSCAN, 
"a", "0", "a*", "1"))
-        .hasMessageContaining(ERROR_SYNTAX);
+    jedis.sadd(KEY, "1");
+    assertThatThrownBy(
+        () -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, KEY, ZERO_CURSOR, 
"a*", "1"))
+            .hasMessageContaining(ERROR_SYNTAX);
   }
 
   @Test
   public void 
givenCount_whenCountParameterIsNotAnInteger_returnsNotIntegerError() {
-    jedis.sadd("a", "1");
+    jedis.sadd(KEY, "1");
     assertThatThrownBy(
-        () -> jedis.sendCommand("a", Protocol.Command.SSCAN, "a", "0", 
"COUNT", "MATCH"))
+        () -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, KEY, ZERO_CURSOR, 
"COUNT", "MATCH"))

Review comment:
       Just to avoid potential confusion, could the argument after "COUNT" here 
be "notAnInteger" or some similar String?

##########
File path: 
geode-for-redis/src/integrationTest/java/org/apache/geode/redis/internal/commands/executor/set/AbstractSScanIntegrationTest.java
##########
@@ -156,152 +222,172 @@ public void 
givenNonexistentKey_andCursorIsNotInteger_returnsInvalidCursorError(
 
   @Test
   public void 
givenExistentSetKey_andCursorIsNotAnInteger_returnsInvalidCursorError() {
-    jedis.set("a", "b");
+    jedis.set(KEY, "b");
 
-    assertThatThrownBy(() -> jedis.sendCommand("a", Protocol.Command.SSCAN, 
"a", "sjfls"))
+    assertThatThrownBy(() -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, 
KEY, "sjfls"))
         .hasMessageContaining(ERROR_CURSOR);
   }
 
   @Test
   public void givenNonexistentKey_returnsEmptyArray() {
-    ScanResult<String> result = jedis.sscan("nonexistent", "0");
+    ScanResult<String> result = jedis.sscan("nonexistent", ZERO_CURSOR);
 
     assertThat(result.isCompleteIteration()).isTrue();
     assertThat(result.getResult()).isEmpty();
   }
 
+  @Test
+  public void givenNegativeCursor_returnsEntriesUsingAbsoluteValueOfCursor() {
+    List<String> set = initializeThreeMemberSet();
+
+    String cursor = "-100";
+    ScanResult<String> result;
+    List<String> allEntries = new ArrayList<>();
+
+    do {
+      result = jedis.sscan(KEY, cursor);
+      allEntries.addAll(result.getResult());
+      cursor = result.getCursor();
+    } while (!result.isCompleteIteration());
+
+    assertThat(allEntries).hasSize(3);
+    assertThat(allEntries).containsExactlyInAnyOrderElementsOf(set);
+  }
+
   @Test
   public void givenSetWithOneMember_returnsMember() {
-    jedis.sadd("a", "1");
-    ScanResult<String> result = jedis.sscan("a", "0");
+    jedis.sadd(KEY, FIELD_ONE);
+    ScanResult<String> result = jedis.sscan(KEY, ZERO_CURSOR);
 
     assertThat(result.isCompleteIteration()).isTrue();
-    assertThat(result.getResult()).containsExactly("1");
+    assertThat(result.getResult()).containsExactly(FIELD_ONE);
   }
 
   @Test
   public void givenSetWithMultipleMembers_returnsAllMembers() {
-    jedis.sadd("a", "1", "2", "3");
-    ScanResult<String> result = jedis.sscan("a", "0");
+    jedis.sadd(KEY, FIELD_ONE, FIELD_TWO, FIELD_THREE);
+    ScanResult<String> result = jedis.sscan(KEY, ZERO_CURSOR);
 
     assertThat(result.isCompleteIteration()).isTrue();
-    assertThat(result.getResult()).containsExactlyInAnyOrder("1", "2", "3");
+    assertThat(result.getResult()).containsExactlyInAnyOrder(FIELD_ONE, 
FIELD_TWO, FIELD_THREE);
   }
 
   @Test
   public void givenCount_returnsAllMembersWithoutDuplicates() {
-    jedis.sadd("a", "1", "2", "3");
+    jedis.sadd(KEY, FIELD_ONE, FIELD_TWO, FIELD_THREE);
 
     ScanParams scanParams = new ScanParams();
     scanParams.count(1);
-    String cursor = "0";
+    String cursor = ZERO_CURSOR;
     ScanResult<byte[]> result;
     List<byte[]> allMembersFromScan = new ArrayList<>();
 
     do {
-      result = jedis.sscan("a".getBytes(), cursor.getBytes(), scanParams);
+      result = jedis.sscan(KEY.getBytes(), cursor.getBytes(), scanParams);
       allMembersFromScan.addAll(result.getResult());
       cursor = result.getCursor();
     } while (!result.isCompleteIteration());
 
-    assertThat(allMembersFromScan).containsExactlyInAnyOrder("1".getBytes(),
-        "2".getBytes(),
-        "3".getBytes());
+    
assertThat(allMembersFromScan).containsExactlyInAnyOrder(FIELD_ONE.getBytes(),
+        FIELD_TWO.getBytes(),
+        FIELD_THREE.getBytes());
   }
 
   @Test
   @SuppressWarnings("unchecked")
   public void givenMultipleCounts_returnsAllEntriesWithoutDuplicates() {

Review comment:
       This test still needs to be improved by only calling SSCAN with multiple 
COUNT arguments once instead of for a full iteration of the set, then asserting 
that the last COUNT value was the one used. The test name should be also 
updated to reflect the behaviour being tested:
   ```
     @Test
     public void givenMultipleCounts_usesLastCountSpecified() {
       Set<byte[]> initialMemberData = initializeThousandMemberByteSet();
   
       // Choose two COUNT arguments with a large difference, so that it's 
extremely unlikely that if
       // the first COUNT is used, a number of members greater than or equal to 
the second COUNT will
       // be returned.
       int firstCount = 1;
       int secondCount = 500;
   
       result = sendCustomSscanCommand(KEY, KEY, ZERO_CURSOR,
           "COUNT", String.valueOf(firstCount),
           "COUNT", String.valueOf(secondCount));
   
       List<byte[]> returnedMembers = this.result.getResult();
       assertThat(returnedMembers.size()).isGreaterThanOrEqualTo(secondCount);
   
       assertThat(returnedMembers).isSubsetOf(initialMemberData);
     }
   ```

##########
File path: 
geode-for-redis/src/integrationTest/java/org/apache/geode/redis/internal/commands/executor/set/AbstractSScanIntegrationTest.java
##########
@@ -54,295 +74,397 @@ public void tearDown() {
   }
 
   @Test
-  public void givenNoKeyArgument_returnsWrongNumberOfArgumentsError() {
-    assertThatThrownBy(() -> jedis.sendCommand("key", Protocol.Command.SSCAN))
-        .hasMessageContaining("ERR wrong number of arguments for 'sscan' 
command");
+  public void givenLessThanTwoArguments_returnsWrongNumberOfArgumentsError() {
+    assertAtLeastNArgs(jedis, Protocol.Command.SSCAN, 2);
+  }
+
+  @Test
+  public void givenNonexistentKey_returnsEmptyArray() {
+    ScanResult<String> result = jedis.sscan("nonexistent", ZERO_CURSOR);
+
+    assertThat(result.isCompleteIteration()).isTrue();
+    assertThat(result.getResult()).isEmpty();
+  }
+
+  @Test
+  public void 
givenNonexistentKeyAndIncorrectOptionalArguments_returnsEmptyArray() {
+    result = sendCustomSscanCommand("nonexistentKey", "nonexistentKey", 
ZERO_CURSOR, "ANY");
+    assertThat(result.getResult()).isEmpty();
   }
 
   @Test
-  public void givenNoCursorArgument_returnsWrongNumberOfArgumentsError() {
-    assertThatThrownBy(() -> jedis.sendCommand("key", Protocol.Command.SSCAN, 
"key"))
+  public void givenIncorrectOptionalArgumentAndKeyExists_returnsSyntaxError() {
+    assertThatThrownBy(() -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, 
KEY))
         .hasMessageContaining("ERR wrong number of arguments for 'sscan' 
command");
   }
 
   @Test
-  public void givenArgumentsAreNotOddAndKeyExists_returnsSyntaxError() {
-    jedis.sadd("a", "1");
-    assertThatThrownBy(() -> jedis.sendCommand("a", Protocol.Command.SSCAN, 
"a", "0", "a*"))
+  public void givenIncorrectOptionalArgumentsAndKeyExists_returnsSyntaxError() 
{
+    jedis.sadd(KEY, "1");
+    assertThatThrownBy(() -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, 
KEY, ZERO_CURSOR, "a*"))
         .hasMessageContaining(ERROR_SYNTAX);
   }
 
   @Test
-  @SuppressWarnings("unchecked")
-  public void givenArgumentsAreNotOddAndKeyDoesNotExist_returnsEmptyArray() {
-    List<Object> result =
-        (List<Object>) jedis.sendCommand("key!", Protocol.Command.SSCAN, 
"key!", "0", "a*");
+  public void 
givenMatchArgumentWithoutPatternOnExistingKey_returnsSyntaxError() {
+    jedis.sadd(KEY, "1");
+    assertThatThrownBy(
+        () -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, KEY, ZERO_CURSOR, 
"MATCH"))
+            .hasMessageContaining(ERROR_SYNTAX);
+  }
 
-    assertThat((byte[]) result.get(0)).isEqualTo("0".getBytes());
-    assertThat((List<Object>) result.get(1)).isEmpty();
+  @Test
+  public void 
givenCountArgumentWithoutNumberOnExistingKey_returnsSyntaxError() {
+    jedis.sadd(KEY, "1");
+    assertThatThrownBy(
+        () -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, KEY, ZERO_CURSOR, 
"COUNT"))
+            .hasMessageContaining(ERROR_SYNTAX);
   }
 
   @Test
   public void givenMatchOrCountKeywordNotSpecified_returnsSyntaxError() {
-    jedis.sadd("a", "1");
-    assertThatThrownBy(() -> jedis.sendCommand("a", Protocol.Command.SSCAN, 
"a", "0", "a*", "1"))
-        .hasMessageContaining(ERROR_SYNTAX);
+    jedis.sadd(KEY, "1");
+    assertThatThrownBy(
+        () -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, KEY, ZERO_CURSOR, 
"a*", "1"))
+            .hasMessageContaining(ERROR_SYNTAX);
   }
 
   @Test
   public void 
givenCount_whenCountParameterIsNotAnInteger_returnsNotIntegerError() {
-    jedis.sadd("a", "1");
+    jedis.sadd(KEY, "1");
     assertThatThrownBy(
-        () -> jedis.sendCommand("a", Protocol.Command.SSCAN, "a", "0", 
"COUNT", "MATCH"))
+        () -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, KEY, ZERO_CURSOR, 
"COUNT", "MATCH"))
             .hasMessageContaining(ERROR_NOT_INTEGER);
   }
 
   @Test
   public void 
givenMultipleCounts_whenAnyCountParameterIsNotAnInteger_returnsNotIntegerError()
 {
-    jedis.sadd("a", "1");
-    assertThatThrownBy(() -> jedis.sendCommand("a", Protocol.Command.SSCAN, 
"a", "0", "COUNT", "2",
-        "COUNT", "sjlfs", "COUNT", "1"))
-            .hasMessageContaining(ERROR_NOT_INTEGER);
+    jedis.sadd(KEY, "1");
+    assertThatThrownBy(
+        () -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, KEY, ZERO_CURSOR, 
"COUNT", "12",
+            "COUNT", "sjlfs", "COUNT", "1"))
+                .hasMessageContaining(ERROR_NOT_INTEGER);
   }
 
   @Test
   public void 
givenMultipleCounts_whenAnyCountParameterIsLessThanOne_returnsSyntaxError() {
-    jedis.sadd("a", "1");
-    assertThatThrownBy(() -> jedis.sendCommand("a", Protocol.Command.SSCAN, 
"a", "0", "COUNT", "2",
-        "COUNT", "0", "COUNT", "1"))
-            .hasMessageContaining(ERROR_SYNTAX);
+    jedis.sadd(KEY, "1");
+    assertThatThrownBy(
+        () -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, KEY, ZERO_CURSOR, 
"COUNT", "12",
+            "COUNT", "0", "COUNT", "1"))
+                .hasMessageContaining(ERROR_SYNTAX);
   }
 
   @Test
   public void givenCount_whenCountParameterIsZero_returnsSyntaxError() {
-    jedis.sadd("a", "1");
-
-    assertThatThrownBy(() -> jedis.sendCommand("a", Protocol.Command.SSCAN, 
"a", "0", "COUNT", "0"))
-        .hasMessageContaining(ERROR_SYNTAX);
+    jedis.sadd(KEY, "1");
+    assertThatThrownBy(
+        () -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, KEY, ZERO_CURSOR, 
"COUNT", "0"))
+            .hasMessageContaining(ERROR_SYNTAX);
   }
 
   @Test
   public void givenCount_whenCountParameterIsNegative_returnsSyntaxError() {
-    jedis.sadd("a", "1");
-
+    jedis.sadd(KEY, "1");
     assertThatThrownBy(
-        () -> jedis.sendCommand("a", Protocol.Command.SSCAN, "a", "0", 
"COUNT", "-37"))
+        () -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, KEY, ZERO_CURSOR, 
"COUNT", "-37"))
             .hasMessageContaining(ERROR_SYNTAX);
   }
 
   @Test
   public void givenKeyIsNotASet_returnsWrongTypeError() {
-    jedis.hset("a", "b", "1");
-
+    jedis.hset(KEY, "b", FIELD_ONE);
     assertThatThrownBy(
-        () -> jedis.sendCommand("a", Protocol.Command.SSCAN, "a", "0", 
"COUNT", "-37"))
+        () -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, KEY, ZERO_CURSOR, 
"COUNT", "-37"))
             .hasMessageContaining(ERROR_WRONG_TYPE);
   }
 
   @Test
   public void 
givenKeyIsNotASet_andCursorIsNotAnInteger_returnsInvalidCursorError() {
-    jedis.hset("a", "b", "1");
-
-    assertThatThrownBy(() -> jedis.sendCommand("a", Protocol.Command.SSCAN, 
"a", "sjfls"))
+    jedis.hset(KEY, "b", FIELD_ONE);
+    assertThatThrownBy(() -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, 
KEY, "not-int"))
         .hasMessageContaining(ERROR_CURSOR);
   }
 
   @Test
   public void 
givenNonexistentKey_andCursorIsNotInteger_returnsInvalidCursorError() {
     assertThatThrownBy(
-        () -> jedis.sendCommand("notReal", Protocol.Command.SSCAN, "notReal", 
"notReal", "sjfls"))
+        () -> jedis.sendCommand("notReal", Protocol.Command.SSCAN, "notReal", 
"notReal", "not-int"))
             .hasMessageContaining(ERROR_CURSOR);
   }
 
   @Test
   public void 
givenExistentSetKey_andCursorIsNotAnInteger_returnsInvalidCursorError() {
-    jedis.set("a", "b");
-
-    assertThatThrownBy(() -> jedis.sendCommand("a", Protocol.Command.SSCAN, 
"a", "sjfls"))
+    jedis.set(KEY, "b");
+    assertThatThrownBy(() -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, 
KEY, "not-int"))
         .hasMessageContaining(ERROR_CURSOR);
   }
 
   @Test
-  public void givenNonexistentKey_returnsEmptyArray() {
-    ScanResult<String> result = jedis.sscan("nonexistent", "0");
-
-    assertThat(result.isCompleteIteration()).isTrue();
-    assertThat(result.getResult()).isEmpty();
+  public void givenNegativeCursor_doesNotError() {
+    initializeThousandMemberSet();
+    assertThatNoException().isThrownBy(() -> jedis.sscan(KEY, "-1"));
   }
 
   @Test
   public void givenSetWithOneMember_returnsMember() {
-    jedis.sadd("a", "1");
-    ScanResult<String> result = jedis.sscan("a", "0");
+    jedis.sadd(KEY, "1");
+
+    ScanResult<String> result = jedis.sscan(KEY, ZERO_CURSOR);
 
     assertThat(result.isCompleteIteration()).isTrue();
-    assertThat(result.getResult()).containsExactly("1");
+    assertThat(result.getResult()).containsOnly(FIELD_ONE);
   }
 
   @Test
-  public void givenSetWithMultipleMembers_returnsAllMembers() {
-    jedis.sadd("a", "1", "2", "3");
-    ScanResult<String> result = jedis.sscan("a", "0");
+  public void givenSetWithMultipleMembers_returnsSubsetOfMembers() {
+    final Set<String> initialMemberData = initializeThousandMemberSet();
 
-    assertThat(result.isCompleteIteration()).isTrue();
-    assertThat(result.getResult()).containsExactlyInAnyOrder("1", "2", "3");
+    ScanResult<String> result = jedis.sscan(KEY, ZERO_CURSOR);
+
+    assertThat(result.getResult()).isSubsetOf(initialMemberData);
   }
 
   @Test
   public void givenCount_returnsAllMembersWithoutDuplicates() {
-    jedis.sadd("a", "1", "2", "3");
-
+    Set<byte[]> initialTotalSet = initializeThousandMemberByteSet();
+    int count = 10;

Review comment:
       The default value of COUNT for SSCAN is 10, so it would be good to use a 
different, larger, value here, to make sure that the value of COUNT is 
correctly being picked up.

##########
File path: 
geode-for-redis/src/integrationTest/java/org/apache/geode/redis/internal/commands/executor/set/AbstractSScanIntegrationTest.java
##########
@@ -54,295 +74,397 @@ public void tearDown() {
   }
 
   @Test
-  public void givenNoKeyArgument_returnsWrongNumberOfArgumentsError() {
-    assertThatThrownBy(() -> jedis.sendCommand("key", Protocol.Command.SSCAN))
-        .hasMessageContaining("ERR wrong number of arguments for 'sscan' 
command");
+  public void givenLessThanTwoArguments_returnsWrongNumberOfArgumentsError() {
+    assertAtLeastNArgs(jedis, Protocol.Command.SSCAN, 2);
+  }
+
+  @Test
+  public void givenNonexistentKey_returnsEmptyArray() {
+    ScanResult<String> result = jedis.sscan("nonexistent", ZERO_CURSOR);
+
+    assertThat(result.isCompleteIteration()).isTrue();
+    assertThat(result.getResult()).isEmpty();
+  }
+
+  @Test
+  public void 
givenNonexistentKeyAndIncorrectOptionalArguments_returnsEmptyArray() {
+    result = sendCustomSscanCommand("nonexistentKey", "nonexistentKey", 
ZERO_CURSOR, "ANY");
+    assertThat(result.getResult()).isEmpty();
   }
 
   @Test
-  public void givenNoCursorArgument_returnsWrongNumberOfArgumentsError() {
-    assertThatThrownBy(() -> jedis.sendCommand("key", Protocol.Command.SSCAN, 
"key"))
+  public void givenIncorrectOptionalArgumentAndKeyExists_returnsSyntaxError() {
+    assertThatThrownBy(() -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, 
KEY))
         .hasMessageContaining("ERR wrong number of arguments for 'sscan' 
command");
   }
 
   @Test
-  public void givenArgumentsAreNotOddAndKeyExists_returnsSyntaxError() {
-    jedis.sadd("a", "1");
-    assertThatThrownBy(() -> jedis.sendCommand("a", Protocol.Command.SSCAN, 
"a", "0", "a*"))
+  public void givenIncorrectOptionalArgumentsAndKeyExists_returnsSyntaxError() 
{
+    jedis.sadd(KEY, "1");
+    assertThatThrownBy(() -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, 
KEY, ZERO_CURSOR, "a*"))
         .hasMessageContaining(ERROR_SYNTAX);
   }
 
   @Test
-  @SuppressWarnings("unchecked")
-  public void givenArgumentsAreNotOddAndKeyDoesNotExist_returnsEmptyArray() {
-    List<Object> result =
-        (List<Object>) jedis.sendCommand("key!", Protocol.Command.SSCAN, 
"key!", "0", "a*");
+  public void 
givenMatchArgumentWithoutPatternOnExistingKey_returnsSyntaxError() {
+    jedis.sadd(KEY, "1");
+    assertThatThrownBy(
+        () -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, KEY, ZERO_CURSOR, 
"MATCH"))
+            .hasMessageContaining(ERROR_SYNTAX);
+  }
 
-    assertThat((byte[]) result.get(0)).isEqualTo("0".getBytes());
-    assertThat((List<Object>) result.get(1)).isEmpty();
+  @Test
+  public void 
givenCountArgumentWithoutNumberOnExistingKey_returnsSyntaxError() {
+    jedis.sadd(KEY, "1");
+    assertThatThrownBy(
+        () -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, KEY, ZERO_CURSOR, 
"COUNT"))
+            .hasMessageContaining(ERROR_SYNTAX);
   }
 
   @Test
   public void givenMatchOrCountKeywordNotSpecified_returnsSyntaxError() {
-    jedis.sadd("a", "1");
-    assertThatThrownBy(() -> jedis.sendCommand("a", Protocol.Command.SSCAN, 
"a", "0", "a*", "1"))
-        .hasMessageContaining(ERROR_SYNTAX);
+    jedis.sadd(KEY, "1");
+    assertThatThrownBy(
+        () -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, KEY, ZERO_CURSOR, 
"a*", "1"))
+            .hasMessageContaining(ERROR_SYNTAX);
   }
 
   @Test
   public void 
givenCount_whenCountParameterIsNotAnInteger_returnsNotIntegerError() {
-    jedis.sadd("a", "1");
+    jedis.sadd(KEY, "1");
     assertThatThrownBy(
-        () -> jedis.sendCommand("a", Protocol.Command.SSCAN, "a", "0", 
"COUNT", "MATCH"))
+        () -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, KEY, ZERO_CURSOR, 
"COUNT", "MATCH"))
             .hasMessageContaining(ERROR_NOT_INTEGER);
   }
 
   @Test
   public void 
givenMultipleCounts_whenAnyCountParameterIsNotAnInteger_returnsNotIntegerError()
 {
-    jedis.sadd("a", "1");
-    assertThatThrownBy(() -> jedis.sendCommand("a", Protocol.Command.SSCAN, 
"a", "0", "COUNT", "2",
-        "COUNT", "sjlfs", "COUNT", "1"))
-            .hasMessageContaining(ERROR_NOT_INTEGER);
+    jedis.sadd(KEY, "1");
+    assertThatThrownBy(
+        () -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, KEY, ZERO_CURSOR, 
"COUNT", "12",
+            "COUNT", "sjlfs", "COUNT", "1"))
+                .hasMessageContaining(ERROR_NOT_INTEGER);
   }
 
   @Test
   public void 
givenMultipleCounts_whenAnyCountParameterIsLessThanOne_returnsSyntaxError() {
-    jedis.sadd("a", "1");
-    assertThatThrownBy(() -> jedis.sendCommand("a", Protocol.Command.SSCAN, 
"a", "0", "COUNT", "2",
-        "COUNT", "0", "COUNT", "1"))
-            .hasMessageContaining(ERROR_SYNTAX);
+    jedis.sadd(KEY, "1");
+    assertThatThrownBy(
+        () -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, KEY, ZERO_CURSOR, 
"COUNT", "12",
+            "COUNT", "0", "COUNT", "1"))
+                .hasMessageContaining(ERROR_SYNTAX);
   }
 
   @Test
   public void givenCount_whenCountParameterIsZero_returnsSyntaxError() {
-    jedis.sadd("a", "1");
-
-    assertThatThrownBy(() -> jedis.sendCommand("a", Protocol.Command.SSCAN, 
"a", "0", "COUNT", "0"))
-        .hasMessageContaining(ERROR_SYNTAX);
+    jedis.sadd(KEY, "1");
+    assertThatThrownBy(
+        () -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, KEY, ZERO_CURSOR, 
"COUNT", "0"))
+            .hasMessageContaining(ERROR_SYNTAX);
   }
 
   @Test
   public void givenCount_whenCountParameterIsNegative_returnsSyntaxError() {
-    jedis.sadd("a", "1");
-
+    jedis.sadd(KEY, "1");
     assertThatThrownBy(
-        () -> jedis.sendCommand("a", Protocol.Command.SSCAN, "a", "0", 
"COUNT", "-37"))
+        () -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, KEY, ZERO_CURSOR, 
"COUNT", "-37"))
             .hasMessageContaining(ERROR_SYNTAX);
   }
 
   @Test
   public void givenKeyIsNotASet_returnsWrongTypeError() {
-    jedis.hset("a", "b", "1");
-
+    jedis.hset(KEY, "b", FIELD_ONE);
     assertThatThrownBy(
-        () -> jedis.sendCommand("a", Protocol.Command.SSCAN, "a", "0", 
"COUNT", "-37"))
+        () -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, KEY, ZERO_CURSOR, 
"COUNT", "-37"))
             .hasMessageContaining(ERROR_WRONG_TYPE);
   }
 
   @Test
   public void 
givenKeyIsNotASet_andCursorIsNotAnInteger_returnsInvalidCursorError() {
-    jedis.hset("a", "b", "1");
-
-    assertThatThrownBy(() -> jedis.sendCommand("a", Protocol.Command.SSCAN, 
"a", "sjfls"))
+    jedis.hset(KEY, "b", FIELD_ONE);
+    assertThatThrownBy(() -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, 
KEY, "not-int"))
         .hasMessageContaining(ERROR_CURSOR);
   }
 
   @Test
   public void 
givenNonexistentKey_andCursorIsNotInteger_returnsInvalidCursorError() {
     assertThatThrownBy(
-        () -> jedis.sendCommand("notReal", Protocol.Command.SSCAN, "notReal", 
"notReal", "sjfls"))
+        () -> jedis.sendCommand("notReal", Protocol.Command.SSCAN, "notReal", 
"notReal", "not-int"))
             .hasMessageContaining(ERROR_CURSOR);
   }
 
   @Test
   public void 
givenExistentSetKey_andCursorIsNotAnInteger_returnsInvalidCursorError() {
-    jedis.set("a", "b");
-
-    assertThatThrownBy(() -> jedis.sendCommand("a", Protocol.Command.SSCAN, 
"a", "sjfls"))
+    jedis.set(KEY, "b");
+    assertThatThrownBy(() -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, 
KEY, "not-int"))
         .hasMessageContaining(ERROR_CURSOR);
   }
 
   @Test
-  public void givenNonexistentKey_returnsEmptyArray() {
-    ScanResult<String> result = jedis.sscan("nonexistent", "0");
-
-    assertThat(result.isCompleteIteration()).isTrue();
-    assertThat(result.getResult()).isEmpty();
+  public void givenNegativeCursor_doesNotError() {
+    initializeThousandMemberSet();
+    assertThatNoException().isThrownBy(() -> jedis.sscan(KEY, "-1"));
   }
 
   @Test
   public void givenSetWithOneMember_returnsMember() {
-    jedis.sadd("a", "1");
-    ScanResult<String> result = jedis.sscan("a", "0");
+    jedis.sadd(KEY, "1");
+
+    ScanResult<String> result = jedis.sscan(KEY, ZERO_CURSOR);
 
     assertThat(result.isCompleteIteration()).isTrue();
-    assertThat(result.getResult()).containsExactly("1");
+    assertThat(result.getResult()).containsOnly(FIELD_ONE);
   }
 
   @Test
-  public void givenSetWithMultipleMembers_returnsAllMembers() {
-    jedis.sadd("a", "1", "2", "3");
-    ScanResult<String> result = jedis.sscan("a", "0");
+  public void givenSetWithMultipleMembers_returnsSubsetOfMembers() {
+    final Set<String> initialMemberData = initializeThousandMemberSet();
 
-    assertThat(result.isCompleteIteration()).isTrue();
-    assertThat(result.getResult()).containsExactlyInAnyOrder("1", "2", "3");
+    ScanResult<String> result = jedis.sscan(KEY, ZERO_CURSOR);
+
+    assertThat(result.getResult()).isSubsetOf(initialMemberData);
   }
 
   @Test
   public void givenCount_returnsAllMembersWithoutDuplicates() {
-    jedis.sadd("a", "1", "2", "3");
-
+    Set<byte[]> initialTotalSet = initializeThousandMemberByteSet();
+    int count = 10;
     ScanParams scanParams = new ScanParams();
-    scanParams.count(1);
-    String cursor = "0";
-    ScanResult<byte[]> result;
-    List<byte[]> allMembersFromScan = new ArrayList<>();
+    scanParams.count(10);
 
-    do {
-      result = jedis.sscan("a".getBytes(), cursor.getBytes(), scanParams);
-      allMembersFromScan.addAll(result.getResult());
-      cursor = result.getCursor();
-    } while (!result.isCompleteIteration());
+    result = jedis.sscan(KEY.getBytes(), ZERO_CURSOR.getBytes(), scanParams);
 
-    assertThat(allMembersFromScan).containsExactlyInAnyOrder("1".getBytes(),
-        "2".getBytes(),
-        "3".getBytes());
+    assertThat(result.getResult().size()).isGreaterThanOrEqualTo(count);
+    assertThat(result.getResult()).isSubsetOf(initialTotalSet);
   }
 
   @Test
-  @SuppressWarnings("unchecked")
   public void givenMultipleCounts_returnsAllEntriesWithoutDuplicates() {
-    jedis.sadd("a", "1", "12", "3");
-
-    List<Object> result;
-
+    Set<byte[]> initialMemberData = initializeThousandMemberByteSet();
     List<byte[]> allEntries = new ArrayList<>();
-    String cursor = "0";
+    String cursor = ZERO_CURSOR;
 
     do {
-      result =
-          (List<Object>) jedis.sendCommand("a", Protocol.Command.SSCAN, "a", 
cursor, "COUNT", "2",
-              "COUNT", "1");
-      allEntries.addAll((List<byte[]>) result.get(1));
-      cursor = new String((byte[]) result.get(0));
-    } while (!Arrays.equals((byte[]) result.get(0), "0".getBytes()));
+      result = sendCustomSscanCommand(KEY, KEY, cursor, "COUNT", "1", "COUNT", 
"2");
+      cursor = result.getCursor();
+      allEntries.addAll(result.getResult());
+    } while (!cursor.equals(ZERO_CURSOR));
 
-    assertThat((byte[]) result.get(0)).isEqualTo("0".getBytes());
-    assertThat(allEntries).containsExactlyInAnyOrder("1".getBytes(),
-        "12".getBytes(),
-        "3".getBytes());
+    
assertThat(allEntries).containsExactlyInAnyOrderElementsOf(initialMemberData);
   }
 
   @Test
-  public void givenMatch_returnsAllMatchingMembersWithoutDuplicates() {
-    jedis.sadd("a", "1", "12", "3");
-
+  public void givenSetWithThreeEntriesAndMatch_returnsOnlyMatchingElements() {
+    jedis.sadd(KEY, FIELD_ONE, FIELD_TWO, FIELD_THREE);
     ScanParams scanParams = new ScanParams();
     scanParams.match("1*");
 
-    ScanResult<byte[]> result =
-        jedis.sscan("a".getBytes(), "0".getBytes(), scanParams);
+    result = jedis.sscan(KEY.getBytes(), ZERO_CURSOR.getBytes(), scanParams);
 
     assertThat(result.isCompleteIteration()).isTrue();
-    assertThat(result.getResult()).containsExactlyInAnyOrder("1".getBytes(),
-        "12".getBytes());
+    assertThat(result.getResult()).containsOnly(FIELD_ONE.getBytes(),
+        FIELD_TWO.getBytes());
   }
 
   @Test
-  @SuppressWarnings("unchecked")
-  public void givenMultipleMatches_returnsMembersMatchingLastMatchParameter() {
-    jedis.sadd("a", "1", "12", "3");
+  public void 
givenSetWithThreeEntriesAndMultipleMatchArguments_returnsOnlyElementsMatchingLastMatchArgument()
 {
+    jedis.sadd(KEY, FIELD_ONE, FIELD_TWO, FIELD_THREE);
 
-    List<Object> result = (List<Object>) jedis.sendCommand("a", 
Protocol.Command.SSCAN, "a", "0",
-        "MATCH", "3*", "MATCH", "1*");
+    result = sendCustomSscanCommand(KEY, KEY, ZERO_CURSOR, "MATCH", "3*", 
"MATCH", "1*");
 
-    assertThat((byte[]) result.get(0)).isEqualTo("0".getBytes());
-    assertThat((List<byte[]>) 
result.get(1)).containsExactlyInAnyOrder("1".getBytes(),
-        "12".getBytes());
+    assertThat(result.getCursor()).isEqualTo(ZERO_CURSOR);
+    assertThat(result.getResult()).containsOnly(FIELD_ONE.getBytes(),
+        FIELD_TWO.getBytes());
   }
 
   @Test
-  public void givenMatchAndCount_returnsAllMembersWithoutDuplicates() {
-    jedis.sadd("a", "1", "12", "3");
-
+  public void 
givenSetWithThreeMembersAndMatchAndCount_returnsAllMatchingMembers() {
+    jedis.sadd(KEY, FIELD_ONE, FIELD_TWO, FIELD_THREE);
     ScanParams scanParams = new ScanParams();
     scanParams.count(1);
     scanParams.match("1*");
-    ScanResult<byte[]> result;
     List<byte[]> allMembersFromScan = new ArrayList<>();
-    String cursor = "0";
+    String cursor = ZERO_CURSOR;
 
     do {
-      result = jedis.sscan("a".getBytes(), cursor.getBytes(), scanParams);
+      result = jedis.sscan(KEY.getBytes(), cursor.getBytes(), scanParams);
       allMembersFromScan.addAll(result.getResult());
       cursor = result.getCursor();
     } while (!result.isCompleteIteration());
 
-    assertThat(allMembersFromScan).containsExactlyInAnyOrder("1".getBytes(),
-        "12".getBytes());
+    assertThat(allMembersFromScan).containsOnly(FIELD_ONE.getBytes(),
+        FIELD_TWO.getBytes());
   }
 
   @Test
-  @SuppressWarnings("unchecked")
-  public void 
givenMultipleCountsAndMatches_returnsAllEntriesWithoutDuplicates() {
-    jedis.sadd("a", "1", "12", "3");
-
-    List<Object> result;
+  public void 
givenSetWithThreeMembersAndMultipleMatchAndCountArguments_returnsAllMatchingMembers()
 {
+    jedis.sadd(KEY, FIELD_ONE, FIELD_TWO, FIELD_THREE);
     List<byte[]> allEntries = new ArrayList<>();
-    String cursor = "0";
+    String cursor = ZERO_CURSOR;
 
     do {
-      result =
-          (List<Object>) jedis.sendCommand("a", Protocol.Command.SSCAN, "a", 
cursor, "COUNT", "37",
-              "MATCH", "3*", "COUNT", "2", "COUNT", "1", "MATCH", "1*");
-      allEntries.addAll((List<byte[]>) result.get(1));
-      cursor = new String((byte[]) result.get(0));
-    } while (!Arrays.equals((byte[]) result.get(0), "0".getBytes()));
+      result = sendCustomSscanCommand(KEY, KEY, cursor, "COUNT", "37", 
"MATCH", "3*", "COUNT",
+          FIELD_TWO, "COUNT", FIELD_ONE, "MATCH", "1*");
+      allEntries.addAll(result.getResult());
+      cursor = result.getCursor();
+    } while (!cursor.equals(ZERO_CURSOR));
 
-    assertThat((byte[]) result.get(0)).isEqualTo("0".getBytes());
-    assertThat(allEntries).containsExactlyInAnyOrder("1".getBytes(),
-        "12".getBytes());
+    assertThat(allEntries).containsOnly(FIELD_ONE.getBytes(),
+        FIELD_TWO.getBytes());
   }
 
   @Test
-  public void givenNegativeCursor_returnsMembersUsingAbsoluteValueOfCursor() {
-    jedis.sadd("b", "green", "orange", "yellow");
+  public void givenNonMatchingPattern_returnsEmptyResult() {
+    jedis.sadd(KEY, "cat dog emu");
+    ScanParams scanParams = new ScanParams();
+    scanParams.match("*fish*");
 
-    List<String> allEntries = new ArrayList<>();
+    ScanResult<byte[]> result =
+        jedis.sscan(KEY.getBytes(), ZERO_CURSOR.getBytes(), scanParams);
+
+    assertThat(result.getResult()).isEmpty();
+  }
+
+  @Test
+  public void should_notReturnValue_givenValueWasRemovedBeforeSscanIsCalled() {
+    initializeThreeMemberSet();
+
+    jedis.srem(KEY, FIELD_THREE);
+    GeodeAwaitility.await().untilAsserted(
+        () -> assertThat(jedis.sismember(KEY, FIELD_THREE)).isFalse());
+    ScanResult<String> result = jedis.sscan(KEY, ZERO_CURSOR);
+
+    assertThat(result.getResult()).doesNotContain(FIELD_THREE);
+  }
+
+  @Test
+  public void should_notErrorGivenNonzeroCursorOnFirstCall() {
+    initializeThreeMemberSet();
+    assertThatNoException().isThrownBy(() -> jedis.sscan(KEY, "5"));
+  }
+
+  @Test
+  public void should_notErrorGivenCountEqualToIntegerMaxValue() {
+    Set<byte[]> set = initializeThreeMemberByteSet();
+    ScanParams scanParams = new ScanParams().count(Integer.MAX_VALUE);
+
+    ScanResult<byte[]> result =
+        jedis.sscan(KEY.getBytes(), ZERO_CURSOR.getBytes(), scanParams);
+
+    assertThat(result.getResult())
+        .containsExactlyInAnyOrderElementsOf(set);
+  }
+
+  @Test
+  public void should_notErrorGivenCountGreaterThanIntegerMaxValue() {
+    initializeThreeMemberByteSet();
+    String greaterThanInt = String.valueOf(2L * Integer.MAX_VALUE);
+
+    result = sendCustomSscanCommand(KEY, KEY, ZERO_CURSOR, "COUNT", 
greaterThanInt);
+
+    assertThat(result.getCursor()).isEqualTo(ZERO_CURSOR);
+    assertThat(result.getResult()).containsExactlyInAnyOrder(
+        FIELD_ONE.getBytes(),
+        FIELD_TWO.getBytes(),
+        FIELD_THREE.getBytes());
+  }
+
+  /**** Concurrency ***/
+
+  @Test
+  public void 
should_returnAllConsistentlyPresentMembers_givenConcurrentThreadsAddingAndRemovingMembers()
 {
+    final Set<String> initialMemberData = initializeThousandMemberSet();
+    final int iterationCount = 500;
+    Jedis jedis1 = jedis.getConnectionFromSlot(SLOT_FOR_KEY);
+    Jedis jedis2 = jedis.getConnectionFromSlot(SLOT_FOR_KEY);
+
+    new ConcurrentLoopingThreads(iterationCount,
+        (i) -> multipleSScanAndAssertOnContentOfResultSet(i, jedis1, 
initialMemberData),
+        (i) -> multipleSScanAndAssertOnContentOfResultSet(i, jedis2, 
initialMemberData),
+        (i) -> {
+          String member = "new_" + BASE_FIELD + i;
+          jedis.sadd(KEY, member);
+          jedis.srem(KEY, member);
+        }).run();
+
+    jedis1.close();
+    jedis2.close();
+  }
+
+  @Test
+  public void should_notAlterUnderlyingData_givenMultipleConcurrentSscans() {
+    final Set<String> initialMemberData = initializeThousandMemberSet();
+    jedis.sadd(KEY, initialMemberData.toArray(new String[0]));
+    final int iterationCount = 500;
+    Jedis jedis1 = jedis.getConnectionFromSlot(SLOT_FOR_KEY);
+    Jedis jedis2 = jedis.getConnectionFromSlot(SLOT_FOR_KEY);
+
+    new ConcurrentLoopingThreads(iterationCount,
+        (i) -> multipleSScanAndAssertOnContentOfResultSet(i, jedis1, 
initialMemberData),
+        (i) -> multipleSScanAndAssertOnContentOfResultSet(i, jedis2, 
initialMemberData))
+            .run();
+    
assertThat(jedis.smembers(KEY)).containsExactlyInAnyOrderElementsOf(initialMemberData);
+
+    jedis1.close();
+    jedis2.close();
+  }
 
-    String cursor = "-100";
+  private void multipleSScanAndAssertOnContentOfResultSet(int iteration, Jedis 
jedis,
+      final Set<String> initialMemberData) {
+
+    List<String> allEntries = new ArrayList<>();
     ScanResult<String> result;
+    String cursor = ZERO_CURSOR;
+
     do {
-      result = jedis.sscan("b", cursor);
-      allEntries.addAll(result.getResult());
+      result = jedis.sscan(KEY, cursor);
       cursor = result.getCursor();
+      List<String> resultEntries = result.getResult();
+      allEntries.addAll(resultEntries);
     } while (!result.isCompleteIteration());
 
-    assertThat(allEntries).containsExactlyInAnyOrder("green", "orange", 
"yellow");
+    assertThat(allEntries).as("failed on iteration " + iteration)
+        .containsAll(initialMemberData);
   }
 
-  @Test
-  public void givenCursorGreaterThanUnsignedLongCapacity_returnsCursorError() {
-    assertThatThrownBy(() -> jedis.sscan("a", "18446744073709551616"))
-        .hasMessageContaining(ERROR_CURSOR);
+  @SuppressWarnings("unchecked")
+  private ScanResult<byte[]> sendCustomSscanCommand(String key, String... 
args) {
+    List<Object> result = (List<Object>) (jedis.sendCommand(key, 
Protocol.Command.SSCAN, args));
+    return new ScanResult<>((byte[]) result.get(0), (List<byte[]>) 
result.get(1));
   }
 
-  @Test
-  public void 
givenNegativeCursorGreaterThanUnsignedLongCapacity_returnsCursorError() {
-    assertThatThrownBy(() -> jedis.sscan("a", "-18446744073709551616"))
-        .hasMessageContaining(ERROR_CURSOR);
+  private void initializeThreeMemberSet() {
+    jedis.sadd(KEY, FIELD_ONE, FIELD_TWO, FIELD_THREE);
   }
 
-  @Test
-  public void givenInvalidRegexSyntax_returnsEmptyArray() {
-    jedis.sadd("a", "1");
-    ScanParams scanParams = new ScanParams();
-    scanParams.count(1);
-    scanParams.match("\\p");
+  private Set<byte[]> initializeThreeMemberByteSet() {
+    Set<byte[]> set = new HashSet<>();
+    set.add(FIELD_ONE.getBytes());
+    set.add(FIELD_TWO.getBytes());
+    set.add(FIELD_THREE.getBytes());
+    jedis.sadd(KEY.getBytes(), FIELD_ONE.getBytes(), FIELD_TWO.getBytes(), 
FIELD_THREE.getBytes());
+    return set;
+  }
 
-    ScanResult<byte[]> result =
-        jedis.sscan("a".getBytes(), "0".getBytes(), scanParams);
+  private Set<String> initializeThousandMemberSet() {
+    Set<String> set = new HashSet<>();
+    int SIZE_OF_SET = 1000;

Review comment:
       This variable, and the one in the method below, should not be in 
all-caps, as that is reserved for static final constants. The [Geode Code Style 
Guide page](https://cwiki.apache.org/confluence/display/GEODE/Code+Style+Guide) 
on the wiki contains a link to the [Google Java Style 
Guide](https://google.github.io/styleguide/javaguide.html) which details the 
conventions we (try to) follow when writing code for Geode.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscr...@geode.apache.org

For queries about this service, please contact Infrastructure at:
us...@infra.apache.org


Reply via email to