Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Introduce parseable DiscoverySelector representations #3737

Merged
merged 43 commits into from
May 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
64be199
Initial PoC of DiscoverySelectors.parse
leonard84 Dec 15, 2022
21e4886
Consistently use DiscoverySelectors methods to reuse validation logic
leonard84 Dec 16, 2022
44bca4c
Add a DiscoverySelector.toSelectorString() ..
leonard84 Dec 16, 2022
9eebabd
Use URLEncode for all characters in UniqueId segment values
leonard84 Dec 16, 2022
d824ba7
Add SelectorParserContext
leonard84 Sep 13, 2023
3c00a82
Fix deprecations and code style issues
leonard84 Mar 15, 2024
5b6d01b
spotlessApply
leonard84 Mar 15, 2024
a51c09b
Introduce TBD as replacement of URI
leonard84 Mar 15, 2024
0bd2e21
Revert "Use URLEncode for all characters in UniqueId segment values"
leonard84 Mar 15, 2024
360bacc
Remove UriEncoding again
leonard84 Mar 15, 2024
d64e235
Rename TBD to DiscoverySelectorIdentifier
marcphilipp Mar 21, 2024
9312afe
Allow covariant return types to avoid casting
marcphilipp Mar 21, 2024
a5545e4
Merge branch 'main'
marcphilipp Mar 21, 2024
9d91596
Make selectors return identifiers
marcphilipp Mar 21, 2024
a114a29
Add more tests and fix implementations
marcphilipp Mar 21, 2024
e0a3f45
Introduce --select and revamp --select-iteration to use identifiers
marcphilipp Mar 21, 2024
0ffc4c9
Reuse fully-qualified method name utilities for method selectors
marcphilipp Mar 22, 2024
9e8ffd8
Introduce StringUtils.splitIntoTwo to reduce duplication
marcphilipp Mar 22, 2024
ee4707f
Change parser to return Optional rather than Stream
marcphilipp Mar 22, 2024
6f542f0
Reuse selectMethod(String)
marcphilipp Mar 22, 2024
f391f83
Add Javadoc
marcphilipp Mar 22, 2024
a80e505
Fix module descriptor
marcphilipp Mar 22, 2024
4fdca80
Revert accidental side effect of rename
marcphilipp Mar 22, 2024
fe9cd57
Add TODO
marcphilipp Mar 22, 2024
720583b
Polish formatting
marcphilipp Mar 22, 2024
252f02f
Mark parsers internal
marcphilipp Mar 22, 2024
b0343b7
Mark toIdentifier() experimental
marcphilipp Mar 22, 2024
8d4644a
Polishing
marcphilipp Mar 22, 2024
bc80531
Polishing
marcphilipp May 1, 2024
9aeec78
Add tests for CollectionUtils#getFirstElement
marcphilipp May 1, 2024
5b3f23a
Merge remote-tracking branch 'origin/main' into leo/discovery-selecto…
marcphilipp May 1, 2024
9ce4e20
Add to release notes
marcphilipp May 1, 2024
9967df6
Load parsers at most once per class loader
marcphilipp May 15, 2024
0c14cc6
Add explicit anchors
marcphilipp May 15, 2024
6143133
Document discovery selectors in User Guide
marcphilipp May 15, 2024
4dc3a7c
Add todo
marcphilipp May 15, 2024
27d1542
Reduce table width
marcphilipp May 15, 2024
964c27b
Add links to Javadoc
marcphilipp May 15, 2024
a31381c
Delete empty paragraph
marcphilipp May 15, 2024
81b6694
Add type-level Javadoc
marcphilipp May 15, 2024
0688942
Introduce `Select` annotation for test suites
marcphilipp May 16, 2024
2bd52e1
Collapse index ranges when formatting iteration selector identifiers
marcphilipp May 16, 2024
a7f4868
Link to suite annotation Javadoc
marcphilipp May 16, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
37 changes: 36 additions & 1 deletion documentation/src/docs/asciidoc/link-attributes.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,35 @@ endif::[]
// Platform Engine
:junit-platform-engine: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/package-summary.html[junit-platform-engine]
: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]
:DiscoverySelectors_selectMethod: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/DiscoverySelectors.html#selectMethod-java.lang.String-[selectMethod(String) in DiscoverySelectors]
:ClasspathResourceSelector: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/ClasspathResourceSelector.html[ClasspathResourceSelector]
:ClasspathRootSelector: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/ClasspathRootSelector.html[ClasspathRootSelector]
:ClassSelector: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/ClassSelector.html[ClassSelector]
:DirectorySelector: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/DirectorySelector.html[DirectorySelector]
:DiscoverySelectors: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/DiscoverySelectors.html[DiscoverySelectors]
:DiscoverySelectors_selectClasspathResource: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/DiscoverySelectors.html#selectClasspathResource(java.lang.String)[selectClasspathResource]
:DiscoverySelectors_selectClasspathRoots: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/DiscoverySelectors.html#selectClasspathRoots(java.util.Set)[selectClasspathRoots]
:DiscoverySelectors_selectClass: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/DiscoverySelectors.html#selectClass(java.lang.String)[selectClass]
:DiscoverySelectors_selectDirectory: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/DiscoverySelectors.html#selectDirectory(java.lang.String)[selectDirectory]
:DiscoverySelectors_selectFile: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/DiscoverySelectors.html#selectFile(java.lang.String)[selectFile]
:DiscoverySelectors_selectIteration: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/DiscoverySelectors.html#selectIteration(org.junit.platform.engine.DiscoverySelector,int\...)[selectIteration]
:DiscoverySelectors_selectMethod: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/DiscoverySelectors.html#selectMethod(java.lang.String)[selectMethod]
:DiscoverySelectors_selectModule: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/DiscoverySelectors.html#selectModule(java.lang.String)[selectModule]
:DiscoverySelectors_selectNestedClass: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/DiscoverySelectors.html#selectNestedClass(java.util.List,java.lang.Class)[selectNestedClass]
: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]
:DiscoverySelectors_selectPackage: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/DiscoverySelectors.html#selectPackage(java.lang.String)[selectPackage]
:DiscoverySelectors_selectUniqueId: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/DiscoverySelectors.html#selectUniqueId(java.lang.String)[selectUniqueId]
:DiscoverySelectors_selectUri: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/DiscoverySelectors.html#selectUri(java.lang.String)[selectUri]
:FileSelector: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/FileSelector.html[FileSelector]
:HierarchicalTestEngine: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/support/hierarchical/HierarchicalTestEngine.html[HierarchicalTestEngine]
:IterationSelector: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/IterationSelector.html[IterationSelector]
:MethodSelector: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/MethodSelector.html[MethodSelector]
:ModuleSelector: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/ModuleSelector.html[ModuleSelector]
:NestedClassSelector: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/NestedClassSelector.html[NestedClassSelector]
:NestedMethodSelector: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/NestedMethodSelector.html[NestedMethodSelector]
:PackageSelector: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/PackageSelector.html[PackageSelector]
:ParallelExecutionConfigurationStrategy: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/support/hierarchical/ParallelExecutionConfigurationStrategy.html[ParallelExecutionConfigurationStrategy]
:UniqueIdSelector: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/UniqueIdSelector.html[UniqueIdSelector]
:UriSelector: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/UriSelector.html[UriSelector]
:TestEngine: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/TestEngine.html[TestEngine]
// Platform Launcher API
:junit-platform-launcher: {javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/package-summary.html[junit-platform-launcher]
Expand All @@ -51,6 +77,15 @@ endif::[]
// Platform Suite
:suite-api-package: {javadoc-root}/org.junit.platform.suite.api/org/junit/platform/suite/api/package-summary.html[org.junit.platform.suite.api]
:junit-platform-suite-engine: {javadoc-root}/org.junit.platform.suite.engine/org/junit/platform/suite/engine/package-summary.html[junit-platform-suite-engine]
:Select: {javadoc-root}/org.junit.platform.suite.api/org/junit/platform/suite/api/Select.html[@Select]
:SelectClasspathResource: {javadoc-root}/org.junit.platform.suite.api/org/junit/platform/suite/api/SelectClasspathResource.html[@SelectClasspathResource]
:SelectClasses: {javadoc-root}/org.junit.platform.suite.api/org/junit/platform/suite/api/SelectClasses.html[@SelectClasses]
:SelectDirectories: {javadoc-root}/org.junit.platform.suite.api/org/junit/platform/suite/api/SelectDirectories.html[@SelectDirectories]
:SelectFile: {javadoc-root}/org.junit.platform.suite.api/org/junit/platform/suite/api/SelectFile.html[@SelectFile]
:SelectMethod: {javadoc-root}/org.junit.platform.suite.api/org/junit/platform/suite/api/SelectMethod.html[@SelectMethod]
:SelectModules: {javadoc-root}/org.junit.platform.suite.api/org/junit/platform/suite/api/SelectModules.html[@SelectModules]
:SelectPackages: {javadoc-root}/org.junit.platform.suite.api/org/junit/platform/suite/api/SelectPackages.html[@SelectPackages]
:SelectUris: {javadoc-root}/org.junit.platform.suite.api/org/junit/platform/suite/api/SelectUris.html[@SelectUris]
// Platform Test Kit
:testkit-engine-package: {javadoc-root}/org.junit.platform.testkit/org/junit/platform/testkit/engine/package-summary.html[org.junit.platform.testkit.engine]
:EngineExecutionResults: {javadoc-root}/org.junit.platform.testkit/org/junit/platform/testkit/engine/EngineExecutionResults.html[EngineExecutionResults]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,19 @@ repository on GitHub.
[[release-notes-5.11.0-M2-junit-platform-new-features-and-improvements]]
==== New Features and Improvements

* 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.
* `NamespacedHierarchicalStore` now throws an `IllegalStateException` for any attempt to
modify or query the store after it has been closed. In addition, an attempt to close a
store that has already been closed will have no effect.
Expand Down
35 changes: 35 additions & 0 deletions documentation/src/docs/asciidoc/user-guide/running-tests.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -704,18 +704,21 @@ The `{ConsoleLauncher}` provides the following subcommands:
include::{consoleLauncherOptionsFile}[]
----

[[running-tests-console-launcher-options-discovering-tests]]
===== Discovering tests

----
include::{consoleLauncherDiscoverOptionsFile}[]
----

[[running-tests-console-launcher-options-executing-tests]]
===== Executing tests

----
include::{consoleLauncherExecuteOptionsFile}[]
----

[[running-tests-console-launcher-options-listing-test-engines]]
===== Listing test engines

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

[[running-tests-discovery-selectors]]
=== Discovery Selectors

The JUnit Platform provides a rich set of discovery selectors that can be used to specify
which tests should be discovered or executed.

Discovery selectors can be created programmatically using the factory methods in the
`{DiscoverySelectors}` class, specified declaratively via annotations when using the
<<junit-platform-suite-engine>>, via options of the <<running-tests-console-launcher>>, or
generically as strings via their identifiers.

The following discovery selectors are provided out of the box:

|===
| Java Type | API | Annotation | Console Launcher | Identifier

| `{ClasspathResourceSelector}` | `{DiscoverySelectors_selectClasspathResource}` | `{SelectClasspathResource}` | `--select-resource /foo.csv` | `resource:/foo.csv`
| `{ClasspathRootSelector}` | `{DiscoverySelectors_selectClasspathRoots}` | -- | `--scan-classpath bin` | `classpath-root:bin`
| `{ClassSelector}` | `{DiscoverySelectors_selectClass}` | `{SelectClasses}` | `--select-class com.acme.Foo` | `class:com.acme.Foo`
| `{DirectorySelector}` | `{DiscoverySelectors_selectDirectory}` | `{SelectDirectories}` | `--select-directory foo/bar` | `directory:foo/bar`
| `{FileSelector}` | `{DiscoverySelectors_selectFile}` | `{SelectFile}` | `--select-file dir/foo.txt` | `file:dir/foo.txt`
| `{IterationSelector}` | `{DiscoverySelectors_selectIteration}` | `{Select}("<identifier>")` | `--select-iteration method=com.acme.Foo#m[1..2]` | `iteration:method:com.acme.Foo#m[1..2]`
| `{MethodSelector}` | `{DiscoverySelectors_selectMethod}` | `{SelectMethod}` | `--select-method com.acme.Foo#m` | `method:com.acme.Foo#m`
| `{ModuleSelector}` | `{DiscoverySelectors_selectModule}` | `{SelectModules}` | `--select-module com.acme` | `module:com.acme`
| `{NestedClassSelector}` | `{DiscoverySelectors_selectNestedClass}` | `{Select}("<identifier>")` | `--select <identifier>` | `nested-class:com.acme.Foo/Bar`
| `{NestedMethodSelector}` | `{DiscoverySelectors_selectNestedMethod}` | `{Select}("<identifier>")` | `--select <identifier>` | `nested-method:com.acme.Foo/Bar#m`
| `{PackageSelector}` | `{DiscoverySelectors_selectPackage}` | `{SelectPackages}` | `--select-package com.acme.foo` | `package:com.acme.foo`
| `{UniqueIdSelector}` | `{DiscoverySelectors_selectUniqueId}` | `{Select}("<identifier>")` | `--select <identifier>` | `uid:...`
| `{UriSelector}` | `{DiscoverySelectors_selectUri}` | `{SelectUris}` | `--select-uri \file:///foo.txt` | `uri:file:///foo.txt`
|===


[[running-tests-config-params]]
=== Configuration Parameters

Expand Down
4 changes: 2 additions & 2 deletions documentation/src/docs/asciidoc/user-guide/writing-tests.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -2401,8 +2401,8 @@ implementations.
`MethodSource` ::
If the `URI` contains the `method` scheme and the fully qualified method name (FQMN) --
for example, `method:org.junit.Foo#bar(java.lang.String, java.lang.String[])`. Please
refer to the Javadoc for `DiscoverySelectors.selectMethod(String)` for the supported
formats for a FQMN.
refer to the Javadoc for `{DiscoverySelectors}.{DiscoverySelectors_selectMethod}` for the
supported formats for a FQMN.

`ClassSource` ::
If the `URI` contains the `class` scheme and the fully qualified class name --
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collector;
Expand Down Expand Up @@ -55,7 +56,7 @@ private CollectionUtils() {
}

/**
* Read the only element of a collection of size 1.
* Get the only element of a collection of size 1.
*
* @param collection the collection to get the element from
* @return the only element of the collection
Expand All @@ -66,7 +67,29 @@ public static <T> T getOnlyElement(Collection<T> collection) {
Preconditions.notNull(collection, "collection must not be null");
Preconditions.condition(collection.size() == 1,
() -> "collection must contain exactly one element: " + collection);
return collection.iterator().next();
return firstElement(collection);
}

/**
* Get the first element of the supplied collection unless it's empty.
*
* @param collection the collection to get the element from
* @return the first element of the collection; empty if the collection is empty
* @throws PreconditionViolationException if the collection is {@code null}
* @since 1.11
*/
@API(status = INTERNAL, since = "1.11")
public static <T> Optional<T> getFirstElement(Collection<T> collection) {
Preconditions.notNull(collection, "collection must not be null");
return collection.isEmpty() //
? Optional.empty() //
: Optional.ofNullable(firstElement(collection));
}

private static <T> T firstElement(Collection<T> collection) {
return collection instanceof List //
? ((List<T>) collection).get(0) //
: collection.iterator().next();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -929,13 +929,34 @@ public static String getFullyQualifiedMethodName(Class<?> clazz, Method method)
* @param methodName the name of the method; never {@code null} or blank
* @param parameterTypes the parameter types of the method; may be {@code null} or empty
* @return fully qualified method name; never {@code null}
* @see #getFullyQualifiedMethodName(Class, Method)
*/
public static String getFullyQualifiedMethodName(Class<?> clazz, String methodName, Class<?>... parameterTypes) {
Preconditions.notNull(clazz, "Class must not be null");
Preconditions.notBlank(methodName, "Method name must not be null or blank");

return String.format("%s#%s(%s)", clazz.getName(), methodName, ClassUtils.nullSafeToString(parameterTypes));
return getFullyQualifiedMethodName(clazz.getName(), methodName, ClassUtils.nullSafeToString(parameterTypes));
}

/**
* Build the <em>fully qualified method name</em> for the method described by the
* supplied class, method name, and parameter types.
*
* <p>Note that the class is not necessarily the class in which the method is
* declared.
*
* @param className the name of the class from which the method should be referenced; never {@code null}
* @param methodName the name of the method; never {@code null} or blank
* @param parameterTypeNames the parameter type names of the method; may be empty but not {@code null}
* @return fully qualified method name; never {@code null}
* @since 1.11
*/
@API(status = INTERNAL, since = "1.11")
public static String getFullyQualifiedMethodName(String className, String methodName, String parameterTypeNames) {
Preconditions.notBlank(className, "Class name must not be null or blank");
Preconditions.notBlank(methodName, "Method name must not be null or blank");
Preconditions.notNull(parameterTypeNames, "Parameter type names must not be null");

return String.format("%s#%s(%s)", className, methodName, parameterTypeNames);
}

/**
Expand Down