Skip to content

Commit

Permalink
Documentation updates and updates from review.
Browse files Browse the repository at this point in the history
Signed-off-by: James R. Perkins <[email protected]>
  • Loading branch information
jamezp committed Jul 3, 2024
1 parent 5f69271 commit 9aaae6d
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 77 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InaccessibleObjectException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
Expand All @@ -18,13 +19,14 @@
import org.jboss.arquillian.test.spi.TestEnricher;
import org.jboss.arquillian.testcontainers.api.DockerRequired;
import org.jboss.arquillian.testcontainers.api.Testcontainer;
import org.testcontainers.DockerClientFactory;
import org.testcontainers.containers.GenericContainer;

/**
* A test enricher for injecting a {@link GenericContainer} into fields annotated with {@link Testcontainer @Testcontainer}.
*
* @author <a href="mailto:[email protected]">James R. Perkins</a>
*/
@SuppressWarnings({ "unchecked", "resource" })
@SuppressWarnings({ "unchecked" })
public class ContainerInjectionTestEnricher implements TestEnricher {
@Inject
private Instance<TestcontainerRegistry> instances;
Expand All @@ -34,9 +36,7 @@ public void enrich(final Object testCase) {
if (!isAnnotatedWith(testCase.getClass(), DockerRequired.class)) {
return;
}
final boolean isDockerAvailable = isDockerAvailable();
for (Field field : getFieldsWithAnnotation(testCase.getClass())) {
checkForDocker(isDockerAvailable);
Object value;
try {
final List<Annotation> qualifiers = Stream.of(field.getAnnotations())
Expand All @@ -61,19 +61,14 @@ public void enrich(final Object testCase) {
}

value = instances.get()
.lookupOrCreate((Class<GenericContainer<?>>) field.getType(), field, testcontainer,
qualifiers);
.lookupOrCreate((Class<GenericContainer<?>>) field.getType(), testcontainer, qualifiers);
} catch (Exception e) {
throw new RuntimeException("Could not lookup value for field " + field, e);
}
try {
if (field.trySetAccessible()) {
field.set(testCase, value);
} else {
throw new RuntimeException("Could not set value for field " + field);
}
} catch (RuntimeException e) {
throw e;
// Field marked as accessible during lookup to fail early if it cannot be made accessible. See the
// getFieldsWithAnnotation() method.
field.set(testCase, value);
} catch (Exception e) {
throw new RuntimeException("Could not set value on field " + field + " using " + value, e);
}
Expand All @@ -85,31 +80,14 @@ public Object[] resolve(final Method method) {
return new Object[method.getParameterTypes().length];
}

private static void checkForDocker(boolean isDockerAvailable) {
final String detailMessage = "No Docker environment is available.";
if (!isDockerAvailable) {
throw new AssertionError(detailMessage);
}
}

@SuppressWarnings({ "resource", "BooleanMethodIsAlwaysInverted" })
private static boolean isDockerAvailable() {
try {
DockerClientFactory.instance().client();
return true;
} catch (Throwable ex) {
return false;
}
}

private static List<Field> getFieldsWithAnnotation(final Class<?> source) {
final List<Field> foundFields = new ArrayList<>();
Class<?> nextSource = source;
while (nextSource != Object.class) {
for (Field field : nextSource.getDeclaredFields()) {
if (field.isAnnotationPresent(Testcontainer.class)) {
if (!field.trySetAccessible()) {
throw new IllegalStateException(String.format("Could not make field %s accessible", field));
throw new InaccessibleObjectException(String.format("Could not make field %s accessible", field));
}
foundFields.add(field);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,14 @@ class TestContainersObserver {
@Inject
private Instance<ContainerRegistry> registry;

/**
* This first checks if the {@link DockerRequired} annotation is present on the test class failing if necessary. It
* then creates the {@link TestcontainerRegistry} and stores it in a {@link ClassScoped} instance.
*
* @param beforeClass the before class event
*
* @throws Throwable if an error occurs
*/
public void createContainer(@Observes(precedence = 500) BeforeClass beforeClass) throws Throwable {
final TestClass javaClass = beforeClass.getTestClass();
final DockerRequired dockerRequired = javaClass.getAnnotation(DockerRequired.class);
Expand All @@ -41,51 +49,38 @@ public void createContainer(@Observes(precedence = 500) BeforeClass beforeClass)
containerRegistry.set(instances);
}

/**
* Stops all containers, even ones not managed via Arquillian, after the test is complete
*
* @param afterClass the after class event
*/
public void stopContainer(@Observes AfterClass afterClass) {
TestcontainerRegistry instances = containerRegistry.get();
if (instances != null) {
for (TestcontainerDescription container : instances) {
TestcontainerRegistry registry = containerRegistry.get();
if (registry != null) {
for (TestcontainerDescription container : registry) {
container.instance.stop();
}
}
}

/**
* Starts all containers after enrichment is done. This happens after the {@link ContainerInjectionTestEnricher} is
* invoked.
*
* @param event the after enrichment event
*/
public void startContainer(@Observes(precedence = 500) final AfterEnrichment event) {
// Look for the servers to start on fields only
for (TestcontainerDescription description : containerRegistry.get()) {
if (description.testcontainer.value()) {
description.instance.start();
TestcontainerRegistry registry = containerRegistry.get();
if (registry != null) {
// Look for the servers to start on fields only
for (TestcontainerDescription description : registry) {
if (description.testcontainer.value()) {
description.instance.start();
}
}
}
}

private void checkForDocker(boolean failIfNoDocker, boolean isDockerAvailable) {
final String detailMessage = "No Docker environment is available.";
if (!isDockerAvailable) {
if (failIfNoDocker) {
throw new AssertionError(detailMessage);
} else {
// First attempt to throw a JUnit 5 assumption
throwAssumption("org.opentest4j.TestAbortedException", detailMessage);
// Not found, attempt to throw a JUnit exception
throwAssumption("org.junit.AssumptionViolatedException", detailMessage);
// No supported test platform found. Throw an AssertionError.
throw new AssertionError(
"Failed to find a support test platform and no Docker environment is available.");
}
}
}

private void throwAssumption(final String type, final String detailMessage) {
try {
Class<?> clazz = Class.forName(type);
Constructor<?> ctor = clazz.getConstructor(String.class);
throw (RuntimeException) ctor.newInstance(detailMessage);
} catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException
| InvocationTargetException ignore) {
}
}

@SuppressWarnings({ "resource", "BooleanMethodIsAlwaysInverted" })
private boolean isDockerAvailable() {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,27 @@

package org.jboss.arquillian.testcontainers;

import java.lang.reflect.AnnotatedElement;

import org.jboss.arquillian.testcontainers.api.Testcontainer;
import org.testcontainers.containers.GenericContainer;

/**
* A holder for information about the Testcontainer being injected into a field.
*
* @author <a href="mailto:[email protected]">James R. Perkins</a>
*/
class TestcontainerDescription {

/**
* The annotation that was on the field
*/
final Testcontainer testcontainer;
/**
* The instance of the container created
*/
final GenericContainer<?> instance;
final AnnotatedElement element;

TestcontainerDescription(final Testcontainer testcontainer, final AnnotatedElement element,
final GenericContainer<?> instance) {
TestcontainerDescription(final Testcontainer testcontainer, final GenericContainer<?> instance) {
this.testcontainer = testcontainer;
this.element = element;
this.instance = instance;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
package org.jboss.arquillian.testcontainers;

import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
Expand All @@ -16,6 +15,9 @@
import org.jboss.arquillian.testcontainers.api.Testcontainer;
import org.testcontainers.containers.GenericContainer;

/**
* A registry to store the testcontainer descriptions.
*/
class TestcontainerRegistry implements Iterable<TestcontainerDescription> {
private final List<TestcontainerDescription> containers;

Expand All @@ -27,20 +29,20 @@ class TestcontainerRegistry implements Iterable<TestcontainerDescription> {
* Lookup the container in the test container instances. If more than one container is found for the type or
* qualifier, an {@link IllegalArgumentException} is thrown.
*
* @param type the type to lookup
* @param qualifiers any qualifying annotations
* @param type the type to lookup
* @param testcontainer the test container annotation
* @param qualifiers any qualifying annotations
*
* @return the generic type
*/
GenericContainer<?> lookupOrCreate(final Class<GenericContainer<?>> type, final AnnotatedElement element,
final Testcontainer testcontainer,
GenericContainer<?> lookupOrCreate(final Class<GenericContainer<?>> type, final Testcontainer testcontainer,
final List<Annotation> qualifiers) {
GenericContainer<?> result = lookup(type, qualifiers);
if (result == null) {
try {
final Constructor<? extends GenericContainer<?>> constructor = getConstructor(type, testcontainer);
result = constructor.newInstance();
this.containers.add(new TestcontainerDescription(testcontainer, element, result));
this.containers.add(new TestcontainerDescription(testcontainer, result));
} catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
throw new IllegalArgumentException(String.format("Could create container %s", type.getName()), e);
}
Expand Down

0 comments on commit 9aaae6d

Please sign in to comment.