DonalEvans commented on a change in pull request #7278: URL: https://github.com/apache/geode/pull/7278#discussion_r796013240
########## File path: geode-for-redis/src/integrationTest/java/org/apache/geode/redis/internal/commands/executor/set/AbstractSScanIntegrationTest.java ########## @@ -54,295 +72,394 @@ 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 givenNoCursorArgument_returnsWrongNumberOfArgumentsError() { - assertThatThrownBy(() -> jedis.sendCommand("key", Protocol.Command.SSCAN, "key")) - .hasMessageContaining("ERR wrong number of arguments for 'sscan' command"); + public void givenNonexistentKey_returnsEmptyArray() { + ScanResult<String> result = jedis.sscan("nonexistentKey", ZERO_CURSOR); + assertThat(result.isCompleteIteration()).isTrue(); + assertThat(result.getResult()).isEmpty(); } @Test - public void givenArgumentsAreNotOddAndKeyExists_returnsSyntaxError() { - jedis.sadd("a", "1"); - assertThatThrownBy(() -> jedis.sendCommand("a", Protocol.Command.SSCAN, "a", "0", "a*")) - .hasMessageContaining(ERROR_SYNTAX); + public void givenNonexistentKeyAndIncorrectOptionalArguments_returnsEmptyArray() { + ScanResult<byte[]> result = + sendCustomSscanCommand("nonexistentKey", "nonexistentKey", ZERO_CURSOR, "ANY"); + assertThat(result.getResult()).isEmpty(); } @Test - @SuppressWarnings("unchecked") - public void givenArgumentsAreNotOddAndKeyDoesNotExist_returnsEmptyArray() { - List<Object> result = - (List<Object>) jedis.sendCommand("key!", Protocol.Command.SSCAN, "key!", "0", "a*"); + public void givenIncorrectOptionalArgumentsAndKeyExists_returnsSyntaxError() { + jedis.sadd(KEY, MEMBER_ONE); + assertThatThrownBy( + () -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, KEY, ZERO_CURSOR, "a*")) + .hasMessageContaining(ERROR_SYNTAX); + } - assertThat((byte[]) result.get(0)).isEqualTo("0".getBytes()); - assertThat((List<Object>) result.get(1)).isEmpty(); + @Test + public void givenMatchArgumentWithoutPatternOnExistingKey_returnsSyntaxError() { + jedis.sadd(KEY, MEMBER_ONE); + assertThatThrownBy( + () -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, KEY, ZERO_CURSOR, "MATCH")) + .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); + public void givenCountArgumentWithoutNumberOnExistingKey_returnsSyntaxError() { + jedis.sadd(KEY, MEMBER_ONE); + assertThatThrownBy( + () -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, KEY, ZERO_CURSOR, "COUNT")) + .hasMessageContaining(ERROR_SYNTAX); + } + + @Test + public void givenAdditionalArgumentNotEqualToMatchOrCount_returnsSyntaxError() { + jedis.sadd(KEY, MEMBER_ONE); + 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, MEMBER_ONE); assertThatThrownBy( - () -> jedis.sendCommand("a", Protocol.Command.SSCAN, "a", "0", "COUNT", "MATCH")) - .hasMessageContaining(ERROR_NOT_INTEGER); + () -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, KEY, ZERO_CURSOR, "COUNT", + "notAnInteger")) + .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, MEMBER_ONE); + assertThatThrownBy( + () -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, KEY, ZERO_CURSOR, "COUNT", "12", + "COUNT", "notAnInteger", "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, MEMBER_ONE); + 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, MEMBER_ONE); + assertThatThrownBy( + () -> jedis.sscan(KEY_BYTES, ZERO_CURSOR_BYTES, new ScanParams().count(0))) + .hasMessageContaining(ERROR_SYNTAX); } @Test public void givenCount_whenCountParameterIsNegative_returnsSyntaxError() { - jedis.sadd("a", "1"); - + jedis.sadd(KEY, MEMBER_ONE); assertThatThrownBy( - () -> jedis.sendCommand("a", Protocol.Command.SSCAN, "a", "0", "COUNT", "-37")) + () -> jedis.sscan(KEY_BYTES, ZERO_CURSOR_BYTES, new ScanParams().count(-37))) .hasMessageContaining(ERROR_SYNTAX); } @Test public void givenKeyIsNotASet_returnsWrongTypeError() { - jedis.hset("a", "b", "1"); - + jedis.hset(KEY, "b", MEMBER_ONE); assertThatThrownBy( - () -> jedis.sendCommand("a", Protocol.Command.SSCAN, "a", "0", "COUNT", "-37")) + () -> jedis.sscan(KEY, ZERO_CURSOR)) .hasMessageContaining(ERROR_WRONG_TYPE); } @Test public void givenKeyIsNotASet_andCursorIsNotAnInteger_returnsInvalidCursorError() { - jedis.hset("a", "b", "1"); - - assertThatThrownBy(() -> jedis.sendCommand("a", Protocol.Command.SSCAN, "a", "sjfls")) - .hasMessageContaining(ERROR_CURSOR); + jedis.hset(KEY, "b", MEMBER_ONE); + assertThatThrownBy( + () -> jedis.sscan(KEY, "notAnInteger")) + .hasMessageContaining(ERROR_CURSOR); } @Test public void givenNonexistentKey_andCursorIsNotInteger_returnsInvalidCursorError() { assertThatThrownBy( - () -> jedis.sendCommand("notReal", Protocol.Command.SSCAN, "notReal", "notReal", "sjfls")) + () -> jedis.sscan("nonexistentKey", "notAnInteger")) .hasMessageContaining(ERROR_CURSOR); } @Test public void givenExistentSetKey_andCursorIsNotAnInteger_returnsInvalidCursorError() { - jedis.set("a", "b"); - - assertThatThrownBy(() -> jedis.sendCommand("a", Protocol.Command.SSCAN, "a", "sjfls")) - .hasMessageContaining(ERROR_CURSOR); + jedis.set(KEY, "b"); + assertThatThrownBy( + () -> jedis.sscan(KEY, "notAnInteger")) + .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, MEMBER_ONE); + + ScanResult<String> result = jedis.sscan(KEY, ZERO_CURSOR); assertThat(result.isCompleteIteration()).isTrue(); - assertThat(result.getResult()).containsExactly("1"); + assertThat(result.getResult()).containsOnly(MEMBER_ONE); } @Test - public void givenSetWithMultipleMembers_returnsAllMembers() { - jedis.sadd("a", "1", "2", "3"); - ScanResult<String> result = jedis.sscan("a", "0"); + public void givenSetWithMultipleMembers_returnsSubsetOfMembers() { + 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 = 99; - ScanParams scanParams = new ScanParams(); - scanParams.count(1); - String cursor = "0"; - ScanResult<byte[]> result; - List<byte[]> allMembersFromScan = new ArrayList<>(); + ScanResult<byte[]> result = + jedis.sscan(KEY_BYTES, ZERO_CURSOR_BYTES, new ScanParams().count(count)); - do { - result = jedis.sscan("a".getBytes(), cursor.getBytes(), scanParams); - allMembersFromScan.addAll(result.getResult()); - cursor = result.getCursor(); - } while (!result.isCompleteIteration()); + assertThat(result.getResult().size()).isGreaterThanOrEqualTo(count); + assertThat(result.getResult()).isSubsetOf(initialTotalSet); + } - assertThat(allMembersFromScan).containsExactlyInAnyOrder("1".getBytes(), - "2".getBytes(), - "3".getBytes()); + @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; + ScanParams scanParams = new ScanParams().count(firstCount).count(secondCount); Review comment: It's not possible to test the server's behaviour with multiple MATCH or COUNT parameters used if you try to specify them using `ScanParameters`, because internally, `ScanParameters` uses a map to store the values, meaning the multiple calls to `match()` or `count()` just overwrite previous calls. In order to test the behaviour we're interested in, you need to use the sendCustomSscanCommand helper method: ``` result = sendCustomSscanCommand(KEY, KEY, ZERO_CURSOR, "COUNT", String.valueOf(firstCount), "COUNT", String.valueOf(secondCount)); ``` ########## File path: geode-for-redis/src/integrationTest/java/org/apache/geode/redis/internal/commands/executor/set/AbstractSScanIntegrationTest.java ########## @@ -54,295 +72,394 @@ 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 givenNoCursorArgument_returnsWrongNumberOfArgumentsError() { - assertThatThrownBy(() -> jedis.sendCommand("key", Protocol.Command.SSCAN, "key")) - .hasMessageContaining("ERR wrong number of arguments for 'sscan' command"); + public void givenNonexistentKey_returnsEmptyArray() { + ScanResult<String> result = jedis.sscan("nonexistentKey", ZERO_CURSOR); + assertThat(result.isCompleteIteration()).isTrue(); + assertThat(result.getResult()).isEmpty(); } @Test - public void givenArgumentsAreNotOddAndKeyExists_returnsSyntaxError() { - jedis.sadd("a", "1"); - assertThatThrownBy(() -> jedis.sendCommand("a", Protocol.Command.SSCAN, "a", "0", "a*")) - .hasMessageContaining(ERROR_SYNTAX); + public void givenNonexistentKeyAndIncorrectOptionalArguments_returnsEmptyArray() { + ScanResult<byte[]> result = + sendCustomSscanCommand("nonexistentKey", "nonexistentKey", ZERO_CURSOR, "ANY"); + assertThat(result.getResult()).isEmpty(); } @Test - @SuppressWarnings("unchecked") - public void givenArgumentsAreNotOddAndKeyDoesNotExist_returnsEmptyArray() { - List<Object> result = - (List<Object>) jedis.sendCommand("key!", Protocol.Command.SSCAN, "key!", "0", "a*"); + public void givenIncorrectOptionalArgumentsAndKeyExists_returnsSyntaxError() { + jedis.sadd(KEY, MEMBER_ONE); + assertThatThrownBy( + () -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, KEY, ZERO_CURSOR, "a*")) + .hasMessageContaining(ERROR_SYNTAX); + } - assertThat((byte[]) result.get(0)).isEqualTo("0".getBytes()); - assertThat((List<Object>) result.get(1)).isEmpty(); + @Test + public void givenMatchArgumentWithoutPatternOnExistingKey_returnsSyntaxError() { + jedis.sadd(KEY, MEMBER_ONE); + assertThatThrownBy( + () -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, KEY, ZERO_CURSOR, "MATCH")) + .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); + public void givenCountArgumentWithoutNumberOnExistingKey_returnsSyntaxError() { + jedis.sadd(KEY, MEMBER_ONE); + assertThatThrownBy( + () -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, KEY, ZERO_CURSOR, "COUNT")) + .hasMessageContaining(ERROR_SYNTAX); + } + + @Test + public void givenAdditionalArgumentNotEqualToMatchOrCount_returnsSyntaxError() { + jedis.sadd(KEY, MEMBER_ONE); + 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, MEMBER_ONE); assertThatThrownBy( - () -> jedis.sendCommand("a", Protocol.Command.SSCAN, "a", "0", "COUNT", "MATCH")) - .hasMessageContaining(ERROR_NOT_INTEGER); + () -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, KEY, ZERO_CURSOR, "COUNT", + "notAnInteger")) + .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, MEMBER_ONE); + assertThatThrownBy( + () -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, KEY, ZERO_CURSOR, "COUNT", "12", + "COUNT", "notAnInteger", "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, MEMBER_ONE); + 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, MEMBER_ONE); + assertThatThrownBy( + () -> jedis.sscan(KEY_BYTES, ZERO_CURSOR_BYTES, new ScanParams().count(0))) + .hasMessageContaining(ERROR_SYNTAX); } @Test public void givenCount_whenCountParameterIsNegative_returnsSyntaxError() { - jedis.sadd("a", "1"); - + jedis.sadd(KEY, MEMBER_ONE); assertThatThrownBy( - () -> jedis.sendCommand("a", Protocol.Command.SSCAN, "a", "0", "COUNT", "-37")) + () -> jedis.sscan(KEY_BYTES, ZERO_CURSOR_BYTES, new ScanParams().count(-37))) .hasMessageContaining(ERROR_SYNTAX); } @Test public void givenKeyIsNotASet_returnsWrongTypeError() { - jedis.hset("a", "b", "1"); - + jedis.hset(KEY, "b", MEMBER_ONE); assertThatThrownBy( - () -> jedis.sendCommand("a", Protocol.Command.SSCAN, "a", "0", "COUNT", "-37")) + () -> jedis.sscan(KEY, ZERO_CURSOR)) .hasMessageContaining(ERROR_WRONG_TYPE); } @Test public void givenKeyIsNotASet_andCursorIsNotAnInteger_returnsInvalidCursorError() { - jedis.hset("a", "b", "1"); - - assertThatThrownBy(() -> jedis.sendCommand("a", Protocol.Command.SSCAN, "a", "sjfls")) - .hasMessageContaining(ERROR_CURSOR); + jedis.hset(KEY, "b", MEMBER_ONE); + assertThatThrownBy( + () -> jedis.sscan(KEY, "notAnInteger")) + .hasMessageContaining(ERROR_CURSOR); } @Test public void givenNonexistentKey_andCursorIsNotInteger_returnsInvalidCursorError() { assertThatThrownBy( - () -> jedis.sendCommand("notReal", Protocol.Command.SSCAN, "notReal", "notReal", "sjfls")) + () -> jedis.sscan("nonexistentKey", "notAnInteger")) .hasMessageContaining(ERROR_CURSOR); } @Test public void givenExistentSetKey_andCursorIsNotAnInteger_returnsInvalidCursorError() { - jedis.set("a", "b"); - - assertThatThrownBy(() -> jedis.sendCommand("a", Protocol.Command.SSCAN, "a", "sjfls")) - .hasMessageContaining(ERROR_CURSOR); + jedis.set(KEY, "b"); + assertThatThrownBy( + () -> jedis.sscan(KEY, "notAnInteger")) + .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, MEMBER_ONE); + + ScanResult<String> result = jedis.sscan(KEY, ZERO_CURSOR); assertThat(result.isCompleteIteration()).isTrue(); - assertThat(result.getResult()).containsExactly("1"); + assertThat(result.getResult()).containsOnly(MEMBER_ONE); } @Test - public void givenSetWithMultipleMembers_returnsAllMembers() { - jedis.sadd("a", "1", "2", "3"); - ScanResult<String> result = jedis.sscan("a", "0"); + public void givenSetWithMultipleMembers_returnsSubsetOfMembers() { + 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 = 99; - ScanParams scanParams = new ScanParams(); - scanParams.count(1); - String cursor = "0"; - ScanResult<byte[]> result; - List<byte[]> allMembersFromScan = new ArrayList<>(); + ScanResult<byte[]> result = + jedis.sscan(KEY_BYTES, ZERO_CURSOR_BYTES, new ScanParams().count(count)); - do { - result = jedis.sscan("a".getBytes(), cursor.getBytes(), scanParams); - allMembersFromScan.addAll(result.getResult()); - cursor = result.getCursor(); - } while (!result.isCompleteIteration()); + assertThat(result.getResult().size()).isGreaterThanOrEqualTo(count); + assertThat(result.getResult()).isSubsetOf(initialTotalSet); + } - assertThat(allMembersFromScan).containsExactlyInAnyOrder("1".getBytes(), - "2".getBytes(), - "3".getBytes()); + @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; + ScanParams scanParams = new ScanParams().count(firstCount).count(secondCount); + + ScanResult<byte[]> result = jedis.sscan(KEY_BYTES, ZERO_CURSOR_BYTES, scanParams); + + List<byte[]> returnedMembers = result.getResult(); + assertThat(returnedMembers.size()).isGreaterThanOrEqualTo(secondCount); + assertThat(returnedMembers).isSubsetOf(initialMemberData); } @Test - @SuppressWarnings("unchecked") - public void givenMultipleCounts_returnsAllEntriesWithoutDuplicates() { - jedis.sadd("a", "1", "12", "3"); + public void givenSetWithThreeEntriesAndMatch_returnsOnlyMatchingElements() { + jedis.sadd(KEY, MEMBER_ONE, MEMBER_TWELVE, MEMBER_THREE); + ScanParams scanParams = new ScanParams().match("1*"); - List<Object> result; + ScanResult<byte[]> result = jedis.sscan(KEY_BYTES, ZERO_CURSOR_BYTES, scanParams); - List<byte[]> allEntries = new ArrayList<>(); - String cursor = "0"; + assertThat(result.isCompleteIteration()).isTrue(); + assertThat(result.getResult()).containsOnly(MEMBER_ONE.getBytes(), + MEMBER_TWELVE.getBytes()); + } - 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())); + @Test + public void givenSetWithThreeEntriesAndMultipleMatchArguments_returnsOnlyElementsMatchingLastMatchArgument() { + jedis.sadd(KEY, MEMBER_ONE, MEMBER_TWELVE, MEMBER_THREE); + ScanParams scanParams = new ScanParams().match("3*").match("1*"); + + ScanResult<byte[]> result = jedis.sscan(KEY_BYTES, ZERO_CURSOR_BYTES, scanParams); - assertThat((byte[]) result.get(0)).isEqualTo("0".getBytes()); - assertThat(allEntries).containsExactlyInAnyOrder("1".getBytes(), - "12".getBytes(), - "3".getBytes()); + assertThat(result.getCursor()).isEqualTo(ZERO_CURSOR); + assertThat(result.getResult()).containsOnly(MEMBER_ONE.getBytes(), + MEMBER_TWELVE.getBytes()); } @Test - public void givenMatch_returnsAllMatchingMembersWithoutDuplicates() { - jedis.sadd("a", "1", "12", "3"); - + public void givenLargeCountAndMatch_returnsOnlyMatchingMembers() { + Set<byte[]> initialMemberData = initializeThousandMemberByteSet(); ScanParams scanParams = new ScanParams(); - scanParams.match("1*"); + // 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); + + ScanResult<byte[]> result = jedis.sscan(KEY_BYTES, ZERO_CURSOR_BYTES, scanParams); + + List<byte[]> returnedMembers = 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")); + } - ScanResult<byte[]> result = - jedis.sscan("a".getBytes(), "0".getBytes(), scanParams); + @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 + ScanParams scanParams = new ScanParams().count(20).match("1*").count(950).match("9*"); Review comment: See the comment above about using `sendCustomSscanCommand()` instead of multiple calls to `ScanParams` methods here. ########## File path: geode-for-redis/src/integrationTest/java/org/apache/geode/redis/internal/commands/executor/set/AbstractSScanIntegrationTest.java ########## @@ -54,295 +72,394 @@ 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 givenNoCursorArgument_returnsWrongNumberOfArgumentsError() { - assertThatThrownBy(() -> jedis.sendCommand("key", Protocol.Command.SSCAN, "key")) - .hasMessageContaining("ERR wrong number of arguments for 'sscan' command"); + public void givenNonexistentKey_returnsEmptyArray() { + ScanResult<String> result = jedis.sscan("nonexistentKey", ZERO_CURSOR); + assertThat(result.isCompleteIteration()).isTrue(); + assertThat(result.getResult()).isEmpty(); } @Test - public void givenArgumentsAreNotOddAndKeyExists_returnsSyntaxError() { - jedis.sadd("a", "1"); - assertThatThrownBy(() -> jedis.sendCommand("a", Protocol.Command.SSCAN, "a", "0", "a*")) - .hasMessageContaining(ERROR_SYNTAX); + public void givenNonexistentKeyAndIncorrectOptionalArguments_returnsEmptyArray() { + ScanResult<byte[]> result = + sendCustomSscanCommand("nonexistentKey", "nonexistentKey", ZERO_CURSOR, "ANY"); + assertThat(result.getResult()).isEmpty(); } @Test - @SuppressWarnings("unchecked") - public void givenArgumentsAreNotOddAndKeyDoesNotExist_returnsEmptyArray() { - List<Object> result = - (List<Object>) jedis.sendCommand("key!", Protocol.Command.SSCAN, "key!", "0", "a*"); + public void givenIncorrectOptionalArgumentsAndKeyExists_returnsSyntaxError() { + jedis.sadd(KEY, MEMBER_ONE); + assertThatThrownBy( + () -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, KEY, ZERO_CURSOR, "a*")) + .hasMessageContaining(ERROR_SYNTAX); + } - assertThat((byte[]) result.get(0)).isEqualTo("0".getBytes()); - assertThat((List<Object>) result.get(1)).isEmpty(); + @Test + public void givenMatchArgumentWithoutPatternOnExistingKey_returnsSyntaxError() { + jedis.sadd(KEY, MEMBER_ONE); + assertThatThrownBy( + () -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, KEY, ZERO_CURSOR, "MATCH")) + .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); + public void givenCountArgumentWithoutNumberOnExistingKey_returnsSyntaxError() { + jedis.sadd(KEY, MEMBER_ONE); + assertThatThrownBy( + () -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, KEY, ZERO_CURSOR, "COUNT")) + .hasMessageContaining(ERROR_SYNTAX); + } + + @Test + public void givenAdditionalArgumentNotEqualToMatchOrCount_returnsSyntaxError() { + jedis.sadd(KEY, MEMBER_ONE); + 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, MEMBER_ONE); assertThatThrownBy( - () -> jedis.sendCommand("a", Protocol.Command.SSCAN, "a", "0", "COUNT", "MATCH")) - .hasMessageContaining(ERROR_NOT_INTEGER); + () -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, KEY, ZERO_CURSOR, "COUNT", + "notAnInteger")) + .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, MEMBER_ONE); + assertThatThrownBy( + () -> jedis.sendCommand(KEY, Protocol.Command.SSCAN, KEY, ZERO_CURSOR, "COUNT", "12", + "COUNT", "notAnInteger", "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, MEMBER_ONE); + 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, MEMBER_ONE); + assertThatThrownBy( + () -> jedis.sscan(KEY_BYTES, ZERO_CURSOR_BYTES, new ScanParams().count(0))) + .hasMessageContaining(ERROR_SYNTAX); } @Test public void givenCount_whenCountParameterIsNegative_returnsSyntaxError() { - jedis.sadd("a", "1"); - + jedis.sadd(KEY, MEMBER_ONE); assertThatThrownBy( - () -> jedis.sendCommand("a", Protocol.Command.SSCAN, "a", "0", "COUNT", "-37")) + () -> jedis.sscan(KEY_BYTES, ZERO_CURSOR_BYTES, new ScanParams().count(-37))) .hasMessageContaining(ERROR_SYNTAX); } @Test public void givenKeyIsNotASet_returnsWrongTypeError() { - jedis.hset("a", "b", "1"); - + jedis.hset(KEY, "b", MEMBER_ONE); assertThatThrownBy( - () -> jedis.sendCommand("a", Protocol.Command.SSCAN, "a", "0", "COUNT", "-37")) + () -> jedis.sscan(KEY, ZERO_CURSOR)) .hasMessageContaining(ERROR_WRONG_TYPE); } @Test public void givenKeyIsNotASet_andCursorIsNotAnInteger_returnsInvalidCursorError() { - jedis.hset("a", "b", "1"); - - assertThatThrownBy(() -> jedis.sendCommand("a", Protocol.Command.SSCAN, "a", "sjfls")) - .hasMessageContaining(ERROR_CURSOR); + jedis.hset(KEY, "b", MEMBER_ONE); + assertThatThrownBy( + () -> jedis.sscan(KEY, "notAnInteger")) + .hasMessageContaining(ERROR_CURSOR); } @Test public void givenNonexistentKey_andCursorIsNotInteger_returnsInvalidCursorError() { assertThatThrownBy( - () -> jedis.sendCommand("notReal", Protocol.Command.SSCAN, "notReal", "notReal", "sjfls")) + () -> jedis.sscan("nonexistentKey", "notAnInteger")) .hasMessageContaining(ERROR_CURSOR); } @Test public void givenExistentSetKey_andCursorIsNotAnInteger_returnsInvalidCursorError() { - jedis.set("a", "b"); - - assertThatThrownBy(() -> jedis.sendCommand("a", Protocol.Command.SSCAN, "a", "sjfls")) - .hasMessageContaining(ERROR_CURSOR); + jedis.set(KEY, "b"); + assertThatThrownBy( + () -> jedis.sscan(KEY, "notAnInteger")) + .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, MEMBER_ONE); + + ScanResult<String> result = jedis.sscan(KEY, ZERO_CURSOR); assertThat(result.isCompleteIteration()).isTrue(); - assertThat(result.getResult()).containsExactly("1"); + assertThat(result.getResult()).containsOnly(MEMBER_ONE); } @Test - public void givenSetWithMultipleMembers_returnsAllMembers() { - jedis.sadd("a", "1", "2", "3"); - ScanResult<String> result = jedis.sscan("a", "0"); + public void givenSetWithMultipleMembers_returnsSubsetOfMembers() { + 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 = 99; - ScanParams scanParams = new ScanParams(); - scanParams.count(1); - String cursor = "0"; - ScanResult<byte[]> result; - List<byte[]> allMembersFromScan = new ArrayList<>(); + ScanResult<byte[]> result = + jedis.sscan(KEY_BYTES, ZERO_CURSOR_BYTES, new ScanParams().count(count)); - do { - result = jedis.sscan("a".getBytes(), cursor.getBytes(), scanParams); - allMembersFromScan.addAll(result.getResult()); - cursor = result.getCursor(); - } while (!result.isCompleteIteration()); + assertThat(result.getResult().size()).isGreaterThanOrEqualTo(count); + assertThat(result.getResult()).isSubsetOf(initialTotalSet); + } - assertThat(allMembersFromScan).containsExactlyInAnyOrder("1".getBytes(), - "2".getBytes(), - "3".getBytes()); + @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; + ScanParams scanParams = new ScanParams().count(firstCount).count(secondCount); + + ScanResult<byte[]> result = jedis.sscan(KEY_BYTES, ZERO_CURSOR_BYTES, scanParams); + + List<byte[]> returnedMembers = result.getResult(); + assertThat(returnedMembers.size()).isGreaterThanOrEqualTo(secondCount); + assertThat(returnedMembers).isSubsetOf(initialMemberData); } @Test - @SuppressWarnings("unchecked") - public void givenMultipleCounts_returnsAllEntriesWithoutDuplicates() { - jedis.sadd("a", "1", "12", "3"); + public void givenSetWithThreeEntriesAndMatch_returnsOnlyMatchingElements() { + jedis.sadd(KEY, MEMBER_ONE, MEMBER_TWELVE, MEMBER_THREE); + ScanParams scanParams = new ScanParams().match("1*"); - List<Object> result; + ScanResult<byte[]> result = jedis.sscan(KEY_BYTES, ZERO_CURSOR_BYTES, scanParams); - List<byte[]> allEntries = new ArrayList<>(); - String cursor = "0"; + assertThat(result.isCompleteIteration()).isTrue(); + assertThat(result.getResult()).containsOnly(MEMBER_ONE.getBytes(), + MEMBER_TWELVE.getBytes()); + } - 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())); + @Test + public void givenSetWithThreeEntriesAndMultipleMatchArguments_returnsOnlyElementsMatchingLastMatchArgument() { + jedis.sadd(KEY, MEMBER_ONE, MEMBER_TWELVE, MEMBER_THREE); + ScanParams scanParams = new ScanParams().match("3*").match("1*"); Review comment: See the comment above about using `sendCustomSscanCommand()` instead of multiple calls to `ScanParams` methods here. -- 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