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 Dec 1, 2024
1 parent e3ab6bd commit a151f9f
Show file tree
Hide file tree
Showing 33 changed files with 337 additions and 302 deletions.
2 changes: 1 addition & 1 deletion bom/application/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3284,7 +3284,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,16 @@ public interface TestConfig {
@WithDefault("false")
boolean displayTestOutput();

/**
* The FQCN of the JUnit <code>ClassOrderer</code> to use. If the class cannot be found, it fallbacks to JUnit
* default behaviour which does not set a <code>ClassOrderer</code> at all.
*
* @see <a href=https://junit.org/junit5/docs/current/user-guide/#writing-tests-test-execution-order-classes>JUnit Class
* Order<a/>
*/
@WithDefault("io.quarkus.test.junit.util.QuarkusTestProfileAwareClassOrderer")
Optional<String> classOrderer();

/**
* Tags that should be included for continuous testing. This supports JUnit Tag Expressions.
*
Expand Down Expand Up @@ -77,7 +89,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 +252,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 +275,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 All @@ -12,13 +14,6 @@ public final class QuarkusConfigFactory extends SmallRyeConfigFactory {

private static volatile SmallRyeConfig config;

/**
* Construct a new instance. Called by service loader.
*/
public QuarkusConfigFactory() {
// todo: replace with {@code provider()} post-Java 11
}

@Override
public SmallRyeConfig getConfigFor(final SmallRyeConfigProviderResolver configProviderResolver,
final ClassLoader classLoader) {
Expand All @@ -30,15 +25,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
22 changes: 4 additions & 18 deletions docs/src/main/asciidoc/getting-started-testing.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -472,24 +472,10 @@ a bit slower, as it adds a shutdown/startup cycle to the test time, but gives a
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].
The behavior of this `ClassOrderer` is configurable via `junit-platform.properties` (see the source code or javadoc for more details).
It can also be disabled entirely by setting another `ClassOrderer` that is provided by JUnit 5 or even your own custom one. +
Please note that as of JUnit 5.8.2 link:https://github.com/junit-team/junit5/issues/2794[only a single `junit-platform.properties` is picked up and a warning is logged if more than one is found].
If you encounter such warnings, you can get rid of them by removing the Quarkus-supplied `junit-platform.properties` from the classpath via an exclusion:
[source,xml]
----
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5-properties</artifactId>
</exclusion>
</exclusions>
</dependency>
----
The behavior of this `ClassOrderer` is configurable via `application.properties` using the property
`quarkus.test.class-orderer`. The property accepts the FQCN of the `ClassOrderer` to use. If the class cannot be found,
it fallbacks to JUnit default behaviour which does not set a `ClassOrderer` at all. It can also be disabled entirely by
setting another `ClassOrderer` that is provided by JUnit 5 or even your own custom one.

=== Writing a Profile

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
import jakarta.enterprise.inject.Instance;

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

import io.quarkus.arc.Arc;
import io.quarkus.runtime.configuration.DurationConverter;
Expand Down Expand Up @@ -147,9 +147,7 @@ private static String adjustExpressionSyntax(String val) {
* Adapted from {@link io.smallrye.config.ExpressionConfigSourceInterceptor}
*/
private static String resolvePropertyExpression(String expr) {
// Force the runtime CL in order to make the DEV UI page work
final ClassLoader cl = SchedulerUtils.class.getClassLoader();
final Config config = ConfigProviderResolver.instance().getConfig(cl);
final Config config = ConfigProvider.getConfig();
final Expression expression = Expression.compile(expr, LENIENT_SYNTAX, NO_TRIM);
final String expanded = expression.evaluate(new BiConsumer<ResolveContext<RuntimeException>, StringBuilder>() {
@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
import java.util.function.Supplier;

import org.eclipse.microprofile.config.Config;
import org.eclipse.microprofile.config.spi.ConfigProviderResolver;
import org.eclipse.microprofile.config.ConfigProvider;
import org.jboss.logging.Logger;

import io.quarkus.arc.CurrentContextFactory;
Expand Down Expand Up @@ -310,9 +310,7 @@ private static String lookUpPropertyValue(String propertyValue) {
* Adapted from {@link io.smallrye.config.ExpressionConfigSourceInterceptor}
*/
private static String resolvePropertyExpression(String expr) {
// Force the runtime CL in order to make the DEV UI page work
final ClassLoader cl = VertxEventBusConsumerRecorder.class.getClassLoader();
final Config config = ConfigProviderResolver.instance().getConfig(cl);
final Config config = ConfigProvider.getConfig();
final Expression expression = Expression.compile(expr, LENIENT_SYNTAX, NO_TRIM);
final String expanded = expression.evaluate(new BiConsumer<ResolveContext<RuntimeException>, StringBuilder>() {
@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1 @@
quarkus.test.continuous-testing=enabled
# this should not be needed, but something in the tests is setting this to 1234 and confusing the test framework, so set it here to match
quarkus.http.non-application-root-path=1234
quarkus.test.continuous-testing=enabled
Original file line number Diff line number Diff line change
@@ -1,3 +1 @@
quarkus.test.continuous-testing=enabled
# this should not be needed, but something in the tests is setting this to 1234 and confusing the test framework, so set it here to match
quarkus.http.non-application-root-path=1234
quarkus.test.continuous-testing=enabled
Original file line number Diff line number Diff line change
@@ -1,3 +1 @@
quarkus.test.continuous-testing=enabled
# this should not be needed, but something in the tests is setting this to 1234 and confusing the test framework, so set it here to match
quarkus.http.non-application-root-path=1234
quarkus.test.continuous-testing=enabled
Original file line number Diff line number Diff line change
@@ -1,3 +1 @@
quarkus.test.continuous-testing=enabled
# this should not be needed, but something in the tests is setting this to 1234 and confusing the test framework, so set it here to match
quarkus.http.non-application-root-path=1234
quarkus.test.continuous-testing=enabled
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,28 @@
package io.quarkus.test.config;

import org.eclipse.microprofile.config.spi.ConfigProviderResolver;
import org.junit.platform.launcher.LauncherSession;
import org.junit.platform.launcher.LauncherSessionListener;

import io.quarkus.runtime.LaunchMode;

/**
* A JUnit {@link LauncherSessionListener}, used to register the initial test config. Test set up code can safely call
* <code>ConfigProvider.getConfig()</code> to retrieve an instance of the Quarkus configuration.
* <p>
* The test config only contains sources known at bootstrap test time. For instance, config sources generated by
* Quarkus are not available in the test config.
*/
public class ConfigLauncherSession implements LauncherSessionListener {
@Override
public void launcherSessionOpened(final LauncherSession session) {
TestConfigProviderResolver resolver = new TestConfigProviderResolver();
ConfigProviderResolver.setInstance(resolver);
resolver.getConfig(LaunchMode.TEST);
}

@Override
public void launcherSessionClosed(final LauncherSession session) {
((TestConfigProviderResolver) ConfigProviderResolver.instance()).restore();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package io.quarkus.test.config;

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

import io.quarkus.runtime.logging.LoggingSetupRecorder;

/**
* A global JUnit extension that enables/sets up basic logging if logging has not already been set up.
* <p/>
* This is useful for getting log output from non-Quarkus tests (if executed separately or before the first Quarkus
* test), but also for getting instant log output from {@code QuarkusTestResourceLifecycleManagers} etc.
*/
public class LoggingSetupExtension implements Extension {
public LoggingSetupExtension() {
LoggingSetupRecorder.handleFailedStart();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package io.quarkus.test.config;

import org.eclipse.microprofile.config.ConfigProvider;
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.smallrye.config.SmallRyeConfig;

/**
* A JUnit {@link ClassOrderer}, used to delegate to a custom implementations of {@link ClassOrderer} set by Quarkus
* config.
*/
public class QuarkusClassOrderer implements ClassOrderer {
private final ClassOrderer delegate;

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) {

}
};
}
Loading

0 comments on commit a151f9f

Please sign in to comment.