From 8b71ee63e734d1dc46231c8ca0ab28627c56d5d2 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Wed, 21 Oct 2020 20:03:12 +0200 Subject: [PATCH] Add possibility to register discovery listeners via SPI `LauncherDiscoveryListeners` can now be registered via Java's `ServiceLoader` mechanism in addition to those passed as part of each `LauncherDiscoveryRequest` and the default one. Whether discovery listeners are picked up via ServiceLoader is configurable via `LauncherConfig` (defaults to true). Closes #2457. Related to #201. Co-authored-by: Marc Philipp --- .../release-notes/release-notes-5.8.0-M1.adoc | 2 + .../asciidoc/user-guide/launcher-api.adoc | 23 +++++-- .../launcher/core/DefaultLauncher.java | 23 +++++-- .../launcher/core/DefaultLauncherConfig.java | 14 +++-- .../core/EngineDiscoveryOrchestrator.java | 20 +++++- .../launcher/core/LauncherConfig.java | 50 ++++++++++++--- .../core/LauncherDiscoveryRequestBuilder.java | 3 +- .../launcher/core/LauncherFactory.java | 29 ++++++++- ...aderLauncherDiscoveryListenerRegistry.java | 38 ++++++++++++ .../module-info.java | 2 + .../testkit/engine/EngineTestKit.java | 2 +- .../TestLauncherDiscoveryListener.java | 30 +++++++++ .../launcher/core/LauncherConfigTests.java | 18 ++++++ .../launcher/core/LauncherFactoryTests.java | 62 ++++++++++++++++--- ...latform.launcher.LauncherDiscoveryListener | 1 + ...it.platform.launcher.TestExecutionListener | 0 .../junit-platform.properties | 0 .../junit-platform-launcher.expected.txt | 1 + .../projects/standalone/expected-err.txt | 2 + 19 files changed, 277 insertions(+), 43 deletions(-) create mode 100644 junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ServiceLoaderLauncherDiscoveryListenerRegistry.java create mode 100644 platform-tests/src/test/java/org/junit/platform/launcher/TestLauncherDiscoveryListener.java create mode 100644 platform-tests/src/test/resources/testservices/META-INF/services/org.junit.platform.launcher.LauncherDiscoveryListener rename platform-tests/src/test/resources/{ => testservices}/META-INF/services/org.junit.platform.launcher.TestExecutionListener (100%) rename platform-tests/src/test/resources/{ => testservices}/junit-platform.properties (100%) diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.8.0-M1.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.8.0-M1.adoc index e5d86fe33fbe..45f2961664e3 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-5.8.0-M1.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.8.0-M1.adoc @@ -34,6 +34,8 @@ on GitHub. `my test`. * The `junit-platform-jfr` module now also reports events for containers, e.g. test classes. +* Custom `LauncherDiscoveryListener` implementations can now be registered via Java’s + `ServiceLoader` mechanism. [[release-notes-5.8.0-M1-junit-jupiter]] diff --git a/documentation/src/docs/asciidoc/user-guide/launcher-api.adoc b/documentation/src/docs/asciidoc/user-guide/launcher-api.adoc index a1964e34e59c..c22b70ef12e2 100644 --- a/documentation/src/docs/asciidoc/user-guide/launcher-api.adoc +++ b/documentation/src/docs/asciidoc/user-guide/launcher-api.adoc @@ -112,24 +112,39 @@ In addition to specifying post-discovery filters as part of a `{LauncherDiscover passed to the `{Launcher}` API, by default custom `{PostDiscoveryFilter}` implementations will be discovered at runtime via Java's `java.util.ServiceLoader` mechanism and automatically applied by the `Launcher` in addition to those that are part of the request. + For example, an `example.CustomTagFilter` class implementing `{PostDiscoveryFilter}` and declared within the `/META-INF/services/org.junit.platform.launcher.PostDiscoveryFilter` file is loaded and applied automatically. +[[launcher-api-launcher-discovery-listeners-custom]] +==== Plugging in your own Discovery Listeners + +In addition to specifying post-discovery filters as part of a `{LauncherDiscoveryRequest}` +passed to the `{Launcher}` API, custom `{LauncherDiscoveryListener}` implementations can +be discovered at runtime via Java's `java.util.ServiceLoader` mechanism and automatically +automatically registered with the `Launcher` created via the `LauncherFactory`. + +For example, an `example.CustomLauncherDiscoveryListener` class implementing +`{LauncherDiscoveryListener}` and declared within the +`/META-INF/services/org.junit.platform.launcher.LauncherDiscoveryListener` file is loaded +and applied automatically. + [[launcher-api-listeners-custom]] -==== Plugging in your own Test Execution Listener +==== Plugging in your own Execution Listener In addition to the public `{Launcher}` API method for registering test execution listeners programmatically, by default custom `{TestExecutionListener}` implementations will be discovered at runtime via Java's `java.util.ServiceLoader` mechanism and -automatically registered with the `Launcher` created via the `LauncherFactory`. For -example, an `example.TestInfoPrinter` class implementing `{TestExecutionListener}` and +automatically registered with the `Launcher` created via the `LauncherFactory`. + +For example, an `example.TestInfoPrinter` class implementing `{TestExecutionListener}` and declared within the `/META-INF/services/org.junit.platform.launcher.TestExecutionListener` file is loaded and registered automatically. [[launcher-api-listeners-custom-deactivation]] -==== Deactivating Test Execution Listeners +==== Deactivating Execution Listeners Sometimes it can be useful to run a test suite _without_ certain execution listeners being active. For example, you might have custom a `TestExecutionListener` that sends the test diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncher.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncher.java index 36b1769d38cf..f42d5d9c3a18 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncher.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncher.java @@ -17,6 +17,7 @@ import org.junit.platform.commons.util.Preconditions; import org.junit.platform.engine.TestEngine; import org.junit.platform.launcher.Launcher; +import org.junit.platform.launcher.LauncherDiscoveryListener; import org.junit.platform.launcher.LauncherDiscoveryRequest; import org.junit.platform.launcher.PostDiscoveryFilter; import org.junit.platform.launcher.TestExecutionListener; @@ -41,17 +42,23 @@ class DefaultLauncher implements Launcher { /** * Construct a new {@code DefaultLauncher} with the supplied test engines. * - * @param testEngines the test engines to delegate to; never {@code null} or empty - * @param filters the additional post discovery filters for discovery requests; never {@code null} + * @param testEngines the test engines to delegate to; never {@code null} or + * empty + * @param postDiscoveryFilters the additional post discovery filters for + * discovery requests; never {@code null} + * @param launcherDiscoveryListeners the additional launcher discovery + * listeners for discovery requests; never {@code null} */ - DefaultLauncher(Iterable testEngines, Collection filters) { + DefaultLauncher(Iterable testEngines, Collection postDiscoveryFilters, + Collection launcherDiscoveryListeners) { Preconditions.condition(testEngines != null && testEngines.iterator().hasNext(), () -> "Cannot create Launcher without at least one TestEngine; " + "consider adding an engine implementation JAR to the classpath"); - Preconditions.notNull(filters, "PostDiscoveryFilter array must not be null"); - Preconditions.containsNoNullElements(filters, "PostDiscoveryFilter array must not contain null elements"); + Preconditions.notNull(postDiscoveryFilters, "PostDiscoveryFilter array must not be null"); + Preconditions.containsNoNullElements(postDiscoveryFilters, + "PostDiscoveryFilter array must not contain null elements"); this.discoveryOrchestrator = new EngineDiscoveryOrchestrator(EngineIdValidator.validate(testEngines), - unmodifiableCollection(filters)); + unmodifiableCollection(postDiscoveryFilters), unmodifiableCollection(launcherDiscoveryListeners)); } @Override @@ -88,6 +95,10 @@ TestExecutionListenerRegistry getTestExecutionListenerRegistry() { return listenerRegistry; } + LauncherDiscoveryListener getLauncherDiscoveryListener(LauncherDiscoveryRequest discoveryRequest) { + return discoveryOrchestrator.getLauncherDiscoveryListener(discoveryRequest); + } + private LauncherDiscoveryResult discover(LauncherDiscoveryRequest discoveryRequest, String phase) { return discoveryOrchestrator.discover(discoveryRequest, phase); } diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherConfig.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherConfig.java index 41bdfeab6e6b..7ca4875e0544 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherConfig.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherConfig.java @@ -26,23 +26,20 @@ class DefaultLauncherConfig implements LauncherConfig { private final boolean testEngineAutoRegistrationEnabled; - + private final boolean launcherDiscoveryListenerAutoRegistrationEnabled; private final boolean testExecutionListenerAutoRegistrationEnabled; - private final boolean postDiscoveryFilterAutoRegistrationEnabled; - private final Collection additionalTestEngines; - private final Collection additionalTestExecutionListeners; - private final Collection additionalPostDiscoveryFilters; DefaultLauncherConfig(boolean testEngineAutoRegistrationEnabled, + boolean launcherDiscoveryListenerAutoRegistrationEnabled, boolean testExecutionListenerAutoRegistrationEnabled, boolean postDiscoveryFilterAutoRegistrationEnabled, Collection additionalTestEngines, Collection additionalTestExecutionListeners, Collection additionalPostDiscoveryFilters) { - + this.launcherDiscoveryListenerAutoRegistrationEnabled = launcherDiscoveryListenerAutoRegistrationEnabled; this.testExecutionListenerAutoRegistrationEnabled = testExecutionListenerAutoRegistrationEnabled; this.testEngineAutoRegistrationEnabled = testEngineAutoRegistrationEnabled; this.postDiscoveryFilterAutoRegistrationEnabled = postDiscoveryFilterAutoRegistrationEnabled; @@ -56,6 +53,11 @@ public boolean isTestEngineAutoRegistrationEnabled() { return this.testEngineAutoRegistrationEnabled; } + @Override + public boolean isLauncherDiscoveryListenerAutoRegistrationEnabled() { + return launcherDiscoveryListenerAutoRegistrationEnabled; + } + @Override public boolean isTestExecutionListenerAutoRegistrationEnabled() { return this.testExecutionListenerAutoRegistrationEnabled; diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineDiscoveryOrchestrator.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineDiscoveryOrchestrator.java index 6c5fd203597f..002944d84ce5 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineDiscoveryOrchestrator.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineDiscoveryOrchestrator.java @@ -11,6 +11,7 @@ package org.junit.platform.launcher.core; import static java.util.stream.Collectors.joining; +import static java.util.stream.Collectors.toList; import static org.apiguardian.api.API.Status.INTERNAL; import static org.junit.platform.engine.Filter.composeFilters; @@ -20,6 +21,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.stream.Stream; import org.apiguardian.api.API; import org.junit.platform.commons.JUnitException; @@ -35,6 +37,7 @@ import org.junit.platform.launcher.LauncherDiscoveryListener; import org.junit.platform.launcher.LauncherDiscoveryRequest; import org.junit.platform.launcher.PostDiscoveryFilter; +import org.junit.platform.launcher.listeners.discovery.LauncherDiscoveryListeners; /** * Orchestrates test discovery using the configured test engines. @@ -49,11 +52,14 @@ public class EngineDiscoveryOrchestrator { private final EngineDiscoveryResultValidator discoveryResultValidator = new EngineDiscoveryResultValidator(); private final Iterable testEngines; private final Collection postDiscoveryFilters; + private final Collection launcherDiscoveryListeners; public EngineDiscoveryOrchestrator(Iterable testEngines, - Collection postDiscoveryFilters) { + Collection postDiscoveryFilters, + Collection launcherDiscoveryListeners) { this.testEngines = testEngines; this.postDiscoveryFilters = postDiscoveryFilters; + this.launcherDiscoveryListeners = launcherDiscoveryListeners; } /** @@ -96,7 +102,7 @@ public LauncherDiscoveryResult discover(LauncherDiscoveryRequest request, String } private TestDescriptor discoverEngineRoot(TestEngine testEngine, LauncherDiscoveryRequest discoveryRequest) { - LauncherDiscoveryListener discoveryListener = discoveryRequest.getDiscoveryListener(); + LauncherDiscoveryListener discoveryListener = getLauncherDiscoveryListener(discoveryRequest); UniqueId uniqueEngineId = UniqueId.forEngine(testEngine.getId()); try { discoveryListener.engineDiscoveryStarted(uniqueEngineId); @@ -114,6 +120,16 @@ private TestDescriptor discoverEngineRoot(TestEngine testEngine, LauncherDiscove } } + LauncherDiscoveryListener getLauncherDiscoveryListener(LauncherDiscoveryRequest discoveryRequest) { + LauncherDiscoveryListener discoveryListener = discoveryRequest.getDiscoveryListener(); + if (!launcherDiscoveryListeners.isEmpty()) { + List allDiscoveryListeners = Stream.concat(Stream.of(discoveryListener), + launcherDiscoveryListeners.stream()).collect(toList()); + discoveryListener = LauncherDiscoveryListeners.composite(allDiscoveryListeners); + } + return discoveryListener; + } + private void applyPostDiscoveryFilters(Map testEngineDescriptors, List filters) { Filter postDiscoveryFilter = composeFilters(filters); diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherConfig.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherConfig.java index afed3e096c8b..f973d8fc4c1f 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherConfig.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherConfig.java @@ -60,6 +60,18 @@ public interface LauncherConfig { */ boolean isTestEngineAutoRegistrationEnabled(); + /** + * Determine if launcher discovery listeners should be discovered at runtime + * using the {@link java.util.ServiceLoader ServiceLoader} mechanism and + * automatically registered. + * + * @return {@code true} if launcher discovery listeners should be + * automatically registered + * @since 1.8 + */ + @API(status = EXPERIMENTAL, since = "1.8") + boolean isLauncherDiscoveryListenerAutoRegistrationEnabled(); + /** * Determine if test execution listeners should be discovered at runtime * using the {@link java.util.ServiceLoader ServiceLoader} mechanism and @@ -77,6 +89,7 @@ public interface LauncherConfig { * * @return {@code true} if post discovery filters should be automatically * registered + * @since 1.7 */ @API(status = EXPERIMENTAL, since = "1.7") boolean isPostDiscoveryFilterAutoRegistrationEnabled(); @@ -105,6 +118,7 @@ public interface LauncherConfig { * * @return the collection of additional post discovery filters; never * {@code null} but potentially empty + * @since 1.7 */ @API(status = EXPERIMENTAL, since = "1.7") Collection getAdditionalPostDiscoveryFilters(); @@ -123,22 +137,35 @@ static Builder builder() { */ class Builder { - private boolean listenerAutoRegistrationEnabled = true; - private boolean engineAutoRegistrationEnabled = true; - + private boolean launcherDiscoveryListenerAutoRegistrationEnabled = true; + private boolean testExecutionListenerAutoRegistrationEnabled = true; private boolean postDiscoveryFilterAutoRegistrationEnabled = true; - private final Collection engines = new LinkedHashSet<>(); - private final Collection listeners = new LinkedHashSet<>(); - private final Collection postDiscoveryFilters = new LinkedHashSet<>(); private Builder() { /* no-op */ } + /** + * Configure the auto-registration flag for launcher discovery + * listeners. + * + *

Defaults to {@code true}. + * + * @param enabled {@code true} if launcher discovery listeners should be + * automatically registered + * @return this builder for method chaining + * @since 1.8 + */ + @API(status = EXPERIMENTAL, since = "1.8") + public Builder enableLauncherDiscoveryListenerAutoRegistration(boolean enabled) { + this.launcherDiscoveryListenerAutoRegistrationEnabled = enabled; + return this; + } + /** * Configure the auto-registration flag for test execution listeners. * @@ -149,7 +176,7 @@ private Builder() { * @return this builder for method chaining */ public Builder enableTestExecutionListenerAutoRegistration(boolean enabled) { - this.listenerAutoRegistrationEnabled = enabled; + this.testExecutionListenerAutoRegistrationEnabled = enabled; return this; } @@ -175,6 +202,7 @@ public Builder enableTestEngineAutoRegistration(boolean enabled) { * @param enabled {@code true} if post discovery filters should be automatically * registered * @return this builder for method chaining + * @since 1.7 */ @API(status = EXPERIMENTAL, since = "1.7") public Builder enablePostDiscoveryFilterAutoRegistration(boolean enabled) { @@ -217,6 +245,7 @@ public Builder addTestExecutionListeners(TestExecutionListener... listeners) { * @param filters additional post discovery filters to register; * never {@code null} or containing {@code null} * @return this builder for method chaining + * @since 1.7 */ @API(status = EXPERIMENTAL, since = "1.7") public Builder addPostDiscoveryFilters(PostDiscoveryFilter... filters) { @@ -231,9 +260,10 @@ public Builder addPostDiscoveryFilters(PostDiscoveryFilter... filters) { * builder. */ public LauncherConfig build() { - return new DefaultLauncherConfig(this.engineAutoRegistrationEnabled, this.listenerAutoRegistrationEnabled, - this.postDiscoveryFilterAutoRegistrationEnabled, this.engines, this.listeners, - this.postDiscoveryFilters); + return new DefaultLauncherConfig(this.engineAutoRegistrationEnabled, + this.launcherDiscoveryListenerAutoRegistrationEnabled, + this.testExecutionListenerAutoRegistrationEnabled, this.postDiscoveryFilterAutoRegistrationEnabled, + this.engines, this.listeners, this.postDiscoveryFilters); } } diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherDiscoveryRequestBuilder.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherDiscoveryRequestBuilder.java index 6de1ed6980c6..272973ad8802 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherDiscoveryRequestBuilder.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherDiscoveryRequestBuilder.java @@ -274,7 +274,8 @@ private LauncherDiscoveryListener getLauncherDiscoveryListener(ConfigurationPara if (discoveryListeners.contains(defaultDiscoveryListener)) { return LauncherDiscoveryListeners.composite(discoveryListeners); } - List allDiscoveryListeners = new ArrayList<>(discoveryListeners); + List allDiscoveryListeners = new ArrayList<>(discoveryListeners.size() + 1); + allDiscoveryListeners.addAll(discoveryListeners); allDiscoveryListeners.add(defaultDiscoveryListener); return LauncherDiscoveryListeners.composite(allDiscoveryListeners); } diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherFactory.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherFactory.java index 739b01baf20f..6b1ee7d93d8c 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherFactory.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherFactory.java @@ -28,6 +28,7 @@ import org.junit.platform.engine.ConfigurationParameters; import org.junit.platform.engine.TestEngine; import org.junit.platform.launcher.Launcher; +import org.junit.platform.launcher.LauncherDiscoveryListener; import org.junit.platform.launcher.PostDiscoveryFilter; import org.junit.platform.launcher.TestExecutionListener; @@ -90,26 +91,48 @@ public static Launcher create() throws PreconditionViolationException { public static Launcher create(LauncherConfig config) throws PreconditionViolationException { Preconditions.notNull(config, "LauncherConfig must not be null"); + Set engines = collectTestEngines(config); + List filters = collectPostDiscoveryFilters(config); + List discoveryListeners = collectLauncherDiscoveryListeners(config); + + Launcher launcher = new DefaultLauncher(engines, filters, discoveryListeners); + + registerTestExecutionListeners(config, launcher); + + return launcher; + } + + private static Set collectTestEngines(LauncherConfig config) { Set engines = new LinkedHashSet<>(); if (config.isTestEngineAutoRegistrationEnabled()) { new ServiceLoaderTestEngineRegistry().loadTestEngines().forEach(engines::add); } engines.addAll(config.getAdditionalTestEngines()); + return engines; + } + private static List collectPostDiscoveryFilters(LauncherConfig config) { List filters = new ArrayList<>(); if (config.isPostDiscoveryFilterAutoRegistrationEnabled()) { new ServiceLoaderPostDiscoveryFilterRegistry().loadPostDiscoveryFilters().forEach(filters::add); } filters.addAll(config.getAdditionalPostDiscoveryFilters()); + return filters; + } - Launcher launcher = new DefaultLauncher(engines, filters); + private static List collectLauncherDiscoveryListeners(LauncherConfig config) { + List discoveryListeners = new ArrayList<>(); + if (config.isLauncherDiscoveryListenerAutoRegistrationEnabled()) { + new ServiceLoaderLauncherDiscoveryListenerRegistry().loadListeners().forEach(discoveryListeners::add); + } + return discoveryListeners; + } + private static void registerTestExecutionListeners(LauncherConfig config, Launcher launcher) { if (config.isTestExecutionListenerAutoRegistrationEnabled()) { loadAndFilterTestExecutionListeners().forEach(launcher::registerTestExecutionListeners); } config.getAdditionalTestExecutionListeners().forEach(launcher::registerTestExecutionListeners); - - return launcher; } private static Stream loadAndFilterTestExecutionListeners() { diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ServiceLoaderLauncherDiscoveryListenerRegistry.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ServiceLoaderLauncherDiscoveryListenerRegistry.java new file mode 100644 index 000000000000..ca4788adaff5 --- /dev/null +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ServiceLoaderLauncherDiscoveryListenerRegistry.java @@ -0,0 +1,38 @@ +/* + * Copyright 2015-2020 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 + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.core; + +import static java.util.stream.Collectors.toList; +import static java.util.stream.StreamSupport.stream; + +import java.util.ServiceLoader; + +import org.junit.platform.commons.logging.Logger; +import org.junit.platform.commons.logging.LoggerFactory; +import org.junit.platform.commons.util.ClassLoaderUtils; +import org.junit.platform.launcher.LauncherDiscoveryListener; + +/** + * @since 1.8 + */ +class ServiceLoaderLauncherDiscoveryListenerRegistry { + + private static final Logger logger = LoggerFactory.getLogger(ServiceLoaderLauncherDiscoveryListenerRegistry.class); + + Iterable loadListeners() { + Iterable listeners = ServiceLoader.load(LauncherDiscoveryListener.class, + ClassLoaderUtils.getDefaultClassLoader()); + logger.config(() -> "Loaded LauncherDiscoveryListener instances: " + + stream(listeners.spliterator(), false).map(Object::toString).collect(toList())); + return listeners; + } + +} diff --git a/junit-platform-launcher/src/module/org.junit.platform.launcher/module-info.java b/junit-platform-launcher/src/module/org.junit.platform.launcher/module-info.java index f9200fda65f4..9ccde3666d41 100644 --- a/junit-platform-launcher/src/module/org.junit.platform.launcher/module-info.java +++ b/junit-platform-launcher/src/module/org.junit.platform.launcher/module-info.java @@ -15,6 +15,7 @@ * * @since 1.0 * @uses org.junit.platform.engine.TestEngine + * @uses org.junit.platform.launcher.LauncherDiscoveryListener * @uses org.junit.platform.launcher.PostDiscoveryFilter * @uses org.junit.platform.launcher.TestExecutionListener */ @@ -30,6 +31,7 @@ exports org.junit.platform.launcher.listeners.discovery; uses org.junit.platform.engine.TestEngine; + uses org.junit.platform.launcher.LauncherDiscoveryListener; uses org.junit.platform.launcher.PostDiscoveryFilter; uses org.junit.platform.launcher.TestExecutionListener; } diff --git a/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EngineTestKit.java b/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EngineTestKit.java index 8826f87b829d..f1c87fe44a44 100644 --- a/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EngineTestKit.java +++ b/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EngineTestKit.java @@ -265,7 +265,7 @@ private static void executeDirectly(TestEngine testEngine, EngineDiscoveryReques private static void executeUsingLauncherOrchestration(TestEngine testEngine, LauncherDiscoveryRequest discoveryRequest, EngineExecutionListener listener) { TestDescriptor engineTestDescriptor; - LauncherDiscoveryResult discoveryResult = new EngineDiscoveryOrchestrator(singleton(testEngine), + LauncherDiscoveryResult discoveryResult = new EngineDiscoveryOrchestrator(singleton(testEngine), emptySet(), emptySet()).discover(discoveryRequest, "testing"); engineTestDescriptor = discoveryResult.getEngineTestDescriptor(testEngine); Preconditions.notNull(engineTestDescriptor, "TestEngine did not yield a TestDescriptor"); diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/TestLauncherDiscoveryListener.java b/platform-tests/src/test/java/org/junit/platform/launcher/TestLauncherDiscoveryListener.java new file mode 100644 index 000000000000..497b649ae2c0 --- /dev/null +++ b/platform-tests/src/test/java/org/junit/platform/launcher/TestLauncherDiscoveryListener.java @@ -0,0 +1,30 @@ +/* + * Copyright 2015-2020 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 + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher; + +public class TestLauncherDiscoveryListener extends LauncherDiscoveryListener { + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + return getClass() == obj.getClass(); + } + + @Override + public int hashCode() { + return 1; + } +} diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherConfigTests.java b/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherConfigTests.java index cd9e2a0e5b48..cd233214e13e 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherConfigTests.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherConfigTests.java @@ -50,8 +50,12 @@ void defaultConfig() { assertTrue(config.isTestEngineAutoRegistrationEnabled(), "Test engine auto-registration should be enabled by default"); + assertTrue(config.isLauncherDiscoveryListenerAutoRegistrationEnabled(), + "Launcher discovery listener auto-registration should be enabled by default"); assertTrue(config.isTestExecutionListenerAutoRegistrationEnabled(), "Test execution listener auto-registration should be enabled by default"); + assertTrue(config.isPostDiscoveryFilterAutoRegistrationEnabled(), + "Post-discovery filter auto-registration should be enabled by default"); assertThat(config.getAdditionalTestEngines()).isEmpty(); @@ -65,6 +69,13 @@ void disableTestEngineAutoRegistration() { assertFalse(config.isTestEngineAutoRegistrationEnabled()); } + @Test + void disableLauncherDiscoveryListenerAutoRegistration() { + var config = LauncherConfig.builder().enableLauncherDiscoveryListenerAutoRegistration(false).build(); + + assertFalse(config.isLauncherDiscoveryListenerAutoRegistrationEnabled()); + } + @Test void disableTestExecutionListenerAutoRegistration() { var config = LauncherConfig.builder().enableTestExecutionListenerAutoRegistration(false).build(); @@ -72,6 +83,13 @@ void disableTestExecutionListenerAutoRegistration() { assertFalse(config.isTestExecutionListenerAutoRegistrationEnabled()); } + @Test + void disablePostDiscoveryFilterAutoRegistration() { + var config = LauncherConfig.builder().enablePostDiscoveryFilterAutoRegistration(false).build(); + + assertFalse(config.isPostDiscoveryFilterAutoRegistrationEnabled()); + } + @Test void addTestEngines() { TestEngine first = new TestEngineStub(); diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherFactoryTests.java b/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherFactoryTests.java index a0b4f7f75445..d510aa3a9c91 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherFactoryTests.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherFactoryTests.java @@ -15,6 +15,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; +import static org.junit.platform.launcher.listeners.discovery.LauncherDiscoveryListeners.abortOnFailure; import java.net.URL; import java.net.URLClassLoader; @@ -26,6 +27,7 @@ import org.junit.platform.launcher.LauncherDiscoveryRequest; import org.junit.platform.launcher.TagFilter; import org.junit.platform.launcher.TestIdentifier; +import org.junit.platform.launcher.TestLauncherDiscoveryListener; import org.junit.platform.launcher.listeners.AnotherUnusedTestExecutionListener; import org.junit.platform.launcher.listeners.NoopTestExecutionListener; import org.junit.platform.launcher.listeners.UnusedTestExecutionListener; @@ -42,20 +44,24 @@ void preconditions() { @Test void noopTestExecutionListenerIsLoadedViaServiceApi() { - var launcher = (DefaultLauncher) LauncherFactory.create(); - var listeners = launcher.getTestExecutionListenerRegistry().getTestExecutionListeners(); - var listener = listeners.stream().filter(NoopTestExecutionListener.class::isInstance).findFirst(); - assertThat(listener).isPresent(); + withTestServices(() -> { + var launcher = (DefaultLauncher) LauncherFactory.create(); + var listeners = launcher.getTestExecutionListenerRegistry().getTestExecutionListeners(); + var listener = listeners.stream().filter(NoopTestExecutionListener.class::isInstance).findFirst(); + assertThat(listener).isPresent(); + }); } @Test void unusedTestExecutionListenerIsNotLoadedViaServiceApi() { - var launcher = (DefaultLauncher) LauncherFactory.create(); - var listeners = launcher.getTestExecutionListenerRegistry().getTestExecutionListeners(); - - assertThat(listeners).filteredOn(AnotherUnusedTestExecutionListener.class::isInstance).isEmpty(); - assertThat(listeners).filteredOn(UnusedTestExecutionListener.class::isInstance).isEmpty(); - assertThat(listeners).filteredOn(NoopTestExecutionListener.class::isInstance).isNotEmpty(); + withTestServices(() -> { + var launcher = (DefaultLauncher) LauncherFactory.create(); + var listeners = launcher.getTestExecutionListenerRegistry().getTestExecutionListeners(); + + assertThat(listeners).filteredOn(AnotherUnusedTestExecutionListener.class::isInstance).isEmpty(); + assertThat(listeners).filteredOn(UnusedTestExecutionListener.class::isInstance).isEmpty(); + assertThat(listeners).filteredOn(NoopTestExecutionListener.class::isInstance).isNotEmpty(); + }); } @Test @@ -162,6 +168,42 @@ void notApplyIfDisabledPostDiscoveryFiltersViaServiceApi() { } } + @Test + void doesNotDiscoverLauncherDiscoverRequestListenerViaServiceApiWhenDisabled() { + withTestServices(() -> { + var launcher = (DefaultLauncher) LauncherFactory.create( + LauncherConfig.builder().enableLauncherDiscoveryListenerAutoRegistration(false).build()); + var launcherDiscoveryListener = launcher.getLauncherDiscoveryListener(request().build()); + + assertThat(launcherDiscoveryListener).isEqualTo(abortOnFailure()); + }); + } + + @Test + void discoversLauncherDiscoverRequestListenerViaServiceApiByDefault() { + withTestServices(() -> { + var launcher = (DefaultLauncher) LauncherFactory.create(); + var launcherDiscoveryListener = launcher.getLauncherDiscoveryListener(request().build()); + + assertThat(launcherDiscoveryListener.getClass().getSimpleName()).startsWith("Composite"); + assertThat(launcherDiscoveryListener).extracting("listeners").asList() // + .containsExactlyInAnyOrder(new TestLauncherDiscoveryListener(), abortOnFailure()); + }); + } + + private static void withTestServices(Runnable runnable) { + var current = Thread.currentThread().getContextClassLoader(); + try { + var url = LauncherFactoryTests.class.getClassLoader().getResource("testservices/"); + var classLoader = new URLClassLoader(new URL[] { url }, current); + Thread.currentThread().setContextClassLoader(classLoader); + runnable.run(); + } + finally { + Thread.currentThread().setContextClassLoader(current); + } + } + private LauncherDiscoveryRequest createLauncherDiscoveryRequestForBothStandardEngineExampleClasses() { // @formatter:off return request() diff --git a/platform-tests/src/test/resources/testservices/META-INF/services/org.junit.platform.launcher.LauncherDiscoveryListener b/platform-tests/src/test/resources/testservices/META-INF/services/org.junit.platform.launcher.LauncherDiscoveryListener new file mode 100644 index 000000000000..3c8c2d5022c4 --- /dev/null +++ b/platform-tests/src/test/resources/testservices/META-INF/services/org.junit.platform.launcher.LauncherDiscoveryListener @@ -0,0 +1 @@ +org.junit.platform.launcher.TestLauncherDiscoveryListener diff --git a/platform-tests/src/test/resources/META-INF/services/org.junit.platform.launcher.TestExecutionListener b/platform-tests/src/test/resources/testservices/META-INF/services/org.junit.platform.launcher.TestExecutionListener similarity index 100% rename from platform-tests/src/test/resources/META-INF/services/org.junit.platform.launcher.TestExecutionListener rename to platform-tests/src/test/resources/testservices/META-INF/services/org.junit.platform.launcher.TestExecutionListener diff --git a/platform-tests/src/test/resources/junit-platform.properties b/platform-tests/src/test/resources/testservices/junit-platform.properties similarity index 100% rename from platform-tests/src/test/resources/junit-platform.properties rename to platform-tests/src/test/resources/testservices/junit-platform.properties diff --git a/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-launcher.expected.txt b/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-launcher.expected.txt index a142504cdcf0..82f90fef731d 100644 --- a/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-launcher.expected.txt +++ b/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-launcher.expected.txt @@ -9,5 +9,6 @@ requires org.apiguardian.api transitive requires org.junit.platform.commons transitive requires org.junit.platform.engine transitive uses org.junit.platform.engine.TestEngine +uses org.junit.platform.launcher.LauncherDiscoveryListener uses org.junit.platform.launcher.PostDiscoveryFilter uses org.junit.platform.launcher.TestExecutionListener diff --git a/platform-tooling-support-tests/projects/standalone/expected-err.txt b/platform-tooling-support-tests/projects/standalone/expected-err.txt index f140d43c746a..27c15985896b 100644 --- a/platform-tooling-support-tests/projects/standalone/expected-err.txt +++ b/platform-tooling-support-tests/projects/standalone/expected-err.txt @@ -2,5 +2,7 @@ .+ Discovered TestEngines with IDs. .junit-jupiter .+ junit-vintage .+ .+ org.junit.platform.launcher.core.ServiceLoaderPostDiscoveryFilterRegistry loadPostDiscoveryFilters .+ Loaded PostDiscoveryFilter instances: .. +.+ org.junit.platform.launcher.core.ServiceLoaderLauncherDiscoveryListenerRegistry loadListeners +.+ Loaded LauncherDiscoveryListener instances: .. .+ org.junit.platform.launcher.core.ServiceLoaderTestExecutionListenerRegistry loadListeners .+ Loaded TestExecutionListener instances: ..