Skip to content

Commit

Permalink
Provide access to ConfigurationParameters via the TestPlan
Browse files Browse the repository at this point in the history
Prior to this commit, a TestExecutionListener did not have access to
JUnit Platform configuration parameters. This meant that a
TestExecutionListener could only be configured via JVM system
properties or some other mechanism outside standard JUnit Platform
features.

This commit introduces a new getConfigurationParameters() method in the
TestPlan which allows a TestExecutionListener to access access the
ConfigurationParameters via the TestPlan supplied to the
testPlanExecutionStarted(TestPlan) and
testPlanExecutionFinished(TestPlan) callback methods.

This commit also refactors UniqueIdTrackingListener to retrieve its
configuration from ConfigurationParameters instead of relying solely on
JVM system properties.

Closes #2631
  • Loading branch information
sbrannen committed Jun 2, 2021
1 parent 0d8be37 commit 9604d8e
Show file tree
Hide file tree
Showing 19 changed files with 178 additions and 105 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ on GitHub.

==== New Features and Improvements

* New `getConfigurationParameters()` method in the `TestPlan` which allows a
`TestExecutionListener` to access
<<../user-guide/index.adoc#running-tests-config-params, configuration parameters>>. See
<<../user-guide/index.adoc#launcher-api-listeners-config, Configuring an Execution
Listener>> for details.
* New `UniqueIdTrackingListener` which is a `TestExecutionListener` that tracks the unique
IDs of all tests that were executed during the `TestPlan` and generates a file
containing the unique IDs. The generated file can be used to rerun those tests -- for
Expand Down
16 changes: 15 additions & 1 deletion documentation/src/docs/asciidoc/user-guide/launcher-api.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ declared within the `/META-INF/services/org.junit.platform.launcher.PostDiscover
file is loaded and applied automatically.

[[launcher-api-launcher-session-listeners-custom]]
==== Plugging in your own Launcher Sessions Listeners
==== Plugging in your own Launcher Session Listeners

Registered implementations of `{LauncherSessionListener}` are notified when a
`{LauncherSession}` is opened, i.e. before a `{Launcher}` first discovers and executes
Expand Down Expand Up @@ -161,6 +161,20 @@ For example, an `example.CustomTestExecutionListener` class implementing
`/META-INF/services/org.junit.platform.launcher.TestExecutionListener` file is loaded and
registered automatically.

[[launcher-api-listeners-config]]
==== Configuring an Execution Listener

When a `{TestExecutionListener}` is registered programmatically via the `{Launcher}` API,
the listener may provide programmatic ways for it to be configured -- for example, via its
constructor, setter methods, etc. However, when a `TestExecutionListener` is registered
automatically via Java's `ServiceLoader` mechanism (see
<<launcher-api-listeners-custom>>), there is no way for the user to directly configure the
listener. In such cases, the author of a `TestExecutionListener` may choose to make the
listener configurable via <<running-tests-config-params, configuration parameters>>. The
listener can then access the configuration parameters via the `TestPlan` supplied to the
`testPlanExecutionStarted(TestPlan)` and `testPlanExecutionFinished(TestPlan)` callback
methods. See the `{UniqueIdTrackingListener}` for an example.

[[launcher-api-listeners-custom-deactivation]]
==== Deactivating Execution Listeners

Expand Down
4 changes: 2 additions & 2 deletions documentation/src/docs/asciidoc/user-guide/running-tests.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -699,8 +699,8 @@ infrastructure.

In addition to instructing the platform which test classes and test engines to include,
which packages to scan, etc., it is sometimes necessary to provide additional custom
configuration parameters that are specific to a particular test engine or registered
extension. For example, the JUnit Jupiter `TestEngine` supports _configuration
configuration parameters that are specific to a particular test engine, listener, or
registered extension. For example, the JUnit Jupiter `TestEngine` supports _configuration
parameters_ for the following use cases.

- <<writing-tests-test-instance-lifecycle-changing-default>>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@
* <p>For example, the JUnit Jupiter engine uses a configuration parameter to
* enable IDEs and build tools to deactivate conditional test execution.
*
* <p>As of JUnit Platform 1.8, configuration parameters are also made available to
* implementations of the {@link org.junit.platform.launcher.TestExecutionListener}
* API via the {@link org.junit.platform.launcher.TestPlan}.
*
* @see TestEngine
* @see EngineDiscoveryRequest
* @see ExecutionRequest
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,13 @@
* Jupiter engines comply with this contract, there is no way to guarantee this for
* third-party engines.
*
* <p>As of JUnit Platform 1.8, a {@code TestExecutionListener} can access
* {@linkplain org.junit.platform.engine.ConfigurationParameters configuration
* parameters} via the {@link TestPlan#getConfigurationParameters()
* getConfigurationParameters()} method in the {@code TestPlan} supplied to
* {@link #testPlanExecutionStarted(TestPlan)} and
* {@link #testPlanExecutionFinished(TestPlan)}.
*
* @since 1.0
* @see Launcher
* @see TestPlan
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import static java.util.Collections.unmodifiableSet;
import static org.apiguardian.api.API.Status.DEPRECATED;
import static org.apiguardian.api.API.Status.INTERNAL;
import static org.apiguardian.api.API.Status.MAINTAINED;
import static org.apiguardian.api.API.Status.STABLE;

import java.util.Collection;
Expand All @@ -28,6 +29,7 @@
import org.apiguardian.api.API;
import org.junit.platform.commons.PreconditionViolationException;
import org.junit.platform.commons.util.Preconditions;
import org.junit.platform.engine.ConfigurationParameters;
import org.junit.platform.engine.TestDescriptor;
import org.junit.platform.engine.TestDescriptor.Visitor;
import org.junit.platform.engine.UniqueId;
Expand Down Expand Up @@ -66,6 +68,8 @@ public class TestPlan {

private final boolean containsTests;

private final ConfigurationParameters configurationParameters;

/**
* Construct a new {@code TestPlan} from the supplied collection of
* {@link TestDescriptor TestDescriptors}.
Expand All @@ -75,20 +79,26 @@ public class TestPlan {
*
* @param engineDescriptors the engine test descriptors from which the test
* plan should be created; never {@code null}
* @param configurationParameters the {@code ConfigurationParameters} for
* this test plan; never {@code null}
* @return a new test plan
*/
@API(status = INTERNAL, since = "1.0")
public static TestPlan from(Collection<TestDescriptor> engineDescriptors) {
public static TestPlan from(Collection<TestDescriptor> engineDescriptors,
ConfigurationParameters configurationParameters) {
Preconditions.notNull(engineDescriptors, "Cannot create TestPlan from a null collection of TestDescriptors");
TestPlan testPlan = new TestPlan(engineDescriptors.stream().anyMatch(TestDescriptor::containsTests));
Preconditions.notNull(configurationParameters, "Cannot create TestPlan from null ConfigurationParameters");
TestPlan testPlan = new TestPlan(engineDescriptors.stream().anyMatch(TestDescriptor::containsTests),
configurationParameters);
Visitor visitor = descriptor -> testPlan.add(TestIdentifier.from(descriptor));
engineDescriptors.forEach(engineDescriptor -> engineDescriptor.accept(visitor));
return testPlan;
}

@API(status = INTERNAL, since = "1.4")
protected TestPlan(boolean containsTests) {
protected TestPlan(boolean containsTests, ConfigurationParameters configurationParameters) {
this.containsTests = containsTests;
this.configurationParameters = configurationParameters;
}

/**
Expand Down Expand Up @@ -222,4 +232,15 @@ public boolean containsTests() {
return containsTests;
}

/**
* Get the {@link ConfigurationParameters} for this test plan.
*
* @return the configuration parameters; never {@code null}
* @since 1.8
*/
@API(status = MAINTAINED, since = "1.8")
public ConfigurationParameters getConfigurationParameters() {
return this.configurationParameters;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,13 @@ class InternalTestPlan extends TestPlan {
private final TestPlan delegate;

static InternalTestPlan from(LauncherDiscoveryResult discoveryResult) {
TestPlan delegate = TestPlan.from(discoveryResult.getEngineTestDescriptors());
TestPlan delegate = TestPlan.from(discoveryResult.getEngineTestDescriptors(),
discoveryResult.getConfigurationParameters());
return new InternalTestPlan(discoveryResult, delegate);
}

private InternalTestPlan(LauncherDiscoveryResult discoveryResult, TestPlan delegate) {
super(delegate.containsTests());
super(delegate.containsTests(), delegate.getConfigurationParameters());
this.discoveryResult = discoveryResult;
this.delegate = delegate;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import org.junit.platform.commons.logging.Logger;
import org.junit.platform.commons.logging.LoggerFactory;
import org.junit.platform.commons.util.StringUtils;
import org.junit.platform.engine.ConfigurationParameters;
import org.junit.platform.engine.TestExecutionResult;
import org.junit.platform.launcher.TestExecutionListener;
import org.junit.platform.launcher.TestIdentifier;
Expand Down Expand Up @@ -56,11 +57,11 @@
* is {@code OUTPUT_DIR}/{@code OUTPUT_FILE}.
*
* <p>The name of the {@code OUTPUT_FILE} defaults to {@link #DEFAULT_FILE_NAME},
* but a custom file name can be configured via the {@link #OUTPUT_FILE_PROPERTY_NAME}
* JVM system property.
* but a custom file name can be set via the {@link #OUTPUT_FILE_PROPERTY_NAME}
* configuration property.
*
* <p>The {@code OUTPUT_DIR} can be set to a custom directory via the
* {@link #OUTPUT_DIR_PROPERTY_NAME} JVM system property. Otherwise the following
* {@link #OUTPUT_DIR_PROPERTY_NAME} configuration property. Otherwise the following
* algorithm is used to select a default output directory.
*
* <ul>
Expand All @@ -78,6 +79,10 @@
* by this listener would be {@code ./build/junit-platform-unique-test-ids.txt}
* by default.
*
* <p>Configuration properties can be set via JVM system properties, via a
* {@code junit-platform.properties} file in the root of the classpath, or as
* JUnit Platform {@linkplain ConfigurationParameters configuration parameters}.
*
* @since 1.8
*/
@API(status = EXPERIMENTAL, since = "1.8")
Expand Down Expand Up @@ -121,10 +126,11 @@ public class UniqueIdTrackingListener implements TestExecutionListener {

private final List<String> uniqueIds = new ArrayList<>();

private final boolean enabled;
private boolean enabled;

public UniqueIdTrackingListener() {
this.enabled = Boolean.getBoolean(LISTENER_ENABLED_PROPERTY_NAME);
@Override
public void testPlanExecutionStarted(TestPlan testPlan) {
this.enabled = testPlan.getConfigurationParameters().getBoolean(LISTENER_ENABLED_PROPERTY_NAME).orElse(false);
}

@Override
Expand All @@ -139,7 +145,7 @@ public void testPlanExecutionFinished(TestPlan testPlan) {
if (this.enabled) {
Path outputFile;
try {
outputFile = getOutputFile();
outputFile = getOutputFile(testPlan.getConfigurationParameters());
}
catch (IOException ex) {
logger.error(ex, () -> "Failed to create output file");
Expand All @@ -158,9 +164,9 @@ public void testPlanExecutionFinished(TestPlan testPlan) {
}
}

private Path getOutputFile() throws IOException {
String filename = System.getProperty(OUTPUT_FILE_PROPERTY_NAME, DEFAULT_FILE_NAME);
Path outputFile = getOutputDir().resolve(filename);
private Path getOutputFile(ConfigurationParameters configurationParameters) throws IOException {
String filename = configurationParameters.get(OUTPUT_FILE_PROPERTY_NAME).orElse(DEFAULT_FILE_NAME);
Path outputFile = getOutputDir(configurationParameters).resolve(filename);

if (Files.exists(outputFile)) {
Files.delete(outputFile);
Expand All @@ -171,11 +177,11 @@ private Path getOutputFile() throws IOException {
return outputFile;
}

Path getOutputDir() throws IOException {
Path getOutputDir(ConfigurationParameters configurationParameters) throws IOException {
Path cwd = currentWorkingDir();
Path outputDir;

String customDir = System.getProperty(OUTPUT_DIR_PROPERTY_NAME);
String customDir = configurationParameters.get(OUTPUT_DIR_PROPERTY_NAME).orElse(null);
if (StringUtils.isNotBlank(customDir)) {
outputDir = cwd.resolve(customDir);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,20 @@
package org.junit.platform.launcher;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;

import java.util.Set;

import org.junit.jupiter.api.Test;
import org.junit.platform.engine.ConfigurationParameters;
import org.junit.platform.engine.UniqueId;
import org.junit.platform.engine.support.descriptor.AbstractTestDescriptor;
import org.junit.platform.engine.support.descriptor.EngineDescriptor;

class TestPlanTests {

private final ConfigurationParameters configParams = mock(ConfigurationParameters.class);

private EngineDescriptor engineDescriptor = new EngineDescriptor(UniqueId.forEngine("foo"), "Foo");

@Test
Expand All @@ -33,7 +37,7 @@ public Type getType() {
}
});

var testPlan = TestPlan.from(Set.of(engineDescriptor));
var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams);

assertThat(testPlan.containsTests()).as("contains tests").isFalse();
}
Expand All @@ -48,7 +52,7 @@ public Type getType() {
}
});

var testPlan = TestPlan.from(Set.of(engineDescriptor));
var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams);

assertThat(testPlan.containsTests()).as("contains tests").isTrue();
}
Expand All @@ -68,8 +72,9 @@ public boolean mayRegisterTests() {
}
});

var testPlan = TestPlan.from(Set.of(engineDescriptor));
var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams);

assertThat(testPlan.containsTests()).as("contains tests").isTrue();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.junit.jupiter.api.fixtures.TrackLogRecords;
import org.junit.platform.commons.logging.LogRecordListener;
import org.junit.platform.commons.util.ReflectionUtils;
import org.junit.platform.engine.ConfigurationParameters;
import org.junit.platform.engine.TestExecutionResult;
import org.junit.platform.engine.UniqueId;
import org.junit.platform.engine.reporting.ReportEntry;
Expand Down Expand Up @@ -91,7 +92,8 @@ void shouldNotThrowExceptionButLogIfTesPlanExecutionStartedListenerMethodFails(
LogRecordListener logRecordListener) {
var testDescriptor = getDemoMethodTestDescriptor();

compositeTestExecutionListener().testPlanExecutionStarted(TestPlan.from(Set.of(testDescriptor)));
compositeTestExecutionListener().testPlanExecutionStarted(
TestPlan.from(Set.of(testDescriptor), mock(ConfigurationParameters.class)));

assertThatTestListenerErrorLogged(logRecordListener, ThrowingTestExecutionListener.class,
"testPlanExecutionStarted");
Expand All @@ -102,7 +104,8 @@ void shouldNotThrowExceptionButLogIfTesPlanExecutionFinishedListenerMethodFails(
LogRecordListener logRecordListener) {
var testDescriptor = getDemoMethodTestDescriptor();

compositeTestExecutionListener().testPlanExecutionFinished(TestPlan.from(Set.of(testDescriptor)));
compositeTestExecutionListener().testPlanExecutionFinished(
TestPlan.from(Set.of(testDescriptor), mock(ConfigurationParameters.class)));

assertThatTestListenerErrorLogged(logRecordListener, ThrowingTestExecutionListener.class,
"testPlanExecutionFinished");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import org.junit.jupiter.api.Test;
import org.junit.platform.commons.util.ReflectionUtils;
import org.junit.platform.engine.ConfigurationParameters;
import org.junit.platform.engine.TestDescriptor;
import org.junit.platform.engine.TestEngine;
import org.junit.platform.engine.UniqueId;
Expand All @@ -35,7 +36,8 @@ class ExecutionListenerAdapterTests {
void testReportingEntryPublished() {
var testDescriptor = getSampleMethodTestDescriptor();

var discoveryResult = new LauncherDiscoveryResult(Map.of(mock(TestEngine.class), testDescriptor), null);
var discoveryResult = new LauncherDiscoveryResult(Map.of(mock(TestEngine.class), testDescriptor),
mock(ConfigurationParameters.class));
var testPlan = InternalTestPlan.from(discoveryResult);
var testIdentifier = testPlan.getTestIdentifier(testDescriptor.getUniqueId().toString());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.platform.commons.test.ConcurrencyTestingUtils.executeConcurrently;
import static org.mockito.Mockito.mock;

import java.io.PrintWriter;
import java.io.StringWriter;
Expand All @@ -24,6 +25,7 @@

import org.junit.jupiter.api.RepeatedTest;
import org.junit.jupiter.api.Test;
import org.junit.platform.engine.ConfigurationParameters;
import org.junit.platform.engine.TestExecutionResult;
import org.junit.platform.engine.TestSource;
import org.junit.platform.engine.UniqueId;
Expand All @@ -38,7 +40,7 @@
class SummaryGenerationTests {

private final SummaryGeneratingListener listener = new SummaryGeneratingListener();
private final TestPlan testPlan = TestPlan.from(List.of());
private final TestPlan testPlan = TestPlan.from(List.of(), mock(ConfigurationParameters.class));

@Test
void emptyReport() {
Expand Down

0 comments on commit 9604d8e

Please sign in to comment.