Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +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.AfterEach;
import org.junit.jupiter.api.io.TempDir;
import org.junit.jupiter.params.ParameterizedTest;
import org.opentest4j.AssertionFailedError;
Expand All @@ -23,6 +25,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();
Expand Down Expand Up @@ -71,6 +74,40 @@ void testGradleLauncherInjectsTracerIntoGradleDaemon(
cmdLineParams != null ? cmdLineParams : "-Duser.country=VALUE_FROM_GRADLE_PROPERTIES_FILE");
}

/**
* 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/<version>/}; if that process has not fully released its file
* 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.
*
* <p>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.
*/
@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<String, String> 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);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use an existing project directory for daemon shutdown

Because projectFolder is an instance @TempDir from AbstractGradleTest, JUnit scopes it to each parameterized invocation and cleans it up when that invocation finishes; by the time this @AfterAll runs, the last projectFolder has already been deleted. In that state ShellCommandExecutor starts ./gradlew --stop with a non-existent working directory, the exception is swallowed by the best-effort catch, and the shared static gradleUserHome is still left for JUnit to delete while Gradle daemons may hold files open.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addressed in c36724f

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<String, String> env = new HashMap<>();
env.put("JAVA_HOME", JAVA_HOME);
Expand Down