/* * Copyright 2014 Ben Manes. All Rights Reserved. * * 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 com.github.benmanes.caffeine; import static com.github.benmanes.caffeine.IsValidSingleConsumerQueue.validate; import static com.github.benmanes.caffeine.testing.IsEmptyIterable.deeplyEmpty; import static com.google.common.collect.Iterators.elementsEqual; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.arrayContaining; import static org.hamcrest.Matchers.emptyArray; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.hasToString; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.nullValue; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; import java.util.Objects; import java.util.Queue; import java.util.concurrent.atomic.AtomicInteger; import org.testng.IInvokedMethod; import org.testng.IInvokedMethodListener; import org.testng.ITestResult; import org.testng.annotations.DataProvider; import org.testng.annotations.Listeners; import org.testng.annotations.Test; import com.github.benmanes.caffeine.SingleConsumerQueue.LinearizableNode; import com.github.benmanes.caffeine.SingleConsumerQueueTest.ValidatingQueueListener; import com.github.benmanes.caffeine.testing.Awaits; import com.github.benmanes.caffeine.testing.ConcurrentTestHarness; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.common.testing.SerializableTester; /** * @author ben.manes@gmail.com (Ben Manes) */ @Listeners(ValidatingQueueListener.class) public class SingleConsumerQueueTest { private static final int PRODUCE = 10_000; private static final int NUM_PRODUCERS = 10; private static final int POPULATED_SIZE = 10; @Test(dataProvider = "empty") public void clear_whenEmpty(Queue<?> queue) { queue.clear(); assertThat(queue, is(deeplyEmpty())); } @Test(dataProvider = "populated") public void clear_whenPopulated(Queue<?> queue) { queue.clear(); assertThat(queue, is(deeplyEmpty())); } @Test(dataProvider = "empty") public void isEmpty_whenEmpty(Queue<?> queue) { assertThat(queue.isEmpty(), is(true)); } @Test(dataProvider = "populated") public void isEmpty_whenPopulated(Queue<?> queue) { assertThat(queue.isEmpty(), is(false)); } @Test(dataProvider = "empty") public void size_whenEmpty(Queue<?> queue) { assertThat(queue.size(), is(0)); assertThat(queue.size(), is(equalTo(Iterables.size(queue)))); } @Test(dataProvider = "populated") public void size_whenPopulated(Queue<?> queue) { assertThat(queue.size(), is(POPULATED_SIZE)); assertThat(Iterables.size(queue), is(POPULATED_SIZE)); assertThat(queue.size(), is(equalTo(Iterables.size(queue)))); } /* ---------------- Contains -------------- */ @Test(dataProvider = "empty") public void contains_withNull(Queue<?> queue) { assertThat(queue.contains(null), is(false)); } @Test(dataProvider = "populated") public void contains_whenFound(Queue<Integer> queue) { assertThat(queue.contains(Iterables.get(queue, POPULATED_SIZE / 2)), is(true)); } @Test(dataProvider = "populated") public void contains_whenNotFound(Queue<Integer> queue) { assertThat(queue.contains(-1), is(false)); } @Test(dataProvider = "empty", expectedExceptions = NullPointerException.class) public void containsAll_withNull(Queue<?> queue) { queue.containsAll(null); } @Test(dataProvider = "populated") @SuppressWarnings("ModifyingCollectionWithItself") public void containsAll_whenFound(Queue<Integer> queue) { assertThat(queue.containsAll( ImmutableList.of(0, POPULATED_SIZE / 2, POPULATED_SIZE - 1)), is(true)); assertThat(queue.containsAll(queue), is(true)); } @Test(dataProvider = "populated") public void containsAll_whenNotFound(Queue<Integer> queue) { assertThat(queue.containsAll( ImmutableList.of(-1, -(POPULATED_SIZE / 2), -POPULATED_SIZE)), is(false)); } /* ---------------- Peek -------------- */ @Test(dataProvider = "empty") public void peek_whenEmpty(Queue<Integer> queue) { assertThat(queue.peek(), is(nullValue())); } @Test(dataProvider = "populated") public void peek_whenPopulated(SingleConsumerQueue<Integer> queue) { Integer first = queue.head.next.value; assertThat(queue.peek(), is(first)); assertThat(queue, hasSize(POPULATED_SIZE)); assertThat(queue.contains(first), is(true)); } /* ---------------- Element -------------- */ @Test(dataProvider = "empty", expectedExceptions = NoSuchElementException.class) public void element_whenEmpty(Queue<Integer> queue) { queue.element(); } @Test(dataProvider = "populated") public void element_whenPopulated(SingleConsumerQueue<Integer> queue) { Integer first = queue.head.next.value; assertThat(queue.element(), is(first)); assertThat(queue, hasSize(POPULATED_SIZE)); assertThat(queue.contains(first), is(true)); } /* ---------------- Offer -------------- */ @Test(dataProvider = "empty") public void offer_whenEmpty(Queue<Integer> queue) { assertThat(queue.offer(1), is(true)); assertThat(queue, hasSize(1)); } @Test(dataProvider = "populated") public void offer_whenPopulated(Queue<Integer> queue) { assertThat(queue.offer(1), is(true)); assertThat(queue, hasSize(POPULATED_SIZE + 1)); } /* ---------------- Add -------------- */ @Test(dataProvider = "empty") public void add_whenEmpty(Queue<Integer> queue) { assertThat(queue.add(1), is(true)); assertThat(queue.peek(), is(1)); assertThat(Iterables.getLast(queue), is(1)); assertThat(queue, hasSize(1)); assertThat(queue.size(), is(equalTo(Iterables.size(queue)))); } @Test(dataProvider = "populated") public void add_whenPopulated(Queue<Integer> queue) { assertThat(queue.add(-1), is(true)); assertThat(queue.peek(), is(not(-1))); assertThat(Iterables.getLast(queue), is(-1)); assertThat(queue, hasSize(POPULATED_SIZE + 1)); assertThat(queue.size(), is(equalTo(Iterables.size(queue)))); } @Test(dataProvider = "empty") public void addAll_whenEmpty(Queue<Integer> queue) { List<Integer> list = new ArrayList<>(); populate(list, POPULATED_SIZE); assertThat(queue.addAll(list), is(true)); assertThat(queue.peek(), is(0)); assertThat(Iterables.getLast(queue), is(POPULATED_SIZE - 1)); assertThat(String.format("%nExpected: %s%n but: %s", queue, list), elementsEqual(queue.iterator(), list.iterator())); } @Test(dataProvider = "singleton,populated") public void addAll_whenPopulated(Queue<Integer> queue) { List<Integer> list = ImmutableList.of(POPULATED_SIZE, POPULATED_SIZE + 1, POPULATED_SIZE + 2); List<Integer> expect = ImmutableList.copyOf(Iterables.concat(queue, list)); assertThat(queue.addAll(list), is(true)); assertThat(queue.peek(), is(0)); assertThat(Iterables.getLast(queue), is(POPULATED_SIZE + 2)); assertThat(String.format("%nExpected: %s%n but: %s", queue, expect), elementsEqual(queue.iterator(), expect.iterator())); } /* ---------------- Poll -------------- */ @Test(dataProvider = "empty") public void poll_whenEmpty(Queue<Integer> queue) { assertThat(queue.poll(), is(nullValue())); } @Test(dataProvider = "populated") public void poll_whenPopulated(Queue<Integer> queue) { Integer first = queue.peek(); assertThat(queue.poll(), is(first)); assertThat(queue, hasSize(POPULATED_SIZE - 1)); assertThat(queue.contains(first), is(false)); } @Test(dataProvider = "populated") public void poll_toEmpty(Queue<Integer> queue) { Integer value; while ((value = queue.poll()) != null) { assertThat(queue.contains(value), is(false)); } assertThat(queue, is(deeplyEmpty())); } /* ---------------- Remove -------------- */ @Test(dataProvider = "empty", expectedExceptions = NoSuchElementException.class) public void remove_whenEmpty(Queue<Integer> queue) { queue.remove(); } @Test(dataProvider = "populated") public void remove_whenPopulated(Queue<Integer> queue) { Integer first = queue.peek(); assertThat(queue.remove(), is(first)); assertThat(queue, hasSize(POPULATED_SIZE - 1)); assertThat(queue.contains(first), is(false)); } @Test(dataProvider = "populated") public void remove_toEmpty(Queue<Integer> queue) { while (!queue.isEmpty()) { Integer value = queue.remove(); assertThat(queue.contains(value), is(false)); } assertThat(queue, is(deeplyEmpty())); } @Test(dataProvider = "empty,singleton,populated") public void removeElement_notFound(Queue<Integer> queue) { assertThat(queue.remove(-1), is(false)); } @Test(dataProvider = "populated") public void removeElement_whenFound(Queue<Integer> queue) { Integer first = queue.peek(); assertThat(queue.remove(first), is(true)); assertThat(queue, hasSize(POPULATED_SIZE - 1)); assertThat(queue.contains(first), is(false)); } @Test(dataProvider = "populated") public void removeElement_toEmpty(Queue<Integer> queue) { while (!queue.isEmpty()) { Integer value = queue.peek(); assertThat(queue.remove(value), is(true)); assertThat(queue.contains(value), is(false)); } assertThat(queue, is(deeplyEmpty())); } @Test(dataProvider = "empty") public void removeAll_withEmpty(Queue<Integer> queue) { assertThat(queue.removeAll(ImmutableList.of()), is(false)); assertThat(queue, is(deeplyEmpty())); } @Test(dataProvider = "populated") public void removeAll_withPopulated(Queue<Integer> queue) { Integer first = queue.peek(); assertThat(queue.removeAll(ImmutableList.of(first)), is(true)); assertThat(queue, hasSize(POPULATED_SIZE - 1)); assertThat(queue.contains(first), is(false)); } @Test(dataProvider = "populated") public void removeAll_toEmpty(Queue<Integer> queue) { assertThat(queue.removeAll(ImmutableList.copyOf(queue)), is(true)); assertThat(queue, is(deeplyEmpty())); } /* ---------------- Retain -------------- */ @Test(dataProvider = "empty") public void retainAll_withEmpty(Queue<Integer> queue) { assertThat(queue.retainAll(ImmutableList.of()), is(false)); assertThat(queue, is(deeplyEmpty())); } @Test(dataProvider = "populated") public void retainAll_withPopulated(Queue<Integer> queue) { Integer first = queue.peek(); assertThat(queue.retainAll(ImmutableList.of(first)), is(true)); assertThat(queue, hasSize(1)); assertThat(queue.contains(first), is(true)); } @Test(dataProvider = "populated") public void retainAll_toEmpty(Queue<Integer> queue) { assertThat(queue.retainAll(ImmutableList.of()), is(true)); assertThat(queue, is(deeplyEmpty())); } /* ---------------- Iterators -------------- */ @Test(dataProvider = "empty", expectedExceptions = NoSuchElementException.class) public void iterator_noMoreElements(Queue<Integer> queue) { queue.iterator().next(); } @Test(dataProvider = "empty") public void iterator_whenEmpty(Queue<Integer> queue) { assertThat(queue.iterator().hasNext(), is(false)); } @Test(dataProvider = "singleton,populated") public void iterator_whenPopulated(Queue<Integer> queue) { List<Integer> copy = new ArrayList<>(); populate(copy, queue.size()); assertThat(String.format("\nExpected: %s%n but: %s", queue, copy), elementsEqual(queue.iterator(), copy.iterator())); } @Test(dataProvider = "populated", expectedExceptions = IllegalStateException.class) public void iterator_removal_unread(Queue<Integer> queue) { queue.iterator().remove(); } @Test(dataProvider = "populated", expectedExceptions = IllegalStateException.class) public void iterator_removal_duplicate(Queue<Integer> queue) { Iterator<Integer> it = queue.iterator(); it.next(); it.remove(); it.remove(); } @Test(dataProvider = "populated") public void iterator_removal(Queue<Integer> queue) { Iterator<Integer> it = queue.iterator(); it.next(); it.remove(); } @Test(dataProvider = "populated") public void iterator_removal_toEmpty(Queue<Integer> queue) { for (Iterator<Integer> it = queue.iterator(); it.hasNext();) { it.next(); it.remove(); } assertThat(queue, is(deeplyEmpty())); } /* ---------------- toArray -------------- */ @Test(dataProvider = "empty,singleton,populated") public void toArray(Queue<Integer> queue) { Object[] expect = new ArrayList<>(queue).toArray(); Object[] actual = queue.toArray(); assertThat(actual, queue.isEmpty() ? emptyArray() : arrayContaining(expect)); } @Test(dataProvider = "empty,singleton,populated") public void toTypedArray(Queue<Integer> queue) { Integer[] expect = new ArrayList<>(queue).toArray(new Integer[] {}); Integer[] actual = queue.toArray(new Integer[] {}); assertThat(actual, queue.isEmpty() ? emptyArray() : arrayContaining(expect)); } /* ---------------- toString -------------- */ @Test(dataProvider = "empty,singleton,populated") public void toString(Queue<Integer> queue) { List<Integer> list = new ArrayList<>(); populate(list, queue.size()); assertThat(queue, hasToString(list.toString())); } /* ---------------- Serialization -------------- */ @Test(dataProvider = "empty,singleton,populated") public void serializable(Queue<Integer> queue) { Queue<Integer> copy = SerializableTester.reserialize(queue); assertThat(String.format("%nExpected: %s%n but: %s", queue, copy), elementsEqual(queue.iterator(), copy.iterator())); } /* ---------------- Concurrency -------------- */ @Test(dataProvider = "empty") public void oneProducer_oneConsumer(Queue<Integer> queue) { AtomicInteger started = new AtomicInteger(); AtomicInteger finished = new AtomicInteger(); ConcurrentTestHarness.execute(() -> { started.incrementAndGet(); Awaits.await().untilAtomic(started, is(2)); for (int i = 0; i < PRODUCE; i++) { queue.add(i); } finished.incrementAndGet(); }); ConcurrentTestHarness.execute(() -> { started.incrementAndGet(); Awaits.await().untilAtomic(started, is(2)); for (int i = 0; i < PRODUCE; i++) { while (queue.poll() == null) {} } finished.incrementAndGet(); }); Awaits.await().untilAtomic(finished, is(2)); assertThat(queue, is(deeplyEmpty())); } @Test(dataProvider = "empty") public void manyProducers_noConsumer(Queue<Integer> queue) { ConcurrentTestHarness.timeTasks(NUM_PRODUCERS, () -> { for (int i = 0; i < PRODUCE; i++) { queue.add(i); } }); assertThat(queue, hasSize(NUM_PRODUCERS * PRODUCE)); assertThat(queue.size(), is(equalTo(Iterables.size(queue)))); } @Test(dataProvider = "empty") public void manyProducers_oneConsumer(Queue<Integer> queue) { AtomicInteger started = new AtomicInteger(); AtomicInteger finished = new AtomicInteger(); ConcurrentTestHarness.execute(() -> { started.incrementAndGet(); Awaits.await().untilAtomic(started, is(NUM_PRODUCERS + 1)); for (int i = 0; i < (NUM_PRODUCERS * PRODUCE); i++) { while (queue.poll() == null) {} } finished.incrementAndGet(); }); ConcurrentTestHarness.timeTasks(NUM_PRODUCERS, () -> { started.incrementAndGet(); Awaits.await().untilAtomic(started, is(NUM_PRODUCERS + 1)); for (int i = 0; i < PRODUCE; i++) { queue.add(i); } finished.incrementAndGet(); }); Awaits.await().untilAtomic(finished, is(NUM_PRODUCERS + 1)); assertThat(queue, is(deeplyEmpty())); } /* ---------------- Queue providers -------------- */ @DataProvider(name = "empty") public Object[][] providesEmpty() { return new Object[][] {{ makePopulated(0, true) }, { makePopulated(0, false) }}; } @DataProvider(name = "singleton") public Object[][] providesSingleton() { return new Object[][] {{ makePopulated(1, true) }, { makePopulated(1, false) }}; } @DataProvider(name = "populated") public Object[][] providesPopulated() { return new Object[][] { { makePopulated(POPULATED_SIZE, true) }, { makePopulated(POPULATED_SIZE, false) }}; } @DataProvider(name = "singleton,populated") public Object[][] providesSingletonAndPopulated() { return new Object[][] { { makePopulated(1, true) }, { makePopulated(1, false) }, { makePopulated(POPULATED_SIZE, true) }, { makePopulated(POPULATED_SIZE, false) }}; } @DataProvider(name = "empty,singleton,populated") public Object[][] providesEmptyAndSingletonAndPopulated() { return new Object[][] { { makePopulated(0, true) }, { makePopulated(0, false) }, { makePopulated(1, true) }, { makePopulated(1, false) }, { makePopulated(POPULATED_SIZE, true) }, { makePopulated(POPULATED_SIZE, false) }}; } static SingleConsumerQueue<Integer> makePopulated(int size, boolean optimistic) { SingleConsumerQueue<Integer> queue = optimistic ? SingleConsumerQueue.optimistic() : SingleConsumerQueue.linearizable(); populate(queue, size); return queue; } static void populate(Collection<Integer> collection, int start) { for (int i = 0; i < start; i++) { collection.add(i); } } /** A listener that validates the internal structure after a successful test execution. */ public static final class ValidatingQueueListener implements IInvokedMethodListener { @Override public void beforeInvocation(IInvokedMethod method, ITestResult testResult) {} @Override public void afterInvocation(IInvokedMethod method, ITestResult testResult) { try { if (testResult.isSuccess()) { for (Object param : testResult.getParameters()) { if (param instanceof SingleConsumerQueue<?>) { assertThat((SingleConsumerQueue<?>) param, is(validate())); } } } } catch (AssertionError caught) { testResult.setStatus(ITestResult.FAILURE); testResult.setThrowable(caught); } finally { cleanUp(testResult); } } } /** Free memory by clearing unused resources after test execution. */ static void cleanUp(ITestResult testResult) { Object[] params = testResult.getParameters(); for (int i = 0; i < params.length; i++) { Object param = params[i]; if ((param instanceof SingleConsumerQueue<?>)) { boolean linearizable = (((SingleConsumerQueue<?>) param).factory.apply(null) instanceof LinearizableNode<?>); params[i] = param.getClass().getSimpleName() + "_" + (linearizable ? "linearizable" : "optimistic"); } else { params[i] = Objects.toString(param); } } } }