/* * Copyright 2013 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.multiway; import java.util.ArrayList; import java.util.List; import java.util.NoSuchElementException; import java.util.concurrent.Callable; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import com.google.common.testing.SerializableTester; import com.twitter.jsr166e.LongAdder; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import static com.google.common.collect.Iterators.elementsEqual; import static com.jayway.awaitility.Awaitility.await; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.nullValue; /** * @author ben.manes@gmail.com (Ben Manes) */ public final class EliminationStackTest { static final int WARMED_SIZE = 100; @Test(dataProvider = "emptyStack") public void clear_whenEmpty(EliminationStack<Integer> stack) { stack.clear(); assertThat(stack.isEmpty(), is(true)); } @Test(dataProvider = "emptyStack") public void clear_whenPopulated(EliminationStack<Integer> stack) { stack.clear(); assertThat(stack.isEmpty(), is(true)); } @Test(dataProvider = "emptyStack") public void isEmpty_whenEmpty(EliminationStack<Integer> stack) { assertThat(stack.isEmpty(), is(true)); } @Test(dataProvider = "warmedStack") public void isEmpty_whenPopulated(EliminationStack<Integer> stack) { assertThat(stack.isEmpty(), is(false)); } @Test(dataProvider = "emptyStack") public void size_whenEmpty(EliminationStack<Integer> stack) { assertThat(stack.size(), is(0)); } @Test(dataProvider = "warmedStack") public void size_whenPopulated(EliminationStack<Integer> stack) { assertThat(stack.size(), is(WARMED_SIZE)); } @Test(dataProvider = "emptyStack", expectedExceptions = NullPointerException.class) public void contains_withNull(EliminationStack<Integer> stack) { stack.contains(null); } @Test(dataProvider = "warmedStack") public void contains_whenFound(EliminationStack<Integer> stack) { assertThat(stack.contains(1), is(true)); } @Test(dataProvider = "warmedStack") public void contains_whenNotFound(EliminationStack<Integer> stack) { assertThat(stack.contains(-1), is(false)); } @Test(dataProvider = "emptyStack", expectedExceptions = NullPointerException.class) public void push_withNull(EliminationStack<Integer> stack) { stack.push(null); } @Test(dataProvider = "emptyStack") public void push_whenEmpty(EliminationStack<Integer> stack) { stack.push(1); assertThat(stack.peek(), is(1)); assertThat(stack.size(), is(1)); } @Test(dataProvider = "warmedStack") public void push_whenPopulated(EliminationStack<Integer> stack) { stack.push(1); assertThat(stack.peek(), is(1)); assertThat(stack.size(), is(WARMED_SIZE + 1)); } @Test(dataProvider = "emptyStack") public void pop_whenEmpty(EliminationStack<Integer> stack) { assertThat(stack.pop(), is(nullValue())); } @Test(dataProvider = "warmedStack") public void pop_whenPopulated(EliminationStack<Integer> stack) { Integer first = stack.peek(); assertThat(stack.pop(), is(first)); assertThat(stack, not(contains(first))); assertThat(stack.size(), is(WARMED_SIZE - 1)); } @Test(dataProvider = "warmedStack") public void pop_toEmpty(EliminationStack<Integer> stack) { while (!stack.isEmpty()) { Integer value = stack.pop(); assertThat(stack.contains(value), is(false)); } assertThat(stack.isEmpty(), is(true)); } @Test(dataProvider = "emptyStack") public void remove_whenEmpty(EliminationStack<Integer> stack) { assertThat(stack.remove(123), is(false)); } @Test(dataProvider = "warmedStack") public void remove_whenPopulated(EliminationStack<Integer> stack) { assertThat(stack.remove(10), is(true)); assertThat(stack, not(contains(10))); assertThat(stack.size(), is(WARMED_SIZE - 1)); } @Test(dataProvider = "warmedStack") public void remove_toEmpty(EliminationStack<Integer> stack) { while (!stack.isEmpty()) { Integer value = stack.peek(); assertThat(stack.remove(value), is(true)); assertThat(stack.contains(value), is(false)); } assertThat(stack.isEmpty(), is(true)); } @Test(dataProvider = "emptyStack") public void concurrent(final EliminationStack<Integer> stack) throws Exception { final LongAdder pushed = new LongAdder(); final LongAdder popped = new LongAdder(); ConcurrentTestHarness.timeTasks(10, new Runnable() { @Override public void run() { stack.push(ThreadLocalRandom.current().nextInt()); pushed.increment(); if (stack.pop() != null) { popped.increment(); } } }); for (AtomicReference<Object> slot : stack.arena) { assertThat(slot.get(), is(nullValue())); } assertThat(pushed.intValue(), is(equalTo(stack.size() + popped.intValue()))); } @Test(dataProvider = "emptyStack") public void scanAndTransfer(final EliminationStack<String> stack) { final AtomicBoolean started = new AtomicBoolean(); final AtomicBoolean done = new AtomicBoolean(); final String value = "test"; final int startIndex = 1; new Thread() { @Override public void run() { started.set(true); while (!done.get()) { if (stack.scanAndTransferToWaiter(value, startIndex)) { done.set(true); } } } }.start(); await().untilTrue(started); try { final AtomicReference<String> found = new AtomicReference<>(); await().until(new Callable<Boolean>() { @Override public Boolean call() { found.set(stack.awaitMatch(startIndex)); return (found.get() != null); } }); assertThat(found.get(), is(equalTo(value))); } finally { done.set(true); } } @Test(dataProvider = "emptyStack") public void awaitExchange(final EliminationStack<String> stack) { final AtomicBoolean started = new AtomicBoolean(); final AtomicBoolean done = new AtomicBoolean(); final String value = "test"; final int startIndex = 1; new Thread() { @Override public void run() { started.set(true); while (!done.get()) { if (stack.awaitExchange(value, startIndex)) { done.set(true); } } } }.start(); await().untilTrue(started); try { final AtomicReference<String> found = new AtomicReference<>(); await().until(new Callable<Boolean>() { @Override public Boolean call() { found.set(stack.awaitMatch(startIndex)); return (found.get() != null); } }); assertThat(found.get(), is(equalTo(value))); } finally { done.set(true); } } @Test(dataProvider = "emptyStack") public void scanAndMatch(final EliminationStack<String> stack) { final AtomicBoolean started = new AtomicBoolean(); final AtomicBoolean done = new AtomicBoolean(); final String value = "test"; final int startIndex = 1; new Thread() { @Override public void run() { started.set(true); while (!done.get()) { if (stack.awaitExchange(value, startIndex)) { done.set(true); } } } }.start(); await().untilTrue(started); try { final AtomicReference<String> found = new AtomicReference<>(); await().until(new Callable<Boolean>() { @Override public Boolean call() { found.set(stack.scanAndMatch(startIndex)); return (found.get() != null); } }); assertThat(found.get(), is(equalTo(value))); } finally { done.set(true); } } @Test(dataProvider = "emptyStack", expectedExceptions = NoSuchElementException.class) public void iterator_noMoreElements(EliminationStack<Integer> stack) { stack.iterator().next(); } @Test(dataProvider = "emptyStack") public void iterator_whenEmpty(EliminationStack<Integer> stack) { assertThat(stack.iterator().hasNext(), is(false)); } @Test(dataProvider = "warmedStack") public void iterator_whenWarmed(EliminationStack<Integer> stack) { assertThat(elementsEqual(stack.iterator(), newWarmedStack().iterator()), is(true)); } @Test(dataProvider = "emptyStack", expectedExceptions = IllegalStateException.class) public void iterator_removalWhenEmpty(EliminationStack<Integer> stack) { stack.iterator().remove(); } @Test(dataProvider = "warmedStack") public void iterator_removalWhenPopulated(EliminationStack<Integer> stack) { stack.iterator().remove(); assertThat(stack.size(), is(WARMED_SIZE - 1)); } @Test(dataProvider = "emptyStack") public void serialize_whenEmpty(EliminationStack<Integer> stack) { List<Integer> expected = new ArrayList<>(stack); List<Integer> actual = new ArrayList<>(SerializableTester.reserialize(stack)); assertThat(expected, is(equalTo(actual))); } @Test(dataProvider = "warmedStack") public void serialize_whenPopulated(EliminationStack<Integer> stack) { List<Integer> expected = new ArrayList<>(stack); List<Integer> actual = new ArrayList<>(SerializableTester.reserialize(stack)); assertThat(expected, is(equalTo(actual))); } /* ---------------- Stack providers -------------- */ @DataProvider public Object[][] emptyStack() { return new Object[][] {{ new EliminationStack<Integer>() }}; } @DataProvider public Object[][] warmedStack() { return new Object[][] {{ newWarmedStack() }}; } EliminationStack<Integer> newWarmedStack() { EliminationStack<Integer> stack = new EliminationStack<Integer>(); for (int i = 0; i < WARMED_SIZE; i++) { stack.push(i); } return stack; } }