From 34c8681ad4e140174f433ba1e36f43605e40b766 Mon Sep 17 00:00:00 2001 From: Andrew Guibert Date: Thu, 6 Feb 2020 11:12:01 -0600 Subject: [PATCH 1/2] Cache loaded ApplicationEnvironment to ensure consistent environment for duration of test run --- .../testing/ApplicationEnvironment.java | 133 ++++++++++-------- .../testing/jaxrs/RestClientBuilder.java | 2 +- .../jupiter/MicroShedTestExtension.java | 2 +- .../config/TestcontainersConfigurationIT.java | 6 +- .../testcontainers/ApplicationContainer.java | 4 +- ...HollowTestcontainersConfigurationTest.java | 6 +- 6 files changed, 83 insertions(+), 70 deletions(-) diff --git a/core/src/main/java/org/microshed/testing/ApplicationEnvironment.java b/core/src/main/java/org/microshed/testing/ApplicationEnvironment.java index d2081e0b..bfe537cd 100644 --- a/core/src/main/java/org/microshed/testing/ApplicationEnvironment.java +++ b/core/src/main/java/org/microshed/testing/ApplicationEnvironment.java @@ -37,6 +37,78 @@ */ public interface ApplicationEnvironment { + public static class Resolver { + private static ApplicationEnvironment loaded = null; + + private Resolver() { + // static singleton + } + + /** + * @return The selected {@link ApplicationEnvironment}. The selection is made using the following criteria: + *
    + *
  1. If the {@link #ENV_CLASS} system property or environment variable is set, the class is used
  2. + *
  3. The {@link ServiceLoader} is used to load all {@link ApplicationEnvironment} instances, which are filtered + * based on {@link #isAvailable()} and then sorted based on {@link #getPriority()} where higher numbers are chosen + * first.
  4. + *
+ */ + public static ApplicationEnvironment load() { + if (loaded != null) + return loaded; + + // First check explicilty configured environment via system property or env var + String strategy = System.getProperty(ENV_CLASS); + if (strategy == null || strategy.isEmpty()) + strategy = System.getenv(ENV_CLASS); + if (strategy != null && !strategy.isEmpty()) { + Class found; + try { + found = Class.forName(strategy); + } catch (ClassNotFoundException e) { + throw new IllegalStateException("Unable to load the selected ApplicationEnvironment class: " + strategy, e); + } + if (!ApplicationEnvironment.class.isAssignableFrom(found)) { + throw new IllegalStateException("ApplicationEnvironment class " + strategy + + " was found, but it does not implement the required interface " + ApplicationEnvironment.class); + } else { + try { + loaded = (ApplicationEnvironment) found.newInstance(); + return loaded; + } catch (InstantiationException | IllegalAccessException e) { + throw new IllegalStateException("Unable to initialize " + found, e); + } + } + } + + Logger LOG = LoggerFactory.getLogger(ApplicationEnvironment.class); + + // If nothing explicitly defined in sysprops or env, check ServiceLoader + Set envs = new HashSet<>(); + ServiceLoader.load(ApplicationEnvironment.class).forEach(envs::add); + Optional selectedEnv = envs.stream() + .map(env -> { + if (LOG.isDebugEnabled()) + LOG.debug("Found ApplicationEnvironment " + env.getClass() + " with priority=" + env.getPriority() + ", available=" + env.isAvailable()); + return env; + }) + .filter(env -> env.isAvailable()) + .sorted((c1, c2) -> c1.getClass().getCanonicalName().compareTo(c2.getClass().getCanonicalName())) + .sorted((c1, c2) -> Integer.compare(c2.getPriority(), c1.getPriority())) + .findFirst(); + loaded = selectedEnv.orElseThrow(() -> new IllegalStateException("No available " + ApplicationEnvironment.class.getSimpleName() + " was discovered.")); + return loaded; + } + + /** + * @param clazz The {@link ApplicationEnvironment} class to check is active + * @return True if the provided {@link ApplicationEnvironment} is currently active, false otherwise + */ + public static boolean isSelected(Class clazz) { + return load().getClass().getCanonicalName().equals(clazz.getCanonicalName()); + } + } + /** * The default priority returned by an implementation of {@link ApplicationEnvironment#isAvailable} * In general, built-in ApplicationEnvironment implementations have a priority less than the default @@ -47,69 +119,10 @@ public interface ApplicationEnvironment { /** * The name of the system property or environment variable that indicates a specific {@link ApplicationEnvironment} * to use. If this property is set, it will be used regardless of the priority or availability. If this property is - * NOT set, the normal resolution rules will be applied as defined in {@link #load()} + * NOT set, the normal resolution rules will be applied as defined in {@link ApplicationEnvironment.Resolver#load()} */ public static final String ENV_CLASS = "MICROSHED_TEST_ENV_CLASS"; - /** - * @return The selected {@link ApplicationEnvironment}. The selection is made using the following criteria: - *
    - *
  1. If the {@link #ENV_CLASS} system property or environment variable is set, the class is used
  2. - *
  3. The {@link ServiceLoader} is used to load all {@link ApplicationEnvironment} instances, which are filtered - * based on {@link #isAvailable()} and then sorted based on {@link #getPriority()} where higher numbers are chosen - * first.
  4. - *
- */ - public static ApplicationEnvironment load() { - // First check explicilty configured environment via system property or env var - String strategy = System.getProperty(ENV_CLASS); - if (strategy == null || strategy.isEmpty()) - strategy = System.getenv(ENV_CLASS); - if (strategy != null && !strategy.isEmpty()) { - Class found; - try { - found = Class.forName(strategy); - } catch (ClassNotFoundException e) { - throw new IllegalStateException("Unable to load the selected ApplicationEnvironment class: " + strategy, e); - } - if (!ApplicationEnvironment.class.isAssignableFrom(found)) { - throw new IllegalStateException("ApplicationEnvironment class " + strategy + - " was found, but it does not implement the required interface " + ApplicationEnvironment.class); - } else { - try { - return (ApplicationEnvironment) found.newInstance(); - } catch (InstantiationException | IllegalAccessException e) { - throw new IllegalStateException("Unable to initialize " + found, e); - } - } - } - - Logger LOG = LoggerFactory.getLogger(ApplicationEnvironment.class); - - // If nothing explicitly defined in sysprops or env, check ServiceLoader - Set envs = new HashSet<>(); - ServiceLoader.load(ApplicationEnvironment.class).forEach(envs::add); - Optional selectedEnv = envs.stream() - .map(env -> { - if (LOG.isDebugEnabled()) - LOG.debug("Found ApplicationEnvironment " + env.getClass() + " with priority=" + env.getPriority() + ", available=" + env.isAvailable()); - return env; - }) - .filter(env -> env.isAvailable()) - .sorted((c1, c2) -> c1.getClass().getCanonicalName().compareTo(c2.getClass().getCanonicalName())) - .sorted((c1, c2) -> Integer.compare(c2.getPriority(), c1.getPriority())) - .findFirst(); - return selectedEnv.orElseThrow(() -> new IllegalStateException("No available " + ApplicationEnvironment.class.getSimpleName() + " was discovered.")); - } - - /** - * @param clazz The {@link ApplicationEnvironment} class to check is active - * @return True if the provided {@link ApplicationEnvironment} is currently active, false otherwise - */ - public static boolean isSelected(Class clazz) { - return load().getClass().equals(clazz); - } - /** * @return true if the ApplicationEnvironment is currently available * false otherwise diff --git a/core/src/main/java/org/microshed/testing/jaxrs/RestClientBuilder.java b/core/src/main/java/org/microshed/testing/jaxrs/RestClientBuilder.java index 06839746..c6bb39b9 100644 --- a/core/src/main/java/org/microshed/testing/jaxrs/RestClientBuilder.java +++ b/core/src/main/java/org/microshed/testing/jaxrs/RestClientBuilder.java @@ -99,7 +99,7 @@ public RestClientBuilder withProviders(Class... providers) { public T build(Class clazz) { // Apply default values if unspecified if (appContextRoot == null) - appContextRoot = ApplicationEnvironment.load().getApplicationURL(); + appContextRoot = ApplicationEnvironment.Resolver.load().getApplicationURL(); if (jaxrsPath == null) jaxrsPath = locateApplicationPath(clazz); if (providers == null) diff --git a/core/src/main/java/org/microshed/testing/jupiter/MicroShedTestExtension.java b/core/src/main/java/org/microshed/testing/jupiter/MicroShedTestExtension.java index 7e43eb04..788db2f8 100644 --- a/core/src/main/java/org/microshed/testing/jupiter/MicroShedTestExtension.java +++ b/core/src/main/java/org/microshed/testing/jupiter/MicroShedTestExtension.java @@ -50,7 +50,7 @@ class MicroShedTestExtension implements BeforeAllCallback { @Override public void beforeAll(ExtensionContext context) throws Exception { Class testClass = context.getRequiredTestClass(); - ApplicationEnvironment config = ApplicationEnvironment.load(); + ApplicationEnvironment config = ApplicationEnvironment.Resolver.load(); LOG.info("Using ApplicationEnvironment class: " + config.getClass().getCanonicalName()); config.applyConfiguration(testClass); config.start(); diff --git a/modules/testcontainers/src/integrationTest/java/org/microshed/testing/testcontainers/config/TestcontainersConfigurationIT.java b/modules/testcontainers/src/integrationTest/java/org/microshed/testing/testcontainers/config/TestcontainersConfigurationIT.java index 1cee84d5..42cff4e3 100644 --- a/modules/testcontainers/src/integrationTest/java/org/microshed/testing/testcontainers/config/TestcontainersConfigurationIT.java +++ b/modules/testcontainers/src/integrationTest/java/org/microshed/testing/testcontainers/config/TestcontainersConfigurationIT.java @@ -53,8 +53,8 @@ public class TestcontainersConfigurationIT { @Test public void testCorrectEnvironment() { - assertEquals(TestcontainersConfiguration.class, ApplicationEnvironment.load().getClass()); - assertTrue(ApplicationEnvironment.isSelected(TestcontainersConfiguration.class), + assertEquals(TestcontainersConfiguration.class, ApplicationEnvironment.Resolver.load().getClass()); + assertTrue(ApplicationEnvironment.Resolver.isSelected(TestcontainersConfiguration.class), "Expected TestcontainersConfiguration to be selected but it was not"); } @@ -66,7 +66,7 @@ public void testExposedPort() { @Test public void testApplicationURL() { - String appUrl = ApplicationEnvironment.load().getApplicationURL(); + String appUrl = ApplicationEnvironment.Resolver.load().getApplicationURL(); assertNotNull(appUrl); assertEquals(appUrl, app.getApplicationURL()); assertTrue(appUrl.startsWith("http://"), "Application URL did not start with 'http://' " + appUrl); diff --git a/modules/testcontainers/src/main/java/org/microshed/testing/testcontainers/ApplicationContainer.java b/modules/testcontainers/src/main/java/org/microshed/testing/testcontainers/ApplicationContainer.java index 19fe81a9..412142f6 100644 --- a/modules/testcontainers/src/main/java/org/microshed/testing/testcontainers/ApplicationContainer.java +++ b/modules/testcontainers/src/main/java/org/microshed/testing/testcontainers/ApplicationContainer.java @@ -118,7 +118,7 @@ private static Future resolveImage(Optional dockerfile) { } private static boolean isHollow() { - ApplicationEnvironment current = ApplicationEnvironment.load(); + ApplicationEnvironment current = ApplicationEnvironment.Resolver.load(); return !(current instanceof TestcontainersConfiguration) || current instanceof HollowTestcontainersConfiguration; } @@ -429,7 +429,7 @@ private String readMpRestClientConfigKey(Class restClientClass) { public ApplicationContainer withMpRestClient(String restClientClass, String hostUrl) { // If we will be running in Docker, sanitize environment variable name using Environment Variables Mapping Rules defined in MP Config: // https://github.com/eclipse/microprofile-config/blob/master/spec/src/main/asciidoc/configsources.asciidoc#environment-variables-mapping-rules - if (ApplicationEnvironment.isSelected(TestcontainersConfiguration.class)) { + if (ApplicationEnvironment.Resolver.isSelected(TestcontainersConfiguration.class)) { restClientClass = restClientClass.replaceAll("[^a-zA-Z0-9_]", "_") + "_mp_rest_url"; } else { restClientClass += "/mp-rest/url"; diff --git a/modules/testcontainers/src/test/java/org/microshed/testing/testcontainers/config/HollowTestcontainersConfigurationTest.java b/modules/testcontainers/src/test/java/org/microshed/testing/testcontainers/config/HollowTestcontainersConfigurationTest.java index 9136262f..5020da3d 100644 --- a/modules/testcontainers/src/test/java/org/microshed/testing/testcontainers/config/HollowTestcontainersConfigurationTest.java +++ b/modules/testcontainers/src/test/java/org/microshed/testing/testcontainers/config/HollowTestcontainersConfigurationTest.java @@ -54,8 +54,8 @@ public class HollowTestcontainersConfigurationTest { @Test public void testCorrectEnvironment() { - assertEquals(HollowTestcontainersConfiguration.class, ApplicationEnvironment.load().getClass()); - assertTrue(ApplicationEnvironment.isSelected(HollowTestcontainersConfiguration.class), + assertEquals(HollowTestcontainersConfiguration.class, ApplicationEnvironment.Resolver.load().getClass()); + assertTrue(ApplicationEnvironment.Resolver.isSelected(HollowTestcontainersConfiguration.class), "Expected HollowTestcontainersConfiguration to be selected but it was not"); assertTrue(HollowTestcontainersConfiguration.available(), "Expected HollowTestcontainersConfiguration to be available but it was not"); @@ -88,7 +88,7 @@ public void testEnvVarUnchanged() { @Test public void testApplicationURL() { - assertEquals("http://localhost:9080", ApplicationEnvironment.load().getApplicationURL()); + assertEquals("http://localhost:9080", ApplicationEnvironment.Resolver.load().getApplicationURL()); } } \ No newline at end of file From fe3e4282a6550f8c4e19fa4d8917261f14f66bf3 Mon Sep 17 00:00:00 2001 From: Andrew Guibert Date: Thu, 6 Feb 2020 11:32:00 -0600 Subject: [PATCH 2/2] Explicitly trigger static init of SharedContainerConfig early in extension lifecycle --- .../microshed/testing/jupiter/MicroShedTestExtension.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/core/src/main/java/org/microshed/testing/jupiter/MicroShedTestExtension.java b/core/src/main/java/org/microshed/testing/jupiter/MicroShedTestExtension.java index 788db2f8..31ded9a3 100644 --- a/core/src/main/java/org/microshed/testing/jupiter/MicroShedTestExtension.java +++ b/core/src/main/java/org/microshed/testing/jupiter/MicroShedTestExtension.java @@ -31,6 +31,7 @@ import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.platform.commons.support.AnnotationSupport; import org.microshed.testing.ApplicationEnvironment; +import org.microshed.testing.SharedContainerConfig; import org.microshed.testing.jaxrs.RESTClient; import org.microshed.testing.jaxrs.RestClientBuilder; import org.microshed.testing.jwt.JwtBuilder; @@ -50,6 +51,12 @@ class MicroShedTestExtension implements BeforeAllCallback { @Override public void beforeAll(ExtensionContext context) throws Exception { Class testClass = context.getRequiredTestClass(); + + // Explicitly trigger static initialization of any SharedContainerConfig before we do further processing + if (testClass.isAnnotationPresent(SharedContainerConfig.class)) { + Class.forName(testClass.getAnnotation(SharedContainerConfig.class).value().getName()); + } + ApplicationEnvironment config = ApplicationEnvironment.Resolver.load(); LOG.info("Using ApplicationEnvironment class: " + config.getClass().getCanonicalName()); config.applyConfiguration(testClass);