Skip to content

Commit f6ecfa4

Browse files
committed
Introduce support for parallel test execution
This commit adds opt-in support for parallel test execution and capturing output to `System.out` and `System.err`. Both features are disabled by default but can be enabled and configured using configuration parameters. The implementation is based on the Fork/Join Framework and designed to be reusable by other test engines that extend HierarchicalTestEngine. The Jupiter API provides annotations to declare which shared resources a test needs to access and in which way. Moreover, the execution mode of a test can be influenced. In addition, a number of TestExecutionListeners have been made thread-safe. The documentation subproject is now configured to execute tests in parallel. All other subprojects will have to wait as Gradle currently blows up when used with parallel test execution. Co-authored-by: Leonard Brünings <[email protected]> Co-authored-by: Christian Stein <[email protected]> Resolves #60. Closes #1461.
1 parent 948841c commit f6ecfa4

File tree

71 files changed

+4084
-258
lines changed

Some content is hidden

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

71 files changed

+4084
-258
lines changed

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

+4
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ endif::[]
2121
:ConsoleLauncher: {javadoc-root}/org/junit/platform/console/ConsoleLauncher.html[ConsoleLauncher]
2222
//
2323
:DiscoverySelectors_selectMethod: {javadoc-root}/org/junit/platform/engine/discovery/DiscoverySelectors.html#selectMethod-java.lang.String-[selectMethod(String) in DiscoverySelectors]
24+
:HierarchicalTestEngine: {javadoc-root}/org/junit/platform/engine/support/hierarchical/HierarchicalTestEngine.html[HierarchicalTestEngine]
25+
:ParallelExecutionConfigurationStrategy: {javadoc-root}/org/junit/platform/engine/support/hierarchical/ParallelExecutionConfigurationStrategy.html[ParallelExecutionConfigurationStrategy]
2426
:TestEngine: {javadoc-root}/org/junit/platform/engine/TestEngine.html[TestEngine]
2527
//
2628
:Launcher: {javadoc-root}/org/junit/platform/launcher/Launcher.html[Launcher]
@@ -54,6 +56,7 @@ endif::[]
5456
:EnabledIfSystemProperty: {javadoc-root}/org/junit/jupiter/api/condition/EnabledIfSystemProperty.html[@EnabledIfSystemProperty]
5557
:EnabledOnJre: {javadoc-root}/org/junit/jupiter/api/condition/EnabledOnJre.html[@EnabledOnJre]
5658
:EnabledOnOs: {javadoc-root}/org/junit/jupiter/api/condition/EnabledOnOs.html[@EnabledOnOs]
59+
:Execution: {javadoc-root}/org/junit/jupiter/api/parallel/Execution.html[@Execution]
5760
:ExecutionCondition: {javadoc-root}/org/junit/jupiter/api/extension/ExecutionCondition.html[ExecutionCondition]
5861
:ExtendWith: {javadoc-root}/org/junit/jupiter/api/extension/ExtendWith.html[@ExtendWith]
5962
:ExtensionContext: {javadoc-root}/org/junit/jupiter/api/extension/ExtensionContext.html[ExtensionContext]
@@ -70,6 +73,7 @@ endif::[]
7073
:TestTemplate: {javadoc-root}/org/junit/jupiter/api/TestTemplate.html[@TestTemplate]
7174
:TestTemplateInvocationContext: {javadoc-root}/org/junit/jupiter/api/extension/TestTemplateInvocationContext.html[TestTemplateInvocationContext]
7275
:TestTemplateInvocationContextProvider: {javadoc-root}/org/junit/jupiter/api/extension/TestTemplateInvocationContextProvider.html[TestTemplateInvocationContextProvider]
76+
:ResourceLock: {javadoc-root}/org/junit/jupiter/api/parallel/ResourceLock.html[@ResourceLock]
7377
//
7478
:DisabledCondition: {current-branch}/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/DisabledCondition.java[DisabledCondition]
7579
:RepetitionInfoParameterResolver: {current-branch}/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/RepetitionInfoParameterResolver.java[RepetitionInfoParameterResolver]

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

+19-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33

44
*Date of Release:* ❓
55

6-
*Scope:* ❓
6+
*Scope:* Parallel test execution, output capturing, test sources for dynamic tests as well
7+
as various minor improvements and bug fixes.
78

89
For a complete list of all _closed_ issues and pull requests for this release, consult the
910
link:{junit5-repo}+/milestone/23?closed=1+[5.3 M1] milestone page in the JUnit repository
@@ -34,6 +35,19 @@ on GitHub.
3435

3536
==== New Features and Improvements
3637

38+
* Experimental support for capturing output printed to `System.out` and
39+
`System.err` during test execution. This feature is disabled by default and can be
40+
enabled using a configuration parameter (cf.
41+
<<../user-guide/index.adoc#running-tests-capturing-output, User Guide>>).
42+
* Reusable support for parallel test execution for test engines that extend
43+
`HierarchicalTestEngine`.
44+
- `HierarchicalTestEngine` implementations may now specify a
45+
`HierarchicalTestExecutorService`
46+
- By default, a `SameThreadHierarchicalTestExecutorService` is used.
47+
- Test engines may use `ForkJoinPoolHierarchicalTestExecutorService` to support
48+
parallel test execution based on the Fork/Join Framework.
49+
- `Node` implementations may provide a set of `ExclusiveResources` and an
50+
`ExecutionMode` to be used by `ForkJoinPoolHierarchicalTestExecutorService`.
3751
* New overloaded variant of `isAnnotated()` in `AnnotationSupport` that accepts
3852
`Optional<? extends AnnotatedElement>` instead of `AnnotatedElement`.
3953
* New `--fail-if-no-tests` command-line option for the `ConsoleLauncher`.
@@ -64,6 +78,10 @@ on GitHub.
6478

6579
==== New Features and Improvements
6680

81+
* Experimental support for parallel test execution. By default, tests are still executed
82+
sequentially; parallelism can be enabled using a configuration parameter (please refer
83+
to the <<../user-guide/index.adoc#writing-tests-parallel-execution, User Guide>> for
84+
examples and configuration options).
6785
* New support for the IBM AIX operating system in `@EnabledOnOs` and `@DisabledOnOs`.
6886
* New `assertThrows` methods in `Assertions` provide a more specific failure message if
6987
the supplied lambda expression or method reference returns a result instead of throwing

documentation/src/docs/asciidoc/user-guide/launcher-api.adoc

+5
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,11 @@ is currently supported via Java's `java.util.ServiceLoader` mechanism. For examp
7878
in a file named `org.junit.platform.engine.TestEngine` within the `/META-INF/services` in
7979
the `junit-jupiter-engine` JAR.
8080

81+
NOTE: `{HierarchicalTestEngine}` is a convenient abstract base implementation (used by
82+
the `{junit-jupiter-engine}`) that only requires implementors to provide the logic for
83+
test discovery. It implements execution of `TestDescriptors` that implement the `Node`
84+
interface, including support for parallel execution.
85+
8186
[[launcher-api-listeners-custom]]
8287
==== Plugging in Your Own Test Execution Listeners
8388

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

+25
Original file line numberDiff line numberDiff line change
@@ -762,3 +762,28 @@ useful.
762762
| +(micro \| integration) & (foo \| baz)+
763763
| all _micro_ or _integration_ tests for *foo* or *baz*
764764
|===
765+
766+
[[running-tests-capturing-output]]
767+
=== Capturing Standard Output/Error
768+
769+
Since version 1.3, the JUnit Platform provides opt-in support for capturing output
770+
printed to `System.out` and `System.err`. To enable it, simply set the
771+
`junit.platform.output.capture.stdout` and/or `junit.platform.output.capture.stderr`
772+
<<running-tests-config-params, configuration parameter>> to `true`. In addition, you may
773+
configure the maximum number of buffered bytes to be used per executed test or container
774+
using `junit.platform.output.capture.maxBuffer`.
775+
776+
If enabled, the JUnit Platform captures the corresponding output and publishes it as a
777+
report entry using the `stdout` or `stderr` keys to all registered
778+
`{TestExecutionListener}` instances immediately before reporting the test or container as
779+
finished.
780+
781+
Please note that the captured output will only contain output emitted by the thread that
782+
was used to execute a container or test. Any output by other threads will be omitted
783+
because particularly when
784+
<<writing-tests-parallel-execution, executing tests in parallel>> it would be impossible
785+
to attribute it to a specific test or container.
786+
787+
WARNING: Capturing output is currently an _experimental_ feature. You're invited to give
788+
it a try and provide feedback to the JUnit team so they can improve and eventually
789+
<<api-evolution, promote>> this feature.

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

+87
Original file line numberDiff line numberDiff line change
@@ -1290,3 +1290,90 @@ The last method generates a nested hierarchy of dynamic tests utilizing
12901290
----
12911291
include::{testDir}/example/DynamicTestsDemo.java[tags=user_guide]
12921292
----
1293+
1294+
1295+
[[writing-tests-parallel-execution]]
1296+
=== Parallel Execution
1297+
1298+
By default, JUnit Jupiter tests are run sequentially in a single thread. Running tests in
1299+
parallel, e.g. to speed up execution, is available as an opt-in feature since version 5.3.
1300+
To enable parallel execution, simply set the set the
1301+
`junit.jupiter.execution.parallel.enabled` configuration parameter to `true`, e.g. in `junit-platform.properties` (see <<running-tests-config-params>> for other options).
1302+
1303+
Once enabled, the JUnit Jupiter engine will execute tests on all levels of the test tree
1304+
fully in parallel according to the provided
1305+
<<writing-tests-parallel-execution-config, configuration>> while observing the declarative
1306+
<<writing-tests-parallel-execution-synchronization, synchronization>> mechanisms. Please
1307+
note that the <<running-tests-capturing-output>> feature needs to enabled separately.
1308+
1309+
WARNING: Parallel test execution is currently an _experimental_ feature. You're invited
1310+
to give it a try and provide feedback to the JUnit team so they can improve and
1311+
eventually <<api-evolution, promote>> this feature.
1312+
1313+
[[writing-tests-parallel-execution-config]]
1314+
==== Configuration
1315+
1316+
Properties like the desired parallelism and the maximum pool size can be configured using
1317+
a `{ParallelExecutionConfigurationStrategy}`. The JUnit Platform provides two
1318+
implementations out of the box: `dynamic` and `fixed`. Alternatively, you may implement a
1319+
`custom` strategy.
1320+
1321+
To select a strategy, simply set the `junit.jupiter.execution.parallel.config.strategy`
1322+
configuration parameter to one of the following options:
1323+
1324+
`dynamic`::
1325+
Computes the desired parallelism based on the number of available processors/cores
1326+
multiplied by the `junit.jupiter.execution.parallel.config.dynamic.factor`
1327+
configuration parameter (defaults to `1`).
1328+
1329+
`fixed`::
1330+
Uses the mandatory `junit.jupiter.execution.parallel.config.fixed.parallelism`
1331+
configuration parameter as desired parallelism.
1332+
1333+
`custom`::
1334+
Allows to specify a custom `{ParallelExecutionConfigurationStrategy}` implementation via
1335+
the mandatory `junit.jupiter.execution.parallel.config.custom.class` configuration
1336+
parameter to determine the desired configuration.
1337+
1338+
If no configuration strategy is set, JUnit Jupiter uses the `dynamic` configuration
1339+
strategy with a factor of 1, i.e. the desired parallelism will equal the number of
1340+
available processors/cores.
1341+
1342+
[[writing-tests-parallel-execution-synchronization]]
1343+
==== Synchronization
1344+
1345+
In the `org.junit.jupiter.api.parallel` package, JUnit Jupiter provides two
1346+
annotation-based declarative mechanisms to change the execution mode and allow for
1347+
synchronization when using shared resources in different tests.
1348+
1349+
If parallel execution is enabled, all classes and methods are executed concurrently by
1350+
default. You can change the execution mode for the annotated element and its subelements
1351+
(if any) by using the `{Execution}` annotation. The following two modes are available:
1352+
1353+
`SAME_THREAD`::
1354+
Force execution in the same thread used by the parent. For example, when used on a test
1355+
method, the test method will be executed in the same thread as any `@BeforeAll` or
1356+
`@AfterAll` methods of the containing test class.
1357+
1358+
`CONCURRENT`::
1359+
Execute concurrently unless a resource constraint forces execution in the same thread.
1360+
1361+
In addition, the `{ResourceLock}` annotation allows to declare that a test class or
1362+
method uses a specific shared resource that requires synchronized access to ensure
1363+
reliable test execution.
1364+
1365+
If the tests in the following example were run in parallel they would be flaky, i.e.
1366+
sometimes pass and other times fail, because of the inherent race condition of
1367+
writing and then reading the same system property.
1368+
1369+
[source,java]
1370+
----
1371+
include::{testDir}/example/SharedResourcesDemo.java[tags=user_guide]
1372+
----
1373+
1374+
When access to shared resources is declared using this annotation, the JUnit Jupiter
1375+
engine uses this information to ensure that no conflicting tests are run in parallel.
1376+
1377+
In addition to the string that uniquely identifies the used resource, you may specify an
1378+
access mode. Two tests that require `READ` access to a resource may run in parallel with
1379+
each other but not while any other test that requires `READ_WRITE` access is running.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
* Copyright 2015-2018 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+
* http://www.eclipse.org/legal/epl-v20.html
9+
*/
10+
11+
package example;
12+
13+
import static org.junit.jupiter.api.Assertions.assertEquals;
14+
import static org.junit.jupiter.api.Assertions.assertNull;
15+
import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT;
16+
import static org.junit.jupiter.api.parallel.ResourceAccessMode.READ;
17+
import static org.junit.jupiter.api.parallel.ResourceAccessMode.READ_WRITE;
18+
import static org.junit.jupiter.api.parallel.Resources.SYSTEM_PROPERTIES;
19+
20+
import java.util.Properties;
21+
22+
import org.junit.jupiter.api.AfterEach;
23+
import org.junit.jupiter.api.BeforeEach;
24+
import org.junit.jupiter.api.Test;
25+
import org.junit.jupiter.api.parallel.Execution;
26+
import org.junit.jupiter.api.parallel.ResourceLock;
27+
28+
// tag::user_guide[]
29+
@Execution(CONCURRENT)
30+
class SharedResourcesDemo {
31+
32+
private Properties backup;
33+
34+
@BeforeEach
35+
void backup() {
36+
backup = new Properties();
37+
backup.putAll(System.getProperties());
38+
}
39+
40+
@AfterEach
41+
void restore() {
42+
System.setProperties(backup);
43+
}
44+
45+
@Test
46+
@ResourceLock(value = SYSTEM_PROPERTIES, mode = READ)
47+
void customPropertyIsNotSetByDefault() {
48+
assertNull(System.getProperty("my.prop"));
49+
}
50+
51+
@Test
52+
@ResourceLock(value = SYSTEM_PROPERTIES, mode = READ_WRITE)
53+
void canSetCustomPropertyToFoo() {
54+
System.setProperty("my.prop", "foo");
55+
assertEquals("foo", System.getProperty("my.prop"));
56+
}
57+
58+
@Test
59+
@ResourceLock(value = SYSTEM_PROPERTIES, mode = READ_WRITE)
60+
void canSetCustomPropertyToBar() {
61+
System.setProperty("my.prop", "bar");
62+
assertEquals("bar", System.getProperty("my.prop"));
63+
}
64+
}
65+
// end::user_guide[]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
/*
2+
* Copyright 2015-2018 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+
* http://www.eclipse.org/legal/epl-v20.html
9+
*/
10+
11+
package example;
12+
13+
// tag::user_guide[]
14+
import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD;
15+
16+
import java.util.stream.IntStream;
17+
18+
import org.junit.jupiter.api.Disabled;
19+
import org.junit.jupiter.api.Test;
20+
import org.junit.jupiter.api.parallel.Execution;
21+
22+
@Disabled
23+
class SlowTests {
24+
25+
@Execution(SAME_THREAD)
26+
@Test
27+
void a() {
28+
foo();
29+
}
30+
31+
@Test
32+
void b() {
33+
foo();
34+
}
35+
36+
@Test
37+
void c() {
38+
foo();
39+
}
40+
41+
@Test
42+
void d() {
43+
foo();
44+
}
45+
46+
@Test
47+
void e() {
48+
foo();
49+
}
50+
51+
@Test
52+
void f() {
53+
foo();
54+
}
55+
56+
@Test
57+
void g() {
58+
foo();
59+
}
60+
61+
@Test
62+
void h() {
63+
foo();
64+
}
65+
66+
@Test
67+
void i() {
68+
foo();
69+
}
70+
71+
@Test
72+
void j() {
73+
foo();
74+
}
75+
76+
@Test
77+
void k() {
78+
foo();
79+
}
80+
81+
@Test
82+
void l() {
83+
foo();
84+
}
85+
86+
@Test
87+
void m() {
88+
foo();
89+
}
90+
91+
@Test
92+
void n() {
93+
foo();
94+
}
95+
96+
@Test
97+
void o() {
98+
foo();
99+
}
100+
101+
@Test
102+
void p() {
103+
foo();
104+
}
105+
106+
@Execution(SAME_THREAD)
107+
@Test
108+
void q() {
109+
foo();
110+
}
111+
112+
@Test
113+
void r() {
114+
foo();
115+
}
116+
117+
@Test
118+
void s() {
119+
foo();
120+
}
121+
122+
private void foo() {
123+
IntStream.range(1, 100_000_000).mapToDouble(i -> Math.pow(i, i)).map(Math::sqrt).max();
124+
}
125+
}
126+
// end::user_guide[]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
junit.jupiter.execution.parallel.enabled=true
2+
junit.jupiter.execution.parallel.config.strategy=fixed
3+
junit.jupiter.execution.parallel.config.fixed.parallelism=6

0 commit comments

Comments
 (0)