From df4ad35057c745f5fd6af20963b1a310beeefe34 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Thu, 15 Oct 2015 14:23:09 +0200 Subject: [PATCH] DATAREDIS-417 - Add failing tests for ScanCursor throwing NoSuchElementException. Add failing tests for scanning key sets where Redis returns a scan iteration pointing to the next cursor but having no actual values itself like in: scan 0 match key*9 1) 1966080 2) (empty list or set) --- .../AbstractConnectionIntegrationTests.java | 21 +++++ ...actConnectionPipelineIntegrationTests.java | 22 +++-- ...ConnectionTransactionIntegrationTests.java | 33 +++++++- .../data/redis/core/ScanCursorUnitTests.java | 84 ++++++++++++++++++- 4 files changed, 152 insertions(+), 8 deletions(-) diff --git a/src/test/java/org/springframework/data/redis/connection/AbstractConnectionIntegrationTests.java b/src/test/java/org/springframework/data/redis/connection/AbstractConnectionIntegrationTests.java index d37731a50..4ee5799af 100644 --- a/src/test/java/org/springframework/data/redis/connection/AbstractConnectionIntegrationTests.java +++ b/src/test/java/org/springframework/data/redis/connection/AbstractConnectionIntegrationTests.java @@ -1977,6 +1977,27 @@ public abstract class AbstractConnectionIntegrationTests { assertThat(i, is(itemCount)); } + /** + * @see DATAREDIS-417 + */ + @Test + @IfProfileValue(name = "redisVersion", value = "2.8+") + @WithRedisDriver({ RedisDriver.JEDIS, RedisDriver.LETTUCE }) + public void scanShouldReadEntireValueRangeWhenIdividualScanIterationsReturnEmptyCollection() { + + connection.execute("DEBUG", "POPULATE".getBytes(), "100".getBytes()); + + Cursor cursor = connection.scan(ScanOptions.scanOptions().match("key*9").count(10).build()); + + int i = 0; + while (cursor.hasNext()) { + assertThat(new String(cursor.next()), containsString("key:")); + i++; + } + + assertThat(i, is(10)); + } + /** * @see DATAREDIS-306 */ diff --git a/src/test/java/org/springframework/data/redis/connection/AbstractConnectionPipelineIntegrationTests.java b/src/test/java/org/springframework/data/redis/connection/AbstractConnectionPipelineIntegrationTests.java index 661e7ca45..4751403cd 100644 --- a/src/test/java/org/springframework/data/redis/connection/AbstractConnectionPipelineIntegrationTests.java +++ b/src/test/java/org/springframework/data/redis/connection/AbstractConnectionPipelineIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2013 the original author or authors. + * Copyright 2011-2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,15 +16,14 @@ package org.springframework.data.redis.connection; -import org.junit.Ignore; -import org.junit.Test; -import org.springframework.test.annotation.IfProfileValue; +import static org.junit.Assert.*; import java.util.ArrayList; import java.util.List; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import org.junit.Ignore; +import org.junit.Test; +import org.springframework.test.annotation.IfProfileValue; /** * Base test class for integration tests that execute each operation of a Connection while a pipeline is open, verifying @@ -34,6 +33,7 @@ import static org.junit.Assert.assertTrue; * test overrides {@link AbstractConnectionIntegrationTests} when result types are different * * @author Jennifer Hickey + * @author Christoph Strobl */ abstract public class AbstractConnectionPipelineIntegrationTests extends AbstractConnectionIntegrationTests { @@ -120,6 +120,16 @@ abstract public class AbstractConnectionPipelineIntegrationTests extends Abstrac assertTrue(results.isEmpty()); } + /** + * @see DATAREDIS-417 + */ + @Test + @Ignore + @Override + public void scanShouldReadEntireValueRangeWhenIdividualScanIterationsReturnEmptyCollection() { + super.scanShouldReadEntireValueRangeWhenIdividualScanIterationsReturnEmptyCollection(); + } + protected void initConnection() { connection.openPipeline(); } diff --git a/src/test/java/org/springframework/data/redis/connection/AbstractConnectionTransactionIntegrationTests.java b/src/test/java/org/springframework/data/redis/connection/AbstractConnectionTransactionIntegrationTests.java index 3d7261203..84cc78610 100644 --- a/src/test/java/org/springframework/data/redis/connection/AbstractConnectionTransactionIntegrationTests.java +++ b/src/test/java/org/springframework/data/redis/connection/AbstractConnectionTransactionIntegrationTests.java @@ -1,6 +1,22 @@ +/* + * Copyright 2013-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package org.springframework.data.redis.connection; -import static org.junit.Assert.assertEquals; +import static org.junit.Assert.*; import java.util.ArrayList; import java.util.List; @@ -9,6 +25,11 @@ import org.junit.Ignore; import org.junit.Test; import org.springframework.test.annotation.IfProfileValue; +/** + * @author Jennifer Hickey + * @author Thomas Darimont + * @author Christoph Strobl + */ abstract public class AbstractConnectionTransactionIntegrationTests extends AbstractConnectionIntegrationTests { @Ignore @@ -86,6 +107,16 @@ abstract public class AbstractConnectionTransactionIntegrationTests extends Abst connection.scriptKill(); } + /** + * @see DATAREDIS-417 + */ + @Test + @Ignore + @Override + public void scanShouldReadEntireValueRangeWhenIdividualScanIterationsReturnEmptyCollection() { + super.scanShouldReadEntireValueRangeWhenIdividualScanIterationsReturnEmptyCollection(); + } + protected void initConnection() { connection.multi(); } diff --git a/src/test/java/org/springframework/data/redis/core/ScanCursorUnitTests.java b/src/test/java/org/springframework/data/redis/core/ScanCursorUnitTests.java index ab20425c6..b94a88f38 100644 --- a/src/test/java/org/springframework/data/redis/core/ScanCursorUnitTests.java +++ b/src/test/java/org/springframework/data/redis/core/ScanCursorUnitTests.java @@ -16,11 +16,15 @@ package org.springframework.data.redis.core; import static org.hamcrest.core.Is.*; +import static org.hamcrest.core.IsCollectionContaining.*; import static org.junit.Assert.*; import java.io.IOException; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.LinkedList; +import java.util.List; import java.util.NoSuchElementException; import java.util.Queue; import java.util.Stack; @@ -163,6 +167,83 @@ public class ScanCursorUnitTests { assertThat(cursor.getPosition(), is(2L)); } + /** + * @see DATAREDIS-417 + */ + @Test + public void hasNextShouldCallScanUntilFinishedWhenScanResultIsAnEmptyCollection() { + + LinkedList> values = new LinkedList>(); + values.add(createIteration(1, "spring")); + values.add(createIteration(2)); + values.add(createIteration(3)); + values.add(createIteration(4)); + values.add(createIteration(5)); + values.add(createIteration(0, "redis")); + Cursor cursor = initCursor(values); + + List result = new ArrayList(); + while (cursor.hasNext()) { + result.add(cursor.next()); + } + + assertThat(result.size(), is(2)); + assertThat(result, hasItems("spring", "redis")); + } + + /** + * @see DATAREDIS-417 + */ + @Test + public void hasNextShouldStopWhenScanResultIsAnEmptyCollectionAndStateIsFinished() { + + LinkedList> values = new LinkedList>(); + values.add(createIteration(1, "spring")); + values.add(createIteration(2)); + values.add(createIteration(3)); + values.add(createIteration(4)); + values.add(createIteration(5)); + values.add(createIteration(6)); + values.add(createIteration(7, "data")); + values.add(createIteration(0)); + Cursor cursor = initCursor(values); + + List result = new ArrayList(); + while (cursor.hasNext()) { + result.add(cursor.next()); + } + + assertThat(result.size(), is(2)); + assertThat(result, hasItems("spring", "data")); + } + + /** + * @see DATAREDIS-417 + */ + @Test + public void hasNextShouldStopCorrectlyWhenWholeScanIterationDoesNotReturnResultsAndStateIsFinished() { + + LinkedList> values = new LinkedList>(); + values.add(createIteration(1)); + values.add(createIteration(2)); + values.add(createIteration(3)); + values.add(createIteration(4)); + values.add(createIteration(5)); + values.add(createIteration(0)); + Cursor cursor = initCursor(values); + + assertThat(cursor.getPosition(), is(0L)); + + int loops = 0; + while (cursor.hasNext()) { + cursor.next(); + loops++; + } + + assertThat(loops, is(0)); + assertThat(cursor.getCursorId(), is(0L)); + } + private CapturingCursorDummy initCursor(Queue> values) { CapturingCursorDummy cursor = new CapturingCursorDummy(values); cursor.open(); @@ -170,7 +251,8 @@ public class ScanCursorUnitTests { } private ScanIteration createIteration(long cursorId, String... values) { - return new ScanIteration(cursorId, Arrays.asList(values)); + return new ScanIteration(cursorId, values.length > 0 ? Arrays.asList(values) + : Collections. emptyList()); } private class CapturingCursorDummy extends ScanCursor {