diff --git a/.evergreen/run-csfle-tests-with-mongocryptd.sh b/.evergreen/run-csfle-tests-with-mongocryptd.sh index a5322955ff7..8979c1759e9 100755 --- a/.evergreen/run-csfle-tests-with-mongocryptd.sh +++ b/.evergreen/run-csfle-tests-with-mongocryptd.sh @@ -67,6 +67,7 @@ echo "Running tests with Java ${JAVA_VERSION}" ./gradlew -PjavaVersion=${JAVA_VERSION} -Dorg.mongodb.test.uri=${MONGODB_URI} \ ${GRADLE_EXTRA_VARS} \ -Dorg.mongodb.test.fle.on.demand.credential.test.failure.enabled=true \ + -Dorg.mongodb.test.diagnostics.thread.dump.enabled=true \ --stacktrace --info --continue \ driver-sync:test \ --tests "*.Client*Encryption*" \ diff --git a/.evergreen/run-tests.sh b/.evergreen/run-tests.sh index 0ee14ed01dc..f97ff5863a2 100755 --- a/.evergreen/run-tests.sh +++ b/.evergreen/run-tests.sh @@ -142,6 +142,7 @@ echo "Running tests with Java ${JAVA_VERSION}" ${MULTI_MONGOS_URI_SYSTEM_PROPERTY} ${API_VERSION} ${GRADLE_EXTRA_VARS} \ ${JAVA_SYSPROP_ASYNC_TRANSPORT} ${JAVA_SYSPROP_NETTY_SSL_PROVIDER} \ -Dorg.mongodb.test.fle.on.demand.credential.test.failure.enabled=true \ + -Dorg.mongodb.test.diagnostics.thread.dump.enabled=true \ --stacktrace --info --continue ${TESTS} | tee -a logs.txt if grep -q 'LEAK:' logs.txt ; then diff --git a/driver-core/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension b/driver-core/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension new file mode 100644 index 00000000000..f862b0ebe84 --- /dev/null +++ b/driver-core/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension @@ -0,0 +1 @@ +com.mongodb.internal.diagnostics.ThreadDumpOnFailureExtension diff --git a/driver-core/src/test/resources/junit-platform.properties b/driver-core/src/test/resources/junit-platform.properties new file mode 100644 index 00000000000..6efc0d5e85c --- /dev/null +++ b/driver-core/src/test/resources/junit-platform.properties @@ -0,0 +1 @@ +junit.jupiter.extensions.autodetection.enabled=true diff --git a/driver-core/src/test/unit/com/mongodb/internal/diagnostics/ThreadDumpOnFailureExtension.java b/driver-core/src/test/unit/com/mongodb/internal/diagnostics/ThreadDumpOnFailureExtension.java new file mode 100644 index 00000000000..6fc1036d413 --- /dev/null +++ b/driver-core/src/test/unit/com/mongodb/internal/diagnostics/ThreadDumpOnFailureExtension.java @@ -0,0 +1,71 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.mongodb.internal.diagnostics; + +import java.lang.management.ManagementFactory; +import java.lang.management.ThreadInfo; +import java.lang.management.ThreadMXBean; + + +import com.mongodb.internal.diagnostics.logging.Logger; +import com.mongodb.internal.diagnostics.logging.Loggers; + +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.TestWatcher; + + +/** + * A JUnit 5 extension that prints a thread dump to the log when a test fails. + * + *
The extension is auto-detected on every test run, but is inert unless explicitly enabled via the + * {@value #ENABLED_PROPERTY} system property. This keeps local runs quiet while allowing Evergreen (which sets the + * property) — or a developer who opts in with {@code -Dorg.mongodb.test.diagnostics.thread.dump.enabled=true} — to + * capture thread dumps for failing tests. + */ +public final class ThreadDumpOnFailureExtension implements TestWatcher { + + static final String ENABLED_PROPERTY = "org.mongodb.test.diagnostics.thread.dump.enabled"; + + private static final Logger LOGGER = Loggers.getLogger(ThreadDumpOnFailureExtension.class.getSimpleName()); + + @Override + public void testFailed(final ExtensionContext context, final Throwable cause) { + if (!Boolean.getBoolean(ENABLED_PROPERTY)) { + return; + } + String testName = context.getDisplayName(); + String threadDump = getAllThreadsDump(); + LOGGER.error("Test failed: " + testName + "\nThread dump:\n" + threadDump); + } + + private static String getAllThreadsDump() { + try { + final ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean(); + ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads( + threadMXBean.isObjectMonitorUsageSupported(), + threadMXBean.isSynchronizerUsageSupported() + ); + StringBuilder sb = new StringBuilder(1024); + for (ThreadInfo info : threadInfos) { + sb.append(info); + } + return sb.toString(); + } catch (final SecurityException exc) { + return "Unable to get thread dump due to security manager restrictions: " + exc.getMessage(); + } + } +}