MultiBackgroundInitializerSupplierTest.java
/*
* 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
*
* https://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.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import java.io.IOException;
import java.lang.reflect.Field;
import org.apache.commons.lang3.function.FailableConsumer;
import org.apache.commons.lang3.function.FailableSupplier;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
/**
* Test class for {@link MultiBackgroundInitializer}.
*/
class MultiBackgroundInitializerSupplierTest extends MultiBackgroundInitializerTest {
/**
* A concrete implementation of {@code BackgroundInitializer} used for
* defining background tasks for {@code MultiBackgroundInitializer}.
*/
private static final class SupplierChildBackgroundInitializer extends AbstractChildBackgroundInitializer {
SupplierChildBackgroundInitializer() {
this((final CloseableCounter cc) -> cc.close());
}
SupplierChildBackgroundInitializer(final FailableConsumer<?, ?> consumer) {
try {
// Use reflection here because the constructors we need are private
final FailableSupplier<?, ?> supplier = this::initializeInternal;
final Field initializer = AbstractConcurrentInitializer.class.getDeclaredField("initializer");
initializer.setAccessible(true);
initializer.set(this, supplier);
final Field closer = AbstractConcurrentInitializer.class.getDeclaredField("closer");
closer.setAccessible(true);
closer.set(this, consumer);
} catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
fail();
}
}
}
private NullPointerException npe;
private IOException ioException;
private FailableConsumer<?, ?> ioExceptionConsumer;
private FailableConsumer<?, ?> nullPointerExceptionConsumer;
/**
* {@inheritDoc}
*/
@Override
protected AbstractChildBackgroundInitializer createChildBackgroundInitializer() {
return new SupplierChildBackgroundInitializer();
}
@BeforeEach
public void setUpException() throws Exception {
npe = new NullPointerException();
ioException = new IOException();
ioExceptionConsumer = (final CloseableCounter cc) -> {
throw ioException;
};
nullPointerExceptionConsumer = (final CloseableCounter cc) -> {
throw npe;
};
}
/**
* Tests that close() method closes the wrapped object
*
* @throws Exception
*/
@Test
void testClose()
throws ConcurrentException, InterruptedException {
final AbstractChildBackgroundInitializer childOne = createChildBackgroundInitializer();
final AbstractChildBackgroundInitializer childTwo = createChildBackgroundInitializer();
assertFalse(initializer.isInitialized(), "Initialized without having anything to initialize");
initializer.addInitializer("child one", childOne);
initializer.addInitializer("child two", childTwo);
assertFalse(childOne.getCloseableCounter().isClosed(), "child one closed() succeeded before start()");
assertFalse(childTwo.getCloseableCounter().isClosed(), "child two closed() succeeded before start()");
initializer.start();
final long startTime = System.currentTimeMillis();
final long waitTime = 3000;
final long endTime = startTime + waitTime;
//wait for the children to start
while (!childOne.isStarted() || !childTwo.isStarted()) {
if (System.currentTimeMillis() > endTime) {
fail("children never started");
Thread.sleep(PERIOD_MILLIS);
}
}
assertFalse(childOne.getCloseableCounter().isClosed(), "child one close() succeeded after start() but before close()");
assertFalse(childTwo.getCloseableCounter().isClosed(), "child two close() succeeded after start() but before close()");
childOne.get(); // ensure this child finishes initializing
childTwo.get(); // ensure this child finishes initializing
assertFalse(childOne.getCloseableCounter().isClosed(), "child one initializing succeeded after start() but before close()");
assertFalse(childTwo.getCloseableCounter().isClosed(), "child two initializing succeeded after start() but before close()");
try {
initializer.close();
} catch (final Exception e) {
fail();
}
assertTrue(childOne.getCloseableCounter().isClosed(), "child one close() did not succeed");
assertTrue(childOne.getCloseableCounter().isClosed(), "child two close() did not succeed");
}
/**
* Tests that close() wraps a checked exception from a child initializer in an ConcurrentException as the first suppressed under in an ConcurrentException
*
* @throws Exception
*/
@Test
void testCloseWithCheckedException() throws Exception {
final AbstractChildBackgroundInitializer childOne = new SupplierChildBackgroundInitializer(ioExceptionConsumer);
initializer.addInitializer("child one", childOne);
initializer.start();
final long startTime = System.currentTimeMillis();
final long waitTime = 3000;
final long endTime = startTime + waitTime;
//wait for the children to start
while (! childOne.isStarted()) {
if (System.currentTimeMillis() > endTime) {
fail("children never started");
Thread.sleep(PERIOD_MILLIS);
}
}
childOne.get(); // ensure the Future has completed.
try {
initializer.close();
fail();
} catch (final Exception e) {
assertInstanceOf(ConcurrentException.class, e);
assertSame(ioException, e.getSuppressed()[0]);
}
}
/**
* Tests that close() wraps a runtime exception from a child initializer as the first suppressed under in an ConcurrentException
*
* @throws Exception
*/
@Test
void testCloseWithRuntimeException() throws Exception {
final AbstractChildBackgroundInitializer childOne = new SupplierChildBackgroundInitializer(nullPointerExceptionConsumer);
initializer.addInitializer("child one", childOne);
initializer.start();
final long startTime = System.currentTimeMillis();
final long waitTime = 3000;
final long endTime = startTime + waitTime;
//wait for the children to start
while (! childOne.isStarted()) {
if (System.currentTimeMillis() > endTime) {
fail("children never started");
Thread.sleep(PERIOD_MILLIS);
}
}
childOne.get(); // ensure the Future has completed.
try {
initializer.close();
fail();
} catch (final Exception e) {
assertInstanceOf(ConcurrentException.class, e);
assertSame(npe, e.getSuppressed()[0]);
}
}
/**
* Tests that calling close() on a MultiBackgroundInitializer with two children that both throw exceptions throws
* an ConcurrentException and both the child exceptions are present
*
* @throws Exception
*/
@Test
void testCloseWithTwoExceptions()
throws ConcurrentException, InterruptedException {
final AbstractChildBackgroundInitializer childOne = new SupplierChildBackgroundInitializer(ioExceptionConsumer);
final AbstractChildBackgroundInitializer childTwo = new SupplierChildBackgroundInitializer(nullPointerExceptionConsumer);
initializer.addInitializer("child one", childOne);
initializer.addInitializer("child two", childTwo);
initializer.start();
final long startTime = System.currentTimeMillis();
final long waitTime = 3000;
final long endTime = startTime + waitTime;
//wait for the children to start
while (! childOne.isStarted() || ! childTwo.isStarted()) {
if (System.currentTimeMillis() > endTime) {
fail("children never started");
Thread.sleep(PERIOD_MILLIS);
}
}
childOne.get(); // ensure this child finishes initializing
childTwo.get(); // ensure this child finishes initializing
try {
initializer.close();
fail();
} catch (final Exception e) {
// We don't actually know which order the children will be closed in
boolean foundChildOneException = false;
boolean foundChildTwoException = false;
for (final Throwable t : e.getSuppressed()) {
if (t.equals(ioException)) {
foundChildOneException = true;
}
if (t.equals(npe)) {
foundChildTwoException = true;
}
}
assertTrue(foundChildOneException);
assertTrue(foundChildTwoException);
}
}
}