diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.0-M1.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.0-M1.adoc index 5f5e5199eec6..a43d29e05a2b 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.0-M1.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.0-M1.adoc @@ -31,6 +31,9 @@ repository on GitHub. redirecting `stdout` and `stderr` output streams to files. * Add `TestDescriptor.Visitor.composite(List)` factory method for creating a composite visitor that delegates to the given visitors in order. +* Introduce test _discovery_ support in `EngineTestKit` to ease testing for discovery + issues produced by a `TestEngine`. Please refer to the + <<../user-guide/index.adoc#testkit-engine, User Guide>> for details. [[release-notes-5.13.0-M1-junit-jupiter]] diff --git a/documentation/src/docs/asciidoc/user-guide/advanced-topics/testkit.adoc b/documentation/src/docs/asciidoc/user-guide/advanced-topics/testkit.adoc index 3e74e28b4b7d..3f134f3f8ba1 100644 --- a/documentation/src/docs/asciidoc/user-guide/advanced-topics/testkit.adoc +++ b/documentation/src/docs/asciidoc/user-guide/advanced-topics/testkit.adoc @@ -1,3 +1,5 @@ +:testDir: ../../../../../src/test/java + [[testkit]] === JUnit Platform Test Kit @@ -9,16 +11,17 @@ JUnit Platform and then verifying the expected results. As of JUnit Platform [[testkit-engine]] ==== Engine Test Kit -The `{testkit-engine-package}` package provides support for executing a `{TestPlan}` for a -given `{TestEngine}` running on the JUnit Platform and then accessing the results via a -fluent API to verify the expected results. The key entry point into this API is the -`{EngineTestKit}` which provides static factory methods named `engine()` and `execute()`. -It is recommended that you select one of the `engine()` variants to benefit from the -fluent API for building a `LauncherDiscoveryRequest`. +The `{testkit-engine-package}` package provides support for discovering and executing a +`{TestPlan}` for a given `{TestEngine}` running on the JUnit Platform and then accessing +the results via convenient result objects. For execution, a fluent API may be used to +verify the expected execution events were received. The key entry point into this API is +the `{EngineTestKit}` which provides static factory methods named `engine()`, +`discover()`, and `execute()`. It is recommended that you select one of the `engine()` +variants to benefit from the fluent API for building a `LauncherDiscoveryRequest`. NOTE: If you prefer to use the `LauncherDiscoveryRequestBuilder` from the `Launcher` API -to build your `LauncherDiscoveryRequest`, you must use one of the `execute()` variants in -`EngineTestKit`. +to build your `LauncherDiscoveryRequest`, you must use one of the `discover()` or +`execute()` variants in `EngineTestKit`. The following test class written using JUnit Jupiter will be used in subsequent examples. @@ -34,8 +37,24 @@ own `TestEngine` implementation, you need to use its unique engine ID. Alternati may test your own `TestEngine` by supplying an instance of it to the `EngineTestKit.engine(TestEngine)` static factory method. +[[testkit-engine-discovery]] +==== Verifying Test Discovery + +The following test demonstrates how to verify that a `TestPlan` was discovered as expected +by the JUnit Jupiter `TestEngine`. + +[source,java,indent=0] +---- +include::{testDir}/example/testkit/EngineTestKitDiscoveryDemo.java[tags=user_guide] +---- +<1> Select the JUnit Jupiter `TestEngine`. +<2> Select the <> test class. +<3> Discover the `TestPlan`. +<4> Assert engine root descriptor has expected display name. +<5> Assert no discovery issues were encountered. + [[testkit-engine-statistics]] -==== Asserting Statistics +==== Asserting Execution Statistics One of the most common features of the Test Kit is the ability to assert statistics against events fired during the execution of a `TestPlan`. The following tests demonstrate diff --git a/documentation/src/test/java/example/testkit/EngineTestKitDiscoveryDemo.java b/documentation/src/test/java/example/testkit/EngineTestKitDiscoveryDemo.java new file mode 100644 index 000000000000..6e92f8724b4a --- /dev/null +++ b/documentation/src/test/java/example/testkit/EngineTestKitDiscoveryDemo.java @@ -0,0 +1,37 @@ +/* + * Copyright 2015-2025 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 example.testkit; + +// tag::user_guide[] +import static java.util.Collections.emptyList; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; + +import example.ExampleTestCase; + +import org.junit.jupiter.api.Test; +import org.junit.platform.testkit.engine.EngineDiscoveryResults; +import org.junit.platform.testkit.engine.EngineTestKit; + +class EngineTestKitDiscoveryDemo { + + @Test + void verifyJupiterDiscovery() { + EngineDiscoveryResults results = EngineTestKit.engine("junit-jupiter") // <1> + .selectors(selectClass(ExampleTestCase.class)) // <2> + .discover(); // <3> + + assertEquals("JUnit Jupiter", results.getEngineDescriptor().getDisplayName()); // <4> + assertEquals(emptyList(), results.getDiscoveryIssues()); // <5> + } + +} +// end::user_guide[] diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DiscoveryIssueNotifier.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DiscoveryIssueNotifier.java index 4f9ad78162e0..8bb14f68dcdd 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DiscoveryIssueNotifier.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DiscoveryIssueNotifier.java @@ -16,6 +16,7 @@ import java.io.PrintWriter; import java.io.StringWriter; +import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.function.Consumer; @@ -35,9 +36,10 @@ */ class DiscoveryIssueNotifier { - static final DiscoveryIssueNotifier NO_ISSUES = new DiscoveryIssueNotifier(emptyList(), emptyList()); + static final DiscoveryIssueNotifier NO_ISSUES = new DiscoveryIssueNotifier(emptyList(), emptyList(), emptyList()); private static final Logger logger = LoggerFactory.getLogger(DiscoveryIssueNotifier.class); + private final List allIssues; private final List criticalIssues; private final List nonCriticalIssues; @@ -47,14 +49,20 @@ static DiscoveryIssueNotifier from(Severity criticalSeverity, List issue.severity().compareTo(criticalSeverity) >= 0)); List criticalIssues = issuesByCriticality.get(true); List nonCriticalIssues = issuesByCriticality.get(false); - return new DiscoveryIssueNotifier(criticalIssues, nonCriticalIssues); + return new DiscoveryIssueNotifier(new ArrayList<>(issues), criticalIssues, nonCriticalIssues); } - private DiscoveryIssueNotifier(List criticalIssues, List nonCriticalIssues) { + private DiscoveryIssueNotifier(List allIssues, List criticalIssues, + List nonCriticalIssues) { + this.allIssues = allIssues; this.criticalIssues = criticalIssues; this.nonCriticalIssues = nonCriticalIssues; } + List getAllIssues() { + return allIssues; + } + boolean hasCriticalIssues() { return !criticalIssues.isEmpty(); } diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherDiscoveryResult.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherDiscoveryResult.java index 4b26b55b7b16..7b3db9368915 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherDiscoveryResult.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherDiscoveryResult.java @@ -16,6 +16,7 @@ import java.util.Collection; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.function.Predicate; @@ -23,6 +24,7 @@ import org.apiguardian.api.API; import org.junit.platform.engine.ConfigurationParameters; +import org.junit.platform.engine.DiscoveryIssue; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestEngine; import org.junit.platform.engine.reporting.OutputDirectoryProvider; @@ -51,6 +53,11 @@ public TestDescriptor getEngineTestDescriptor(TestEngine testEngine) { return getEngineResult(testEngine).getRootDescriptor(); } + @API(status = INTERNAL, since = "1.13") + public List getDiscoveryIssues(TestEngine testEngine) { + return getEngineResult(testEngine).getDiscoveryIssueNotifier().getAllIssues(); + } + EngineResultInfo getEngineResult(TestEngine testEngine) { return this.testEngineResults.get(testEngine); } diff --git a/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EngineDiscoveryResults.java b/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EngineDiscoveryResults.java new file mode 100644 index 000000000000..901462794d31 --- /dev/null +++ b/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EngineDiscoveryResults.java @@ -0,0 +1,58 @@ +/* + * Copyright 2015-2025 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.testkit.engine; + +import static java.util.Collections.unmodifiableList; +import static org.apiguardian.api.API.Status.EXPERIMENTAL; + +import java.util.List; + +import org.apiguardian.api.API; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.engine.DiscoveryIssue; +import org.junit.platform.engine.TestDescriptor; + +/** + * {@code EngineDiscoveryResults} represents the results of test discovery + * by a {@link org.junit.platform.engine.TestEngine TestEngine} on the JUnit + * Platform and provides access to the {@link TestDescriptor} of the engine + * and any {@link DiscoveryIssue DiscoveryIssues} that were encountered. + * + * @since 1.13 + */ +@API(status = EXPERIMENTAL, since = "1.13") +public class EngineDiscoveryResults { + + private final TestDescriptor engineDescriptor; + private final List discoveryIssues; + + EngineDiscoveryResults(TestDescriptor engineDescriptor, List discoveryIssues) { + this.engineDescriptor = Preconditions.notNull(engineDescriptor, "Engine descriptor must not be null"); + this.discoveryIssues = unmodifiableList( + Preconditions.notNull(discoveryIssues, "Discovery issues list must not be null")); + Preconditions.containsNoNullElements(discoveryIssues, "Discovery issues list must not contain null elements"); + } + + /** + * {@return the root {@link TestDescriptor} of the engine} + */ + public TestDescriptor getEngineDescriptor() { + return engineDescriptor; + } + + /** + * {@return the issues that were encountered during discovery} + */ + public List getDiscoveryIssues() { + return discoveryIssues; + } + +} 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 2813765250e0..df1505b1c4d8 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 @@ -16,9 +16,11 @@ import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.MAINTAINED; import static org.apiguardian.api.API.Status.STABLE; +import static org.junit.platform.launcher.core.EngineDiscoveryOrchestrator.Phase.DISCOVERY; import static org.junit.platform.launcher.core.EngineDiscoveryOrchestrator.Phase.EXECUTION; import java.nio.file.Path; +import java.util.List; import java.util.Map; import java.util.ServiceLoader; import java.util.stream.Stream; @@ -29,6 +31,7 @@ import org.junit.platform.commons.util.CollectionUtils; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.engine.DiscoveryFilter; +import org.junit.platform.engine.DiscoveryIssue; import org.junit.platform.engine.DiscoverySelector; import org.junit.platform.engine.EngineDiscoveryRequest; import org.junit.platform.engine.EngineExecutionListener; @@ -46,15 +49,24 @@ import org.junit.platform.launcher.core.ServiceLoaderTestEngineRegistry; /** - * {@code EngineTestKit} provides support for executing a test plan for a given - * {@link TestEngine} and then accessing the results via - * {@linkplain EngineExecutionResults a fluent API} to verify the expected results. + * {@code EngineTestKit} provides support for discovering and executing tests + * for a given {@link TestEngine} and provides convenient access to the results. + * + *

For discovery, {@link EngineDiscoveryResults} provides access to + * the {@link TestDescriptor} of the engine and any {@link DiscoveryIssue + * DiscoveryIssues} that were encountered. + * + *

For execution, {@link EngineExecutionResults} provides a fluent + * API to verify the expected results. * * @since 1.4 * @see #engine(String) * @see #engine(TestEngine) + * @see #discover(String, LauncherDiscoveryRequest) + * @see #discover(TestEngine, LauncherDiscoveryRequest) * @see #execute(String, LauncherDiscoveryRequest) * @see #execute(TestEngine, LauncherDiscoveryRequest) + * @see EngineDiscoveryResults * @see EngineExecutionResults */ @API(status = MAINTAINED, since = "1.7") @@ -121,6 +133,65 @@ public static Builder engine(TestEngine testEngine) { return new Builder(testEngine); } + /** + * Discover tests for the given {@link LauncherDiscoveryRequest} using the + * {@link TestEngine} with the supplied ID. + * + *

The {@code TestEngine} will be loaded via Java's {@link ServiceLoader} + * mechanism, analogous to the manner in which test engines are loaded in + * the JUnit Platform Launcher API. + * + *

{@link org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder} + * provides a convenient way to build an appropriate discovery request to + * supply to this method. As an alternative, consider using + * {@link #engine(TestEngine)} for a more fluent API. + * + * @param engineId the ID of the {@code TestEngine} to use; must not be + * {@code null} or blank + * @param discoveryRequest the {@code LauncherDiscoveryRequest} to use + * @return the results of the discovery + * @throws PreconditionViolationException for invalid arguments or if the + * {@code TestEngine} with the supplied ID cannot be loaded + * @since 1.13 + * @see #discover(TestEngine, LauncherDiscoveryRequest) + * @see #engine(String) + * @see #engine(TestEngine) + */ + @API(status = EXPERIMENTAL, since = "1.13") + public static EngineDiscoveryResults discover(String engineId, LauncherDiscoveryRequest discoveryRequest) { + Preconditions.notBlank(engineId, "TestEngine ID must not be null or blank"); + return discover(loadTestEngine(engineId.trim()), discoveryRequest); + } + + /** + * Discover tests for the given {@link LauncherDiscoveryRequest} using the + * supplied {@link TestEngine}. + * + *

{@link org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder} + * provides a convenient way to build an appropriate discovery request to + * supply to this method. As an alternative, consider using + * {@link #engine(TestEngine)} for a more fluent API. + * + * @param testEngine the {@code TestEngine} to use; must not be {@code null} + * @param discoveryRequest the {@code EngineDiscoveryResults} to use; must + * not be {@code null} + * @return the recorded {@code EngineExecutionResults} + * @throws PreconditionViolationException for invalid arguments + * @since 1.13 + * @see #discover(String, LauncherDiscoveryRequest) + * @see #engine(String) + * @see #engine(TestEngine) + */ + @API(status = EXPERIMENTAL, since = "1.13") + public static EngineDiscoveryResults discover(TestEngine testEngine, LauncherDiscoveryRequest discoveryRequest) { + Preconditions.notNull(testEngine, "TestEngine must not be null"); + Preconditions.notNull(discoveryRequest, "EngineDiscoveryRequest must not be null"); + LauncherDiscoveryResult discoveryResult = discover(testEngine, discoveryRequest, DISCOVERY); + TestDescriptor engineDescriptor = discoveryResult.getEngineTestDescriptor(testEngine); + List discoveryIssues = discoveryResult.getDiscoveryIssues(testEngine); + return new EngineDiscoveryResults(engineDescriptor, discoveryIssues); + } + /** * Execute tests for the given {@link EngineDiscoveryRequest} using the * {@link TestEngine} with the supplied ID. @@ -260,13 +331,16 @@ private static void executeDirectly(TestEngine testEngine, EngineDiscoveryReques private static void executeUsingLauncherOrchestration(TestEngine testEngine, LauncherDiscoveryRequest discoveryRequest, EngineExecutionListener listener) { - LauncherDiscoveryResult discoveryResult = new EngineDiscoveryOrchestrator(singleton(testEngine), - emptySet()).discover(discoveryRequest, EXECUTION); - TestDescriptor engineTestDescriptor = discoveryResult.getEngineTestDescriptor(testEngine); - Preconditions.notNull(engineTestDescriptor, "TestEngine did not yield a TestDescriptor"); + LauncherDiscoveryResult discoveryResult = discover(testEngine, discoveryRequest, EXECUTION); new EngineExecutionOrchestrator().execute(discoveryResult, listener); } + private static LauncherDiscoveryResult discover(TestEngine testEngine, LauncherDiscoveryRequest discoveryRequest, + EngineDiscoveryOrchestrator.Phase phase) { + return new EngineDiscoveryOrchestrator(singleton(testEngine), emptySet()) // + .discover(discoveryRequest, phase); + } + @SuppressWarnings("unchecked") private static TestEngine loadTestEngine(String engineId) { Iterable testEngines = new ServiceLoaderTestEngineRegistry().loadTestEngines(); @@ -446,6 +520,25 @@ public Builder outputDirectoryProvider(OutputDirectoryProvider outputDirectoryPr return this; } + /** + * Discover tests for the configured {@link TestEngine}, + * {@linkplain DiscoverySelector discovery selectors}, + * {@linkplain DiscoveryFilter discovery filters}, and + * configuration parameters. + * + * @return the recorded {@code EngineDiscoveryResults} + * @since 1.13 + * @see #selectors(DiscoverySelector...) + * @see #filters(Filter...) + * @see #configurationParameter(String, String) + * @see #configurationParameters(Map) + */ + @API(status = EXPERIMENTAL, since = "1.13") + public EngineDiscoveryResults discover() { + LauncherDiscoveryRequest request = this.requestBuilder.build(); + return EngineTestKit.discover(this.testEngine, request); + } + /** * Execute tests for the configured {@link TestEngine}, * {@linkplain DiscoverySelector discovery selectors}, diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/DisplayNameGenerationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/DisplayNameGenerationTests.java index 001134e4ec44..27578670dab7 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/api/DisplayNameGenerationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/DisplayNameGenerationTests.java @@ -31,6 +31,7 @@ import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.testkit.engine.Event; /** * Check generated display names. @@ -247,10 +248,12 @@ void indicativeSentencesOnSubClass() { void indicativeSentencesOnClassTemplate() { check(ClassTemplateTestCase.class, // "CONTAINER: Class template", // + "CONTAINER: [1] Class template", // "TEST: Class template, some test", // "CONTAINER: Class template, Regular Nested Test Case", // "TEST: Class template, Regular Nested Test Case, some nested test", // "CONTAINER: Class template, Nested Class Template", // + "CONTAINER: [1] Class template, Nested Class Template", // "TEST: Class template, Nested Class Template, some nested test" // ); @@ -271,7 +274,9 @@ void indicativeSentencesOnClassTemplate() { private void check(Class testClass, String... expectedDisplayNames) { var request = request().selectors(selectClass(testClass)).build(); - var descriptors = discoverTests(request).getDescendants(); + var descriptors = executeTests(request).allEvents().started().stream() // + .map(Event::getTestDescriptor) // + .skip(1); // Skip engine descriptor assertThat(descriptors).map(this::describe).containsExactlyInAnyOrder(expectedDisplayNames); } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/parallel/ResourceLockAnnotationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/parallel/ResourceLockAnnotationTests.java index aedc16e597f6..cc300beee64e 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/api/parallel/ResourceLockAnnotationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/parallel/ResourceLockAnnotationTests.java @@ -189,8 +189,8 @@ void addSharedResourcesViaAnnotationValueAndProviders() { @Test void addSharedResourcesViaAnnotationValueAndProvidersForClassTemplate() { - var engineDescriptor = discoverTests( - selectClass(SharedResourcesViaAnnotationValueAndProvidersClassTemplateTestCase.class)); + var selector = selectClass(SharedResourcesViaAnnotationValueAndProvidersClassTemplateTestCase.class); + var engineDescriptor = discoverTests(selector).getEngineDescriptor(); engineDescriptor.accept(TestDescriptor::prune); var classTemplateTestDescriptor = (JupiterTestDescriptor) getOnlyElement(engineDescriptor.getChildren()); @@ -214,8 +214,9 @@ void addSharedResourcesViaAnnotationValueAndProvidersForClassTemplate() { @Test void addSharedResourcesViaAnnotationValueAndProvidersForClassTemplateInvocation() { - var engineDescriptor = discoverTests( - selectIteration(selectClass(SharedResourcesViaAnnotationValueAndProvidersClassTemplateTestCase.class), 0)); + var selector = selectIteration( + selectClass(SharedResourcesViaAnnotationValueAndProvidersClassTemplateTestCase.class), 0); + var engineDescriptor = discoverTests(selector).getEngineDescriptor(); engineDescriptor.accept(TestDescriptor::prune); var classTemplateTestDescriptor = (JupiterTestDescriptor) getOnlyElement(engineDescriptor.getChildren()); @@ -239,8 +240,8 @@ void addSharedResourcesViaAnnotationValueAndProvidersForClassTemplateInvocation( @Test void addSharedResourcesViaAnnotationValueAndProvidersForMethodInClassTemplate() { - var engineDescriptor = discoverTests( - selectMethod(SharedResourcesViaAnnotationValueAndProvidersClassTemplateTestCase.class, "test")); + var selector = selectMethod(SharedResourcesViaAnnotationValueAndProvidersClassTemplateTestCase.class, "test"); + var engineDescriptor = discoverTests(selector).getEngineDescriptor(); engineDescriptor.accept(TestDescriptor::prune); var classTemplateTestDescriptor = (JupiterTestDescriptor) getOnlyElement(engineDescriptor.getChildren()); diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/AbstractJupiterTestEngineTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/AbstractJupiterTestEngineTests.java index 1469e0e4d951..c8b93723188c 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/AbstractJupiterTestEngineTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/AbstractJupiterTestEngineTests.java @@ -17,14 +17,13 @@ import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; import static org.junit.platform.launcher.core.OutputDirectoryProviders.dummyOutputDirectoryProvider; -import java.util.Set; import java.util.function.Consumer; import org.junit.platform.engine.DiscoverySelector; -import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.UniqueId; import org.junit.platform.launcher.LauncherDiscoveryRequest; import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder; +import org.junit.platform.testkit.engine.EngineDiscoveryResults; import org.junit.platform.testkit.engine.EngineExecutionResults; import org.junit.platform.testkit.engine.EngineTestKit; @@ -59,11 +58,11 @@ protected EngineExecutionResults executeTests(LauncherDiscoveryRequest request) return EngineTestKit.execute(this.engine, request); } - protected TestDescriptor discoverTestsForClass(Class testClass) { + protected EngineDiscoveryResults discoverTestsForClass(Class testClass) { return discoverTests(selectClass(testClass)); } - protected TestDescriptor discoverTests(DiscoverySelector... selectors) { + protected EngineDiscoveryResults discoverTests(DiscoverySelector... selectors) { return discoverTests(defaultRequest().selectors(selectors).build()); } @@ -73,15 +72,16 @@ private static LauncherDiscoveryRequestBuilder defaultRequest() { .configurationParameter(STACKTRACE_PRUNING_ENABLED_PROPERTY_NAME, String.valueOf(false)); } - protected TestDescriptor discoverTests(LauncherDiscoveryRequest request) { - return engine.discover(request, UniqueId.forEngine(engine.getId())); + protected EngineDiscoveryResults discoverTests(LauncherDiscoveryRequest request) { + return EngineTestKit.discover(this.engine, request); } protected UniqueId discoverUniqueId(Class clazz, String methodName) { - TestDescriptor engineDescriptor = discoverTests(selectMethod(clazz, methodName)); - Set descendants = engineDescriptor.getDescendants(); + var results = discoverTests(selectMethod(clazz, methodName)); + var engineDescriptor = results.getEngineDescriptor(); + var descendants = engineDescriptor.getDescendants(); // @formatter:off - TestDescriptor testDescriptor = descendants.stream() + var testDescriptor = descendants.stream() .skip(descendants.size() - 1) .findFirst() .orElseGet(() -> fail("no descendants")); diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/ClassTemplateInvocationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/ClassTemplateInvocationTests.java index d5c07688bbdd..364f78a7306d 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/ClassTemplateInvocationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/ClassTemplateInvocationTests.java @@ -979,7 +979,8 @@ void templateWithPreparations() { @Test void propagatesTagsFromEnclosingClassesToNestedClassTemplates() { - var engineDescriptor = discoverTestsForClass(NestedClassTemplateWithTagOnEnclosingClassTestCase.class); + var engineDescriptor = discoverTestsForClass( + NestedClassTemplateWithTagOnEnclosingClassTestCase.class).getEngineDescriptor(); var classDescriptor = getOnlyElement(engineDescriptor.getChildren()); var nestedClassTemplateDescriptor = getOnlyElement(classDescriptor.getChildren()); diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/DefaultExecutionModeTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/DefaultExecutionModeTests.java index 57fdb111c7a7..c67182cd7283 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/DefaultExecutionModeTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/DefaultExecutionModeTests.java @@ -109,7 +109,7 @@ private JupiterEngineDescriptor discoverTestsWithDefaultExecutionMode(Class t if (executionMode != null) { request.configurationParameter(Constants.DEFAULT_PARALLEL_EXECUTION_MODE, executionMode.name()); } - return (JupiterEngineDescriptor) discoverTests(request.build()); + return (JupiterEngineDescriptor) discoverTests(request.build()).getEngineDescriptor(); } private static void assertExecutionMode(TestDescriptor testDescriptor, ExecutionMode expectedExecutionMode) { diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/DynamicNodeGenerationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/DynamicNodeGenerationTests.java index 30dcea34760c..c1284f58931a 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/DynamicNodeGenerationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/DynamicNodeGenerationTests.java @@ -67,7 +67,7 @@ class DynamicNodeGenerationTests extends AbstractJupiterTestEngineTests { @Test void testFactoryMethodsAreCorrectlyDiscoveredForClassSelector() { LauncherDiscoveryRequest request = request().selectors(selectClass(MyDynamicTestCase.class)).build(); - TestDescriptor engineDescriptor = discoverTests(request); + TestDescriptor engineDescriptor = discoverTests(request).getEngineDescriptor(); assertThat(engineDescriptor.getDescendants()).as("# resolved test descriptors").hasSize(13); } @@ -75,7 +75,7 @@ void testFactoryMethodsAreCorrectlyDiscoveredForClassSelector() { void testFactoryMethodIsCorrectlyDiscoveredForMethodSelector() { LauncherDiscoveryRequest request = request().selectors( selectMethod(MyDynamicTestCase.class, "dynamicStream")).build(); - TestDescriptor engineDescriptor = discoverTests(request); + TestDescriptor engineDescriptor = discoverTests(request).getEngineDescriptor(); assertThat(engineDescriptor.getDescendants()).as("# resolved test descriptors").hasSize(2); } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/NestedTestClassesTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/NestedTestClassesTests.java index 20f4bf9b4642..328ef26a73db 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/NestedTestClassesTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/NestedTestClassesTests.java @@ -43,7 +43,7 @@ class NestedTestClassesTests extends AbstractJupiterTestEngineTests { @Test void nestedTestsAreCorrectlyDiscovered() { LauncherDiscoveryRequest request = request().selectors(selectClass(TestCaseWithNesting.class)).build(); - TestDescriptor engineDescriptor = discoverTests(request); + TestDescriptor engineDescriptor = discoverTests(request).getEngineDescriptor(); assertEquals(5, engineDescriptor.getDescendants().size(), "# resolved test descriptors"); } @@ -64,7 +64,7 @@ void nestedTestsAreExecuted() { @Test void doublyNestedTestsAreCorrectlyDiscovered() { LauncherDiscoveryRequest request = request().selectors(selectClass(TestCaseWithDoubleNesting.class)).build(); - TestDescriptor engineDescriptor = discoverTests(request); + TestDescriptor engineDescriptor = discoverTests(request).getEngineDescriptor(); assertEquals(8, engineDescriptor.getDescendants().size(), "# resolved test descriptors"); } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/StandardTestClassTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/StandardTestClassTests.java index 18459ff98194..1d458e174f32 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/StandardTestClassTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/StandardTestClassTests.java @@ -37,7 +37,7 @@ class StandardTestClassTests extends AbstractJupiterTestEngineTests { @Test void standardTestClassIsCorrectlyDiscovered() { LauncherDiscoveryRequest request = request().selectors(selectClass(MyStandardTestCase.class)).build(); - TestDescriptor engineDescriptor = discoverTests(request); + TestDescriptor engineDescriptor = discoverTests(request).getEngineDescriptor(); assertEquals(1 /*class*/ + 6 /*methods*/, engineDescriptor.getDescendants().size(), "# resolved test descriptors"); } @@ -45,7 +45,7 @@ void standardTestClassIsCorrectlyDiscovered() { @Test void moreThanOneTestClassIsCorrectlyDiscovered() { LauncherDiscoveryRequest request = request().selectors(selectClass(SecondOfTwoTestCases.class)).build(); - TestDescriptor engineDescriptor = discoverTests(request); + TestDescriptor engineDescriptor = discoverTests(request).getEngineDescriptor(); assertEquals(1 /*class*/ + 3 /*methods*/, engineDescriptor.getDescendants().size(), "# resolved test descriptors"); } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/DiscoveryTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/DiscoveryTests.java index 7d5a91951bc3..002980399131 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/DiscoveryTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/DiscoveryTests.java @@ -47,14 +47,14 @@ class DiscoveryTests extends AbstractJupiterTestEngineTests { @Test void discoverTestClass() { LauncherDiscoveryRequest request = request().selectors(selectClass(LocalTestCase.class)).build(); - TestDescriptor engineDescriptor = discoverTests(request); + TestDescriptor engineDescriptor = discoverTests(request).getEngineDescriptor(); assertEquals(7, engineDescriptor.getDescendants().size(), "# resolved test descriptors"); } @Test void doNotDiscoverAbstractTestClass() { LauncherDiscoveryRequest request = request().selectors(selectClass(AbstractTestCase.class)).build(); - TestDescriptor engineDescriptor = discoverTests(request); + TestDescriptor engineDescriptor = discoverTests(request).getEngineDescriptor(); assertEquals(0, engineDescriptor.getDescendants().size(), "# resolved test descriptors"); } @@ -62,7 +62,7 @@ void doNotDiscoverAbstractTestClass() { void discoverMethodByUniqueId() { LauncherDiscoveryRequest request = request().selectors( selectUniqueId(JupiterUniqueIdBuilder.uniqueIdForMethod(LocalTestCase.class, "test1()"))).build(); - TestDescriptor engineDescriptor = discoverTests(request); + TestDescriptor engineDescriptor = discoverTests(request).getEngineDescriptor(); assertEquals(2, engineDescriptor.getDescendants().size(), "# resolved test descriptors"); } @@ -70,7 +70,7 @@ void discoverMethodByUniqueId() { void discoverMethodByUniqueIdForOverloadedMethod() { LauncherDiscoveryRequest request = request().selectors( selectUniqueId(JupiterUniqueIdBuilder.uniqueIdForMethod(LocalTestCase.class, "test4()"))).build(); - TestDescriptor engineDescriptor = discoverTests(request); + TestDescriptor engineDescriptor = discoverTests(request).getEngineDescriptor(); assertEquals(2, engineDescriptor.getDescendants().size(), "# resolved test descriptors"); } @@ -78,7 +78,7 @@ void discoverMethodByUniqueIdForOverloadedMethod() { void discoverMethodByUniqueIdForOverloadedMethodVariantThatAcceptsArguments() { LauncherDiscoveryRequest request = request().selectors(selectUniqueId(JupiterUniqueIdBuilder.uniqueIdForMethod( LocalTestCase.class, "test4(" + TestInfo.class.getName() + ")"))).build(); - TestDescriptor engineDescriptor = discoverTests(request); + TestDescriptor engineDescriptor = discoverTests(request).getEngineDescriptor(); assertEquals(2, engineDescriptor.getDescendants().size(), "# resolved test descriptors"); } @@ -87,7 +87,7 @@ void discoverMethodByMethodReference() throws NoSuchMethodException { Method testMethod = LocalTestCase.class.getDeclaredMethod("test3"); LauncherDiscoveryRequest request = request().selectors(selectMethod(LocalTestCase.class, testMethod)).build(); - TestDescriptor engineDescriptor = discoverTests(request); + TestDescriptor engineDescriptor = discoverTests(request).getEngineDescriptor(); assertEquals(2, engineDescriptor.getDescendants().size(), "# resolved test descriptors"); } @@ -96,7 +96,7 @@ void discoverMultipleMethodsOfSameClass() { LauncherDiscoveryRequest request = request().selectors(selectMethod(LocalTestCase.class, "test1"), selectMethod(LocalTestCase.class, "test2")).build(); - TestDescriptor engineDescriptor = discoverTests(request); + TestDescriptor engineDescriptor = discoverTests(request).getEngineDescriptor(); assertThat(engineDescriptor.getChildren()).hasSize(1); TestDescriptor classDescriptor = getOnlyElement(engineDescriptor.getChildren()); @@ -109,7 +109,7 @@ void discoverCompositeSpec() { selectUniqueId(JupiterUniqueIdBuilder.uniqueIdForMethod(LocalTestCase.class, "test2()")), selectClass(LocalTestCase.class)).build(); - TestDescriptor engineDescriptor = discoverTests(spec); + TestDescriptor engineDescriptor = discoverTests(spec).getEngineDescriptor(); assertEquals(7, engineDescriptor.getDescendants().size(), "# resolved test descriptors"); } @@ -118,7 +118,7 @@ void discoverTestTemplateMethodByUniqueId() { LauncherDiscoveryRequest spec = request().selectors( selectUniqueId(uniqueIdForTestTemplateMethod(TestTemplateClass.class, "testTemplate()"))).build(); - TestDescriptor engineDescriptor = discoverTests(spec); + TestDescriptor engineDescriptor = discoverTests(spec).getEngineDescriptor(); assertEquals(2, engineDescriptor.getDescendants().size(), "# resolved test descriptors"); } @@ -127,7 +127,7 @@ void discoverTestTemplateMethodByMethodSelector() { LauncherDiscoveryRequest spec = request().selectors( selectMethod(TestTemplateClass.class, "testTemplate")).build(); - TestDescriptor engineDescriptor = discoverTests(spec); + TestDescriptor engineDescriptor = discoverTests(spec).getEngineDescriptor(); assertEquals(2, engineDescriptor.getDescendants().size(), "# resolved test descriptors"); } @@ -139,7 +139,7 @@ void discoverDeeplyNestedTestMethodByNestedMethodSelector() throws Exception { AbstractSuperClass.NestedInAbstractClass.class.getDeclaredMethod("test")); LauncherDiscoveryRequest spec = request().selectors(selector).build(); - TestDescriptor engineDescriptor = discoverTests(spec); + TestDescriptor engineDescriptor = discoverTests(spec).getEngineDescriptor(); ClassTestDescriptor topLevelClassDescriptor = (ClassTestDescriptor) getOnlyElement( engineDescriptor.getChildren()); diff --git a/platform-tests/src/test/java/org/junit/platform/testkit/engine/EngineDiscoveryResultsIntegrationTests.java b/platform-tests/src/test/java/org/junit/platform/testkit/engine/EngineDiscoveryResultsIntegrationTests.java new file mode 100644 index 000000000000..29945442f388 --- /dev/null +++ b/platform-tests/src/test/java/org/junit/platform/testkit/engine/EngineDiscoveryResultsIntegrationTests.java @@ -0,0 +1,99 @@ +/* + * Copyright 2015-2025 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.testkit.engine; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; +import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedClass; +import org.junit.jupiter.params.provider.EnumSource; +import org.junit.platform.engine.DiscoveryIssue; +import org.junit.platform.engine.DiscoveryIssue.Severity; +import org.junit.platform.engine.DiscoverySelector; +import org.junit.platform.engine.EngineDiscoveryRequest; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.TestEngine; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.support.descriptor.ClassSource; +import org.junit.platform.fakes.TestEngineStub; + +@ParameterizedClass +@EnumSource +record EngineDiscoveryResultsIntegrationTests(TestKitApi testKit) { + + @Test + void returnsEngineDescriptor() { + var results = testKit.discover("junit-jupiter", selectClass(TestCase.class)); + + assertThat(results.getEngineDescriptor().getDisplayName()).isEqualTo("JUnit Jupiter"); + assertThat(getOnlyElement(results.getEngineDescriptor().getChildren()).getSource()) // + .contains(ClassSource.from(TestCase.class)); + } + + @Test + void collectsDiscoveryIssues() { + var issue = DiscoveryIssue.create(Severity.WARNING, "warning"); + var testEngine = new TestEngineStub() { + @Override + public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { + var listener = discoveryRequest.getDiscoveryListener(); + listener.issueEncountered(uniqueId, issue); + return super.discover(discoveryRequest, uniqueId); + } + }; + + var results = testKit.discover(testEngine); + + assertThat(results.getDiscoveryIssues()).containsExactly(issue); + } + + @SuppressWarnings("JUnitMalformedDeclaration") + static class TestCase { + @Test + void test() { + } + } + + enum TestKitApi { + + STATIC_METHOD { + @Override + EngineDiscoveryResults discover(String engineId, DiscoverySelector selector) { + return EngineTestKit.discover(engineId, request().selectors(selector).build()); + } + + @Override + EngineDiscoveryResults discover(TestEngine testEngine) { + return EngineTestKit.discover(testEngine, request().build()); + } + }, + + FLUENT_API { + @Override + EngineDiscoveryResults discover(String engineId, DiscoverySelector selector) { + return EngineTestKit.engine(engineId).selectors(selector).discover(); + } + + @Override + EngineDiscoveryResults discover(TestEngine testEngine) { + return EngineTestKit.engine(testEngine).discover(); + } + }; + + @SuppressWarnings("SameParameterValue") + abstract EngineDiscoveryResults discover(String engineId, DiscoverySelector selector); + + abstract EngineDiscoveryResults discover(TestEngine testEngine); + } +}