Skip to content

Commit fddd6b9

Browse files
PreInterruptCallback extension
Added PreInterruptCallback extension to allow to hook into the @timeout extension before the executing Thread is interrupted. The default implementation of PreInterruptCallback will simply print the stacks of all Thread to System.out. It is disabled by default and must be enabled with: junit.jupiter.execution.timeout.threaddump.enabled = true Issue: #2938 Co-authored-by: Marc Philipp <[email protected]>
1 parent f7b7975 commit fddd6b9

File tree

41 files changed

+768
-87
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+768
-87
lines changed

documentation/src/docs/asciidoc/link-attributes.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ endif::[]
156156
:TestTemplateInvocationContext: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/TestTemplateInvocationContext.html[TestTemplateInvocationContext]
157157
:TestTemplateInvocationContextProvider: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/TestTemplateInvocationContextProvider.html[TestTemplateInvocationContextProvider]
158158
:TestWatcher: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/TestWatcher.html[TestWatcher]
159+
:PreInterruptCallback: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/PreInterruptCallback.html[PreInterruptCallback]
159160
// Jupiter Conditions
160161
:DisabledForJreRange: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/DisabledForJreRange.html[@DisabledForJreRange]
161162
:DisabledIf: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/DisabledIf.html[@DisabledIf]

documentation/src/docs/asciidoc/release-notes/release-notes-5.12.0-M1.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ JUnit repository on GitHub.
8787
a test-scoped `ExtensionContext` in `Extension` methods called during test class
8888
instantiation. This behavior will become the default in future versions of JUnit.
8989
* `@TempDir` is now supported on test class constructors.
90+
* Added `PreInterruptCallback`
9091

9192

9293
[[release-notes-5.12.0-M1-junit-vintage]]

documentation/src/docs/asciidoc/user-guide/extensions.adoc

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -715,6 +715,15 @@ test methods.
715715
include::{testDir}/example/exception/MultipleHandlersTestCase.java[tags=user_guide]
716716
----
717717

718+
[[extensions-preinterrupt-callback]]
719+
=== Pre-Interrupt Callback
720+
721+
`{PreInterruptCallback}` defines the API for `Extensions` that wish to react on
722+
timeouts before the `Thread.interrupt()` is called.
723+
724+
Please refer to <<writing-tests-declarative-timeouts-debugging>> for additional information.
725+
726+
718727
[[extensions-intercepting-invocations]]
719728
=== Intercepting Invocations
720729

documentation/src/docs/asciidoc/user-guide/writing-tests.adoc

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2659,6 +2659,22 @@ asynchronous tests, consider using a dedicated library such as
26592659
link:https://github.com/awaitility/awaitility[Awaitility].
26602660

26612661

2662+
[[writing-tests-declarative-timeouts-debugging]]
2663+
=== Debugging Timeouts
2664+
2665+
Registered <<extensions-preinterrupt-callback>> extensions are called prior to invoking
2666+
`Thread.interrupt()` on the thread that is executing the timed out method. This allows to
2667+
inspect the application state and output additional information that might be helpful for
2668+
diagnosing the cause of a timeout.
2669+
2670+
2671+
[[writing-tests-declarative-timeouts-debugging-thread-dump]]
2672+
==== Thread Dump on Timeout
2673+
JUnit registers a default implementation of the <<extensions-preinterrupt-callback>> extension point that
2674+
dumps the stacks of all threads to `System.out` if enabled by setting the
2675+
`junit.jupiter.execution.timeout.threaddump.enabled` configuration parameter to `true`.
2676+
2677+
26622678
[[writing-tests-declarative-timeouts-mode]]
26632679
==== Disable @Timeout Globally
26642680
When stepping through your code in a debug session, a fixed timeout limit may influence

junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeoutPreemptively.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ private static <T, E extends Throwable> T resolveFutureAndHandleException(Future
113113
cause = new ExecutionTimeoutException("Execution timed out in thread " + thread.getName());
114114
cause.setStackTrace(thread.getStackTrace());
115115
}
116-
throw failureFactory.createTimeoutFailure(timeout, messageSupplier, cause);
116+
throw failureFactory.createTimeoutFailure(timeout, messageSupplier, cause, thread);
117117
}
118118
catch (ExecutionException ex) {
119119
throw throwAsUncheckedException(ex.getCause());
@@ -124,7 +124,7 @@ private static <T, E extends Throwable> T resolveFutureAndHandleException(Future
124124
}
125125

126126
private static AssertionFailedError createAssertionFailure(Duration timeout, Supplier<String> messageSupplier,
127-
Throwable cause) {
127+
Throwable cause, Thread thread) {
128128
return assertionFailure() //
129129
.message(messageSupplier) //
130130
.reason("execution timed out after " + timeout.toMillis() + " ms") //

junit-jupiter-api/src/main/java/org/junit/jupiter/api/Assertions.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3662,6 +3662,6 @@ public interface TimeoutFailureFactory<T extends Throwable> {
36623662
*
36633663
* @return timeout failure; never {@code null}
36643664
*/
3665-
T createTimeoutFailure(Duration timeout, Supplier<String> messageSupplier, Throwable cause);
3665+
T createTimeoutFailure(Duration timeout, Supplier<String> messageSupplier, Throwable cause, Thread testThread);
36663666
}
36673667
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* Copyright 2015-2024 the original author or authors.
3+
*
4+
* All rights reserved. This program and the accompanying materials are
5+
* made available under the terms of the Eclipse Public License v2.0 which
6+
* accompanies this distribution and is available at
7+
*
8+
* https://www.eclipse.org/legal/epl-v20.html
9+
*/
10+
11+
package org.junit.jupiter.api.extension;
12+
13+
import static org.apiguardian.api.API.Status.EXPERIMENTAL;
14+
15+
import org.apiguardian.api.API;
16+
17+
/**
18+
* {@code PreInterruptCallback} defines the API for {@link Extension
19+
* Extensions} that wish to be called prior to invocations of
20+
* {@link Thread#interrupt()} by the {@link org.junit.jupiter.api.Timeout}
21+
* extension.
22+
*
23+
* <p>JUnit registers a default implementation that dumps the stacks of all
24+
* {@linkplain Thread threads} to {@code System.out} if the
25+
* {@value #THREAD_DUMP_ENABLED_PROPERTY_NAME} configuration parameter is set to
26+
* {@code true}.
27+
*
28+
* @since 5.12
29+
* @see org.junit.jupiter.api.Timeout
30+
*/
31+
@API(status = EXPERIMENTAL, since = "5.12")
32+
public interface PreInterruptCallback extends Extension {
33+
34+
/**
35+
* Property name used to enable dumping the stack of all
36+
* {@linkplain Thread threads} to {@code System.out} when a timeout has occurred.
37+
*
38+
* <p>This behavior is disabled by default.
39+
*
40+
* @since 5.12
41+
*/
42+
@API(status = EXPERIMENTAL, since = "5.12")
43+
String THREAD_DUMP_ENABLED_PROPERTY_NAME = "junit.jupiter.execution.timeout.threaddump.enabled";
44+
45+
/**
46+
* Callback that is invoked <em>before</em> a {@link Thread} is interrupted with
47+
* {@link Thread#interrupt()}.
48+
*
49+
* <p>Note: There is no guarantee on which {@link Thread} this callback will be
50+
* executed.
51+
*
52+
* @param preInterruptContext the context with the target {@link Thread}, which will get interrupted.
53+
* @param extensionContext the extension context for the callback; never {@code null}
54+
* @since 5.12
55+
* @see PreInterruptContext
56+
*/
57+
@API(status = EXPERIMENTAL, since = "5.12")
58+
void beforeThreadInterrupt(PreInterruptContext preInterruptContext, ExtensionContext extensionContext)
59+
throws Exception;
60+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Copyright 2015-2024 the original author or authors.
3+
*
4+
* All rights reserved. This program and the accompanying materials are
5+
* made available under the terms of the Eclipse Public License v2.0 which
6+
* accompanies this distribution and is available at
7+
*
8+
* https://www.eclipse.org/legal/epl-v20.html
9+
*/
10+
11+
package org.junit.jupiter.api.extension;
12+
13+
import static org.apiguardian.api.API.Status.EXPERIMENTAL;
14+
15+
import org.apiguardian.api.API;
16+
17+
/**
18+
* {@code PreInterruptContext} encapsulates the <em>context</em> in which an
19+
* {@link PreInterruptCallback#beforeThreadInterrupt(PreInterruptContext) beforeThreadInterrupt} method is called.
20+
*
21+
* @since 5.12
22+
* @see PreInterruptCallback
23+
*/
24+
@API(status = EXPERIMENTAL, since = "5.12")
25+
public interface PreInterruptContext {
26+
27+
/**
28+
* Get the {@link Thread} which will be interrupted.
29+
*
30+
* @return the Thread; never {@code null}
31+
* @since 5.12
32+
*/
33+
@API(status = EXPERIMENTAL, since = "5.12")
34+
Thread getThreadToInterrupt();
35+
}

junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/Constants.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,17 @@ public final class Constants {
108108
*/
109109
public static final String EXTENSIONS_AUTODETECTION_ENABLED_PROPERTY_NAME = JupiterConfiguration.EXTENSIONS_AUTODETECTION_ENABLED_PROPERTY_NAME;
110110

111+
/**
112+
* Property name used to enable dumping the stack of all
113+
* {@linkplain Thread threads} to {@code System.out} when a timeout has occurred.
114+
*
115+
* <p>This behavior is disabled by default.
116+
*
117+
* @since 5.12
118+
*/
119+
@API(status = EXPERIMENTAL, since = "5.12")
120+
public static final String EXTENSIONS_TIMEOUT_THREAD_DUMP_ENABLED_PROPERTY_NAME = JupiterConfiguration.EXTENSIONS_TIMEOUT_THREAD_DUMP_ENABLED_PROPERTY_NAME;
121+
111122
/**
112123
* Property name used to set the default test instance lifecycle mode: {@value}
113124
*
@@ -192,7 +203,7 @@ public final class Constants {
192203
* <p>When set to {@code false} the underlying fork-join pool will reject
193204
* additional tasks if all available workers are busy and the maximum
194205
* pool-size would be exceeded.
195-
206+
*
196207
* <p>Value must either {@code true} or {@code false}; defaults to {@code true}.
197208
*
198209
* <p>Note: This property only takes affect on Java 9+.

junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/CachingJupiterConfiguration.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,12 @@ public boolean isExtensionAutoDetectionEnabled() {
6868
__ -> delegate.isExtensionAutoDetectionEnabled());
6969
}
7070

71+
@Override
72+
public boolean isThreadDumpOnTimeoutEnabled() {
73+
return (boolean) cache.computeIfAbsent(EXTENSIONS_TIMEOUT_THREAD_DUMP_ENABLED_PROPERTY_NAME,
74+
__ -> delegate.isThreadDumpOnTimeoutEnabled());
75+
}
76+
7177
@Override
7278
public ExecutionMode getDefaultExecutionMode() {
7379
return (ExecutionMode) cache.computeIfAbsent(DEFAULT_EXECUTION_MODE_PROPERTY_NAME,

0 commit comments

Comments
 (0)