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 859f95c917c..c2b7b5ddf95 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 @@ -13,8 +13,13 @@ import datadog.trace.api.config.TraceInstrumentationConfig; import datadog.trace.api.config.TracerConfig; import java.io.File; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -22,6 +27,7 @@ import java.util.List; import java.util.Map; import java.util.stream.Collectors; +import java.util.stream.Stream; import org.junit.jupiter.api.io.TempDir; public abstract class CiVisibilitySmokeTest { @@ -40,6 +46,11 @@ public abstract class CiVisibilitySmokeTest { private static final Map DEFAULT_TRACER_CONFIG = defaultJvmArguments(); + private static final Path JVM_CRASH_LOGS_DIR = + Paths.get( + System.getProperty("datadog.smoketest.builddir", "build"), "reports", "jvm-crash-logs"); + private static final int JVM_CRASH_LOG_PRINTED_LINES = 60; + @TempDir protected Path prefsDir; protected static String buildJavaHome() { @@ -149,6 +160,45 @@ protected String preventJulPrefsFileLock() { return "-Djava.util.prefs.userRoot=" + resolved.toAbsolutePath(); } + /** + * Prints and preserves the JVM fatal error logs ({@code hs_err_pid*.log}) that forked processes + * wrote under {@code directory}. Forked JVMs write these logs to their working directory, which + * is usually a temporary folder deleted after the test. + */ + protected static void reportJvmCrashLogs(Path directory) { + if (directory == null || !Files.isDirectory(directory)) { + return; + } + List crashLogs; + try (Stream files = Files.walk(directory)) { + crashLogs = + files + .filter(p -> p.getFileName().toString().matches("hs_err_pid\\d+\\.log")) + .collect(Collectors.toList()); + } catch (IOException | UncheckedIOException e) { + System.err.println("Failed to look for JVM crash logs in " + directory + ": " + e); + return; + } + for (Path crashLog : crashLogs) { + try { + Files.createDirectories(JVM_CRASH_LOGS_DIR); + Path copy = JVM_CRASH_LOGS_DIR.resolve(crashLog.getFileName()); + Files.copy(crashLog, copy, StandardCopyOption.REPLACE_EXISTING); + try (Stream lines = Files.lines(crashLog, StandardCharsets.ISO_8859_1)) { + System.out.println( + "JVM crash log " + + crashLog + + " (copied to " + + copy.toAbsolutePath() + + "):\n" + + lines.limit(JVM_CRASH_LOG_PRINTED_LINES).collect(Collectors.joining("\n"))); + } + } catch (IOException | UncheckedIOException e) { + System.err.println("Failed to report JVM crash log " + crashLog + ": " + e); + } + } + } + private static Path tempUserPrefsPath() { String uniqueId = System.currentTimeMillis() + "_" + System.nanoTime() + "_" + Thread.currentThread().getId(); 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 6cb475766af..6daf45348e2 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 @@ -22,6 +22,7 @@ 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.AfterEach; import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.TestInstance; @@ -53,6 +54,11 @@ void resetMockBackend() { mockBackend.reset(); } + @AfterEach + void reportForkedJvmCrashes() { + reportJvmCrashLogs(projectFolder); + } + @AfterAll void closeMockBackend() throws Exception { mockBackend.close(); diff --git a/dd-smoke-tests/maven/src/test/java/datadog/smoketest/MavenSmokeTest.java b/dd-smoke-tests/maven/src/test/java/datadog/smoketest/MavenSmokeTest.java index 1f8bca1da85..a8f22032ad9 100644 --- a/dd-smoke-tests/maven/src/test/java/datadog/smoketest/MavenSmokeTest.java +++ b/dd-smoke-tests/maven/src/test/java/datadog/smoketest/MavenSmokeTest.java @@ -40,6 +40,7 @@ import java.util.concurrent.TimeoutException; import org.apache.maven.wrapper.MavenWrapperMain; import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.condition.DisabledIf; import org.junit.jupiter.api.condition.EnabledForJreRange; @@ -81,6 +82,11 @@ void resetMockBackend() { mockBackend.reset(); } + @AfterEach + void reportForkedJvmCrashes() { + reportJvmCrashLogs(projectHome); + } + @AfterAll static void closeMockBackend() throws Exception { mockBackend.close();