This is an automated email from the ASF dual-hosted git repository.
ibessonov pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ignite-3.git
The following commit(s) were added to refs/heads/main by this push:
new eb8049f977 IGNITE-18638 Make tests print thread dump when timing out
(#1585)
eb8049f977 is described below
commit eb8049f977a0bc454de70cd7bd4c7eedf89349d5
Author: Roman Puchkovskiy <[email protected]>
AuthorDate: Fri Jan 27 11:02:26 2023 +0400
IGNITE-18638 Make tests print thread dump when timing out (#1585)
---
buildscripts/java-integration-test.gradle | 7 ++
buildscripts/java-junit5.gradle | 7 ++
modules/core/build.gradle | 3 +
.../internal/util/IgniteStripedLockSelfTest.java | 3 +
.../testframework/junit/DumpThreadsOnTimeout.java | 99 ++++++++++++++++++++++
5 files changed, 119 insertions(+)
diff --git a/buildscripts/java-integration-test.gradle
b/buildscripts/java-integration-test.gradle
index 5ce10afc4f..47fe614955 100644
--- a/buildscripts/java-integration-test.gradle
+++ b/buildscripts/java-integration-test.gradle
@@ -47,6 +47,13 @@ testing {
testTask.configure {
shouldRunAfter(test)
maxHeapSize = '16g'
+
+ // Define default test timeouts to avoid everhanging
tests.
+ systemProperty
'junit.jupiter.execution.timeout.testable.method.default', '60s'
+ systemProperty
'junit.jupiter.execution.timeout.lifecycle.method.default', '60s'
+
+ // Enable auto-detection of JUnit 5 extensions to
automatically apply DumpThreadsOnTimeout extension.
+ systemProperty
'junit.jupiter.extensions.autodetection.enabled', true
}
}
}
diff --git a/buildscripts/java-junit5.gradle b/buildscripts/java-junit5.gradle
index 97de37d5f8..718c5db78b 100644
--- a/buildscripts/java-junit5.gradle
+++ b/buildscripts/java-junit5.gradle
@@ -20,6 +20,13 @@ test {
testLogging {
events "passed", "skipped", "failed", "standard_error"
}
+
+ // Define default test timeouts to avoid everhanging tests.
+ systemProperty 'junit.jupiter.execution.timeout.testable.method.default',
'60s'
+ systemProperty 'junit.jupiter.execution.timeout.lifecycle.method.default',
'60s'
+
+ // Enable auto-detection of JUnit 5 extensions to automatically apply
DumpThreadsOnTimeout extension.
+ systemProperty 'junit.jupiter.extensions.autodetection.enabled', true
}
dependencies {
diff --git a/modules/core/build.gradle b/modules/core/build.gradle
index a0b667955d..36cbae098e 100644
--- a/modules/core/build.gradle
+++ b/modules/core/build.gradle
@@ -35,6 +35,8 @@ dependencies {
testImplementation libs.mockito.inline
testImplementation libs.mockito.junit
+ testFixturesAnnotationProcessor libs.auto.service
+
testFixturesImplementation libs.hamcrest.core
testFixturesImplementation libs.mockito.core
testFixturesImplementation libs.junit.testkit
@@ -43,6 +45,7 @@ dependencies {
testFixturesImplementation libs.junit5.impl
testFixturesImplementation libs.junit5.params
testFixturesImplementation libs.mockito.core
+ testFixturesImplementation libs.auto.service.annotations
}
test {
diff --git
a/modules/core/src/test/java/org/apache/ignite/internal/util/IgniteStripedLockSelfTest.java
b/modules/core/src/test/java/org/apache/ignite/internal/util/IgniteStripedLockSelfTest.java
index bfff3757d1..f2c8136691 100644
---
a/modules/core/src/test/java/org/apache/ignite/internal/util/IgniteStripedLockSelfTest.java
+++
b/modules/core/src/test/java/org/apache/ignite/internal/util/IgniteStripedLockSelfTest.java
@@ -24,13 +24,16 @@ import static org.junit.jupiter.api.Assertions.fail;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.concurrent.CyclicBarrier;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.Timeout;
/**
* Test for {@link IgniteStripedLock}.
*/
+@Timeout(value = 10, unit = TimeUnit.MINUTES)
public class IgniteStripedLockSelfTest {
private static final int STRIPE_COUNT = 16;
diff --git
a/modules/core/src/testFixtures/java/org/apache/ignite/internal/testframework/junit/DumpThreadsOnTimeout.java
b/modules/core/src/testFixtures/java/org/apache/ignite/internal/testframework/junit/DumpThreadsOnTimeout.java
new file mode 100644
index 0000000000..f44da76019
--- /dev/null
+++
b/modules/core/src/testFixtures/java/org/apache/ignite/internal/testframework/junit/DumpThreadsOnTimeout.java
@@ -0,0 +1,99 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.ignite.internal.testframework.junit;
+
+import com.google.auto.service.AutoService;
+import java.lang.management.ManagementFactory;
+import java.lang.management.ThreadInfo;
+import java.lang.management.ThreadMXBean;
+import java.util.concurrent.TimeoutException;
+import org.junit.jupiter.api.extension.Extension;
+import org.junit.jupiter.api.extension.ExtensionContext;
+import
org.junit.jupiter.api.extension.LifecycleMethodExecutionExceptionHandler;
+import org.junit.jupiter.api.extension.TestExecutionExceptionHandler;
+
+/**
+ * This extension prints a thread dump (to the standard out) if a testable
method (for instance, a method annotated with
+ * {@link org.junit.jupiter.api.Test} or a lifecycle method (like {@link
org.junit.jupiter.api.BeforeEach} method) times out.
+ *
+ * <p>This extension is designed to be
+ * <a
href="https://junit.org/junit5/docs/current/user-guide/#extensions-registration-automatic">automatically
registered</a>
+ * via META-INF/services/org.junit.jupiter.api.extension.Extension.
+ * For this to work, system property {@code
junit.jupiter.extensions.autodetection.enabled} must be set to {@code true}.
+ * If the property is set (currently, this is done via Gradle build scripts),
it is enough to add this module as a dependency
+ * to make tests automatically register this extension, like this:
+ *
+ * <pre>
+ * integrationTestImplementation(testFixtures(project(':ignite-core')))
+ * </pre>
+ */
+@AutoService(Extension.class)
+public class DumpThreadsOnTimeout implements TestExecutionExceptionHandler,
LifecycleMethodExecutionExceptionHandler {
+ @Override
+ public void handleTestExecutionException(ExtensionContext context,
Throwable throwable) throws Throwable {
+ handleThrowable(throwable, "Test method");
+ }
+
+ private static void handleThrowable(Throwable throwable, String category)
throws Throwable {
+ if (isJunitMethodTimeout(throwable)) {
+ // We take throwable.getMessage() because TimeoutExtension puts a
description of 'what has timed out' to it.
+ System.out.println(category + " " + throwable.getMessage() + ",
dumping threads");
+
+ dumpThreadsToStdout();
+ }
+
+ // Rethrow the thing to make sure the framework sees the failure.
+ throw throwable;
+ }
+
+ private static boolean isJunitMethodTimeout(Throwable throwable) {
+ // This is a pretty hacky and not too reliable way to determine
whether this is the timeout event we are
+ // interested in (meaning, a timed-out testable/lifecycle method
execution), but it looks like this is the
+ // best we can do.
+ return throwable instanceof TimeoutException &&
throwable.getMessage().contains(" timed out after ");
+ }
+
+ private static void dumpThreadsToStdout() {
+ ThreadMXBean bean = ManagementFactory.getThreadMXBean();
+ ThreadInfo[] infos = bean.dumpAllThreads(true, true);
+
+ for (ThreadInfo info : infos) {
+ System.out.println(info);
+ }
+ }
+
+ @Override
+ public void handleBeforeAllMethodExecutionException(ExtensionContext
context, Throwable throwable) throws Throwable {
+ handleThrowable(throwable, "Before all method");
+ }
+
+ @Override
+ public void handleBeforeEachMethodExecutionException(ExtensionContext
context, Throwable throwable) throws Throwable {
+ handleThrowable(throwable, "Before each method");
+ }
+
+ @Override
+ public void handleAfterEachMethodExecutionException(ExtensionContext
context, Throwable throwable) throws Throwable {
+ handleThrowable(throwable, "After each method");
+ }
+
+ @Override
+ public void handleAfterAllMethodExecutionException(ExtensionContext
context, Throwable throwable) throws Throwable {
+ handleThrowable(throwable, "After all method");
+ }
+}