Skip to content

Add support for parallel test execution #1461

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 89 commits into from
Closed
Show file tree
Hide file tree
Changes from 88 commits
Commits
Show all changes
89 commits
Select commit Hold shift + click to select a range
72bb365
Prototype implementation of parallel test execution
marcphilipp Nov 9, 2017
836bec8
Introduce TestTask parameter object
marcphilipp Nov 9, 2017
975e75b
Fix compiler warnings for AutoCloseable.close()
marcphilipp Nov 9, 2017
ff5027c
Apply spotless
marcphilipp Nov 9, 2017
a6948be
Make TreePrintingListener thread-safe
sormuras Nov 12, 2017
aa38916
Merge branch 'master' into experiments/parallel-execution
marcphilipp Jan 2, 2018
eabacfb
Prototype implementation of declarative read/write resource locks
marcphilipp Jan 2, 2018
bea2ee3
Introduce value object instead of using annotation directly
marcphilipp Jan 2, 2018
897600e
Refactor CompositeLock into specialized locks, ...
leonard84 Jan 8, 2018
f613910
Implement Node.getExecutionMode() in Jupiter, fix build
marcphilipp Jan 15, 2018
4599111
WIP from session with @leonard84 on 2018-01-15
marcphilipp Jan 15, 2018
05637ec
Introduce NodeWalker to compute execution plan
marcphilipp Jan 29, 2018
c289ce2
Implement HierarchicalTestExecutorService based on work of NodeWalker
marcphilipp Jan 29, 2018
02cd8a4
Make LogRecordListener thread-safe
marcphilipp Jan 29, 2018
7b88e58
Make XmlReportAssertions thread-safe
marcphilipp Jan 29, 2018
e4c19d7
Make tests for CloseableResource thread-safe
marcphilipp Jan 29, 2018
e246821
Merge branch 'master' into experiments/parallel-execution
marcphilipp Jan 29, 2018
d50500e
Update copyright for 2018
marcphilipp Jan 29, 2018
f86830d
A few minor refactorings
marcphilipp Jan 29, 2018
9fa34dc
Try to stabilize CI builds
marcphilipp Jan 30, 2018
e82d519
Make parallelism level configurable
marcphilipp Jan 30, 2018
5ca5bfb
Increase parallelism by using ForkJoinTask API
marcphilipp Feb 1, 2018
4c96a22
Use ForkJoinPool constructor available in Java >= 9
marcphilipp Feb 4, 2018
d82a791
Optimize thread usage for nodes with only one child
marcphilipp Feb 19, 2018
1730fe7
Use @TestFactory's execution mode for dynamic tests/containers
marcphilipp Feb 20, 2018
44a0764
Merge branch 'master' into experiments/parallel-execution
marcphilipp Mar 10, 2018
9a39995
Introduce StreamInterceptor
marcphilipp Mar 10, 2018
484b93a
Intercept and report stdout and stderr in DefaultLauncher
marcphilipp Mar 10, 2018
366f11b
Fix successfulParallelTest in ParallelExecutionTests on Windows
sormuras Mar 10, 2018
6b5ccae
Report stdout/stderr for containers
marcphilipp Mar 11, 2018
4394e38
Another attempt to fix test on Windows
marcphilipp Mar 11, 2018
7478c7d
Use single ByteArrayOutputStream per thread
marcphilipp Mar 11, 2018
780cc3c
Yet another attempt to fix test on Windows
marcphilipp Mar 11, 2018
a1e0244
fixup! Yet another attempt to fix test on Windows
marcphilipp Mar 11, 2018
0614d75
Keep track of marked positions in RewindableByteArrayOutputStream
marcphilipp Mar 13, 2018
086540c
Introduce ParallelExecutionConfiguration and strategy implementations
marcphilipp Apr 16, 2018
6a35800
Merge remote-tracking branch 'origin/master' into experiments/paralle…
marcphilipp Apr 29, 2018
ddb00b4
Polishing
marcphilipp Apr 29, 2018
6165445
Introduce PrefixedConfigurationParameters to avoid passing around prefix
marcphilipp Apr 29, 2018
f2f1839
Merge branch 'master' into experiments/parallel-execution
marcphilipp May 31, 2018
89dc94c
Use Mockito.never() instead of times(0)
marcphilipp May 31, 2018
fb8afcb
Resolve TODO wrt. exception handling
marcphilipp May 31, 2018
416e522
Fix JarDescribeModuleTests
marcphilipp May 31, 2018
2680502
Use ConcurrentHashMap instead of synchronizedMap()
marcphilipp May 31, 2018
bb04198
Let NodeExecutor implement TestTask directly
marcphilipp May 31, 2018
3c3de47
Rename NodeExecutor to NodeTestTask
marcphilipp May 31, 2018
db71118
Move ExclusiveTask into ExecutorService
marcphilipp May 31, 2018
3bb0e1f
Reduce visibility
marcphilipp May 31, 2018
aadfb04
Prefer write locks over read locks for same resource
marcphilipp May 31, 2018
c55e84a
Unify error handling for converting config params
marcphilipp May 31, 2018
5788ce4
Polishing
marcphilipp May 31, 2018
7ae9a49
Merge branch 'master' into experiments/parallel-execution
marcphilipp Jun 1, 2018
85e510c
Add unit tests for CompositeLock and SingleLock
marcphilipp Jun 1, 2018
02c51c9
Fail dynamic tests that declare exclusive resources
marcphilipp Jun 1, 2018
12f46d2
Move annotations to control parallel execution into Jupiter API
marcphilipp Jun 1, 2018
b9d194c
Add locking integration tests
marcphilipp Jun 1, 2018
e632773
Delete dead code
marcphilipp Jun 1, 2018
2993587
Ensure root task is submitted to ForkJoinPool
marcphilipp Jun 1, 2018
d04c31d
Make stream capturing an opt-in feature
marcphilipp Jun 1, 2018
2a401e4
Remove unnecessary system property
marcphilipp Jun 1, 2018
c4d5a60
Polishing
marcphilipp Jun 1, 2018
b4d6c2b
Polish NodeTestTask
marcphilipp Jun 1, 2018
b4ba8f8
Fix flaky test
marcphilipp Jun 2, 2018
5dc6f44
Add integration test for StreamInterceptingTestExecutionListener
marcphilipp Jun 2, 2018
0124642
Delete unused method
marcphilipp Jun 2, 2018
6b603d3
Make enums top-level again
marcphilipp Jun 2, 2018
610034b
Document parallel execution in User Guide
marcphilipp Jun 2, 2018
f5277d1
Make enum constant upper-case to follow convention
marcphilipp Jun 2, 2018
ae3cf88
Let Node return Set of resources because engines cannot force order
marcphilipp Jun 3, 2018
dfd285b
Use Comparator instead of implementing Comparable
marcphilipp Jun 3, 2018
8c05cbd
Only query Node.getExclusiveResources() once
marcphilipp Jun 3, 2018
43aac26
Ensure stream interceptor is called before any other listener
marcphilipp Jun 3, 2018
f7de513
Introduce Resources to collect common constants
marcphilipp Jun 3, 2018
c5706f8
Document output capturing in User Guide
marcphilipp Jun 3, 2018
77228bd
Document which output is captured
marcphilipp Jun 3, 2018
d835388
First batch of Javadoc
marcphilipp Jun 3, 2018
c45aade
Second batch of Javadoc
marcphilipp Jun 7, 2018
de46321
Reduce surface area and document ReleaseLock
marcphilipp Jun 8, 2018
ef26df9
More Javadoc
marcphilipp Jun 8, 2018
c480701
Final batch of Javadocs
marcphilipp Jun 8, 2018
cdf9f93
Merge remote-tracking branch 'origin/master' into experiments/paralle…
marcphilipp Jun 10, 2018
b7a6bec
Rename launcher config properties
marcphilipp Jun 12, 2018
de69c49
Fix Javadoc and check additional preconditions
marcphilipp Jun 12, 2018
650fa52
Use ThreadLocal instead of CopyOnWriteArrayList
marcphilipp Jun 21, 2018
4bf6044
Extract method for separate steps
marcphilipp Jun 21, 2018
ba7fb76
Check that locks are acquired in order
marcphilipp Jun 21, 2018
4b40b64
Check lock class in unit tests
marcphilipp Jun 21, 2018
135a07d
Use enum singleton pattern to cache Schema
marcphilipp Jun 22, 2018
61441bb
Rename UseResource to ResourceLock
marcphilipp Jun 22, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions documentation/src/docs/asciidoc/link-attributes.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ endif::[]
:ConsoleLauncher: {javadoc-root}/org/junit/platform/console/ConsoleLauncher.html[ConsoleLauncher]
//
:DiscoverySelectors_selectMethod: {javadoc-root}/org/junit/platform/engine/discovery/DiscoverySelectors.html#selectMethod-java.lang.String-[selectMethod(String) in DiscoverySelectors]
:HierarchicalTestEngine: {javadoc-root}/org/junit/platform/engine/support/hierarchical/HierarchicalTestEngine.html[HierarchicalTestEngine]
:ParallelExecutionConfigurationStrategy: {javadoc-root}/org/junit/platform/engine/support/hierarchical/ParallelExecutionConfigurationStrategy.html[ParallelExecutionConfigurationStrategy]
:TestEngine: {javadoc-root}/org/junit/platform/engine/TestEngine.html[TestEngine]
//
:Launcher: {javadoc-root}/org/junit/platform/launcher/Launcher.html[Launcher]
Expand Down Expand Up @@ -54,6 +56,7 @@ endif::[]
:EnabledIfSystemProperty: {javadoc-root}/org/junit/jupiter/api/condition/EnabledIfSystemProperty.html[@EnabledIfSystemProperty]
:EnabledOnJre: {javadoc-root}/org/junit/jupiter/api/condition/EnabledOnJre.html[@EnabledOnJre]
:EnabledOnOs: {javadoc-root}/org/junit/jupiter/api/condition/EnabledOnOs.html[@EnabledOnOs]
:Execution: {javadoc-root}/org/junit/jupiter/api/parallel/Execution.html[@Execution]
:ExecutionCondition: {javadoc-root}/org/junit/jupiter/api/extension/ExecutionCondition.html[ExecutionCondition]
:ExtendWith: {javadoc-root}/org/junit/jupiter/api/extension/ExtendWith.html[@ExtendWith]
:ExtensionContext: {javadoc-root}/org/junit/jupiter/api/extension/ExtensionContext.html[ExtensionContext]
Expand All @@ -70,6 +73,7 @@ endif::[]
:TestTemplate: {javadoc-root}/org/junit/jupiter/api/TestTemplate.html[@TestTemplate]
:TestTemplateInvocationContext: {javadoc-root}/org/junit/jupiter/api/extension/TestTemplateInvocationContext.html[TestTemplateInvocationContext]
:TestTemplateInvocationContextProvider: {javadoc-root}/org/junit/jupiter/api/extension/TestTemplateInvocationContextProvider.html[TestTemplateInvocationContextProvider]
:UseResource: {javadoc-root}/org/junit/jupiter/api/parallel/UseResource.html[@UseResource]
//
:DisabledCondition: {current-branch}/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/DisabledCondition.java[DisabledCondition]
:RepetitionInfoParameterResolver: {current-branch}/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/RepetitionInfoParameterResolver.java[RepetitionInfoParameterResolver]
Expand Down
5 changes: 5 additions & 0 deletions documentation/src/docs/asciidoc/user-guide/launcher-api.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,11 @@ is currently supported via Java's `java.util.ServiceLoader` mechanism. For examp
in a file named `org.junit.platform.engine.TestEngine` within the `/META-INF/services` in
the `junit-jupiter-engine` JAR.

NOTE: `{HierarchicalTestEngine}` is a convenient abstract base implementation (used by
the `{junit-jupiter-engine}`) that only requires implementors to provide the logic for
test discovery. It implements execution of `TestDescriptors` that implement the `Node`
interface, including support for parallel execution.

[[launcher-api-listeners-custom]]
==== Plugging in Your Own Test Execution Listeners

Expand Down
25 changes: 25 additions & 0 deletions documentation/src/docs/asciidoc/user-guide/running-tests.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -755,3 +755,28 @@ useful.
| +(micro \| integration) & (foo \| baz)+
| all _micro_ or _integration_ tests for *foo* or *baz*
|===

[[running-tests-capturing-output]]
=== Capturing Standard Output/Error

Since version 1.3, the JUnit Platform provides opt-in support for capturing output
printed to `System.out` and `System.err`. To enable it, simply set the
`junit.platform.output.capture.stdout` and/or `junit.platform.output.capture.stderr`
<<running-tests-config-params, configuration parameter>> to `true`. In addition, you may
configure the maximum number of buffered bytes to be used per executed test or container
using `junit.platform.output.capture.maxBuffer`.

If enabled, the JUnit Platform captures the corresponding output and publishes it as a
report entry using the `stdout` or `stderr` keys to all registered
`{TestExecutionListener}` instances immediately before reporting the test or container as
finished.

Please note that the captured output will only contain output emitted by the thread that
was used to execute a container or test. Any output by other threads will be omitted
because particularly when
<<writing-tests-parallel-execution, executing tests in parallel>> it would be impossible
to attribute it to a specific test or container.

WARNING: Capturing output is currently an _experimental_ feature. You're invited to give
it a try and provide feedback to the JUnit team so they can improve and eventually
<<api-evolution, promote>> this feature.
87 changes: 87 additions & 0 deletions documentation/src/docs/asciidoc/user-guide/writing-tests.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -1290,3 +1290,90 @@ The last method generates a nested hierarchy of dynamic tests utilizing
----
include::{testDir}/example/DynamicTestsDemo.java[tags=user_guide]
----


[[writing-tests-parallel-execution]]
=== Parallel Execution

By default, JUnit Jupiter tests are run sequentially in a single thread. Running tests in
parallel, e.g. to speed up execution, is available as an opt-in feature since version 5.3.
To enable parallel execution, simply set the set the
`junit.jupiter.execution.parallel.enabled` configuration parameter to `true`, e.g. in `junit-platform.properties` (see <<running-tests-config-params>> for other options).

Once enabled, the JUnit Jupiter engine will execute tests on all levels of the test tree
fully in parallel according to the provided
<<writing-tests-parallel-execution-config, configuration>> while observing the declarative
<<writing-tests-parallel-execution-synchronization, synchronization>> mechanisms. Please
note that the <<running-tests-capturing-output>> feature needs to enabled separately.

WARNING: Parallel test execution is currently an _experimental_ feature. You're invited
to give it a try and provide feedback to the JUnit team so they can improve and
eventually <<api-evolution, promote>> this feature.

[[writing-tests-parallel-execution-config]]
==== Configuration

Properties like the desired parallelism and the maximum pool size can be configured using
a `{ParallelExecutionConfigurationStrategy}`. The JUnit Platform provides two
implementations out of the box: `dynamic` and `fixed`. Alternatively, you may implement a
`custom` strategy.

To select a strategy, simply set the `junit.jupiter.execution.parallel.config.strategy`
configuration parameter to one of the following options:

`dynamic`::
Computes the desired parallelism based on the number of available processors/cores
multiplied by the `junit.jupiter.execution.parallel.config.dynamic.factor`
configuration parameter (defaults to `1`).

`fixed`::
Uses the mandatory `junit.jupiter.execution.parallel.config.fixed.parallelism`
configuration parameter as desired parallelism.

`custom`::
Allows to specify a custom `{ParallelExecutionConfigurationStrategy}` implementation via
the mandatory `junit.jupiter.execution.parallel.config.custom.class` configuration
parameter to determine the desired configuration.

If no configuration strategy is set, JUnit Jupiter uses the `dynamic` configuration
strategy with a factor of 1, i.e. the desired parallelism will equal the number of
available processors/cores.

[[writing-tests-parallel-execution-synchronization]]
==== Synchronization

In the `org.junit.jupiter.api.parallel` package, JUnit Jupiter provides two
annotation-based declarative mechanisms to change the execution mode and allow for
synchronization when using shared resources in different tests.

If parallel execution is enabled, all classes and methods are executed concurrently by
default. You can change the execution mode for the annotated element and its subelements
(if any) by using the `{Execution}` annotation. The following two modes are available:

`SAME_THREAD`::
Force execution in the same thread used by the parent. For example, when used on a test
method, the test method will be executed in the same thread as any `@BeforeAll` or
`@AfterAll` methods of the containing test class.

`CONCURRENT`::
Execute concurrently unless a resource constraint forces execution in the same thread.

In addition, the `{UseResource}` annotation allows to declare that a test class or
method uses a specific shared resource that requires synchronized access to ensure
reliable test execution.

If the tests in the following example were run in parallel they would be flaky, i.e.
sometimes pass and other times fail, because of the inherent race condition of
writing and then reading the same system property.

[source,java]
----
include::{testDir}/example/SharedResourcesDemo.java[tags=user_guide]
----

When access to shared resources is declared using this annotation, the JUnit Jupiter
engine uses this information to ensure that no conflicting tests are run in parallel.

In addition to the string that uniquely identifies the used resource, you may specify an
access mode. Two tests that require `READ` access to a resource may run in parallel with
each other but not while any other test that requires `READ_WRITE` access is running.
65 changes: 65 additions & 0 deletions documentation/src/test/java/example/SharedResourcesDemo.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* Copyright 2015-2018 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* http://www.eclipse.org/legal/epl-v20.html
*/

package example;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT;
import static org.junit.jupiter.api.parallel.ResourceAccessMode.READ;
import static org.junit.jupiter.api.parallel.ResourceAccessMode.READ_WRITE;
import static org.junit.jupiter.api.parallel.Resources.SYSTEM_PROPERTIES;

import java.util.Properties;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.parallel.Execution;
import org.junit.jupiter.api.parallel.UseResource;

// tag::user_guide[]
@Execution(CONCURRENT)
class SharedResourcesDemo {

private Properties backup;

@BeforeEach
void backup() {
backup = new Properties();
backup.putAll(System.getProperties());
}

@AfterEach
void restore() {
System.setProperties(backup);
}

@Test
@UseResource(value = SYSTEM_PROPERTIES, mode = READ)
void customPropertyIsNotSetByDefault() {
assertNull(System.getProperty("my.prop"));
}

@Test
@UseResource(value = SYSTEM_PROPERTIES, mode = READ_WRITE)
void canSetCustomPropertyToFoo() {
System.setProperty("my.prop", "foo");
assertEquals("foo", System.getProperty("my.prop"));
}

@Test
@UseResource(value = SYSTEM_PROPERTIES, mode = READ_WRITE)
void canSetCustomPropertyToBar() {
System.setProperty("my.prop", "bar");
assertEquals("bar", System.getProperty("my.prop"));
}
}
// end::user_guide[]
126 changes: 126 additions & 0 deletions documentation/src/test/java/example/SlowTests.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
/*
* Copyright 2015-2018 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* http://www.eclipse.org/legal/epl-v20.html
*/

package example;

// tag::user_guide[]
import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD;

import java.util.stream.IntStream;

import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.parallel.Execution;

@Disabled
class SlowTests {

@Execution(SAME_THREAD)
@Test
void a() {
foo();
}

@Test
void b() {
foo();
}

@Test
void c() {
foo();
}

@Test
void d() {
foo();
}

@Test
void e() {
foo();
}

@Test
void f() {
foo();
}

@Test
void g() {
foo();
}

@Test
void h() {
foo();
}

@Test
void i() {
foo();
}

@Test
void j() {
foo();
}

@Test
void k() {
foo();
}

@Test
void l() {
foo();
}

@Test
void m() {
foo();
}

@Test
void n() {
foo();
}

@Test
void o() {
foo();
}

@Test
void p() {
foo();
}

@Execution(SAME_THREAD)
@Test
void q() {
foo();
}

@Test
void r() {
foo();
}

@Test
void s() {
foo();
}

private void foo() {
IntStream.range(1, 100_000_000).mapToDouble(i -> Math.pow(i, i)).map(Math::sqrt).max();
}
}
// end::user_guide[]
3 changes: 3 additions & 0 deletions documentation/src/test/resources/junit-platform.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
junit.jupiter.execution.parallel.enabled=true
junit.jupiter.execution.parallel.config.strategy=fixed
junit.jupiter.execution.parallel.config.fixed.parallelism=6
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright 2015-2018 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* http://www.eclipse.org/legal/epl-v20.html
*/

package org.junit.jupiter.api.parallel;

import static org.apiguardian.api.API.Status.EXPERIMENTAL;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.apiguardian.api.API;

/**
* {@code @Execution} is used to configure the parallel execution mode of a test
* class or test method.
*
* @see Resources
* @see ResourceAccessMode
* @see UseResources
* @since 5.3
*/
@API(status = EXPERIMENTAL, since = "5.3")
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
public @interface Execution {

/**
* The required/preferred execution mode.
*
* @see ExecutionMode
*/
ExecutionMode value();

}
Loading