/* * Copyright 2014-2017 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.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; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.springframework.dao.InvalidDataAccessApiUsageException; /** * @author Christoph Strobl */ public class ScanCursorUnitTests { public @Rule ExpectedException exception = ExpectedException.none(); @Test // DATAREDIS-290 public void cursorShouldNotLoopWhenNoValuesFound() { CapturingCursorDummy cursor = initCursor(new LinkedList<ScanIteration<String>>()); assertThat(cursor.hasNext(), is(false)); } @Test(expected = NoSuchElementException.class) // DATAREDIS-290 public void cursorShouldReturnNullWhenNoNextElementAvailable() { initCursor(new LinkedList<ScanIteration<String>>()).next(); } @Test // DATAREDIS-290 public void cursorShouldNotLoopWhenReachingStartingPointInFistLoop() { LinkedList<ScanIteration<String>> values = new LinkedList<ScanIteration<String>>(); values.add(createIteration(0, "spring", "data", "redis")); CapturingCursorDummy cursor = initCursor(values); assertThat(cursor.next(), is("spring")); assertThat(cursor.getCursorId(), is(0L)); assertThat(cursor.hasNext(), is(true)); assertThat(cursor.next(), is("data")); assertThat(cursor.getCursorId(), is(0L)); assertThat(cursor.hasNext(), is(true)); assertThat(cursor.next(), is("redis")); assertThat(cursor.getCursorId(), is(0L)); assertThat(cursor.hasNext(), is(false)); } @Test // DATAREDIS-290 public void cursorShouldStopLoopWhenReachingStartingPoint() { LinkedList<ScanIteration<String>> values = new LinkedList<ScanIteration<String>>(); values.add(createIteration(1, "spring")); values.add(createIteration(2, "data")); values.add(createIteration(0, "redis")); CapturingCursorDummy cursor = initCursor(values); assertThat(cursor.next(), is("spring")); assertThat(cursor.getCursorId(), is(1L)); assertThat(cursor.hasNext(), is(true)); assertThat(cursor.next(), is("data")); assertThat(cursor.getCursorId(), is(2L)); assertThat(cursor.hasNext(), is(true)); assertThat(cursor.next(), is("redis")); assertThat(cursor.getCursorId(), is(0L)); assertThat(cursor.hasNext(), is(false)); } @Test // DATAREDIS-290 public void shouldThrowExceptionWhenAccessingClosedCursor() { CapturingCursorDummy cursor = new CapturingCursorDummy(null); assertThat(cursor.isClosed(), is(false)); exception.expect(InvalidDataAccessApiUsageException.class); exception.expectMessage("closed cursor"); cursor.next(); } @Test(expected = InvalidDataAccessApiUsageException.class) // DATAREDIS-290 public void repoeningCursorShouldHappenAtLastPosition() throws IOException { LinkedList<ScanIteration<String>> values = new LinkedList<ScanIteration<String>>(); values.add(createIteration(1, "spring")); values.add(createIteration(2, "data")); values.add(createIteration(0, "redis")); Cursor<String> cursor = initCursor(values).open(); assertThat(cursor.next(), is("spring")); assertThat(cursor.getCursorId(), is(1L)); // close the cursor cursor.close(); assertThat(cursor.isClosed(), is(true)); // reopen cursor at last position cursor.open(); } @Test // DATAREDIS-290 public void positionShouldBeIncrementedCorrectly() throws IOException { LinkedList<ScanIteration<String>> values = new LinkedList<ScanIteration<String>>(); values.add(createIteration(1, "spring")); values.add(createIteration(2, "data")); values.add(createIteration(0, "redis")); Cursor<String> cursor = initCursor(values); assertThat(cursor.getPosition(), is(0L)); cursor.next(); assertThat(cursor.getPosition(), is(1L)); cursor.next(); assertThat(cursor.getPosition(), is(2L)); } @Test // DATAREDIS-417 public void hasNextShouldCallScanUntilFinishedWhenScanResultIsAnEmptyCollection() { LinkedList<ScanIteration<String>> values = new LinkedList<ScanIteration<String>>(); 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<String> cursor = initCursor(values); List<String> result = new ArrayList<String>(); while (cursor.hasNext()) { result.add(cursor.next()); } assertThat(result.size(), is(2)); assertThat(result, hasItems("spring", "redis")); } @Test // DATAREDIS-417 public void hasNextShouldStopWhenScanResultIsAnEmptyCollectionAndStateIsFinished() { LinkedList<ScanIteration<String>> values = new LinkedList<ScanIteration<String>>(); 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<String> cursor = initCursor(values); List<String> result = new ArrayList<String>(); while (cursor.hasNext()) { result.add(cursor.next()); } assertThat(result.size(), is(2)); assertThat(result, hasItems("spring", "data")); } @Test // DATAREDIS-417 public void hasNextShouldStopCorrectlyWhenWholeScanIterationDoesNotReturnResultsAndStateIsFinished() { LinkedList<ScanIteration<String>> values = new LinkedList<ScanIteration<String>>(); 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<String> 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<ScanIteration<String>> values) { CapturingCursorDummy cursor = new CapturingCursorDummy(values); cursor.open(); return cursor; } private ScanIteration<String> createIteration(long cursorId, String... values) { return new ScanIteration<String>(cursorId, values.length > 0 ? Arrays.asList(values) : Collections.<String> emptyList()); } private class CapturingCursorDummy extends ScanCursor<String> { private Queue<ScanIteration<String>> values; private Stack<Long> cursors; public CapturingCursorDummy(Queue<ScanIteration<String>> values) { this.values = values; } @Override protected ScanIteration<String> doScan(long cursorId, ScanOptions options) { if (cursors == null) { cursors = new Stack<Long>(); } this.cursors.push(cursorId); return this.values.poll(); } } }