diff --git a/.github/workflows/ci-actions-incremental.yml b/.github/workflows/ci-actions-incremental.yml index 52b1a2b0cdc40..89765207d8c9b 100644 --- a/.github/workflows/ci-actions-incremental.yml +++ b/.github/workflows/ci-actions-incremental.yml @@ -430,7 +430,7 @@ jobs: MAVEN_OPTS: -Xmx1g # Skip main in forks if: "needs.calculate-test-jobs.outputs.run_gradle == 'true' && (github.repository == 'quarkusio/quarkus' || !endsWith(github.ref, '/main'))" - timeout-minutes: 80 + timeout-minutes: 120 strategy: fail-fast: false matrix: diff --git a/build-parent/pom.xml b/build-parent/pom.xml index 6c61cac1a87be..89b2d95825de8 100644 --- a/build-parent/pom.xml +++ b/build-parent/pom.xml @@ -65,7 +65,7 @@ <!-- These 2 properties are used by CreateProjectMojo to add the Maven Wrapper --> <proposed-maven-version>3.8.8</proposed-maven-version> <maven-wrapper.version>3.2.0</maven-wrapper.version> - <gradle-wrapper.version>8.0.2</gradle-wrapper.version> + <gradle-wrapper.version>8.1</gradle-wrapper.version> <quarkus-gradle-plugin.version>${project.version}</quarkus-gradle-plugin.version> <quarkus-maven-plugin.version>${project.version}</quarkus-maven-plugin.version> <maven-plugin-plugin.version>3.8.1</maven-plugin-plugin.version> diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/QuarkusPlugin.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/QuarkusPlugin.java index 55348a663780c..1e6faad216359 100644 --- a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/QuarkusPlugin.java +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/QuarkusPlugin.java @@ -233,9 +233,8 @@ public boolean isSatisfiedBy(Task t) { tasks.register(BUILD_NATIVE_TASK_NAME, DefaultTask.class, task -> { task.finalizedBy(quarkusBuild); - task.doFirst(t -> project.getLogger() + task.doFirst(t -> t.getLogger() .warn("The 'buildNative' task has been deprecated in favor of 'build -Dquarkus.package.type=native'")); - }); configureBuildNativeTask(project); diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusTask.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusTask.java index a67a3a8a40052..1fc804e23f468 100644 --- a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusTask.java +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusTask.java @@ -62,6 +62,11 @@ private void configureProcessWorkerSpec(ProcessWorkerSpec processWorkerSpec, Map customizations.forEach(a -> a.execute(forkOptions)); + String quarkusWorkerMaxHeap = System.getProperty("quarkus.gradle-worker.max-heap"); + if (quarkusWorkerMaxHeap != null && forkOptions.getAllJvmArgs().stream().noneMatch(arg -> arg.startsWith("-Xmx"))) { + forkOptions.jvmArgs("-Xmx" + quarkusWorkerMaxHeap); + } + // Pass all environment variables forkOptions.environment(System.getenv()); diff --git a/devtools/gradle/gradle/wrapper/gradle-wrapper.properties b/devtools/gradle/gradle/wrapper/gradle-wrapper.properties index a2efda8857b1b..d5e7b2ac5e417 100644 --- a/devtools/gradle/gradle/wrapper/gradle-wrapper.properties +++ b/devtools/gradle/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,8 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionSha256Sum=47a5bfed9ef814f90f8debcbbb315e8e7c654109acd224595ea39fca95c5d4da -distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-all.zip +# https://gradle.org/release-checksums/ +distributionSha256Sum=2cbafcd2c47a101cb2165f636b4677fac0b954949c9429c1c988da399defe6a9 +distributionUrl=https\://services.gradle.org/distributions/gradle-8.1-all.zip networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/independent-projects/bootstrap/pom.xml b/independent-projects/bootstrap/pom.xml index df396f74b67b2..e1f3a0173fe29 100644 --- a/independent-projects/bootstrap/pom.xml +++ b/independent-projects/bootstrap/pom.xml @@ -77,7 +77,7 @@ <plexus-utils.version>3.5.1</plexus-utils.version> <smallrye-common.version>2.1.0</smallrye-common.version> <smallrye-beanbag.version>1.1.0</smallrye-beanbag.version> - <gradle-tooling.version>8.0.2</gradle-tooling.version> + <gradle-tooling.version>8.1</gradle-tooling.version> <quarkus-fs-util.version>0.0.9</quarkus-fs-util.version> <org-crac.version>0.1.3</org-crac.version> <formatter-maven-plugin.version>2.22.0</formatter-maven-plugin.version> diff --git a/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus/tooling/gradle-wrapper/codestart.yml b/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus/tooling/gradle-wrapper/codestart.yml index 031e2b3791c9d..e67e0bfd333e5 100644 --- a/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus/tooling/gradle-wrapper/codestart.yml +++ b/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus/tooling/gradle-wrapper/codestart.yml @@ -6,7 +6,7 @@ language: base: data: gradle: - version: 8.0.2 + version: 8.1 shared-data: buildtool: cli: ./gradlew diff --git a/independent-projects/tools/devtools-testing/src/main/resources/fake-catalog.json b/independent-projects/tools/devtools-testing/src/main/resources/fake-catalog.json index a5f64b1b928e7..97e6b6045cc25 100644 --- a/independent-projects/tools/devtools-testing/src/main/resources/fake-catalog.json +++ b/independent-projects/tools/devtools-testing/src/main/resources/fake-catalog.json @@ -388,7 +388,7 @@ "supported-maven-versions": "[3.6.2,)", "proposed-maven-version": "3.8.8", "maven-wrapper-version": "3.2.0", - "gradle-wrapper-version": "8.0.2" + "gradle-wrapper-version": "8.1" } }, "codestarts-artifacts": [ diff --git a/independent-projects/tools/pom.xml b/independent-projects/tools/pom.xml index 74c66ebe8ae7a..003456dfc2dec 100644 --- a/independent-projects/tools/pom.xml +++ b/independent-projects/tools/pom.xml @@ -42,7 +42,7 @@ <!-- These properties are used by CreateProjectMojo to add the Maven Wrapper --> <proposed-maven-version>3.8.8</proposed-maven-version> <maven-wrapper.version>3.2.0</maven-wrapper.version> - <gradle-wrapper.version>8.0.2</gradle-wrapper.version> + <gradle-wrapper.version>8.1</gradle-wrapper.version> <!-- These properties are needed in order for them to be resolvable by the generated projects --> <!-- Quarkus uses jboss-parent and it comes with 3.8.1-jboss-1, we don't want that in the templates --> diff --git a/integration-tests/gradle/gradle.properties b/integration-tests/gradle/gradle.properties index e35f70a5669de..cfd6f9d25cf4f 100644 --- a/integration-tests/gradle/gradle.properties +++ b/integration-tests/gradle/gradle.properties @@ -1,4 +1,12 @@ version=999-SNAPSHOT # Idle timeout in milliseconds -org.gradle.daemon.idletimeout=10000 +systemProp.org.gradle.daemon.idletimeout=10000 + +# Override Java default heap size calculation for the Quarkus Gradle plugin worker processes, important for CI +systemProp.quarkus.gradle-worker.max-heap=256m + +# Use the miniumu Java heap sizes for a Gradle daemon, important for CI +org.gradle.jvmargs=-Xms128m -Xmx256m \ + -Dfile.encoding=UTF-8 \ + -Duser.language=en -Duser.country=US -Duser.variant= diff --git a/integration-tests/gradle/gradle/wrapper/gradle-wrapper.properties b/integration-tests/gradle/gradle/wrapper/gradle-wrapper.properties index a2efda8857b1b..d5e7b2ac5e417 100644 --- a/integration-tests/gradle/gradle/wrapper/gradle-wrapper.properties +++ b/integration-tests/gradle/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,8 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionSha256Sum=47a5bfed9ef814f90f8debcbbb315e8e7c654109acd224595ea39fca95c5d4da -distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-all.zip +# https://gradle.org/release-checksums/ +distributionSha256Sum=2cbafcd2c47a101cb2165f636b4677fac0b954949c9429c1c988da399defe6a9 +distributionUrl=https\://services.gradle.org/distributions/gradle-8.1-all.zip networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/integration-tests/gradle/pom.xml b/integration-tests/gradle/pom.xml index 40d9246502ea7..c85a73c94a045 100644 --- a/integration-tests/gradle/pom.xml +++ b/integration-tests/gradle/pom.xml @@ -430,13 +430,13 @@ </build> </profile> <profile> - <id>jdk19-workarounds</id> + <id>jdk20-workarounds</id> <activation> - <jdk>[19,)</jdk> + <jdk>[20,)</jdk> </activation> <properties> <!-- auto-exclude tests that are known to fail on JDK 19 or above (tagged via @Tag("failsOnJDK19")) --> - <excludedGroups>failsOnJDK19</excludedGroups> + <excludedGroups>failsOnJDK20</excludedGroups> </properties> </profile> </profiles> diff --git a/integration-tests/gradle/src/main/resources/test-resources-vs-main-resources/src/main/java/org/acme/Config.java b/integration-tests/gradle/src/main/resources/test-resources-vs-main-resources/src/main/java/org/acme/Config.java index f92b0bd9b98a3..9e6b13f177070 100644 --- a/integration-tests/gradle/src/main/resources/test-resources-vs-main-resources/src/main/java/org/acme/Config.java +++ b/integration-tests/gradle/src/main/resources/test-resources-vs-main-resources/src/main/java/org/acme/Config.java @@ -1,8 +1,8 @@ package org.acme; -import io.quarkus.arc.config.ConfigProperties; +import io.smallrye.config.ConfigMapping; -@ConfigProperties(prefix = "example") -public class Config { - public String message; +@ConfigMapping(prefix = "example") +public interface Config { + public String message(); } diff --git a/integration-tests/gradle/src/main/resources/test-resources-vs-main-resources/src/main/java/org/acme/ExampleResource.java b/integration-tests/gradle/src/main/resources/test-resources-vs-main-resources/src/main/java/org/acme/ExampleResource.java index 35f468a85b354..a17aa6569ab7c 100644 --- a/integration-tests/gradle/src/main/resources/test-resources-vs-main-resources/src/main/java/org/acme/ExampleResource.java +++ b/integration-tests/gradle/src/main/resources/test-resources-vs-main-resources/src/main/java/org/acme/ExampleResource.java @@ -15,6 +15,6 @@ public class ExampleResource { @GET @Produces(MediaType.TEXT_PLAIN) public String hello() { - return config.message; + return config.message(); } } diff --git a/integration-tests/gradle/src/test/java/io/quarkus/gradle/BuildConfigurationTest.java b/integration-tests/gradle/src/test/java/io/quarkus/gradle/BuildConfigurationTest.java index 67c522460b6e8..22022d77024fd 100644 --- a/integration-tests/gradle/src/test/java/io/quarkus/gradle/BuildConfigurationTest.java +++ b/integration-tests/gradle/src/test/java/io/quarkus/gradle/BuildConfigurationTest.java @@ -101,8 +101,6 @@ void verify(String packageType) { private void verifyBuild(String override) throws IOException, InterruptedException, URISyntaxException { File rootDir = getProjectDir(ROOT_PROJECT_NAME); BuildResult buildResult = runGradleWrapper(rootDir, "clean", "quarkusBuild", - // Package type is not included in the Gradle cache inputs, see https://github.com/quarkusio/quarkus/issues/30852 - "--no-build-cache", override != null ? "-Dquarkus.package.type=" + override : "-Dfoo=bar"); soft.assertThat(buildResult.unsuccessfulTasks()).isEmpty(); diff --git a/integration-tests/gradle/src/test/java/io/quarkus/gradle/FastJarFormatWorksTest.java b/integration-tests/gradle/src/test/java/io/quarkus/gradle/FastJarFormatWorksTest.java index 030a181377fa4..6bfa73d0c71a2 100644 --- a/integration-tests/gradle/src/test/java/io/quarkus/gradle/FastJarFormatWorksTest.java +++ b/integration-tests/gradle/src/test/java/io/quarkus/gradle/FastJarFormatWorksTest.java @@ -22,9 +22,7 @@ public void testFastJarFormatWorks() throws Exception { final File projectDir = getProjectDir("test-that-fast-jar-format-works"); - BuildResult result = runGradleWrapper(projectDir, "clean", "build"); - result.getTasks().forEach((k, v) -> System.err.println(" " + k + " --> " + v)); - System.err.println(result.getOutput()); + runGradleWrapper(projectDir, "clean", "build"); final Path quarkusApp = projectDir.toPath().resolve("build").resolve("quarkus-app"); assertThat(quarkusApp).exists(); diff --git a/integration-tests/gradle/src/test/java/io/quarkus/gradle/GrpcMultiModuleQuarkusBuildTest.java b/integration-tests/gradle/src/test/java/io/quarkus/gradle/GrpcMultiModuleQuarkusBuildTest.java index 4ae97ee4a8c22..f19fdb2324152 100644 --- a/integration-tests/gradle/src/test/java/io/quarkus/gradle/GrpcMultiModuleQuarkusBuildTest.java +++ b/integration-tests/gradle/src/test/java/io/quarkus/gradle/GrpcMultiModuleQuarkusBuildTest.java @@ -35,7 +35,7 @@ public void testProtocErrorOutput() throws Exception { final Path protoDirectory = new File(projectDir, "application/src/main/proto/").toPath(); Files.copy(projectDir.toPath().resolve("invalid.proto"), protoDirectory.resolve("invalid.proto")); try { - final BuildResult buildResult = runGradleWrapper(projectDir, ":application:quarkusBuild", "--info"); + final BuildResult buildResult = runGradleWrapper(true, projectDir, ":application:quarkusBuild", "--info"); assertTrue(buildResult.getOutput().contains("invalid.proto:5:1: Missing field number.")); } finally { Files.delete(protoDirectory.resolve("invalid.proto")); diff --git a/integration-tests/gradle/src/test/java/io/quarkus/gradle/JandexMultiModuleTest.java b/integration-tests/gradle/src/test/java/io/quarkus/gradle/JandexMultiModuleTest.java index 5fd64106e7697..20f71b1d5f9ba 100644 --- a/integration-tests/gradle/src/test/java/io/quarkus/gradle/JandexMultiModuleTest.java +++ b/integration-tests/gradle/src/test/java/io/quarkus/gradle/JandexMultiModuleTest.java @@ -10,11 +10,14 @@ public class JandexMultiModuleTest extends QuarkusGradleWrapperTestBase { @Test public void testBasicMultiModuleBuildKordamp() throws Exception { + // Kordamp Jandex plugin's not compatible w/ the Gradle configuration cache + gradleConfigurationCache(false); jandexTest("jandex-basic-multi-module-project-kordamp", ":common:jandex"); } @Test public void testBasicMultiModuleBuildJandex() throws Exception { + gradleConfigurationCache(true); jandexTest("jandex-basic-multi-module-project-vlsi", ":common:processJandexIndex"); } diff --git a/integration-tests/gradle/src/test/java/io/quarkus/gradle/QuarkusGradleDevToolsTestBase.java b/integration-tests/gradle/src/test/java/io/quarkus/gradle/QuarkusGradleDevToolsTestBase.java index 7a51b0246a45c..cfd18d149b450 100644 --- a/integration-tests/gradle/src/test/java/io/quarkus/gradle/QuarkusGradleDevToolsTestBase.java +++ b/integration-tests/gradle/src/test/java/io/quarkus/gradle/QuarkusGradleDevToolsTestBase.java @@ -29,6 +29,7 @@ static void disableDevToolsTestConfig() { @Override protected void setupTestCommand() { + gradleNoWatchFs(false); for (Map.Entry<?, ?> prop : devToolsProps.entrySet()) { setSystemProperty(prop.getKey().toString(), prop.getValue().toString()); } diff --git a/integration-tests/gradle/src/test/java/io/quarkus/gradle/QuarkusGradleWrapperTestBase.java b/integration-tests/gradle/src/test/java/io/quarkus/gradle/QuarkusGradleWrapperTestBase.java index 612d7f498e5ed..3ac4c82177109 100644 --- a/integration-tests/gradle/src/test/java/io/quarkus/gradle/QuarkusGradleWrapperTestBase.java +++ b/integration-tests/gradle/src/test/java/io/quarkus/gradle/QuarkusGradleWrapperTestBase.java @@ -11,6 +11,8 @@ import java.util.Map; import java.util.concurrent.TimeUnit; +import org.assertj.core.api.Assertions; + public class QuarkusGradleWrapperTestBase extends QuarkusGradleTestBase { private static final String GRADLE_WRAPPER_WINDOWS = "gradlew.bat"; @@ -19,19 +21,47 @@ public class QuarkusGradleWrapperTestBase extends QuarkusGradleTestBase { private Map<String, String> systemProps; + private boolean configurationCacheEnable = true; + private boolean noWatchFs = true; + protected void setupTestCommand() { } + /** + * Gradle's configuration cache is enabled by default for all tests. This option can be used to disable the + * configuration test. + */ + protected void gradleConfigurationCache(boolean configurationCacheEnable) { + this.configurationCacheEnable = configurationCacheEnable; + } + + /** + * Gradle is run by default with {@code --no-watch-fs} to reduce I/O load during tests. Some tests might run into issues + * with this option. + */ + protected void gradleNoWatchFs(boolean noWatchFs) { + this.noWatchFs = noWatchFs; + } + public BuildResult runGradleWrapper(File projectDir, String... args) throws IOException, InterruptedException { + return runGradleWrapper(false, projectDir, args); + } + + public BuildResult runGradleWrapper(boolean expectError, File projectDir, String... args) + throws IOException, InterruptedException { setupTestCommand(); List<String> command = new ArrayList<>(); command.add(getGradleWrapperCommand()); addSystemProperties(command); command.add("-Dorg.gradle.console=plain"); - command.add("-Dorg.gradle.daemon=false"); - command.add("--configuration-cache"); + if (configurationCacheEnable) { + command.add("--configuration-cache"); + } command.add("--stacktrace"); + if (noWatchFs) { + command.add("--no-watch-fs"); + } command.add("--info"); command.add("--daemon"); command.addAll(Arrays.asList(args)); @@ -39,13 +69,18 @@ public BuildResult runGradleWrapper(File projectDir, String... args) throws IOEx File logOutput = new File(projectDir, "command-output.log"); System.out.println("$ " + String.join(" ", command)); - Process p = new ProcessBuilder() + ProcessBuilder pb = new ProcessBuilder() .directory(projectDir) .command(command) .redirectInput(ProcessBuilder.Redirect.INHERIT) - .redirectError(logOutput) .redirectOutput(logOutput) - .start(); + // Should prevent "fragmented" output (parts of stdout and stderr interleaved) + .redirectErrorStream(true); + if (System.getenv("JAVA_HOME") == null) { + // This helps running the tests in IntelliJ w/o configuring an explicit JAVA_HOME env var. + pb.environment().put("JAVA_HOME", System.getProperty("java.home")); + } + Process p = pb.start(); //long timeout for native tests //that may also need to download docker @@ -55,8 +90,14 @@ public BuildResult runGradleWrapper(File projectDir, String... args) throws IOEx } final BuildResult commandResult = BuildResult.of(logOutput); int exitCode = p.exitValue(); - if (exitCode != 0) { + + // The test failed, if the Gradle build exits with != 0 and the tests expects no failure, or if the test + // expects a failure and the exit code is 0. + if (expectError == (exitCode == 0)) { + // Only print the output, if the test does not expect a failure. printCommandOutput(projectDir, command, commandResult, exitCode); + // Fail hard, if the test does not expect a failure. + Assertions.fail("Gradle build failed with exit code %d", exitCode); } return commandResult; } diff --git a/integration-tests/gradle/src/test/java/io/quarkus/gradle/devmode/BasicKotlinApplicationModuleDevModeTest.java b/integration-tests/gradle/src/test/java/io/quarkus/gradle/devmode/BasicKotlinApplicationModuleDevModeTest.java index 56c7dafba0f16..80d3ca4602e14 100644 --- a/integration-tests/gradle/src/test/java/io/quarkus/gradle/devmode/BasicKotlinApplicationModuleDevModeTest.java +++ b/integration-tests/gradle/src/test/java/io/quarkus/gradle/devmode/BasicKotlinApplicationModuleDevModeTest.java @@ -6,7 +6,7 @@ import com.google.common.collect.ImmutableMap; -@org.junit.jupiter.api.Tag("failsOnJDK19") +@org.junit.jupiter.api.Tag("failsOnJDK20") public class BasicKotlinApplicationModuleDevModeTest extends QuarkusDevGradleTestBase { @Override diff --git a/integration-tests/gradle/src/test/java/io/quarkus/gradle/devmode/MultiModuleKotlinProjectDevModeTest.java b/integration-tests/gradle/src/test/java/io/quarkus/gradle/devmode/MultiModuleKotlinProjectDevModeTest.java index a59a5d9c0004c..a86c659a42380 100644 --- a/integration-tests/gradle/src/test/java/io/quarkus/gradle/devmode/MultiModuleKotlinProjectDevModeTest.java +++ b/integration-tests/gradle/src/test/java/io/quarkus/gradle/devmode/MultiModuleKotlinProjectDevModeTest.java @@ -2,12 +2,9 @@ import static org.assertj.core.api.Assertions.assertThat; -import org.junit.jupiter.api.Disabled; - import com.google.common.collect.ImmutableMap; -@Disabled -@org.junit.jupiter.api.Tag("failsOnJDK19") +@org.junit.jupiter.api.Tag("failsOnJDK20") public class MultiModuleKotlinProjectDevModeTest extends QuarkusDevGradleTestBase { @Override diff --git a/integration-tests/gradle/src/test/java/io/quarkus/gradle/devmode/MultiSourceProjectDevModeTest.java b/integration-tests/gradle/src/test/java/io/quarkus/gradle/devmode/MultiSourceProjectDevModeTest.java index f9420d4a123c7..2ed7b5895a71d 100644 --- a/integration-tests/gradle/src/test/java/io/quarkus/gradle/devmode/MultiSourceProjectDevModeTest.java +++ b/integration-tests/gradle/src/test/java/io/quarkus/gradle/devmode/MultiSourceProjectDevModeTest.java @@ -4,7 +4,7 @@ import com.google.common.collect.ImmutableMap; -@org.junit.jupiter.api.Tag("failsOnJDK19") +@org.junit.jupiter.api.Tag("failsOnJDK20") public class MultiSourceProjectDevModeTest extends QuarkusDevGradleTestBase { @Override diff --git a/integration-tests/gradle/src/test/java/io/quarkus/gradle/devmode/QuarkusDevGradleTestBase.java b/integration-tests/gradle/src/test/java/io/quarkus/gradle/devmode/QuarkusDevGradleTestBase.java index fc62794cebc42..2602e2e0e502f 100644 --- a/integration-tests/gradle/src/test/java/io/quarkus/gradle/devmode/QuarkusDevGradleTestBase.java +++ b/integration-tests/gradle/src/test/java/io/quarkus/gradle/devmode/QuarkusDevGradleTestBase.java @@ -7,6 +7,9 @@ import java.io.File; import java.io.IOException; import java.nio.file.Files; +import java.util.Collections; +import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -14,6 +17,8 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Supplier; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.apache.commons.io.FileUtils; import org.junit.jupiter.api.Test; @@ -27,24 +32,56 @@ public abstract class QuarkusDevGradleTestBase extends QuarkusGradleWrapperTestB private Future<?> quarkusDev; protected File projectDir; + @Override + protected void setupTestCommand() { + gradleNoWatchFs(false); + } + @Test public void main() throws Exception { - projectDir = getProjectDir(); beforeQuarkusDev(); ExecutorService executor = null; AtomicReference<BuildResult> buildResult = new AtomicReference<>(); + List<String> processesBeforeTest = dumpProcesses(); + List<String> processesAfterTest = Collections.emptyList(); try { - executor = Executors.newSingleThreadExecutor(); - quarkusDev = executor.submit(() -> { - try { - buildResult.set(build()); - } catch (Exception e) { - throw new IllegalStateException("Failed to build the project", e); + try { + executor = Executors.newSingleThreadExecutor(); + quarkusDev = executor.submit(() -> { + try { + buildResult.set(build()); + } catch (Exception e) { + throw new IllegalStateException("Failed to build the project", e); + } + }); + testDevMode(); + } finally { + processesAfterTest = dumpProcesses(); + + if (quarkusDev != null) { + quarkusDev.cancel(true); } - }); - testDevMode(); + if (executor != null) { + executor.shutdownNow(); + } + + // Kill all processes that were (indirectly) spawned by the current process. + List<ProcessHandle> childProcesses = DevModeTestUtils.killDescendingProcesses(); + + DevModeTestUtils.awaitUntilServerDown(); + + // sanity: forcefully terminate left-over processes + childProcesses.forEach(ProcessHandle::destroyForcibly); + } } catch (Exception | AssertionError e) { + System.err.println("PROCESSES BEFORE TEST:"); + processesBeforeTest.forEach(System.err::println); + System.err.println("PROCESSES AFTER TEST (BEFORE CLEANUP):"); + processesAfterTest.forEach(System.err::println); + System.err.println("PROCESSES AFTER CLEANUP:"); + dumpProcesses().forEach(System.err::println); + if (buildResult.get() != null) { System.err.println("BELOW IS THE CAPTURED LOGGING OF THE FAILED GRADLE TEST PROJECT BUILD"); System.err.println(buildResult.get().getOutput()); @@ -65,24 +102,25 @@ public void main() throws Exception { } throw e; } finally { - if (quarkusDev != null) { - quarkusDev.cancel(true); - } - if (executor != null) { - executor.shutdownNow(); - } - - // Kill all processes that were (indirectly) spawned by the current process. - DevModeTestUtils.killDescendingProcesses(); - - DevModeTestUtils.awaitUntilServerDown(); - if (projectDir != null && projectDir.isDirectory()) { FileUtils.deleteQuietly(projectDir); } } } + public static List<String> dumpProcesses() { + // ProcessHandle.Info.command()/arguments()/commandLine() are always empty on Windows: + // https://bugs.openjdk.java.net/browse/JDK-8176725 + ProcessHandle current = ProcessHandle.current(); + return Stream.concat(Stream.of(current), current.descendants()).map(p -> { + ProcessHandle.Info i = p.info(); + return String.format("PID %8d (%8d) started:%s CPU:%s - %s", p.pid(), + p.parent().map(ProcessHandle::pid).orElse(-1L), + i.startInstant().orElse(null), i.totalCpuDuration().orElse(null), + i.commandLine().orElse("<command line not available>")); + }).collect(Collectors.toList()); + } + protected BuildResult build() throws Exception { return runGradleWrapper(projectDir, buildArguments()); } @@ -117,7 +155,7 @@ protected String getHttpResponse() { } protected String getHttpResponse(String path) { - return getHttpResponse(path, 1, TimeUnit.MINUTES); + return getHttpResponse(path, devModeTimeoutSeconds(), TimeUnit.SECONDS); } protected String getHttpResponse(String path, long timeout, TimeUnit tu) { @@ -141,7 +179,16 @@ protected void replace(String srcFile, Map<String, String> tokens) { } protected void assertUpdatedResponseContains(String path, String value) { - assertUpdatedResponseContains(path, value, 1, TimeUnit.MINUTES); + assertUpdatedResponseContains(path, value, devModeTimeoutSeconds(), TimeUnit.SECONDS); + } + + protected int devModeTimeoutSeconds() { + // It's a wild guess, but maybe Windows is just slower - at least: a successful Gradle-CI-jobs on Windows is + // 2.5x slower than the same Gradle-CI-job on Linux. + if (System.getProperty("os.name").toLowerCase(Locale.ENGLISH).contains("windows")) { + return 90; + } + return 60; } protected void assertUpdatedResponseContains(String path, String value, long waitAtMost, TimeUnit timeUnit) { diff --git a/integration-tests/gradle/src/test/java/io/quarkus/gradle/extension/ExtensionUnitTestTest.java b/integration-tests/gradle/src/test/java/io/quarkus/gradle/extension/ExtensionUnitTestTest.java index e664f3ffe50db..946138dc2cb3d 100644 --- a/integration-tests/gradle/src/test/java/io/quarkus/gradle/extension/ExtensionUnitTestTest.java +++ b/integration-tests/gradle/src/test/java/io/quarkus/gradle/extension/ExtensionUnitTestTest.java @@ -13,6 +13,8 @@ public class ExtensionUnitTestTest extends QuarkusGradleWrapperTestBase { @Test public void shouldRunTestWithSuccess() throws Exception { + gradleConfigurationCache(false); + File projectDir = getProjectDir("extensions/simple-extension"); BuildResult buildResult = runGradleWrapper(projectDir, "clean", ":deployment:test", "--no-build-cache"); diff --git a/test-framework/devmode-test-utils/src/main/java/io/quarkus/test/devmode/util/DevModeTestUtils.java b/test-framework/devmode-test-utils/src/main/java/io/quarkus/test/devmode/util/DevModeTestUtils.java index 55c7315b2ea2b..22c21602bc37d 100644 --- a/test-framework/devmode-test-utils/src/main/java/io/quarkus/test/devmode/util/DevModeTestUtils.java +++ b/test-framework/devmode-test-utils/src/main/java/io/quarkus/test/devmode/util/DevModeTestUtils.java @@ -9,25 +9,36 @@ import java.net.URL; import java.nio.charset.StandardCharsets; import java.time.Instant; +import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Supplier; +import java.util.stream.Collectors; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; public class DevModeTestUtils { - public static void killDescendingProcesses() { + public static List<ProcessHandle> killDescendingProcesses() { // Warning: Do not try to evaluate ProcessHandle.Info.arguments() or .commandLine() as those are always empty on Windows: // https://bugs.openjdk.java.net/browse/JDK-8176725 - ProcessHandle.current().descendants() + // + // Intentionally collecting the ProcessHandles before calling .destroy(), because it seemed that, at least on + // Windows, not all processes were properly killed, leaving (some) processes around, causing following dev-mode + // tests to time-out. + List<ProcessHandle> childProcesses = ProcessHandle.current().descendants() // destroy younger descendants first .sorted((ph1, ph2) -> ph2.info().startInstant().orElse(Instant.EPOCH) .compareTo(ph1.info().startInstant().orElse(Instant.EPOCH))) - .forEach(ProcessHandle::destroy); + .collect(Collectors.toList()); + + childProcesses.forEach(ProcessHandle::destroy); + + // Returning all child processes for callers that want to do a "kill -9" + return childProcesses; } public static void filter(File input, Map<String, String> variables) throws IOException {