Skip to content

Commit

Permalink
Gradle integration tests: Try to tackle CI issues
Browse files Browse the repository at this point in the history
... by limiting the Java heap and adding some constant properties.

* Also ensures that tests assert on the `gradlew` exit-code (they never did).
* Also adopt the test-project for `TestResourcesVsMainResourcesTest` to "recent Quarkus versions", see
  `integration-tests/gradle/src/main/resources/test-resources-vs-main-resources/src/main/java/org/acme/Config.java`
  (was still using `io.quarkus.arc.config.ConfigProperties`, failed to compile, but the IT didn't fail.)
* Also add a programmatic way to disable some Gradle features
* Allow configuring the max heap size for the Quarkus Gradle worker processes for integration tests
  • Loading branch information
snazy committed Apr 13, 2023
1 parent 5a0c9ce commit 2e8fe40
Show file tree
Hide file tree
Showing 11 changed files with 136 additions and 41 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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());

Expand Down
9 changes: 8 additions & 1 deletion integration-tests/gradle/gradle.properties
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
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 Quarkus CI
systemProp.quarkus.gradle-worker.max-heap=256m

org.gradle.jvmargs=-Xms128m -Xmx256m \
-Dfile.encoding=UTF-8 \
-Duser.language=en -Duser.country=US -Duser.variant=
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;

import io.quarkus.devtools.testing.RegistryClientTestHelper;

Expand All @@ -27,6 +28,11 @@ static void disableDevToolsTestConfig() {
RegistryClientTestHelper.disableRegistryClientTestConfig();
}

@BeforeEach
void setupGradle() {
gradleNoWatchFs(false);
}

@Override
protected void setupTestCommand() {
for (Map.Entry<?, ?> prop : devToolsProps.entrySet()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -19,44 +21,73 @@ public class QuarkusGradleWrapperTestBase extends QuarkusGradleTestBase {

private Map<String, String> systemProps;

private boolean configurationCacheEnable = true;
private boolean noWatchFs = true;

protected void setupTestCommand() {

}

protected void gradleConfigurationCache(boolean configurationCacheEnable) {
this.configurationCacheEnable = configurationCacheEnable;
}

/** Limit I/O load for (most) tests. */
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));

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
boolean done = p.waitFor(10, TimeUnit.MINUTES);
if (!done) {
destroyProcess(p);
}
final BuildResult commandResult = BuildResult.of(logOutput);
int exitCode = p.exitValue();
if (exitCode != 0) {
final BuildResult commandResult = BuildResult.of(logOutput);
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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,20 @@
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
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.BeforeEach;
import org.junit.jupiter.api.Test;

import io.quarkus.gradle.BuildResult;
Expand All @@ -27,24 +32,61 @@ public abstract class QuarkusDevGradleTestBase extends QuarkusGradleWrapperTestB
private Future<?> quarkusDev;
protected File projectDir;

@BeforeEach
void setupGradle() {
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);
}
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);

if (projectDir != null && projectDir.isDirectory()) {
FileUtils.deleteQuietly(projectDir);
}
});
testDevMode();
}
} 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());
Expand All @@ -64,25 +106,22 @@ 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());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,31 @@
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()
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);

return childProcesses;
}

public static void filter(File input, Map<String, String> variables) throws IOException {
Expand Down

0 comments on commit 2e8fe40

Please sign in to comment.