Skip to content

Commit

Permalink
Introduce continuous testing
Browse files Browse the repository at this point in the history
This commit adds the ability to test Quarkus applications
running in dev mode in a continous manner, only running
tests that are affected by the changed code.

To this is it changes the test ClassLoading model to work
the same as the dev mode model, and adds the ability to
launch a completly isolated test application inside the
same JVM as an existing Quarkus app.
  • Loading branch information
stuartwdouglas committed Apr 20, 2021
1 parent ab00a99 commit 5fc1d79
Show file tree
Hide file tree
Showing 145 changed files with 6,576 additions and 731 deletions.
7 changes: 6 additions & 1 deletion bom/application/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@
<avro.version>1.10.2</avro.version>
<jacoco.version>0.8.6</jacoco.version>
<testcontainers.version>1.15.2</testcontainers.version>
<aesh-readline.version>2.1</aesh-readline.version>
</properties>

<dependencyManagement>
Expand Down Expand Up @@ -4760,7 +4761,6 @@
</exclusion>
</exclusions>
</dependency>

<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>apache-client</artifactId>
Expand Down Expand Up @@ -5159,6 +5159,11 @@
<classifier>windows-x86_64</classifier>
<version>${grpc.version}</version>
</dependency>
<dependency>
<groupId>org.aesh</groupId>
<artifactId>readline</artifactId>
<version>${aesh-readline.version}</version>
</dependency>

<!-- Picocli -->
<dependency>
Expand Down
23 changes: 21 additions & 2 deletions build-parent/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -483,13 +483,11 @@
<exclude>io.quarkus:quarkus-test-*</exclude>
<exclude>io.rest-assured:*</exclude>
<exclude>org.assertj:*</exclude>
<exclude>org.junit.jupiter:*</exclude>
</excludes>
<includes>
<include>io.quarkus:quarkus-test-*:*:*:test</include>
<include>io.rest-assured:*:*:*:test</include>
<include>org.assertj:*:*:*:test</include>
<include>org.junit.jupiter:*:*:*:test</include>
</includes>
<message>Found test dependencies with wrong scope:</message>
</bannedDependencies>
Expand All @@ -500,6 +498,27 @@
<goal>enforce</goal>
</goals>
</execution>
<execution>
<id>enforce-test-deps-junit-scope</id>
<configuration>
<rules>
<bannedDependencies>
<searchTransitive>false</searchTransitive>
<excludes>
<exclude>org.junit.jupiter:*</exclude>
</excludes>
<includes>
<include>org.junit.jupiter:*:*:*:test</include>
</includes>
<message>Found JUnit dependencies with wrong scope:</message>
</bannedDependencies>
</rules>
<skip>${enforce-test-deps-scope.skip}</skip>
</configuration>
<goals>
<goal>enforce</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
Expand Down
27 changes: 27 additions & 0 deletions core/deployment/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@
<name>Quarkus - Core - Deployment</name>

<dependencies>
<dependency>
<groupId>org.aesh</groupId>
<artifactId>readline</artifactId>
</dependency>
<dependency>
<groupId>org.wildfly.common</groupId>
<artifactId>wildfly-common</artifactId>
Expand Down Expand Up @@ -106,10 +110,33 @@
</exclusions>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-launcher</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<artifactId>maven-enforcer-plugin</artifactId>

<executions>
<execution>
<id>enforce-test-deps-junit-scope</id>
<configuration>
<skip>true</skip>
</configuration>
<goals>
<goal>enforce</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@

import java.io.StringWriter;
import java.io.Writer;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.function.Predicate;

import io.quarkus.bootstrap.BootstrapDebug;
import io.quarkus.bootstrap.classloading.ClassPathElement;
import io.quarkus.bootstrap.classloading.QuarkusClassLoader;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.builditem.GeneratedClassBuildItem;
import io.quarkus.gizmo.ClassOutput;
Expand All @@ -33,6 +37,18 @@ public GeneratedClassGizmoAdaptor(BuildProducer<GeneratedClassBuildItem> generat
this.sources = BootstrapDebug.DEBUG_SOURCES_DIR != null ? new ConcurrentHashMap<>() : null;
}

public GeneratedClassGizmoAdaptor(BuildProducer<GeneratedClassBuildItem> generatedClasses,
Function<String, String> generatedToBaseNameFunction) {
this.generatedClasses = generatedClasses;
this.applicationClassPredicate = new Predicate<String>() {
@Override
public boolean test(String s) {
return isApplicationClass(generatedToBaseNameFunction.apply(s));
}
};
this.sources = BootstrapDebug.DEBUG_SOURCES_DIR != null ? new ConcurrentHashMap<>() : null;
}

@Override
public void write(String className, byte[] bytes) {
String source = null;
Expand All @@ -56,4 +72,13 @@ public Writer getSourceWriter(String className) {
return ClassOutput.super.getSourceWriter(className);
}

public static boolean isApplicationClass(String className) {
QuarkusClassLoader cl = (QuarkusClassLoader) Thread.currentThread()
.getContextClassLoader();
//if the class file is present in this (and not the parent) CL then it is an application class
List<ClassPathElement> res = cl
.getElementsWithResource(className.replace(".", "/") + ".class", true);
return !res.isEmpty();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ public class QuarkusAugmentor {
private final String baseName;
private final Consumer<ConfigBuilder> configCustomizer;
private final boolean rebuild;
private final boolean auxiliaryApplication;

QuarkusAugmentor(Builder builder) {
this.classLoader = builder.classLoader;
Expand All @@ -78,6 +79,7 @@ public class QuarkusAugmentor {
this.deploymentClassLoader = builder.deploymentClassLoader;
this.rebuild = builder.rebuild;
this.devModeType = builder.devModeType;
this.auxiliaryApplication = builder.auxiliaryApplication;
}

public BuildResult run() throws Exception {
Expand Down Expand Up @@ -141,7 +143,7 @@ public BuildResult run() throws Exception {
.produce(new ShutdownContextBuildItem())
.produce(new RawCommandLineArgumentsBuildItem())
.produce(new LaunchModeBuildItem(launchMode,
devModeType == null ? Optional.empty() : Optional.of(devModeType)))
devModeType == null ? Optional.empty() : Optional.of(devModeType), auxiliaryApplication))
.produce(new BuildSystemTargetBuildItem(targetDir, baseName, rebuild,
buildSystemProperties == null ? new Properties() : buildSystemProperties))
.produce(new DeploymentClassLoaderBuildItem(deploymentClassLoader))
Expand Down Expand Up @@ -196,6 +198,7 @@ public static final class Builder {
Consumer<ConfigBuilder> configCustomizer;
ClassLoader deploymentClassLoader;
DevModeType devModeType;
boolean auxiliaryApplication;

public Builder addBuildChainCustomizer(Consumer<BuildChainBuilder> customizer) {
this.buildChainCustomizers.add(customizer);
Expand All @@ -216,6 +219,11 @@ public Builder excludeFromIndexing(Collection<Path> excludedFromIndexing) {
return this;
}

public Builder setAuxiliaryApplication(boolean auxiliaryApplication) {
this.auxiliaryApplication = auxiliaryApplication;
return this;
}

public LaunchMode getLaunchMode() {
return launchMode;
}
Expand Down
108 changes: 107 additions & 1 deletion core/deployment/src/main/java/io/quarkus/deployment/TestConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,101 @@
/**
* This is used currently only to suppress warnings about unknown properties
* when the user supplies something like: -Dquarkus.test.profile=someProfile or -Dquarkus.test.native-image-profile=someProfile
*
* <p>
* TODO refactor code to actually use these values
*/
@ConfigRoot
public class TestConfig {

/**
* If continuous testing is enabled.
*
* The default value is 'paused', which will allow you to start testing
* from the console or the Dev UI, but will not run tests on startup.
*
* If this is set to 'enabled' then testing will start as soon as the
* application has started.
*
* If this is 'disabled' then continuous testing is not enabled, and can't
* be enabled without restarting the application.
*
*/
@ConfigItem(defaultValue = "paused")
public Mode continuousTesting;

/**
* If output from the running tests should be displayed in the console.
*/
@ConfigItem(defaultValue = "false")
public boolean displayTestOutput;

/**
* Tags that should be included for continuous testing.
*/
@ConfigItem
public Optional<List<String>> includeTags;

/**
* Tags that should be excluded by default with continuous testing.
*
* This is ignored if include-tags has been set.
*
* Defaults to 'slow'
*/
@ConfigItem(defaultValue = "slow")
public Optional<List<String>> excludeTags;

/**
* Tests that should be included for continuous testing. This is a regular expression.
*/
@ConfigItem
public Optional<String> includePattern;

/**
* Tests that should be excluded with continuous testing. This is a regular expression.
*
* This is ignored if include-pattern has been set.
*
*/
@ConfigItem
public Optional<String> excludePattern;
/**
* Disable the testing status/prompt message at the bottom of the console
* and log these messages to STDOUT instead.
*
* Use this option if your terminal does not support ANSI escape sequences.
*/
@ConfigItem(defaultValue = "false")
public boolean basicConsole;

/**
* Disable color in the testing status and prompt messages.
*
* Use this option if your terminal does not support color.
*/
@ConfigItem(defaultValue = "false")
public boolean disableColor;

/**
* If test results and status should be displayed in the console.
*
* If this is false results can still be viewed in the dev console.
*/
@ConfigItem(defaultValue = "true")
public boolean console;

/**
* Changes tests to use the 'flat' ClassPath used in Quarkus 1.x versions.
*
* This means all Quarkus and test classes are loaded in the same ClassLoader,
* however it means you cannot use continuous testing.
*
* Note that if you find this necessary for your application then you
* may also have problems running in development mode, which cannot use
* a flat class path.
*/
@ConfigItem(defaultValue = "false")
public boolean flatClassPath;
/**
* Duration to wait for the native image to built during testing
*/
Expand All @@ -35,6 +124,16 @@ public class TestConfig {
@ConfigItem
Profile profile;

/**
* Configures the hang detection in @QuarkusTest. If no activity happens (i.e. no test callbacks are called) over
* this period then QuarkusTest will dump all threads stack traces, to help diagnose a potential hang.
*
* Note that the initial timeout (before Quarkus has started) will only apply if provided by a system property, as
* it is not possible to read all config sources until Quarkus has booted.
*/
@ConfigItem(defaultValue = "10m")
Duration hangDetectionTimeout;

@ConfigGroup
public static class Profile {

Expand All @@ -53,4 +152,11 @@ public static class Profile {
@ConfigItem(defaultValue = "")
Optional<List<String>> tags;
}

public enum Mode {
PAUSED,
ENABLED,
DISABLED

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,12 @@ public final class LaunchModeBuildItem extends SimpleBuildItem {

private final Optional<DevModeType> devModeType;

public LaunchModeBuildItem(LaunchMode launchMode, Optional<DevModeType> devModeType) {
private final boolean auxiliaryApplication;

public LaunchModeBuildItem(LaunchMode launchMode, Optional<DevModeType> devModeType, boolean auxiliaryApplication) {
this.launchMode = launchMode;
this.devModeType = devModeType;
this.auxiliaryApplication = auxiliaryApplication;
}

public LaunchMode getLaunchMode() {
Expand All @@ -26,11 +29,21 @@ public LaunchMode getLaunchMode() {

/**
* The development mode type.
*
* <p>
* Note that even for NORMAL launch modes this could be generating an application for the local side of remote
* dev mode, so this may be set even for launch mode normal.
*/
public Optional<DevModeType> getDevModeType() {
return devModeType;
}

/**
* An Auxiliary Application is a second application running in the same JVM as a primary application.
* <p>
* Currently this is done to allow running tests in dev mode, while the main dev mode process continues to
* run.
*/
public boolean isAuxiliaryApplication() {
return auxiliaryApplication;
}
}

This file was deleted.

Loading

0 comments on commit 5fc1d79

Please sign in to comment.