/*
* Copyright (C) 2011 The Guava 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 com.google.common.collect;
import com.google.common.util.concurrent.Uninterruptibles;
import junit.framework.TestCase;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
/**
* Tests for {@link Queues}.
*
* @author Dimitris Andreou
*/
public class QueuesTest extends TestCase {
/*
* All the following tests relate to BlockingQueue methods in Queues.
*/
public static List<BlockingQueue<Object>> blockingQueues() {
return ImmutableList.<BlockingQueue<Object>>of(
new LinkedBlockingQueue<Object>(),
new LinkedBlockingQueue<Object>(10),
new SynchronousQueue<Object>(),
new ArrayBlockingQueue<Object>(10),
new PriorityBlockingQueue<Object>(10, Ordering.arbitrary()));
}
private ExecutorService threadPool;
@Override
public void setUp() {
threadPool = Executors.newCachedThreadPool();
}
@Override
public void tearDown() throws InterruptedException {
// notice that if a Producer is interrupted (a bug), the Producer will go into an infinite
// loop, which will be noticed here
threadPool.shutdown();
assertTrue("Some worker didn't finish in time",
threadPool.awaitTermination(1, TimeUnit.SECONDS));
}
private static <T> int drain(BlockingQueue<T> q, Collection<? super T> buffer, int maxElements,
long timeout, TimeUnit unit, boolean interruptibly) throws InterruptedException {
return interruptibly
? Queues.drain(q, buffer, maxElements, timeout, unit)
: Queues.drainUninterruptibly(q, buffer, maxElements, timeout, unit);
}
public void testMultipleProducers() throws Exception {
for (BlockingQueue<Object> q : blockingQueues()) {
testMultipleProducers(q);
}
}
private void testMultipleProducers(BlockingQueue<Object> q)
throws InterruptedException {
for (boolean interruptibly : new boolean[] { true, false }) {
threadPool.submit(new Producer(q, 20));
threadPool.submit(new Producer(q, 20));
threadPool.submit(new Producer(q, 20));
threadPool.submit(new Producer(q, 20));
threadPool.submit(new Producer(q, 20));
List<Object> buf = Lists.newArrayList();
int elements = drain(q, buf, 100, Long.MAX_VALUE, TimeUnit.NANOSECONDS, interruptibly);
assertEquals(100, elements);
assertEquals(100, buf.size());
assertDrained(q);
}
}
public void testDrainTimesOut() throws Exception {
for (BlockingQueue<Object> q : blockingQueues()) {
testDrainTimesOut(q);
}
}
private void testDrainTimesOut(BlockingQueue<Object> q) throws Exception {
for (boolean interruptibly : new boolean[] { true, false }) {
assertEquals(0, Queues.drain(q, ImmutableList.of(), 1, 10, TimeUnit.MILLISECONDS));
// producing one, will ask for two
Future<?> submitter = threadPool.submit(new Producer(q, 1));
// make sure we time out
long startTime = System.nanoTime();
int drained = drain(q, Lists.newArrayList(), 2, 10, TimeUnit.MILLISECONDS, interruptibly);
assertTrue(drained <= 1);
assertTrue((System.nanoTime() - startTime) >= TimeUnit.MILLISECONDS.toNanos(10));
// If even the first one wasn't there, clean up so that the next test doesn't see an element.
submitter.get();
if (drained == 0) {
assertNotNull(q.poll());
}
}
}
public void testZeroElements() throws Exception {
for (BlockingQueue<Object> q : blockingQueues()) {
testZeroElements(q);
}
}
private void testZeroElements(BlockingQueue<Object> q) throws InterruptedException {
for (boolean interruptibly : new boolean[] { true, false }) {
// asking to drain zero elements
assertEquals(0, drain(q, ImmutableList.of(), 0, 10, TimeUnit.MILLISECONDS, interruptibly));
}
}
public void testEmpty() throws Exception {
for (BlockingQueue<Object> q : blockingQueues()) {
testEmpty(q);
}
}
private void testEmpty(BlockingQueue<Object> q) {
assertDrained(q);
}
public void testNegativeMaxElements() throws Exception {
for (BlockingQueue<Object> q : blockingQueues()) {
testNegativeMaxElements(q);
}
}
private void testNegativeMaxElements(BlockingQueue<Object> q) throws InterruptedException {
threadPool.submit(new Producer(q, 1));
List<Object> buf = Lists.newArrayList();
int elements = Queues.drain(q, buf, -1, Long.MAX_VALUE, TimeUnit.NANOSECONDS);
assertEquals(elements, 0);
assertTrue(buf.isEmpty());
// Clean up produced element to free the producer thread, otherwise it will complain
// when we shutdown the threadpool.
Queues.drain(q, buf, 1, Long.MAX_VALUE, TimeUnit.NANOSECONDS);
}
public void testDrain_throws() throws Exception {
for (BlockingQueue<Object> q : blockingQueues()) {
testDrain_throws(q);
}
}
private void testDrain_throws(BlockingQueue<Object> q) {
threadPool.submit(new Interrupter(Thread.currentThread()));
try {
Queues.drain(q, ImmutableList.of(), 100, Long.MAX_VALUE, TimeUnit.NANOSECONDS);
fail();
} catch (InterruptedException expected) {
}
}
public void testDrainUninterruptibly_doesNotThrow() throws Exception {
for (BlockingQueue<Object> q : blockingQueues()) {
testDrainUninterruptibly_doesNotThrow(q);
}
}
private void testDrainUninterruptibly_doesNotThrow(final BlockingQueue<Object> q) {
final Thread mainThread = Thread.currentThread();
threadPool.submit(new Runnable() {
public void run() {
new Producer(q, 50).run();
new Interrupter(mainThread).run();
new Producer(q, 50).run();
}
});
List<Object> buf = Lists.newArrayList();
int elements =
Queues.drainUninterruptibly(q, buf, 100, Long.MAX_VALUE, TimeUnit.NANOSECONDS);
// so when this drains all elements, we know the thread has also been interrupted in between
assertTrue(Thread.interrupted());
assertEquals(100, elements);
assertEquals(100, buf.size());
}
public void testNewLinkedBlockingQueueCapacity() {
try {
Queues.newLinkedBlockingQueue(0);
fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException expected) {
// any capacity less than 1 should throw IllegalArgumentException
}
assertEquals(1, Queues.newLinkedBlockingQueue(1).remainingCapacity());
assertEquals(11, Queues.newLinkedBlockingQueue(11).remainingCapacity());
}
/**
* Checks that #drain() invocations behave correctly for a drained (empty) queue.
*/
private void assertDrained(BlockingQueue<Object> q) {
assertNull(q.peek());
assertInterruptibleDrained(q);
assertUninterruptibleDrained(q);
}
private void assertInterruptibleDrained(BlockingQueue<Object> q) {
// nothing to drain, thus this should wait doing nothing
try {
assertEquals(0, Queues.drain(q, ImmutableList.of(), 0, 10, TimeUnit.MILLISECONDS));
} catch (InterruptedException e) {
throw new AssertionError();
}
// but does the wait actually occurs?
threadPool.submit(new Interrupter(Thread.currentThread()));
try {
// if waiting works, this should get stuck
Queues.drain(q, Lists.newArrayList(), 1, Long.MAX_VALUE, TimeUnit.NANOSECONDS);
fail();
} catch (InterruptedException expected) {
// we indeed waited; a slow thread had enough time to interrupt us
}
}
// same as above; uninterruptible version
private void assertUninterruptibleDrained(BlockingQueue<Object> q) {
assertEquals(0,
Queues.drainUninterruptibly(q, ImmutableList.of(), 0, 10, TimeUnit.MILLISECONDS));
// but does the wait actually occurs?
threadPool.submit(new Interrupter(Thread.currentThread()));
long startTime = System.nanoTime();
Queues.drainUninterruptibly(
q, Lists.newArrayList(), 1, 10, TimeUnit.MILLISECONDS);
assertTrue((System.nanoTime() - startTime) >= TimeUnit.MILLISECONDS.toNanos(10));
// wait for interrupted status and clear it
while (!Thread.interrupted()) { Thread.yield(); }
}
private static class Producer implements Runnable {
final BlockingQueue<Object> q;
final int elements;
Producer(BlockingQueue<Object> q, int elements) {
this.q = q;
this.elements = elements;
}
@Override public void run() {
try {
for (int i = 0; i < elements; i++) {
q.put(new Object());
}
} catch (InterruptedException e) {
// TODO(user): replace this when there is a better way to spawn threads in tests and
// have threads propagate their errors back to the test thread.
e.printStackTrace();
// never returns, so that #tearDown() notices that one worker isn't done
Uninterruptibles.sleepUninterruptibly(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
}
}
}
private static class Interrupter implements Runnable {
final Thread threadToInterrupt;
Interrupter(Thread threadToInterrupt) {
this.threadToInterrupt = threadToInterrupt;
}
@Override public void run() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new AssertionError();
} finally {
threadToInterrupt.interrupt();
}
}
}
}