Skip to content

Commit

Permalink
Register a Config instance to bootstrap tests
Browse files Browse the repository at this point in the history
  • Loading branch information
radcortez committed Nov 21, 2024
1 parent 26db59d commit ed598a8
Show file tree
Hide file tree
Showing 25 changed files with 217 additions and 251 deletions.
2 changes: 1 addition & 1 deletion bom/application/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3286,7 +3286,7 @@
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5-properties</artifactId>
<artifactId>quarkus-junit5-config</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
import io.quarkus.runtime.annotations.ConfigDocMapKey;
import io.quarkus.runtime.annotations.ConfigPhase;
import io.quarkus.runtime.annotations.ConfigRoot;
import io.quarkus.runtime.configuration.TrimmedStringConverter;
import io.smallrye.config.ConfigMapping;
import io.smallrye.config.WithConverter;
import io.smallrye.config.WithDefault;
import io.smallrye.config.WithParentName;

Expand Down Expand Up @@ -45,6 +47,11 @@ public interface TestConfig {
@WithDefault("false")
boolean displayTestOutput();

/**
* TODO
*/
Optional<String> classOrderer();

/**
* Tags that should be included for continuous testing. This supports JUnit Tag Expressions.
*
Expand Down Expand Up @@ -77,7 +84,6 @@ public interface TestConfig {
* is matched against the test class name (not the file name).
* <p>
* This is ignored if include-pattern has been set.
*
*/
@WithDefault(".*\\.IT[^.]+|.*IT|.*ITCase")
Optional<String> excludePattern();
Expand Down Expand Up @@ -241,7 +247,6 @@ public interface TestConfig {
* is matched against the module groupId:artifactId.
* <p>
* This is ignored if include-module-pattern has been set.
*
*/
Optional<String> excludeModulePattern();

Expand All @@ -265,7 +270,7 @@ interface Profile {
* then Quarkus will only execute tests that are annotated with a {@code @TestProfile} that has at least one of the
* supplied (via the aforementioned system property) tags.
*/
Optional<List<String>> tags();
Optional<List<@WithConverter(TrimmedStringConverter.class) String>> tags();
}

interface Container {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package io.quarkus.runtime.configuration;

import org.eclipse.microprofile.config.spi.ConfigProviderResolver;

import io.quarkus.runtime.LaunchMode;
import io.smallrye.config.SmallRyeConfig;
import io.smallrye.config.SmallRyeConfigFactory;
Expand Down Expand Up @@ -30,15 +32,12 @@ public SmallRyeConfig getConfigFor(final SmallRyeConfigProviderResolver configPr
}

public static void setConfig(SmallRyeConfig config) {
SmallRyeConfigProviderResolver configProviderResolver = (SmallRyeConfigProviderResolver) SmallRyeConfigProviderResolver
.instance();
ConfigProviderResolver configProviderResolver = ConfigProviderResolver.instance();
// Uninstall previous config
if (QuarkusConfigFactory.config != null) {
configProviderResolver.releaseConfig(QuarkusConfigFactory.config);
QuarkusConfigFactory.config = null;
}
// Also release the TCCL config, in case that config was not QuarkusConfigFactory.config
configProviderResolver.releaseConfig(Thread.currentThread().getContextClassLoader());
// Install new config
if (config != null) {
QuarkusConfigFactory.config = config;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,10 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
Expand All @@ -31,7 +29,6 @@
import java.util.logging.LogRecord;

import org.eclipse.microprofile.config.ConfigProvider;
import org.eclipse.microprofile.config.spi.ConfigSource;
import org.jboss.logmanager.ExtFormatter;
import org.jboss.logmanager.LogContext;
import org.jboss.logmanager.LogContextInitializer;
Expand All @@ -57,7 +54,6 @@
import io.quarkus.runtime.LaunchMode;
import io.quarkus.runtime.RuntimeValue;
import io.quarkus.runtime.annotations.Recorder;
import io.quarkus.runtime.configuration.QuarkusConfigBuilderCustomizer;
import io.quarkus.runtime.console.ConsoleRuntimeConfig;
import io.quarkus.runtime.logging.LogBuildTimeConfig.CategoryBuildTimeConfig;
import io.quarkus.runtime.logging.LogRuntimeConfig.CategoryConfig;
Expand All @@ -67,7 +63,6 @@
import io.quarkus.runtime.logging.LogRuntimeConfig.SocketConfig;
import io.quarkus.runtime.shutdown.ShutdownListener;
import io.smallrye.config.SmallRyeConfig;
import io.smallrye.config.SmallRyeConfigBuilder;

@Recorder
public class LoggingSetupRecorder {
Expand All @@ -87,34 +82,9 @@ public static void handleFailedStart() {

public static void handleFailedStart(RuntimeValue<Optional<Supplier<String>>> banner) {
SmallRyeConfig config = ConfigProvider.getConfig().unwrap(SmallRyeConfig.class);
// There may be cases where a Config with the mappings is already available, but we can't be sure, so we wrap
// the original Config and map the logging classes.
SmallRyeConfig loggingConfig = new SmallRyeConfigBuilder()
.withCustomizers(new QuarkusConfigBuilderCustomizer())
.withMapping(LogBuildTimeConfig.class)
.withMapping(LogRuntimeConfig.class)
.withMapping(ConsoleRuntimeConfig.class)
.withSources(new ConfigSource() {
@Override
public Set<String> getPropertyNames() {
Set<String> properties = new HashSet<>();
config.getPropertyNames().forEach(properties::add);
return properties;
}

@Override
public String getValue(final String propertyName) {
return config.getRawValue(propertyName);
}

@Override
public String getName() {
return "Logging Config";
}
}).build();
LogRuntimeConfig logRuntimeConfig = loggingConfig.getConfigMapping(LogRuntimeConfig.class);
LogBuildTimeConfig logBuildTimeConfig = loggingConfig.getConfigMapping(LogBuildTimeConfig.class);
ConsoleRuntimeConfig consoleRuntimeConfig = loggingConfig.getConfigMapping(ConsoleRuntimeConfig.class);
LogRuntimeConfig logRuntimeConfig = config.getConfigMapping(LogRuntimeConfig.class);
LogBuildTimeConfig logBuildTimeConfig = config.getConfigMapping(LogBuildTimeConfig.class);
ConsoleRuntimeConfig consoleRuntimeConfig = config.getConfigMapping(ConsoleRuntimeConfig.class);
new LoggingSetupRecorder(new RuntimeValue<>(consoleRuntimeConfig)).initializeLogging(logRuntimeConfig,
logBuildTimeConfig,
DiscoveredLogComponents.ofEmpty(), emptyMap(), false, null, emptyList(), emptyList(), emptyList(), emptyList(),
Expand Down
2 changes: 2 additions & 0 deletions docs/src/main/asciidoc/getting-started-testing.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,8 @@ To get around this Quarkus supports the idea of a test profile. If a test has a
run test then Quarkus will be shut down and started with the new profile before running the tests. This is obviously
a bit slower, as it adds a shutdown/startup cycle to the test time, but gives a great deal of flexibility.

// TODO - Rewrite docs

To reduce the amount of times Quarkus needs to restart, `io.quarkus.test.junit.util.QuarkusTestProfileAwareClassOrderer`
is registered as a global `ClassOrderer` as described in the
link:https://junit.org/junit5/docs/current/user-guide/#writing-tests-test-execution-order-classes[JUnit 5 User Guide].
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,10 @@
import java.util.regex.Pattern;

import org.eclipse.microprofile.config.Config;
import org.eclipse.microprofile.config.ConfigProvider;

import io.quarkus.runtime.LaunchMode;
import io.quarkus.runtime.configuration.ConfigUtils;
import io.quarkus.runtime.configuration.QuarkusConfigFactory;
import io.quarkus.test.common.http.TestHTTPResourceManager;
import io.quarkus.utilities.OS;
import io.smallrye.config.SmallRyeConfig;

public final class LauncherUtil {

Expand All @@ -38,9 +35,7 @@ private LauncherUtil() {
}

public static Config installAndGetSomeConfig() {
SmallRyeConfig config = ConfigUtils.configBuilder(false, LaunchMode.NORMAL).build();
QuarkusConfigFactory.setConfig(config);
return config;
return ConfigProvider.getConfig();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,6 @@
import org.jboss.jandex.DotName;
import org.jboss.jandex.IndexView;

import io.smallrye.config.SmallRyeConfigProviderResolver;

/**
* Manages {@link QuarkusTestResourceLifecycleManager}
*/
Expand Down Expand Up @@ -214,18 +212,6 @@ public void close() {
throw new RuntimeException("Unable to stop Quarkus test resource " + entry.getTestResource(), e);
}
}
// TODO using QuarkusConfigFactory.setConfig(null) here makes continuous testing fail,
// e.g. in io.quarkus.hibernate.orm.HibernateHotReloadTestCase
// or io.quarkus.opentelemetry.deployment.OpenTelemetryContinuousTestingTest;
// maybe this cleanup is not really necessary and just "doesn't hurt" because
// the released config is still cached in QuarkusConfigFactory#config
// and will be restored soon after when QuarkusConfigFactory#getConfigFor is called?
// In that case we should remove this cleanup.
try {
((SmallRyeConfigProviderResolver) SmallRyeConfigProviderResolver.instance())
.releaseConfig(Thread.currentThread().getContextClassLoader());
} catch (Throwable ignored) {
}
configProperties.clear();
}

Expand Down
35 changes: 35 additions & 0 deletions test-framework/junit5-config/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-test-framework</artifactId>
<version>999-SNAPSHOT</version>
</parent>

<artifactId>quarkus-junit5-config</artifactId>
<name>Quarkus - Test Framework - JUnit 5 Config</name>

<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
</dependency>
<dependency>
<groupId>io.smallrye.config</groupId>
<artifactId>smallrye-config</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-core</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-core-deployment</artifactId>
</dependency>
</dependencies>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package io.quarkus.test.config;

import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.Extension;
import org.junit.jupiter.api.extension.ExtensionContext;

import io.quarkus.runtime.logging.LoggingSetupRecorder;

public class LoggingSetupExtension implements Extension, BeforeAllCallback {
@Override
public void beforeAll(final ExtensionContext context) throws Exception {
LoggingSetupRecorder.handleFailedStart();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package io.quarkus.test.config;

import org.eclipse.microprofile.config.ConfigProvider;
import org.eclipse.microprofile.config.spi.ConfigProviderResolver;
import org.junit.jupiter.api.ClassOrderer;
import org.junit.jupiter.api.ClassOrdererContext;
import org.junit.platform.commons.util.ReflectionUtils;

import io.quarkus.deployment.dev.testing.TestConfig;
import io.quarkus.runtime.LaunchMode;
import io.smallrye.config.SmallRyeConfig;

public class QuarkusClassOrderer implements ClassOrderer {
private final ClassOrderer delegate;

static {
TestConfigProviderResolver resolver = new TestConfigProviderResolver();
ConfigProviderResolver.setInstance(resolver);
resolver.getConfig(LaunchMode.TEST);
}

public QuarkusClassOrderer() {
SmallRyeConfig config = ConfigProvider.getConfig().unwrap(SmallRyeConfig.class);
TestConfig testConfig = config.getConfigMapping(TestConfig.class);

delegate = testConfig.classOrderer()
.map(klass -> ReflectionUtils.tryToLoadClass(klass)
.andThenTry(ReflectionUtils::newInstance)
.andThenTry(instance -> (ClassOrderer) instance)
.toOptional().orElse(EMPTY))
.orElse(EMPTY);
}

@Override
public void orderClasses(final ClassOrdererContext context) {
delegate.orderClasses(context);
}

private static final ClassOrderer EMPTY = new ClassOrderer() {
@Override
public void orderClasses(final ClassOrdererContext context) {

}
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package io.quarkus.test.config;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;

import org.eclipse.microprofile.config.Config;

import io.quarkus.deployment.dev.testing.TestConfig;
import io.quarkus.runtime.LaunchMode;
import io.quarkus.runtime.configuration.ConfigUtils;
import io.smallrye.config.SmallRyeConfig;
import io.smallrye.config.SmallRyeConfigBuilder;
import io.smallrye.config.SmallRyeConfigProviderResolver;

public class TestConfigProviderResolver extends SmallRyeConfigProviderResolver {
private static final SmallRyeConfigProviderResolver resolver = (SmallRyeConfigProviderResolver) SmallRyeConfigProviderResolver
.instance();

private static final Map<LaunchMode, SmallRyeConfig> configs = new ConcurrentHashMap<>();
private static final ClassLoader classLoader = Thread.currentThread().getContextClassLoader();

@Override
public Config getConfig() {
return resolver.getConfig();
}

public Config getConfig(final LaunchMode mode) {
if (classLoader.equals(Thread.currentThread().getContextClassLoader())) {
resolver.releaseConfig(classLoader);
SmallRyeConfig config = configs.computeIfAbsent(mode, new Function<LaunchMode, SmallRyeConfig>() {
@Override
public SmallRyeConfig apply(final LaunchMode launchMode) {
return ConfigUtils.configBuilder(false, true, mode)
.withProfile(mode.getDefaultProfile())
.withMapping(TestConfig.class, "quarkus.test")
.build();
}
});
resolver.registerConfig(config, classLoader);
return config;
}
throw new IllegalStateException();
}

public void restoreConfig() {
if (classLoader.equals(Thread.currentThread().getContextClassLoader())) {
resolver.releaseConfig(classLoader);
resolver.registerConfig(configs.get(LaunchMode.TEST), classLoader);
} else {
throw new IllegalStateException();
}
}

@Override
public Config getConfig(final ClassLoader loader) {
return resolver.getConfig(loader);
}

@Override
public SmallRyeConfigBuilder getBuilder() {
return resolver.getBuilder();
}

@Override
public void registerConfig(final Config config, final ClassLoader classLoader) {
resolver.registerConfig(config, classLoader);
}

@Override
public void releaseConfig(final Config config) {
resolver.releaseConfig(config);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
io.quarkus.test.config.LoggingSetupExtension
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
junit.jupiter.extensions.autodetection.enabled=true
junit.jupiter.testclass.order.default=io.quarkus.test.config.QuarkusClassOrderer
Loading

0 comments on commit ed598a8

Please sign in to comment.