From 9aaae6d301e27e9e7277edca69f00390e531ba05 Mon Sep 17 00:00:00 2001 From: "James R. Perkins" Date: Wed, 3 Jul 2024 15:28:22 -0700 Subject: [PATCH] Documentation updates and updates from review. Signed-off-by: James R. Perkins --- .../ContainerInjectionTestEnricher.java | 40 +++--------- .../TestContainersObserver.java | 63 +++++++++---------- .../TestcontainerDescription.java | 15 +++-- .../testcontainers/TestcontainerRegistry.java | 14 +++-- 4 files changed, 55 insertions(+), 77 deletions(-) diff --git a/src/main/java/org/jboss/arquillian/testcontainers/ContainerInjectionTestEnricher.java b/src/main/java/org/jboss/arquillian/testcontainers/ContainerInjectionTestEnricher.java index 7e42a90..0a36343 100644 --- a/src/main/java/org/jboss/arquillian/testcontainers/ContainerInjectionTestEnricher.java +++ b/src/main/java/org/jboss/arquillian/testcontainers/ContainerInjectionTestEnricher.java @@ -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; @@ -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 James R. Perkins */ -@SuppressWarnings({ "unchecked", "resource" }) +@SuppressWarnings({ "unchecked" }) public class ContainerInjectionTestEnricher implements TestEnricher { @Inject private Instance instances; @@ -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 qualifiers = Stream.of(field.getAnnotations()) @@ -61,19 +61,14 @@ public void enrich(final Object testCase) { } value = instances.get() - .lookupOrCreate((Class>) field.getType(), field, testcontainer, - qualifiers); + .lookupOrCreate((Class>) 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); } @@ -85,23 +80,6 @@ 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 getFieldsWithAnnotation(final Class source) { final List foundFields = new ArrayList<>(); Class nextSource = source; @@ -109,7 +87,7 @@ private static List getFieldsWithAnnotation(final Class source) { 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); } diff --git a/src/main/java/org/jboss/arquillian/testcontainers/TestContainersObserver.java b/src/main/java/org/jboss/arquillian/testcontainers/TestContainersObserver.java index 97af33d..dcc2517 100644 --- a/src/main/java/org/jboss/arquillian/testcontainers/TestContainersObserver.java +++ b/src/main/java/org/jboss/arquillian/testcontainers/TestContainersObserver.java @@ -29,6 +29,14 @@ class TestContainersObserver { @Inject private Instance 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); @@ -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 { diff --git a/src/main/java/org/jboss/arquillian/testcontainers/TestcontainerDescription.java b/src/main/java/org/jboss/arquillian/testcontainers/TestcontainerDescription.java index bdb2eba..5fc46ed 100644 --- a/src/main/java/org/jboss/arquillian/testcontainers/TestcontainerDescription.java +++ b/src/main/java/org/jboss/arquillian/testcontainers/TestcontainerDescription.java @@ -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 James R. Perkins */ 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; } } diff --git a/src/main/java/org/jboss/arquillian/testcontainers/TestcontainerRegistry.java b/src/main/java/org/jboss/arquillian/testcontainers/TestcontainerRegistry.java index 0fec75b..36dabc8 100644 --- a/src/main/java/org/jboss/arquillian/testcontainers/TestcontainerRegistry.java +++ b/src/main/java/org/jboss/arquillian/testcontainers/TestcontainerRegistry.java @@ -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; @@ -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 { private final List containers; @@ -27,20 +29,20 @@ class TestcontainerRegistry implements Iterable { * 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> type, final AnnotatedElement element, - final Testcontainer testcontainer, + GenericContainer lookupOrCreate(final Class> type, final Testcontainer testcontainer, final List qualifiers) { GenericContainer result = lookup(type, qualifiers); if (result == null) { try { final Constructor> 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); }