Skip to content

Commit

Permalink
Introduce Select annotation for test suites
Browse files Browse the repository at this point in the history
  • Loading branch information
marcphilipp committed May 16, 2024
1 parent 81b6694 commit d5c98e4
Show file tree
Hide file tree
Showing 13 changed files with 202 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ repository on GitHub.
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
10 changes: 5 additions & 5 deletions documentation/src/docs/asciidoc/user-guide/running-tests.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -904,20 +904,20 @@ generically as strings via their identifiers.
The following discovery selectors are provided out of the box:

|===
| Java Type | API | Annotation | Console Launcher | Identifier
| 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-iteration method=com.acme.Foo#m[1..2]` | `iteration:method:com.acme.Foo#m[1..2]`
| `{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>` | `nested-class:com.acme.Foo/Bar`
| `{NestedMethodSelector}` | `{DiscoverySelectors_selectNestedMethod}` | -- | `--select <identifier>` | `nested-method:com.acme.Foo/Bar#m`
| `{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>` | `uid:...`
| `{UniqueIdSelector}` | `{DiscoverySelectors_selectUniqueId}` | `@Select("<identifier>")` | `--select <identifier>` | `uid:...`
| `{UriSelector}` | `{DiscoverySelectors_selectUri}` | `@SelectUris` | `--select-uri \file:///foo.txt` | `uri:file:///foo.txt`
|===

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@
*/
class DiscoverySelectorIdentifierParsers {

static Stream<? extends DiscoverySelector> parseAll(String... identifiers) {
Preconditions.notNull(identifiers, "identifiers must not be null");
return Stream.of(identifiers) //
.map(DiscoverySelectorIdentifierParsers::parse) //
.filter(Optional::isPresent) //
.map(Optional::get);
}

static Stream<? extends DiscoverySelector> parseAll(Collection<DiscoverySelectorIdentifier> identifiers) {
Preconditions.notNull(identifiers, "identifiers must not be null");
return identifiers.stream() //
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -969,6 +969,20 @@ public static Optional<? extends DiscoverySelector> parse(DiscoverySelectorIdent
return DiscoverySelectorIdentifierParsers.parse(identifier);
}

/**
* Parse the supplied string representations of
* {@link DiscoverySelectorIdentifier DiscoverySelectorIdentifiers}.
*
* @param identifiers the string representations of
* {@code DiscoverySelectorIdentifiers} to parse; never {@code null}
* @since 1.11
* @see DiscoverySelectorIdentifierParser
*/
@API(status = EXPERIMENTAL, since = "1.11")
public static Stream<? extends DiscoverySelector> parseAll(String... identifiers) {
return DiscoverySelectorIdentifierParsers.parseAll(identifiers);
}

/**
* Parse the supplied {@link DiscoverySelectorIdentifier
* DiscoverySelectorIdentifiers}.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import org.junit.platform.suite.api.IncludeEngines;
import org.junit.platform.suite.api.IncludePackages;
import org.junit.platform.suite.api.IncludeTags;
import org.junit.platform.suite.api.Select;
import org.junit.platform.suite.api.SelectClasses;
import org.junit.platform.suite.api.SelectClasspathResource;
import org.junit.platform.suite.api.SelectDirectories;
Expand Down Expand Up @@ -87,6 +88,7 @@
* ClassNameFilter#STANDARD_INCLUDE_PATTERN}).
*
* @since 1.0
* @see Select
* @see SelectClasses
* @see SelectClasspathResource
* @see SelectDirectories
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright 2015-2024 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.suite.api;

import static org.apiguardian.api.API.Status.EXPERIMENTAL;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.apiguardian.api.API;

/**
* {@code @Select} is a {@linkplain Repeatable repeatable} annotation that
* specifies which tests to <em>select</em> based on prefixed
* {@linkplain org.junit.platform.engine.DiscoverySelectorIdentifier selector identifiers}.
*
* @since 1.11
* @see Suite
* @see org.junit.platform.engine.discovery.DiscoverySelectors#parse(String)
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Inherited
@Documented
@API(status = EXPERIMENTAL, since = "1.11")
@Repeatable(Selects.class)
public @interface Select {

/**
* One or more prefixed
* {@linkplain org.junit.platform.engine.DiscoverySelectorIdentifier selector identifiers}
* to select.
*/
String[] value();

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Copyright 2015-2024 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.suite.api;

import static org.apiguardian.api.API.Status.EXPERIMENTAL;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.apiguardian.api.API;

/**
* {@code @Selects} is a container for one or more
* {@link Select @Select} declarations.
*
* <p>Note, however, that use of the {@code @Selects} container is
* completely optional since {@code @Select} is a
* {@linkplain java.lang.annotation.Repeatable repeatable} annotation.
*
* @since 1.11
* @see Select
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Inherited
@Documented
@API(status = EXPERIMENTAL, since = "1.11")
public @interface Selects {

/**
* An array of one or more {@link Select @Select} declarations.
*/
Select[] value();

}
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
* configuration parameters are taken into account.
*
* @since 1.8
* @see Select
* @see SelectClasses
* @see SelectClasspathResource
* @see SelectDirectories
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import org.junit.platform.commons.util.Preconditions;
import org.junit.platform.commons.util.StringUtils;
import org.junit.platform.engine.DiscoverySelector;
import org.junit.platform.engine.discovery.ClassSelector;
import org.junit.platform.engine.discovery.ClasspathResourceSelector;
import org.junit.platform.engine.discovery.DirectorySelector;
Expand Down Expand Up @@ -112,6 +113,11 @@ static ClasspathResourceSelector selectClasspathResource(String classpathResourc
return DiscoverySelectors.selectClasspathResource(classpathResourceName, FilePosition.from(line, column));
}

static List<DiscoverySelector> parseIdentifiers(String[] identifiers) {
return DiscoverySelectors.parseAll(identifiers) //
.collect(Collectors.toList());
}

private static <T> Stream<T> uniqueStreamOf(T[] elements) {
return Arrays.stream(elements).distinct();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
import org.junit.platform.suite.api.IncludeEngines;
import org.junit.platform.suite.api.IncludePackages;
import org.junit.platform.suite.api.IncludeTags;
import org.junit.platform.suite.api.Select;
import org.junit.platform.suite.api.SelectClasses;
import org.junit.platform.suite.api.SelectClasspathResource;
import org.junit.platform.suite.api.SelectDirectories;
Expand Down Expand Up @@ -393,6 +394,9 @@ public SuiteLauncherDiscoveryRequestBuilder applySelectorsAndFiltersFromSuite(Cl
findAnnotationValues(suiteClass, SelectPackages.class, SelectPackages::value)
.map(AdditionalDiscoverySelectors::selectPackages)
.ifPresent(this::selectors);
findAnnotationValues(suiteClass, Select.class, Select::value)
.map(AdditionalDiscoverySelectors::parseIdentifiers)
.ifPresent(this::selectors);
// @formatter:on
return this;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
import org.junit.platform.suite.api.IncludeEngines;
import org.junit.platform.suite.api.IncludePackages;
import org.junit.platform.suite.api.IncludeTags;
import org.junit.platform.suite.api.Select;
import org.junit.platform.suite.api.SelectClasses;
import org.junit.platform.suite.api.SelectClasspathResource;
import org.junit.platform.suite.api.SelectDirectories;
Expand Down Expand Up @@ -585,6 +586,28 @@ class Suite {
assertEquals(Optional.empty(), configurationParameters.get("parent"));
}

@Test
void selectByIdentifier() {
// @formatter:off
@Select({
"class:org.junit.platform.suite.commons.SuiteLauncherDiscoveryRequestBuilderTests$NonLocalTestCase",
"method:org.junit.platform.suite.commons.SuiteLauncherDiscoveryRequestBuilderTests$NoParameterTestCase#testMethod"
})
// @formatter:on
class Suite {
}

LauncherDiscoveryRequest request = builder.applySelectorsAndFiltersFromSuite(Suite.class).build();
List<ClassSelector> classSelectors = request.getSelectorsByType(ClassSelector.class);
assertEquals(NonLocalTestCase.class, exactlyOne(classSelectors).getJavaClass());
List<MethodSelector> methodSelectors = request.getSelectorsByType(MethodSelector.class);
// @formatter:off
assertThat(exactlyOne(methodSelectors))
.extracting(MethodSelector::getJavaClass, MethodSelector::getMethodName)
.containsExactly(NoParameterTestCase.class, "testMethod");
// @formatter:on
}

private static <T> T exactlyOne(List<T> list) {
return CollectionUtils.getOnlyElement(list);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
import org.junit.platform.suite.engine.testsuites.MultiEngineSuite;
import org.junit.platform.suite.engine.testsuites.MultipleSuite;
import org.junit.platform.suite.engine.testsuites.NestedSuite;
import org.junit.platform.suite.engine.testsuites.SelectByIdentifierSuite;
import org.junit.platform.suite.engine.testsuites.SelectClassesSuite;
import org.junit.platform.suite.engine.testsuites.SelectMethodsSuite;
import org.junit.platform.suite.engine.testsuites.SuiteDisplayNameSuite;
Expand Down Expand Up @@ -423,6 +424,19 @@ void threePartCyclicSuite() {
// @formatter:on
}

@Test
void selectByIdentifier() {
// @formatter:off
EngineTestKit.engine(ENGINE_ID)
.selectors(selectClass(SelectByIdentifierSuite.class))
.execute()
.testEvents()
.assertThatEvents()
.haveExactly(1, event(test(SelectByIdentifierSuite.class.getName()), finishedSuccessfully()))
.haveExactly(1, event(test(SingleTestTestCase.class.getName()), finishedSuccessfully()));
// @formatter:on
}

@Suite
@SelectClasses(SingleTestTestCase.class)
private static class PrivateSuite {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Copyright 2015-2024 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.suite.engine.testsuites;

import org.junit.platform.suite.api.IncludeClassNamePatterns;
import org.junit.platform.suite.api.Select;
import org.junit.platform.suite.api.Suite;

/**
* @since 1.11
*/
@Suite
@IncludeClassNamePatterns(".*")
@Select("class:org.junit.platform.suite.engine.testcases.SingleTestTestCase")
public class SelectByIdentifierSuite {
}

0 comments on commit d5c98e4

Please sign in to comment.