Skip to content

Commit 2a777a8

Browse files
Introduce parseable DiscoverySelector representations (#3737)
All Platform implementations of `DiscoverySelector` now have a parseable string representation that can be generated by calling the new `DiscoverySelector.toIdentifier()` method and `toString()` on the returned `DiscoverySelectorIdentifier`. This string representation can be used to reconstruct the original `DiscoverySelector` by calling the new `DiscoverySelectors.parse()` method. This change will allow build tools and IDEs to provide generic mechanisms for specifying selectors on the command line or in configuration files without having to support each selector type individually. The Console Launcher supports specifying selectors via their identifiers using the `--select` option. For example, `--select class:foo.Bar` will run all tests in the `foo.Bar` class. Similarly, the JUnit Platform Suite engine provides a new `@Select("<identifier>)` annotation. Co-authored-by: Marc Philipp <[email protected]>
1 parent bc55d0e commit 2a777a8

File tree

47 files changed

+2058
-288
lines changed

Some content is hidden

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

47 files changed

+2058
-288
lines changed

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

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,35 @@ endif::[]
2222
// Platform Engine
2323
:junit-platform-engine: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/package-summary.html[junit-platform-engine]
2424
:junit-platform-engine-support-discovery: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/support/discovery/package-summary.html[org.junit.platform.engine.support.discovery]
25-
:DiscoverySelectors_selectMethod: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/DiscoverySelectors.html#selectMethod-java.lang.String-[selectMethod(String) in DiscoverySelectors]
25+
:ClasspathResourceSelector: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/ClasspathResourceSelector.html[ClasspathResourceSelector]
26+
:ClasspathRootSelector: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/ClasspathRootSelector.html[ClasspathRootSelector]
27+
:ClassSelector: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/ClassSelector.html[ClassSelector]
28+
:DirectorySelector: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/DirectorySelector.html[DirectorySelector]
29+
:DiscoverySelectors: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/DiscoverySelectors.html[DiscoverySelectors]
30+
:DiscoverySelectors_selectClasspathResource: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/DiscoverySelectors.html#selectClasspathResource(java.lang.String)[selectClasspathResource]
31+
:DiscoverySelectors_selectClasspathRoots: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/DiscoverySelectors.html#selectClasspathRoots(java.util.Set)[selectClasspathRoots]
32+
:DiscoverySelectors_selectClass: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/DiscoverySelectors.html#selectClass(java.lang.String)[selectClass]
33+
:DiscoverySelectors_selectDirectory: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/DiscoverySelectors.html#selectDirectory(java.lang.String)[selectDirectory]
34+
:DiscoverySelectors_selectFile: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/DiscoverySelectors.html#selectFile(java.lang.String)[selectFile]
35+
:DiscoverySelectors_selectIteration: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/DiscoverySelectors.html#selectIteration(org.junit.platform.engine.DiscoverySelector,int\...)[selectIteration]
36+
:DiscoverySelectors_selectMethod: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/DiscoverySelectors.html#selectMethod(java.lang.String)[selectMethod]
37+
:DiscoverySelectors_selectModule: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/DiscoverySelectors.html#selectModule(java.lang.String)[selectModule]
38+
:DiscoverySelectors_selectNestedClass: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/DiscoverySelectors.html#selectNestedClass(java.util.List,java.lang.Class)[selectNestedClass]
39+
:DiscoverySelectors_selectNestedMethod: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/DiscoverySelectors.html#selectNestedMethod(java.util.List,java.lang.Class,java.lang.String)[selectNestedMethod]
40+
:DiscoverySelectors_selectPackage: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/DiscoverySelectors.html#selectPackage(java.lang.String)[selectPackage]
41+
:DiscoverySelectors_selectUniqueId: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/DiscoverySelectors.html#selectUniqueId(java.lang.String)[selectUniqueId]
42+
:DiscoverySelectors_selectUri: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/DiscoverySelectors.html#selectUri(java.lang.String)[selectUri]
43+
:FileSelector: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/FileSelector.html[FileSelector]
2644
:HierarchicalTestEngine: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/support/hierarchical/HierarchicalTestEngine.html[HierarchicalTestEngine]
45+
:IterationSelector: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/IterationSelector.html[IterationSelector]
46+
:MethodSelector: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/MethodSelector.html[MethodSelector]
47+
:ModuleSelector: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/ModuleSelector.html[ModuleSelector]
48+
:NestedClassSelector: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/NestedClassSelector.html[NestedClassSelector]
49+
:NestedMethodSelector: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/NestedMethodSelector.html[NestedMethodSelector]
50+
:PackageSelector: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/PackageSelector.html[PackageSelector]
2751
:ParallelExecutionConfigurationStrategy: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/support/hierarchical/ParallelExecutionConfigurationStrategy.html[ParallelExecutionConfigurationStrategy]
52+
:UniqueIdSelector: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/UniqueIdSelector.html[UniqueIdSelector]
53+
:UriSelector: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/UriSelector.html[UriSelector]
2854
:TestEngine: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/TestEngine.html[TestEngine]
2955
// Platform Launcher API
3056
:junit-platform-launcher: {javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/package-summary.html[junit-platform-launcher]
@@ -51,6 +77,15 @@ endif::[]
5177
// Platform Suite
5278
:suite-api-package: {javadoc-root}/org.junit.platform.suite.api/org/junit/platform/suite/api/package-summary.html[org.junit.platform.suite.api]
5379
:junit-platform-suite-engine: {javadoc-root}/org.junit.platform.suite.engine/org/junit/platform/suite/engine/package-summary.html[junit-platform-suite-engine]
80+
:Select: {javadoc-root}/org.junit.platform.suite.api/org/junit/platform/suite/api/Select.html[@Select]
81+
:SelectClasspathResource: {javadoc-root}/org.junit.platform.suite.api/org/junit/platform/suite/api/SelectClasspathResource.html[@SelectClasspathResource]
82+
:SelectClasses: {javadoc-root}/org.junit.platform.suite.api/org/junit/platform/suite/api/SelectClasses.html[@SelectClasses]
83+
:SelectDirectories: {javadoc-root}/org.junit.platform.suite.api/org/junit/platform/suite/api/SelectDirectories.html[@SelectDirectories]
84+
:SelectFile: {javadoc-root}/org.junit.platform.suite.api/org/junit/platform/suite/api/SelectFile.html[@SelectFile]
85+
:SelectMethod: {javadoc-root}/org.junit.platform.suite.api/org/junit/platform/suite/api/SelectMethod.html[@SelectMethod]
86+
:SelectModules: {javadoc-root}/org.junit.platform.suite.api/org/junit/platform/suite/api/SelectModules.html[@SelectModules]
87+
:SelectPackages: {javadoc-root}/org.junit.platform.suite.api/org/junit/platform/suite/api/SelectPackages.html[@SelectPackages]
88+
:SelectUris: {javadoc-root}/org.junit.platform.suite.api/org/junit/platform/suite/api/SelectUris.html[@SelectUris]
5489
// Platform Test Kit
5590
:testkit-engine-package: {javadoc-root}/org.junit.platform.testkit/org/junit/platform/testkit/engine/package-summary.html[org.junit.platform.testkit.engine]
5691
:EngineExecutionResults: {javadoc-root}/org.junit.platform.testkit/org/junit/platform/testkit/engine/EngineExecutionResults.html[EngineExecutionResults]

documentation/src/docs/asciidoc/release-notes/release-notes-5.11.0-M2.adoc

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,19 @@ repository on GitHub.
3737
[[release-notes-5.11.0-M2-junit-platform-new-features-and-improvements]]
3838
==== New Features and Improvements
3939

40+
* All Platform implementations of `DiscoverySelector` now have a parseable string
41+
representation that can be generated by calling the new
42+
`DiscoverySelector.toIdentifier()` method and `toString()` on the returned
43+
`DiscoverySelectorIdentifier`. This string representation can be used to reconstruct
44+
the original `DiscoverySelector` by calling the new `DiscoverySelectors.parse()` method.
45+
This change will allow build tools and IDEs to provide generic mechanisms for specifying
46+
selectors on the command line or in configuration files without having to support each
47+
selector type individually.
48+
- The Console Launcher supports specifying selectors via their identifiers using the
49+
`--select` option. For example, `--select class:foo.Bar` will run all tests in the
50+
`foo.Bar` class.
51+
- Similarly, the JUnit Platform Suite engine provides a new `@Select("<identifier>)`
52+
annotation.
4053
* `NamespacedHierarchicalStore` now throws an `IllegalStateException` for any attempt to
4154
modify or query the store after it has been closed. In addition, an attempt to close a
4255
store that has already been closed will have no effect.

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

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -704,18 +704,21 @@ The `{ConsoleLauncher}` provides the following subcommands:
704704
include::{consoleLauncherOptionsFile}[]
705705
----
706706

707+
[[running-tests-console-launcher-options-discovering-tests]]
707708
===== Discovering tests
708709

709710
----
710711
include::{consoleLauncherDiscoverOptionsFile}[]
711712
----
712713

714+
[[running-tests-console-launcher-options-executing-tests]]
713715
===== Executing tests
714716

715717
----
716718
include::{consoleLauncherExecuteOptionsFile}[]
717719
----
718720

721+
[[running-tests-console-launcher-options-listing-test-engines]]
719722
===== Listing test engines
720723

721724
----
@@ -887,6 +890,38 @@ WARNING: Test classes and suites annotated with `@RunWith(JUnitPlatform.class)`
887890
documented in some IDEs). Such classes and suites can only be executed using JUnit 4
888891
infrastructure.
889892

893+
[[running-tests-discovery-selectors]]
894+
=== Discovery Selectors
895+
896+
The JUnit Platform provides a rich set of discovery selectors that can be used to specify
897+
which tests should be discovered or executed.
898+
899+
Discovery selectors can be created programmatically using the factory methods in the
900+
`{DiscoverySelectors}` class, specified declaratively via annotations when using the
901+
<<junit-platform-suite-engine>>, via options of the <<running-tests-console-launcher>>, or
902+
generically as strings via their identifiers.
903+
904+
The following discovery selectors are provided out of the box:
905+
906+
|===
907+
| Java Type | API | Annotation | Console Launcher | Identifier
908+
909+
| `{ClasspathResourceSelector}` | `{DiscoverySelectors_selectClasspathResource}` | `{SelectClasspathResource}` | `--select-resource /foo.csv` | `resource:/foo.csv`
910+
| `{ClasspathRootSelector}` | `{DiscoverySelectors_selectClasspathRoots}` | -- | `--scan-classpath bin` | `classpath-root:bin`
911+
| `{ClassSelector}` | `{DiscoverySelectors_selectClass}` | `{SelectClasses}` | `--select-class com.acme.Foo` | `class:com.acme.Foo`
912+
| `{DirectorySelector}` | `{DiscoverySelectors_selectDirectory}` | `{SelectDirectories}` | `--select-directory foo/bar` | `directory:foo/bar`
913+
| `{FileSelector}` | `{DiscoverySelectors_selectFile}` | `{SelectFile}` | `--select-file dir/foo.txt` | `file:dir/foo.txt`
914+
| `{IterationSelector}` | `{DiscoverySelectors_selectIteration}` | `{Select}("<identifier>")` | `--select-iteration method=com.acme.Foo#m[1..2]` | `iteration:method:com.acme.Foo#m[1..2]`
915+
| `{MethodSelector}` | `{DiscoverySelectors_selectMethod}` | `{SelectMethod}` | `--select-method com.acme.Foo#m` | `method:com.acme.Foo#m`
916+
| `{ModuleSelector}` | `{DiscoverySelectors_selectModule}` | `{SelectModules}` | `--select-module com.acme` | `module:com.acme`
917+
| `{NestedClassSelector}` | `{DiscoverySelectors_selectNestedClass}` | `{Select}("<identifier>")` | `--select <identifier>` | `nested-class:com.acme.Foo/Bar`
918+
| `{NestedMethodSelector}` | `{DiscoverySelectors_selectNestedMethod}` | `{Select}("<identifier>")` | `--select <identifier>` | `nested-method:com.acme.Foo/Bar#m`
919+
| `{PackageSelector}` | `{DiscoverySelectors_selectPackage}` | `{SelectPackages}` | `--select-package com.acme.foo` | `package:com.acme.foo`
920+
| `{UniqueIdSelector}` | `{DiscoverySelectors_selectUniqueId}` | `{Select}("<identifier>")` | `--select <identifier>` | `uid:...`
921+
| `{UriSelector}` | `{DiscoverySelectors_selectUri}` | `{SelectUris}` | `--select-uri \file:///foo.txt` | `uri:file:///foo.txt`
922+
|===
923+
924+
890925
[[running-tests-config-params]]
891926
=== Configuration Parameters
892927

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2429,8 +2429,8 @@ implementations.
24292429
`MethodSource` ::
24302430
If the `URI` contains the `method` scheme and the fully qualified method name (FQMN) --
24312431
for example, `method:org.junit.Foo#bar(java.lang.String, java.lang.String[])`. Please
2432-
refer to the Javadoc for `DiscoverySelectors.selectMethod(String)` for the supported
2433-
formats for a FQMN.
2432+
refer to the Javadoc for `{DiscoverySelectors}.{DiscoverySelectors_selectMethod}` for the
2433+
supported formats for a FQMN.
24342434

24352435
`ClassSource` ::
24362436
If the `URI` contains the `class` scheme and the fully qualified class name --

junit-platform-commons/src/main/java/org/junit/platform/commons/util/CollectionUtils.java

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import java.util.Iterator;
2626
import java.util.List;
2727
import java.util.ListIterator;
28+
import java.util.Optional;
2829
import java.util.Set;
2930
import java.util.function.Consumer;
3031
import java.util.stream.Collector;
@@ -55,7 +56,7 @@ private CollectionUtils() {
5556
}
5657

5758
/**
58-
* Read the only element of a collection of size 1.
59+
* Get the only element of a collection of size 1.
5960
*
6061
* @param collection the collection to get the element from
6162
* @return the only element of the collection
@@ -66,7 +67,29 @@ public static <T> T getOnlyElement(Collection<T> collection) {
6667
Preconditions.notNull(collection, "collection must not be null");
6768
Preconditions.condition(collection.size() == 1,
6869
() -> "collection must contain exactly one element: " + collection);
69-
return collection.iterator().next();
70+
return firstElement(collection);
71+
}
72+
73+
/**
74+
* Get the first element of the supplied collection unless it's empty.
75+
*
76+
* @param collection the collection to get the element from
77+
* @return the first element of the collection; empty if the collection is empty
78+
* @throws PreconditionViolationException if the collection is {@code null}
79+
* @since 1.11
80+
*/
81+
@API(status = INTERNAL, since = "1.11")
82+
public static <T> Optional<T> getFirstElement(Collection<T> collection) {
83+
Preconditions.notNull(collection, "collection must not be null");
84+
return collection.isEmpty() //
85+
? Optional.empty() //
86+
: Optional.ofNullable(firstElement(collection));
87+
}
88+
89+
private static <T> T firstElement(Collection<T> collection) {
90+
return collection instanceof List //
91+
? ((List<T>) collection).get(0) //
92+
: collection.iterator().next();
7093
}
7194

7295
/**

junit-platform-commons/src/main/java/org/junit/platform/commons/util/ReflectionUtils.java

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -929,13 +929,34 @@ public static String getFullyQualifiedMethodName(Class<?> clazz, Method method)
929929
* @param methodName the name of the method; never {@code null} or blank
930930
* @param parameterTypes the parameter types of the method; may be {@code null} or empty
931931
* @return fully qualified method name; never {@code null}
932-
* @see #getFullyQualifiedMethodName(Class, Method)
933932
*/
934933
public static String getFullyQualifiedMethodName(Class<?> clazz, String methodName, Class<?>... parameterTypes) {
935934
Preconditions.notNull(clazz, "Class must not be null");
936935
Preconditions.notBlank(methodName, "Method name must not be null or blank");
937936

938-
return String.format("%s#%s(%s)", clazz.getName(), methodName, ClassUtils.nullSafeToString(parameterTypes));
937+
return getFullyQualifiedMethodName(clazz.getName(), methodName, ClassUtils.nullSafeToString(parameterTypes));
938+
}
939+
940+
/**
941+
* Build the <em>fully qualified method name</em> for the method described by the
942+
* supplied class, method name, and parameter types.
943+
*
944+
* <p>Note that the class is not necessarily the class in which the method is
945+
* declared.
946+
*
947+
* @param className the name of the class from which the method should be referenced; never {@code null}
948+
* @param methodName the name of the method; never {@code null} or blank
949+
* @param parameterTypeNames the parameter type names of the method; may be empty but not {@code null}
950+
* @return fully qualified method name; never {@code null}
951+
* @since 1.11
952+
*/
953+
@API(status = INTERNAL, since = "1.11")
954+
public static String getFullyQualifiedMethodName(String className, String methodName, String parameterTypeNames) {
955+
Preconditions.notBlank(className, "Class name must not be null or blank");
956+
Preconditions.notBlank(methodName, "Method name must not be null or blank");
957+
Preconditions.notNull(parameterTypeNames, "Parameter type names must not be null");
958+
959+
return String.format("%s#%s(%s)", className, methodName, parameterTypeNames);
939960
}
940961

941962
/**

0 commit comments

Comments
 (0)