From 136bb6ee87af5a86a12a88b0fe1595756807e80b Mon Sep 17 00:00:00 2001 From: Daniel Mohedano Date: Fri, 29 May 2026 12:50:25 +0200 Subject: [PATCH 1/7] fix: cleanup gradle launcher build daemon --- .../smoketest/GradleLauncherSmokeTest.java | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/dd-smoke-tests/gradle/src/test/java/datadog/smoketest/GradleLauncherSmokeTest.java b/dd-smoke-tests/gradle/src/test/java/datadog/smoketest/GradleLauncherSmokeTest.java index dc5c4435d2c..d59a531142f 100644 --- a/dd-smoke-tests/gradle/src/test/java/datadog/smoketest/GradleLauncherSmokeTest.java +++ b/dd-smoke-tests/gradle/src/test/java/datadog/smoketest/GradleLauncherSmokeTest.java @@ -7,6 +7,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.params.ParameterizedTest; import org.opentest4j.AssertionFailedError; @@ -23,6 +24,7 @@ class GradleLauncherSmokeTest extends AbstractGradleTest { private static final Logger LOGGER = LoggerFactory.getLogger(GradleLauncherSmokeTest.class); private static final int GRADLE_BUILD_TIMEOUT_MILLIS = 90_000; + private static final int GRADLE_STOP_TIMEOUT_MILLIS = 30_000; private static final int GRADLE_WRAPPER_RETRIES = 3; private static final String JAVA_HOME = buildJavaHome(); @@ -71,6 +73,29 @@ void testGradleLauncherInjectsTracerIntoGradleDaemon( cmdLineParams != null ? cmdLineParams : "-Duser.country=VALUE_FROM_GRADLE_PROPERTIES_FILE"); } + /** + * Stops the Gradle build daemon spawned by the launcher before JUnit deletes the {@link + * #gradleUserHome} temp directory. The launcher starts a single-use build daemon that writes into + * {@code $GRADLE_USER_HOME/daemon//}; if that process has not fully released its file + * handles by the time the {@code @TempDir} cleanup runs, the recursive delete fails with a {@code + * DirectoryNotEmptyException}. + */ + @AfterAll + void stopGradleBuildDaemon() { + Map env = new HashMap<>(); + env.put("JAVA_HOME", JAVA_HOME); + env.put("GRADLE_USER_HOME", gradleUserHome.toString()); + env.put("GRADLE_OPTS", ""); + ShellCommandExecutor shellCommandExecutor = + new ShellCommandExecutor(projectFolder.toFile(), GRADLE_STOP_TIMEOUT_MILLIS, env); + try { + shellCommandExecutor.executeCommand(IOUtils::readFully, "./gradlew", "--stop"); + } catch (Exception e) { + // Best-effort: a failure here should not fail the test run. + LOGGER.warn("Failed to stop Gradle daemon during cleanup", e); + } + } + private void givenGradleWrapper(String gradleVersion) throws Exception { Map env = new HashMap<>(); env.put("JAVA_HOME", JAVA_HOME); From c36724f7e907627e26b2ae2954098d56af3b7776 Mon Sep 17 00:00:00 2001 From: Daniel Mohedano Date: Fri, 29 May 2026 14:26:25 +0200 Subject: [PATCH 2/7] fix: move cleanup to aftereach --- .../smoketest/GradleLauncherSmokeTest.java | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/dd-smoke-tests/gradle/src/test/java/datadog/smoketest/GradleLauncherSmokeTest.java b/dd-smoke-tests/gradle/src/test/java/datadog/smoketest/GradleLauncherSmokeTest.java index d59a531142f..399a65f0543 100644 --- a/dd-smoke-tests/gradle/src/test/java/datadog/smoketest/GradleLauncherSmokeTest.java +++ b/dd-smoke-tests/gradle/src/test/java/datadog/smoketest/GradleLauncherSmokeTest.java @@ -2,12 +2,13 @@ import datadog.communication.util.IOUtils; import datadog.trace.civisibility.utils.ShellCommandExecutor; +import java.nio.file.Files; import java.nio.file.Path; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; -import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.params.ParameterizedTest; import org.opentest4j.AssertionFailedError; @@ -74,14 +75,25 @@ void testGradleLauncherInjectsTracerIntoGradleDaemon( } /** - * Stops the Gradle build daemon spawned by the launcher before JUnit deletes the {@link - * #gradleUserHome} temp directory. The launcher starts a single-use build daemon that writes into + * Stops the Gradle build daemon spawned by the launcher after each test. Even though the launcher + * is run with {@code --no-daemon}, Gradle still starts a single-use build daemon that writes into * {@code $GRADLE_USER_HOME/daemon//}; if that process has not fully released its file - * handles by the time the {@code @TempDir} cleanup runs, the recursive delete fails with a {@code - * DirectoryNotEmptyException}. + * handles by the time JUnit deletes the shared (static) {@link #gradleUserHome} temp directory at + * class teardown, the recursive delete fails with a {@code DirectoryNotEmptyException}. Stopping + * the daemon here releases those handles ahead of cleanup. + * + *

This runs in {@code @AfterEach} rather than {@code @AfterAll} on purpose: {@link + * #projectFolder} (which holds the {@code gradlew} script used as the working directory) is an + * instance {@code @TempDir}, so JUnit deletes it at the end of each test invocation. By the time + * an {@code @AfterAll} method would run, that working directory no longer exists and the {@code + * --stop} command could not be launched. */ - @AfterAll + @AfterEach void stopGradleBuildDaemon() { + if (!Files.exists(projectFolder.resolve("gradlew"))) { + // The test was skipped or failed before the wrapper was set up, so no daemon was started. + return; + } Map env = new HashMap<>(); env.put("JAVA_HOME", JAVA_HOME); env.put("GRADLE_USER_HOME", gradleUserHome.toString()); From ee9646da0ff83b00b1ba1dfd022430a72c32652a Mon Sep 17 00:00:00 2001 From: Daniel Mohedano Date: Fri, 29 May 2026 16:24:46 +0200 Subject: [PATCH 3/7] fix: bump smoke test memory --- .../java/datadog/trace/civisibility/CiVisibilitySmokeTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dd-java-agent/agent-ci-visibility/civisibility-test-fixtures/src/main/java/datadog/trace/civisibility/CiVisibilitySmokeTest.java b/dd-java-agent/agent-ci-visibility/civisibility-test-fixtures/src/main/java/datadog/trace/civisibility/CiVisibilitySmokeTest.java index 5481a87ad47..dcce5447295 100644 --- a/dd-java-agent/agent-ci-visibility/civisibility-test-fixtures/src/main/java/datadog/trace/civisibility/CiVisibilitySmokeTest.java +++ b/dd-java-agent/agent-ci-visibility/civisibility-test-fixtures/src/main/java/datadog/trace/civisibility/CiVisibilitySmokeTest.java @@ -100,7 +100,7 @@ private static Map buildJvmArgMap( protected List buildJvmArguments( String mockBackendIntakeUrl, String serviceName, Map additionalArgs) { - List arguments = new ArrayList<>(Arrays.asList("-Xms256m", "-Xmx256m")); + List arguments = new ArrayList<>(Arrays.asList("-Xms512m", "-Xmx512m")); arguments.add(preventJulPrefsFileLock()); From c1097df9cdb7edd15de5a1f298db4400c4067106 Mon Sep 17 00:00:00 2001 From: Daniel Mohedano Date: Fri, 29 May 2026 16:25:04 +0200 Subject: [PATCH 4/7] fix: introduce testkit closing in afterall --- .../datadog/smoketest/GradleDaemonSmokeTest.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/dd-smoke-tests/gradle/src/test/java/datadog/smoketest/GradleDaemonSmokeTest.java b/dd-smoke-tests/gradle/src/test/java/datadog/smoketest/GradleDaemonSmokeTest.java index 47cec74af8e..54bb2daea06 100644 --- a/dd-smoke-tests/gradle/src/test/java/datadog/smoketest/GradleDaemonSmokeTest.java +++ b/dd-smoke-tests/gradle/src/test/java/datadog/smoketest/GradleDaemonSmokeTest.java @@ -30,6 +30,7 @@ import org.gradle.wrapper.Install; import org.gradle.wrapper.PathAssembler; import org.gradle.wrapper.WrapperConfiguration; +import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.params.ParameterizedTest; @@ -46,6 +47,19 @@ class GradleDaemonSmokeTest extends AbstractGradleTest { @TempDir static Path testKitFolder; + /** + * Stops the Gradle TestKit daemons before JUnit deletes the shared (static) {@link + * #testKitFolder} at class teardown. + */ + @AfterAll + void stopGradleTestKitDaemons() { + try { + DefaultGradleConnector.close(); + } catch (Exception e) { + System.out.println("Failed to stop Gradle TestKit daemons during cleanup: " + e); + } + } + @TableTest({ "scenario | gradleVersion | projectName | successExpected | expectedTraces | expectedCoverages", "succeed-old-gradle-3.5 | 3.5 | test-succeed-old-gradle | true | 5 | 1 ", From 0d3642487f9d701f177986034655a80f3c699a35 Mon Sep 17 00:00:00 2001 From: Daniel Mohedano Date: Fri, 29 May 2026 16:47:48 +0200 Subject: [PATCH 5/7] nit: drop minimum heap size value [ci: NON_DEFAULT_JVMS] --- .../java/datadog/trace/civisibility/CiVisibilitySmokeTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dd-java-agent/agent-ci-visibility/civisibility-test-fixtures/src/main/java/datadog/trace/civisibility/CiVisibilitySmokeTest.java b/dd-java-agent/agent-ci-visibility/civisibility-test-fixtures/src/main/java/datadog/trace/civisibility/CiVisibilitySmokeTest.java index dcce5447295..859f95c917c 100644 --- a/dd-java-agent/agent-ci-visibility/civisibility-test-fixtures/src/main/java/datadog/trace/civisibility/CiVisibilitySmokeTest.java +++ b/dd-java-agent/agent-ci-visibility/civisibility-test-fixtures/src/main/java/datadog/trace/civisibility/CiVisibilitySmokeTest.java @@ -100,7 +100,7 @@ private static Map buildJvmArgMap( protected List buildJvmArguments( String mockBackendIntakeUrl, String serviceName, Map additionalArgs) { - List arguments = new ArrayList<>(Arrays.asList("-Xms512m", "-Xmx512m")); + List arguments = new ArrayList<>(Arrays.asList("-Xms256m", "-Xmx512m")); arguments.add(preventJulPrefsFileLock()); From 69f64a033a3681d444ddb4ed919dfb55c46a545e Mon Sep 17 00:00:00 2001 From: Daniel Mohedano Date: Fri, 29 May 2026 18:00:28 +0200 Subject: [PATCH 6/7] fix: harsher cleanup of pending gradle daemons [ci: NON_DEFAULTS_JVM] --- .../datadog/smoketest/AbstractGradleTest.java | 80 +++++++++++++++++++ .../smoketest/GradleDaemonSmokeTest.java | 13 +-- 2 files changed, 88 insertions(+), 5 deletions(-) diff --git a/dd-smoke-tests/gradle/src/test/java/datadog/smoketest/AbstractGradleTest.java b/dd-smoke-tests/gradle/src/test/java/datadog/smoketest/AbstractGradleTest.java index 7ba519bf935..9c69f8a80cd 100644 --- a/dd-smoke-tests/gradle/src/test/java/datadog/smoketest/AbstractGradleTest.java +++ b/dd-smoke-tests/gradle/src/test/java/datadog/smoketest/AbstractGradleTest.java @@ -13,10 +13,13 @@ import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; import java.util.Collections; +import java.util.Locale; import java.util.Map; import java.util.Properties; +import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Stream; import org.gradle.internal.impldep.org.apache.commons.io.FileUtils; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Assumptions; @@ -35,6 +38,11 @@ public abstract class AbstractGradleTest extends CiVisibilitySmokeTest { private static final ComparableVersion GRADLE_9 = new ComparableVersion("9.0.0"); + // Gradle daemons may keep file handles on their temp directory open for a short while after being + // stopped; retry a few times to give them a chance to release before giving up. + private static final int TEMP_DIR_CLEANUP_RETRIES = 10; + private static final long TEMP_DIR_CLEANUP_RETRY_DELAY_MILLIS = 200; + @TempDir protected Path projectFolder; protected final MockBackend mockBackend = new MockBackend(); @@ -49,6 +57,78 @@ void closeMockBackend() throws Exception { mockBackend.close(); } + /** + * Recursively deletes a directory that a Gradle daemon has been writing into, on a best-effort + * basis. We delete it ourselves (rather than letting JUnit's {@code @TempDir} cleanup do it at + * class teardown) because a daemon may not have released its file handles on {@code + * caches/} by the time the recursive delete runs, which makes the delete fail with a + * {@link java.nio.file.DirectoryNotEmptyException}. + */ + protected static void deleteTempDirectoryQuietly(Path directory) { + if (directory == null) { + return; + } + for (int attempt = 0; + attempt < TEMP_DIR_CLEANUP_RETRIES && Files.exists(directory); + attempt++) { + FileUtils.deleteQuietly(directory.toFile()); + if (!Files.exists(directory)) { + return; + } + try { + Thread.sleep(TEMP_DIR_CLEANUP_RETRY_DELAY_MILLIS); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return; + } + } + if (Files.exists(directory)) { + System.out.println( + "WARNING: could not fully delete temp directory " + + directory + + " after stopping Gradle daemons; leaving it for the OS to reap. " + + "A Gradle daemon likely still holds a file handle on it."); + } + } + + /** Kills the Gradle daemons whose logs live under {@code testKitDir}, on a best-effort basis. */ + protected static void killGradleDaemonsIn(Path testKitDir) { + if (testKitDir == null || !Files.exists(testKitDir)) { + return; + } + boolean windows = System.getProperty("os.name").toLowerCase(Locale.ROOT).contains("win"); + try (Stream files = Files.walk(testKitDir)) { + files + .filter(Files::isRegularFile) + .forEach( + file -> { + String name = file.getFileName().toString(); + if (!name.startsWith("daemon-") || !name.endsWith(".out.log")) { + return; + } + String pid = + name.substring("daemon-".length(), name.length() - ".out.log".length()); + if (!pid.matches("\\d+")) { + // skip the UUID fallback Gradle uses when the PID is unavailable + return; + } + ProcessBuilder kill = + windows + ? new ProcessBuilder("taskkill", "/F", "/PID", pid) + : new ProcessBuilder("kill", pid); + try { + kill.redirectErrorStream(true).start().waitFor(5, TimeUnit.SECONDS); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } catch (Exception e) { + // best effort — the daemon may already be stopped + } + }); + } catch (Exception e) { + // best effort — failing to enumerate daemon logs must not fail the test run + } + } + private static final Pattern PLACEHOLDER_PATTERN = Pattern.compile("\\$\\{(.+?)\\}"); protected void givenGradleProjectFiles(String projectFilesSources) throws IOException { diff --git a/dd-smoke-tests/gradle/src/test/java/datadog/smoketest/GradleDaemonSmokeTest.java b/dd-smoke-tests/gradle/src/test/java/datadog/smoketest/GradleDaemonSmokeTest.java index 54bb2daea06..11f8db54632 100644 --- a/dd-smoke-tests/gradle/src/test/java/datadog/smoketest/GradleDaemonSmokeTest.java +++ b/dd-smoke-tests/gradle/src/test/java/datadog/smoketest/GradleDaemonSmokeTest.java @@ -32,6 +32,7 @@ import org.gradle.wrapper.WrapperConfiguration; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Assumptions; +import org.junit.jupiter.api.io.CleanupMode; import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.params.ParameterizedTest; import org.tabletest.junit.TableTest; @@ -45,12 +46,12 @@ class GradleDaemonSmokeTest extends AbstractGradleTest { // Gradle's default timeout is 10s private static final int GRADLE_DISTRIBUTION_NETWORK_TIMEOUT = 30_000; - @TempDir static Path testKitFolder; + // Cleanup is handled manually in stopGradleTestKitDaemons() instead of by JUnit: the TestKit + // daemons may still hold file handles on this directory at class teardown, which would make + // JUnit's recursive delete fail and turn the class into an executionError. + @TempDir(cleanup = CleanupMode.NEVER) + static Path testKitFolder; - /** - * Stops the Gradle TestKit daemons before JUnit deletes the shared (static) {@link - * #testKitFolder} at class teardown. - */ @AfterAll void stopGradleTestKitDaemons() { try { @@ -58,6 +59,8 @@ void stopGradleTestKitDaemons() { } catch (Exception e) { System.out.println("Failed to stop Gradle TestKit daemons during cleanup: " + e); } + killGradleDaemonsIn(testKitFolder); + deleteTempDirectoryQuietly(testKitFolder); } @TableTest({ From 6d42899563dbda36e4d8b550a2fe72f94bda3424 Mon Sep 17 00:00:00 2001 From: Daniel Mohedano Date: Fri, 29 May 2026 20:04:12 +0200 Subject: [PATCH 7/7] nit: address comments [ci: NON_DEFAULTS_JVM] --- .../src/test/java/datadog/smoketest/AbstractGradleTest.java | 6 +++--- .../test/java/datadog/smoketest/GradleDaemonSmokeTest.java | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/dd-smoke-tests/gradle/src/test/java/datadog/smoketest/AbstractGradleTest.java b/dd-smoke-tests/gradle/src/test/java/datadog/smoketest/AbstractGradleTest.java index 9c69f8a80cd..68c2b2832ef 100644 --- a/dd-smoke-tests/gradle/src/test/java/datadog/smoketest/AbstractGradleTest.java +++ b/dd-smoke-tests/gradle/src/test/java/datadog/smoketest/AbstractGradleTest.java @@ -1,6 +1,7 @@ package datadog.smoketest; import datadog.environment.JavaVirtualMachine; +import datadog.environment.OperatingSystem; import datadog.trace.civisibility.CiVisibilitySmokeTest; import datadog.trace.util.ComparableVersion; import java.io.IOException; @@ -13,7 +14,6 @@ import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; import java.util.Collections; -import java.util.Locale; import java.util.Map; import java.util.Properties; import java.util.concurrent.TimeUnit; @@ -83,7 +83,7 @@ protected static void deleteTempDirectoryQuietly(Path directory) { } } if (Files.exists(directory)) { - System.out.println( + System.err.println( "WARNING: could not fully delete temp directory " + directory + " after stopping Gradle daemons; leaving it for the OS to reap. " @@ -96,7 +96,7 @@ protected static void killGradleDaemonsIn(Path testKitDir) { if (testKitDir == null || !Files.exists(testKitDir)) { return; } - boolean windows = System.getProperty("os.name").toLowerCase(Locale.ROOT).contains("win"); + boolean windows = OperatingSystem.isWindows(); try (Stream files = Files.walk(testKitDir)) { files .filter(Files::isRegularFile) diff --git a/dd-smoke-tests/gradle/src/test/java/datadog/smoketest/GradleDaemonSmokeTest.java b/dd-smoke-tests/gradle/src/test/java/datadog/smoketest/GradleDaemonSmokeTest.java index 11f8db54632..4be236366cc 100644 --- a/dd-smoke-tests/gradle/src/test/java/datadog/smoketest/GradleDaemonSmokeTest.java +++ b/dd-smoke-tests/gradle/src/test/java/datadog/smoketest/GradleDaemonSmokeTest.java @@ -57,7 +57,7 @@ void stopGradleTestKitDaemons() { try { DefaultGradleConnector.close(); } catch (Exception e) { - System.out.println("Failed to stop Gradle TestKit daemons during cleanup: " + e); + System.err.println("Failed to stop Gradle TestKit daemons during cleanup: " + e); } killGradleDaemonsIn(testKitFolder); deleteTempDirectoryQuietly(testKitFolder);