/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.commons.lang3.concurrent;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.junit.Before;
import org.junit.Test;
/**
* Test class for {@link MultiBackgroundInitializer}.
*
* @version $Id$
*/
public class MultiBackgroundInitializerTest {
/** Constant for the names of the child initializers. */
private static final String CHILD_INIT = "childInitializer";
/** The initializer to be tested. */
private MultiBackgroundInitializer initializer;
@Before
public void setUp() throws Exception {
initializer = new MultiBackgroundInitializer();
}
/**
* Tests whether a child initializer has been executed. Optionally the
* expected executor service can be checked, too.
*
* @param child the child initializer
* @param expExec the expected executor service (null if the executor should
* not be checked)
* @throws ConcurrentException if an error occurs
*/
private void checkChild(final BackgroundInitializer<?> child,
final ExecutorService expExec) throws ConcurrentException {
final ChildBackgroundInitializer cinit = (ChildBackgroundInitializer) child;
final Integer result = cinit.get();
assertEquals("Wrong result", 1, result.intValue());
assertEquals("Wrong number of executions", 1, cinit.initializeCalls);
if (expExec != null) {
assertEquals("Wrong executor service", expExec,
cinit.currentExecutor);
}
}
/**
* Tests addInitializer() if a null name is passed in. This should cause an
* exception.
*/
@Test(expected = IllegalArgumentException.class)
public void testAddInitializerNullName() {
initializer.addInitializer(null, new ChildBackgroundInitializer());
}
/**
* Tests addInitializer() if a null initializer is passed in. This should
* cause an exception.
*/
@Test(expected = IllegalArgumentException.class)
public void testAddInitializerNullInit() {
initializer.addInitializer(CHILD_INIT, null);
}
/**
* Tests the background processing if there are no child initializers.
*/
@Test
public void testInitializeNoChildren() throws ConcurrentException {
assertTrue("Wrong result of start()", initializer.start());
final MultiBackgroundInitializer.MultiBackgroundInitializerResults res = initializer
.get();
assertTrue("Got child initializers", res.initializerNames().isEmpty());
assertTrue("Executor not shutdown", initializer.getActiveExecutor()
.isShutdown());
}
/**
* Helper method for testing the initialize() method. This method can
* operate with both an external and a temporary executor service.
*
* @return the result object produced by the initializer
*/
private MultiBackgroundInitializer.MultiBackgroundInitializerResults checkInitialize()
throws ConcurrentException {
final int count = 5;
for (int i = 0; i < count; i++) {
initializer.addInitializer(CHILD_INIT + i,
new ChildBackgroundInitializer());
}
initializer.start();
final MultiBackgroundInitializer.MultiBackgroundInitializerResults res = initializer
.get();
assertEquals("Wrong number of child initializers", count, res
.initializerNames().size());
for (int i = 0; i < count; i++) {
final String key = CHILD_INIT + i;
assertTrue("Name not found: " + key, res.initializerNames()
.contains(key));
assertEquals("Wrong result object", Integer.valueOf(1), res
.getResultObject(key));
assertFalse("Exception flag", res.isException(key));
assertNull("Got an exception", res.getException(key));
checkChild(res.getInitializer(key), initializer.getActiveExecutor());
}
return res;
}
/**
* Tests background processing if a temporary executor is used.
*/
@Test
public void testInitializeTempExec() throws ConcurrentException {
checkInitialize();
assertTrue("Executor not shutdown", initializer.getActiveExecutor()
.isShutdown());
}
/**
* Tests background processing if an external executor service is provided.
*/
@Test
public void testInitializeExternalExec() throws ConcurrentException {
final ExecutorService exec = Executors.newCachedThreadPool();
try {
initializer = new MultiBackgroundInitializer(exec);
checkInitialize();
assertEquals("Wrong executor", exec, initializer
.getActiveExecutor());
assertFalse("Executor was shutdown", exec.isShutdown());
} finally {
exec.shutdown();
}
}
/**
* Tests the behavior of initialize() if a child initializer has a specific
* executor service. Then this service should not be overridden.
*/
@Test
public void testInitializeChildWithExecutor() throws ConcurrentException {
final String initExec = "childInitializerWithExecutor";
final ExecutorService exec = Executors.newSingleThreadExecutor();
try {
final ChildBackgroundInitializer c1 = new ChildBackgroundInitializer();
final ChildBackgroundInitializer c2 = new ChildBackgroundInitializer();
c2.setExternalExecutor(exec);
initializer.addInitializer(CHILD_INIT, c1);
initializer.addInitializer(initExec, c2);
initializer.start();
initializer.get();
checkChild(c1, initializer.getActiveExecutor());
checkChild(c2, exec);
} finally {
exec.shutdown();
}
}
/**
* Tries to add another child initializer after the start() method has been
* called. This should not be allowed.
*/
@Test
public void testAddInitializerAfterStart() throws ConcurrentException {
initializer.start();
try {
initializer.addInitializer(CHILD_INIT,
new ChildBackgroundInitializer());
fail("Could add initializer after start()!");
} catch (final IllegalStateException istex) {
initializer.get();
}
}
/**
* Tries to query an unknown child initializer from the results object. This
* should cause an exception.
*/
@Test(expected = NoSuchElementException.class)
public void testResultGetInitializerUnknown() throws ConcurrentException {
final MultiBackgroundInitializer.MultiBackgroundInitializerResults res = checkInitialize();
res.getInitializer("unknown");
}
/**
* Tries to query the results of an unknown child initializer from the
* results object. This should cause an exception.
*/
@Test(expected = NoSuchElementException.class)
public void testResultGetResultObjectUnknown() throws ConcurrentException {
final MultiBackgroundInitializer.MultiBackgroundInitializerResults res = checkInitialize();
res.getResultObject("unknown");
}
/**
* Tries to query the exception of an unknown child initializer from the
* results object. This should cause an exception.
*/
@Test(expected = NoSuchElementException.class)
public void testResultGetExceptionUnknown() throws ConcurrentException {
final MultiBackgroundInitializer.MultiBackgroundInitializerResults res = checkInitialize();
res.getException("unknown");
}
/**
* Tries to query the exception flag of an unknown child initializer from
* the results object. This should cause an exception.
*/
@Test(expected = NoSuchElementException.class)
public void testResultIsExceptionUnknown() throws ConcurrentException {
final MultiBackgroundInitializer.MultiBackgroundInitializerResults res = checkInitialize();
res.isException("unknown");
}
/**
* Tests that the set with the names of the initializers cannot be modified.
*/
@Test(expected = UnsupportedOperationException.class)
public void testResultInitializerNamesModify() throws ConcurrentException {
checkInitialize();
final MultiBackgroundInitializer.MultiBackgroundInitializerResults res = initializer
.get();
final Iterator<String> it = res.initializerNames().iterator();
it.next();
it.remove();
}
/**
* Tests the behavior of the initializer if one of the child initializers
* throws a runtime exception.
*/
@Test
public void testInitializeRuntimeEx() {
final ChildBackgroundInitializer child = new ChildBackgroundInitializer();
child.ex = new RuntimeException();
initializer.addInitializer(CHILD_INIT, child);
initializer.start();
try {
initializer.get();
fail("Runtime exception not thrown!");
} catch (final Exception ex) {
assertEquals("Wrong exception", child.ex, ex);
}
}
/**
* Tests the behavior of the initializer if one of the child initializers
* throws a checked exception.
*/
@Test
public void testInitializeEx() throws ConcurrentException {
final ChildBackgroundInitializer child = new ChildBackgroundInitializer();
child.ex = new Exception();
initializer.addInitializer(CHILD_INIT, child);
initializer.start();
final MultiBackgroundInitializer.MultiBackgroundInitializerResults res = initializer
.get();
assertTrue("No exception flag", res.isException(CHILD_INIT));
assertNull("Got a results object", res.getResultObject(CHILD_INIT));
final ConcurrentException cex = res.getException(CHILD_INIT);
assertEquals("Wrong cause", child.ex, cex.getCause());
}
/**
* Tests the isSuccessful() method of the result object if no child
* initializer has thrown an exception.
*/
@Test
public void testInitializeResultsIsSuccessfulTrue()
throws ConcurrentException {
final ChildBackgroundInitializer child = new ChildBackgroundInitializer();
initializer.addInitializer(CHILD_INIT, child);
initializer.start();
final MultiBackgroundInitializer.MultiBackgroundInitializerResults res = initializer
.get();
assertTrue("Wrong success flag", res.isSuccessful());
}
/**
* Tests the isSuccessful() method of the result object if at least one
* child initializer has thrown an exception.
*/
@Test
public void testInitializeResultsIsSuccessfulFalse()
throws ConcurrentException {
final ChildBackgroundInitializer child = new ChildBackgroundInitializer();
child.ex = new Exception();
initializer.addInitializer(CHILD_INIT, child);
initializer.start();
final MultiBackgroundInitializer.MultiBackgroundInitializerResults res = initializer
.get();
assertFalse("Wrong success flag", res.isSuccessful());
}
/**
* Tests whether MultiBackgroundInitializers can be combined in a nested
* way.
*/
@Test
public void testInitializeNested() throws ConcurrentException {
final String nameMulti = "multiChildInitializer";
initializer
.addInitializer(CHILD_INIT, new ChildBackgroundInitializer());
final MultiBackgroundInitializer mi2 = new MultiBackgroundInitializer();
final int count = 3;
for (int i = 0; i < count; i++) {
mi2
.addInitializer(CHILD_INIT + i,
new ChildBackgroundInitializer());
}
initializer.addInitializer(nameMulti, mi2);
initializer.start();
final MultiBackgroundInitializer.MultiBackgroundInitializerResults res = initializer
.get();
final ExecutorService exec = initializer.getActiveExecutor();
checkChild(res.getInitializer(CHILD_INIT), exec);
final MultiBackgroundInitializer.MultiBackgroundInitializerResults res2 = (MultiBackgroundInitializer.MultiBackgroundInitializerResults) res
.getResultObject(nameMulti);
assertEquals("Wrong number of initializers", count, res2
.initializerNames().size());
for (int i = 0; i < count; i++) {
checkChild(res2.getInitializer(CHILD_INIT + i), exec);
}
assertTrue("Executor not shutdown", exec.isShutdown());
}
/**
* A concrete implementation of {@code BackgroundInitializer} used for
* defining background tasks for {@code MultiBackgroundInitializer}.
*/
private static class ChildBackgroundInitializer extends
BackgroundInitializer<Integer> {
/** Stores the current executor service. */
volatile ExecutorService currentExecutor;
/** A counter for the invocations of initialize(). */
volatile int initializeCalls;
/** An exception to be thrown by initialize(). */
Exception ex;
/**
* Records this invocation. Optionally throws an exception.
*/
@Override
protected Integer initialize() throws Exception {
currentExecutor = getActiveExecutor();
initializeCalls++;
if (ex != null) {
throw ex;
}
return Integer.valueOf(initializeCalls);
}
}
}