diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 2078c532b4c8..2313cecda3ff 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -28,10 +28,10 @@ Issue: #999
## Pull Requests
Our [Definition of Done](https://github.com/junit-team/junit5/wiki/Definition-of-Done)
-offers some guidelines on what we expect from a pull request.
+(DoD) offers some guidelines on what we expect from a pull request.
Feel free to open a pull request that does not fulfill all criteria, e.g. to discuss
a certain change before polishing it, but please be aware that we will only merge it
-in case the DoD is met.
+once the DoD is met.
Please add the following lines to your pull request description:
@@ -95,16 +95,15 @@ code -- class names, method names, variable names, etc.
### Javadoc
- Javadoc comments should be wrapped after 80 characters whenever possible.
-- This first paragraph must be a single, concise sentence that ends with a period (".").
-- Place `
` on the same line as the first line in a new paragraph and precede `
` with a blank line.
+- This first paragraph must be a single, concise sentence that ends with a period (`.`).
+- Place `
` on the same line as the first line of a new paragraph and precede `
` with a blank line.
- Insert a blank line before at-clauses/tags.
- Favor `{@code foo}` over `foo
`.
- Favor literals (e.g., `{@literal @}`) over HTML entities.
-- New classes and methods should have `@since ...` annotation.
-- Use `@since 5.0` instead of `@since 5.0.0`.
-- Do not use `@author` tags. Instead, contributors are listed on [GitHub](https://github.com/junit-team/junit5/graphs/contributors).
-- Do not use verbs in third person form (e.g. use "Discover tests..." instead of "Discovers tests...")
- in the first sentence describing a method.
+- New classes and methods should declare a `@since ...` tag.
+- Use `@since 5.10` instead of `@since 5.10.0`.
+- Do not use `@author` tags. Instead, contributors are listed on the [GitHub](https://github.com/junit-team/junit5/graphs/contributors) page.
+- Do not use verbs in third-person form in the first sentence of the Javadoc for a method -- for example, use "Discover tests..." instead of "Discovers tests...".
#### Examples
@@ -121,11 +120,11 @@ See [`ExtensionContext`](junit-jupiter-api/src/main/java/org/junit/jupiter/api/e
#### Assertions
-- Use `org.junit.jupiter.api.Assertions` wherever possible.
+- Use `org.junit.jupiter.api.Assertions` for simple assertions.
- Use AssertJ when richer assertions are needed.
- Do not use `org.junit.Assert` or `junit.framework.Assert`.
-#### Mocking
+#### Mocking and Stubbing
- Use either [Mockito](https://github.com/mockito/mockito) or hand-written test doubles.
@@ -143,10 +142,11 @@ See [`ExtensionContext`](junit-jupiter-api/src/main/java/org/junit/jupiter/api/e
### Deprecation
-Publicly available interfaces, classes and methods have a defined lifecycle
+The JUnit 5 project uses the `@API` annotation from [API Guardian](https://github.com/apiguardian-team/apiguardian).
+Publicly available interfaces, classes, and methods have a defined lifecycle
which is described in detail in the [User Guide](https://junit.org/junit5/docs/current/user-guide/#api-evolution).
-This process is using the `@API` annotation from [API Guardian](https://github.com/apiguardian-team/apiguardian).
-It also describes the deprecation process followed for API items.
+
+That following describes the deprecation process followed for API items.
To deprecate an item:
- Update the `@API.status` to `DEPRECATED`.
diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.10.0-RC1.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.10.0-RC1.adoc
index 53db2d882329..c3a9bdaea88f 100644
--- a/documentation/src/docs/asciidoc/release-notes/release-notes-5.10.0-RC1.adoc
+++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.10.0-RC1.adoc
@@ -28,8 +28,11 @@ JUnit repository on GitHub.
==== New Features and Improvements
* New `@SelectMethod` selector support in the `@Suite` test engine.
-* Classes may now be selected by fully-qualified name via new the `names` attribute in
+* Classes may now be selected by fully-qualified name via the `names` attribute in
`@SelectClasses`.
+* New `selectMethod()` and `selectNestedMethod()` variants in `DiscoverySelectors` that
+ accept a `Class>...` argument of parameter types as a type-safe alternative to
+ providing the names of parameter types as a comma-delimited string.
[[release-notes-5.10.0-RC1-junit-jupiter]]
@@ -52,6 +55,11 @@ JUnit repository on GitHub.
* Lifecycle and thread-safety semantics are now documented for the `TempDirFactory` SPI.
* New `reason` attribute in `@Execution` which can be used to document the reason for
using the selected execution mode.
+* The <<../user-guide/index.adoc#extensions-RandomNumberExtension, User Guide>> now
+ includes an example implementation of the `RandomNumberExtension` in order to improve
+ the documentation for extension registration via `@ExtendWith` on fields.
+* The scope of applicability for `TestWatcher` implementations is now more extensively
+ documented in the User Guide and Javadoc.
[[release-notes-5.10.0-RC1-junit-vintage]]
diff --git a/documentation/src/docs/asciidoc/user-guide/extensions.adoc b/documentation/src/docs/asciidoc/user-guide/extensions.adoc
index a1fee08b8303..09ca2ec5e88b 100644
--- a/documentation/src/docs/asciidoc/user-guide/extensions.adoc
+++ b/documentation/src/docs/asciidoc/user-guide/extensions.adoc
@@ -115,6 +115,25 @@ include::{testDir}/example/extensions/Random.java[tags=user_guide]
include::{testDir}/example/extensions/RandomNumberDemo.java[tags=user_guide]
----
+[[extensions-RandomNumberExtension]]
+The following code listing provides an example of how one might choose to implement such a
+`RandomNumberExtension`. This implementation works for the use cases in
+`RandomNumberDemo`; however, it may not prove robust enough to cover all use cases – for
+example, the random number generation support is limited to integers, it uses
+`java.util.Random` instead of `java.security.SecureRandom`, etc. In any case, it is
+important to note which extension APIs are implemented and for what reasons.
+
+Specifically, `RandomNumberExtension` implements the following extension APIs:
+
+- `BeforeAllCallback`: to support static field injection
+- `TestInstancePostProcessor`: to support non-static field injection
+- `ParameterResolver`: to support constructor and method injection
+
+[source,java,indent=0]
+----
+include::{testDir}/example/extensions/RandomNumberExtension.java[tags=user_guide]
+----
+
[TIP]
.Extension Registration Order for `@ExtendWith` on Fields
====
@@ -386,11 +405,11 @@ test instance, invoking custom de-initialization methods on the test instance, e
runtime.
If a _test class_ constructor, _test method_, or _lifecycle method_ (see
-<>) declares a parameter, the parameter must be
-_resolved_ at runtime by a `ParameterResolver`. A `ParameterResolver` can either be
-built-in (see `{TestInfoParameterResolver}`) or <>. Generally speaking, parameters may be resolved by _name_, _type_,
-_annotation_, or any combination thereof.
+<>) declares a parameter, the parameter must be _resolved_ at
+runtime by a `ParameterResolver`. A `ParameterResolver` can either be built-in (see
+`{TestInfoParameterResolver}`) or <>.
+Generally speaking, parameters may be resolved by _name_, _type_, _annotation_, or any
+combination thereof.
If you wish to implement a custom `{ParameterResolver}` that resolves parameters based
solely on the type of the parameter, you may find it convenient to extend the
@@ -436,20 +455,44 @@ information for the following events.
* `testFailed`: invoked after a _test method_ has failed
NOTE: In contrast to the definition of "test method" presented in
-<>, in this context _test method_ refers to any
-`@Test` method or `@TestTemplate` method (for example, a `@RepeatedTest` or
-`@ParameterizedTest`).
+<>, in this context _test method_ refers to any `@Test` method
+or `@TestTemplate` method (for example, a `@RepeatedTest` or `@ParameterizedTest`).
+
+Extensions implementing this interface can be registered at the class level, instance
+level, or method level. When registered at the class level, a `TestWatcher` will be
+invoked for any contained _test method_ including those in `@Nested` classes. When
+registered at the method level, a `TestWatcher` will only be invoked for the _test method_
+for which it was registered.
+
+[WARNING]
+====
+If a `TestWatcher` is registered via a non-static (instance) field – for example, using
+`@RegisterExtension` – and the test class is configured with
+`@TestInstance(Lifecycle.PER_METHOD)` semantics (which is the default lifecycle mode), the
+`TestWatcher` will **not** be invoked with events for `@TestTemplate` methods (for
+example, `@RepeatedTest` or `@ParameterizedTest`).
+
+To ensure that a `TestWatcher` is invoked for all _test methods_ in a given class, it is
+therefore recommended that the `TestWatcher` be registered at the class level with
+`@ExtendWith` or via a `static` field with `@RegisterExtension` or `@ExtendWith`.
+====
+
+If there is a failure at the class level — for example, an exception thrown by a
+`@BeforeAll` method — no test results will be reported. Similarly, if the test class is
+disabled via an `ExecutionCondition` — for example, `@Disabled` — no test results will be
+reported.
-Extensions implementing this interface can be registered at the method level or at the
-class level. In the latter case they will be invoked for any contained _test method_
-including those in `@Nested` classes.
+In contrast to other Extension APIs, a `TestWatcher` is not permitted to adversely
+influence the execution of tests. Consequently, any exception thrown by a method in the
+`TestWatcher` API will be logged at `WARNING` level and will not be allowed to propagate
+or fail test execution.
[WARNING]
====
Any instances of `ExtensionContext.Store.CloseableResource` stored in the `Store` of the
-provided `{ExtensionContext}` will be closed _before_ methods in this API are invoked (see
-<>). You can use the parent context's `Store` to work with such
-resources.
+provided `{ExtensionContext}` will be closed _before_ methods in the `TestWatcher` API are
+invoked (see <>). You can use the parent context's `Store` to
+work with such resources.
====
[[extensions-lifecycle-callbacks]]
@@ -797,7 +840,7 @@ callbacks implemented by `Extension2`. `Extension1` is therefore said to _wrap_
`Extension2`.
JUnit Jupiter also guarantees _wrapping_ behavior within class and interface hierarchies
-for user-supplied _lifecycle methods_ (see <>).
+for user-supplied _lifecycle methods_ (see <>).
* `@BeforeAll` methods are inherited from superclasses as long as they are not _hidden_,
_overridden_, or _superseded_ (i.e., replaced based on signature only, irrespective of
@@ -945,7 +988,6 @@ image::extensions_BrokenLifecycleMethodConfigDemo.png[caption='',title='BrokenLi
[TIP]
====
Due to the aforementioned behavior, the JUnit Team recommends that developers declare at
-most one of each type of _lifecycle method_ (see <>)
-per test class or test interface unless there are no dependencies between such lifecycle
-methods.
+most one of each type of _lifecycle method_ (see <>) per test
+class or test interface unless there are no dependencies between such lifecycle methods.
====
diff --git a/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc b/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc
index 142f752e47c3..97991db4fb86 100644
--- a/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc
+++ b/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc
@@ -592,8 +592,7 @@ deterministic but intentionally nonobvious. This ensures that subsequent runs of
suite execute test classes and test methods in the same order, thereby allowing for
repeatable builds.
-NOTE: See <> for a definition of _test method_ and
-_test class_.
+NOTE: See <> for a definition of _test method_ and _test class_.
[[writing-tests-test-execution-order-methods]]
==== Method Order
@@ -665,8 +664,8 @@ dependencies between test classes, or you may wish to order test classes to opti
time as outlined in the following scenarios.
* Run previously failing tests and faster tests first: "fail fast" mode
-* With parallel execution enabled, run longer tests first: "shortest test plan execution
- duration" mode
+* With parallel execution enabled, schedule longer tests first: "shortest test plan
+ execution duration" mode
* Various other use cases
To configure test class execution order _globally_ for the entire test suite, use the
@@ -728,8 +727,8 @@ include::{testDir}/example/OrderedNestedTestClassesDemo.java[tags=user_guide]
In order to allow individual test methods to be executed in isolation and to avoid
unexpected side effects due to mutable test instance state, JUnit creates a new instance
of each test class before executing each _test method_ (see
-<>). This "per-method" test instance lifecycle is the
-default behavior in JUnit Jupiter and is analogous to all previous versions of JUnit.
+<>). This "per-method" test instance lifecycle is the default
+behavior in JUnit Jupiter and is analogous to all previous versions of JUnit.
NOTE: Please note that the test class will still be instantiated if a given _test method_
is _disabled_ via a <> (e.g., `@Disabled`,
@@ -840,8 +839,8 @@ constructors and methods.
`{ParameterResolver}` defines the API for test extensions that wish to _dynamically_
resolve parameters at runtime. If a _test class_ constructor, a _test method_, or a
-_lifecycle method_ (see <>) accepts a parameter, the
-parameter must be resolved at runtime by a registered `ParameterResolver`.
+_lifecycle method_ (see <>) accepts a parameter, the parameter
+must be resolved at runtime by a registered `ParameterResolver`.
There are currently three built-in resolvers that are registered automatically.
@@ -2303,6 +2302,12 @@ might conflict with the configured execution order. Thus, in both cases, test me
such test classes are only executed concurrently if the `@Execution(CONCURRENT)`
annotation is present on the test class or method.
+When parallel execution is enabled and a default `{ClassOrderer}` (see
+<> for details) is registered, top-level test
+classes will initially be sorted accordingly and scheduled in that order. However, they
+are not guaranteed to be started in exactly that order since the threads they are executed
+on are not controlled directly by JUnit.
+
All nodes of the test tree that are configured with the `CONCURRENT` execution mode will
be executed fully in parallel according to the provided
<> while observing the
diff --git a/documentation/src/test/java/example/extensions/RandomNumberDemo.java b/documentation/src/test/java/example/extensions/RandomNumberDemo.java
index d52af0a65ef8..8dd274bda73c 100644
--- a/documentation/src/test/java/example/extensions/RandomNumberDemo.java
+++ b/documentation/src/test/java/example/extensions/RandomNumberDemo.java
@@ -11,31 +11,34 @@
package example.extensions;
import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
-@Disabled("RandomNumberExtension has not been implemented")
-//tag::user_guide[]
+// tag::user_guide[]
class RandomNumberDemo {
- // use random number field in test methods and @BeforeEach
- // or @AfterEach lifecycle methods
+ // Use static randomNumber0 field anywhere in the test class,
+ // including @BeforeAll or @AfterEach lifecycle methods.
+ @Random
+ private static Integer randomNumber0;
+
+ // Use randomNumber1 field in test methods and @BeforeEach
+ // or @AfterEach lifecycle methods.
@Random
private int randomNumber1;
RandomNumberDemo(@Random int randomNumber2) {
- // use random number in constructor
+ // Use randomNumber2 in constructor
}
@BeforeEach
void beforeEach(@Random int randomNumber3) {
- // use random number in @BeforeEach method
+ // Use randomNumber3 in @BeforeEach method.
}
@Test
void test(@Random int randomNumber4) {
- // use random number in test method
+ // Use randomNumber4 in test method.
}
}
-//end::user_guide[]
+// end::user_guide[]
diff --git a/documentation/src/test/java/example/extensions/RandomNumberExtension.java b/documentation/src/test/java/example/extensions/RandomNumberExtension.java
index 98f1d1ace3f8..3bbece08e206 100644
--- a/documentation/src/test/java/example/extensions/RandomNumberExtension.java
+++ b/documentation/src/test/java/example/extensions/RandomNumberExtension.java
@@ -10,30 +10,85 @@
package example.extensions;
+// tag::user_guide[]
+
+import static org.junit.platform.commons.support.AnnotationSupport.findAnnotatedFields;
+
+import java.lang.reflect.Field;
+import java.util.function.Predicate;
+
import org.junit.jupiter.api.extension.BeforeAllCallback;
-import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ParameterContext;
import org.junit.jupiter.api.extension.ParameterResolver;
+import org.junit.jupiter.api.extension.TestInstancePostProcessor;
+import org.junit.platform.commons.support.ModifierSupport;
-class RandomNumberExtension implements BeforeAllCallback, BeforeEachCallback, ParameterResolver {
+// end::user_guide[]
+// @formatter:off
+// tag::user_guide[]
+class RandomNumberExtension
+ implements BeforeAllCallback, TestInstancePostProcessor, ParameterResolver {
+ private final java.util.Random random = new java.util.Random(System.nanoTime());
+
+ /**
+ * Inject a random integer into static fields that are annotated with
+ * {@code @Random} and can be assigned an integer value.
+ */
@Override
- public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {
- return false;
+ public void beforeAll(ExtensionContext context) {
+ Class> testClass = context.getRequiredTestClass();
+ injectFields(testClass, null, ModifierSupport::isStatic);
}
+ /**
+ * Inject a random integer into non-static fields that are annotated with
+ * {@code @Random} and can be assigned an integer value.
+ */
@Override
- public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {
- return null;
+ public void postProcessTestInstance(Object testInstance, ExtensionContext context) {
+ Class> testClass = context.getRequiredTestClass();
+ injectFields(testClass, testInstance, ModifierSupport::isNotStatic);
}
+ /**
+ * Determine if the parameter is annotated with {@code @Random} and can be
+ * assigned an integer value.
+ */
@Override
- public void beforeAll(ExtensionContext context) {
+ public boolean supportsParameter(ParameterContext pc, ExtensionContext ec) {
+ return pc.isAnnotated(Random.class) && isInteger(pc.getParameter().getType());
}
+ /**
+ * Resolve a random integer.
+ */
@Override
- public void beforeEach(ExtensionContext context) {
+ public Integer resolveParameter(ParameterContext pc, ExtensionContext ec) {
+ return this.random.nextInt();
+ }
+
+ private void injectFields(Class> testClass, Object testInstance,
+ Predicate predicate) {
+
+ predicate = predicate.and(field -> isInteger(field.getType()));
+ findAnnotatedFields(testClass, Random.class, predicate)
+ .forEach(field -> {
+ try {
+ field.setAccessible(true);
+ field.set(testInstance, this.random.nextInt());
+ }
+ catch (Exception ex) {
+ throw new RuntimeException(ex);
+ }
+ });
+ }
+
+ private static boolean isInteger(Class> type) {
+ return int.class.isAssignableFrom(type);
}
}
+// end::user_guide[]
+// @formatter:on
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index c4e291f9fca5..6c9ad74561ca 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -5,8 +5,8 @@ asciidoctor-pdf = "1.5.3"
asciidoctor-plugins = "4.0.0-alpha.1" # Check if workaround in documentation.gradle.kts can be removed when upgrading
assertj = "3.24.2"
bnd = "6.4.0"
-checkstyle = "10.12.0"
-gradleVersionsPlugin = "0.46.0"
+checkstyle = "10.12.1"
+gradleVersionsPlugin = "0.47.0"
jacoco = "0.8.7"
jmh = "1.36"
junit4 = "4.13.2"
@@ -30,10 +30,10 @@ bartholdy = { module = "de.sormuras:bartholdy", version = "0.2.3" }
bndlib = { module = "biz.aQute.bnd:biz.aQute.bndlib", version.ref = "bnd" }
checkstyle = { module = "com.puppycrawl.tools:checkstyle", version.ref = "checkstyle" }
classgraph = { module = "io.github.classgraph:classgraph", version = "4.8.160" }
-commons-io = { module = "commons-io:commons-io", version = "2.12.0" }
+commons-io = { module = "commons-io:commons-io", version = "2.13.0" }
gradle-commonCustomUserData = { module = "com.gradle:common-custom-user-data-gradle-plugin", version = "1.11" }
gradle-foojayResolver = { module = "org.gradle.toolchains:foojay-resolver", version = "0.5.0" }
-gradle-enterprise = { module = "com.gradle:gradle-enterprise-gradle-plugin", version = "3.13.3" }
+gradle-enterprise = { module = "com.gradle:gradle-enterprise-gradle-plugin", version = "3.13.4" }
gradle-bnd = { module = "biz.aQute.bnd:biz.aQute.bnd.gradle", version.ref = "bnd" }
gradle-shadow = { module = "gradle.plugin.com.github.johnrengelman:shadow", version = "8.0.0" }
gradle-spotless = { module = "com.diffplug.spotless:spotless-plugin-gradle", version = "6.19.0" }
@@ -53,7 +53,7 @@ log4j-jul = { module = "org.apache.logging.log4j:log4j-jul", version.ref = "log4
maven = { module = "org.apache.maven:apache-maven", version = "3.9.2" }
mavenSurefirePlugin = { module = "org.apache.maven.plugins:maven-surefire-plugin", version.ref = "surefire" }
memoryfilesystem = { module = "com.github.marschall:memoryfilesystem", version = "2.6.1" }
-mockito = { module = "org.mockito:mockito-junit-jupiter", version = "5.3.1" }
+mockito = { module = "org.mockito:mockito-junit-jupiter", version = "5.4.0" }
opentest4j = { module = "org.opentest4j:opentest4j", version.ref = "opentest4j" }
openTestReporting-events = { module = "org.opentest4j.reporting:open-test-reporting-events", version.ref = "openTestReporting" }
openTestReporting-tooling = { module = "org.opentest4j.reporting:open-test-reporting-tooling", version.ref = "openTestReporting" }
diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild.osgi-conventions.gradle.kts b/gradle/plugins/common/src/main/kotlin/junitbuild.osgi-conventions.gradle.kts
index ce36b6127823..03de80c3edd8 100644
--- a/gradle/plugins/common/src/main/kotlin/junitbuild.osgi-conventions.gradle.kts
+++ b/gradle/plugins/common/src/main/kotlin/junitbuild.osgi-conventions.gradle.kts
@@ -14,7 +14,7 @@ val projectDescription = objects.property().convention(provider { projec
// metadata into the jar
tasks.withType().matching { task: Jar ->
task.name == "jar" || task.name == "shadowJar"
-}.configureEach {
+}.all { // configure tasks eagerly as workaround for https://github.com/bndtools/bnd/issues/5695
extra["importAPIGuardian"] = importAPIGuardian
extensions.create(BundleTaskExtension.NAME, this).apply {
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index c1962a79e29d..f03f475efe8c 100644
Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 2c3425d49ec4..264e083fcdce 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,7 +1,8 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionSha256Sum=e111cb9948407e26351227dabce49822fb88c37ee72f1d1582a69c68af2e702f
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip
+distributionSha256Sum=0906569bf96e8ebefbc1aa56318c74aeafd9710455b2817c70d709c5d77785c4
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-rc-2-bin.zip
networkTimeout=10000
+validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
index aeb74cbb43e3..fcb6fca147c0 100755
--- a/gradlew
+++ b/gradlew
@@ -130,10 +130,13 @@ location of your Java installation."
fi
else
JAVACMD=java
- which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+ if ! command -v java >/dev/null 2>&1
+ then
+ die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
+ fi
fi
# Increase the maximum file descriptors if we can.
diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Assertions.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Assertions.java
index fbde194825dc..4be1561689dc 100644
--- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Assertions.java
+++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Assertions.java
@@ -2845,14 +2845,24 @@ public static void assertNotEquals(Object unexpected, Object actual, SupplierAssert that {@code expected} and {@code actual} refer to the same object.
+ * Assert that the {@code expected} object and the {@code actual} object
+ * are the same object.
+ * This method should only be used to assert identity between objects.
+ * To assert equality between two objects or two primitive values,
+ * use one of the {@code assertEquals(...)} methods instead — for example,
+ * use {@code assertEquals(999, 999)} instead of {@code assertSame(999, 999)}.
*/
public static void assertSame(Object expected, Object actual) {
AssertSame.assertSame(expected, actual);
}
/**
- * Assert that {@code expected} and {@code actual} refer to the same object.
+ * Assert that the {@code expected} object and the {@code actual} object
+ * are the same object.
+ *
This method should only be used to assert identity between objects.
+ * To assert equality between two objects or two primitive values,
+ * use one of the {@code assertEquals(...)} methods instead — for example,
+ * use {@code assertEquals(999, 999)} instead of {@code assertSame(999, 999)}.
*
Fails with the supplied failure {@code message}.
*/
public static void assertSame(Object expected, Object actual, String message) {
@@ -2860,8 +2870,14 @@ public static void assertSame(Object expected, Object actual, String message) {
}
/**
- * Assert that {@code expected} and {@code actual} refer to the same object.
- *
If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}.
+ * Assert that the {@code expected} object and the {@code actual} object
+ * are the same object.
+ *
This method should only be used to assert identity between objects.
+ * To assert equality between two objects or two primitive values,
+ * use one of the {@code assertEquals(...)} methods instead — for example,
+ * use {@code assertEquals(999, 999)} instead of {@code assertSame(999, 999)}.
+ *
If necessary, the failure message will be retrieved lazily from the supplied
+ * {@code messageSupplier}.
*/
public static void assertSame(Object expected, Object actual, Supplier messageSupplier) {
AssertSame.assertSame(expected, actual, messageSupplier);
@@ -2870,14 +2886,22 @@ public static void assertSame(Object expected, Object actual, Supplier m
// --- assertNotSame -------------------------------------------------------
/**
- * Assert that {@code expected} and {@code actual} do not refer to the same object.
+ * Assert that the {@code unexpected} object and the {@code actual}
+ * object are not the same object.
+ * This method should only be used to compare the identity of two
+ * objects. To assert that two objects or two primitive values are not
+ * equal, use one of the {@code assertNotEquals(...)} methods instead.
*/
public static void assertNotSame(Object unexpected, Object actual) {
AssertNotSame.assertNotSame(unexpected, actual);
}
/**
- * Assert that {@code expected} and {@code actual} do not refer to the same object.
+ * Assert that the {@code unexpected} object and the {@code actual}
+ * object are not the same object.
+ *
This method should only be used to compare the identity of two
+ * objects. To assert that two objects or two primitive values are not
+ * equal, use one of the {@code assertNotEquals(...)} methods instead.
*
Fails with the supplied failure {@code message}.
*/
public static void assertNotSame(Object unexpected, Object actual, String message) {
@@ -2885,8 +2909,13 @@ public static void assertNotSame(Object unexpected, Object actual, String messag
}
/**
- * Assert that {@code expected} and {@code actual} do not refer to the same object.
- *
If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}.
+ * Assert that the {@code unexpected} object and the {@code actual}
+ * object are not the same object.
+ *
This method should only be used to compare the identity of two
+ * objects. To assert that two objects or two primitive values are not
+ * equal, use one of the {@code assertNotEquals(...)} methods instead.
+ *
If necessary, the failure message will be retrieved lazily from the supplied
+ * {@code messageSupplier}.
*/
public static void assertNotSame(Object unexpected, Object actual, Supplier messageSupplier) {
AssertNotSame.assertNotSame(unexpected, actual, messageSupplier);
diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestWatcher.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestWatcher.java
index a720af0e061e..a38ff8d088a9 100644
--- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestWatcher.java
+++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestWatcher.java
@@ -31,9 +31,29 @@
* {@link org.junit.jupiter.api.TestTemplate @TestTemplate} methods (e.g.,
* {@code @RepeatedTest} and {@code @ParameterizedTest}). Moreover, if there is a
* failure at the class level — for example, an exception thrown by a
- * {@code @BeforeAll} method — no test results will be reported.
+ * {@code @BeforeAll} method — no test results will be reported. Similarly,
+ * if the test class is disabled via an {@link ExecutionCondition} — for
+ * example, {@code @Disabled} — no test results will be reported.
*
- * Extensions implementing this API can be registered at any level.
+ *
Extensions implementing this interface can be registered at the class level,
+ * instance level, or method level. When registered at the class level, a
+ * {@code TestWatcher} will be invoked for any contained test method including
+ * those in {@link org.junit.jupiter.api.Nested @Nested} classes. When registered
+ * at the method level, a {@code TestWatcher} will only be invoked for the test
+ * method for which it was registered.
+ *
+ *
WARNING: If a {@code TestWatcher} is registered via a
+ * non-static (instance) field — for example, using
+ * {@link RegisterExtension @RegisterExtension} — and the test class is
+ * configured with
+ * {@link org.junit.jupiter.api.TestInstance @TestInstance(Lifecycle.PER_METHOD)}
+ * semantics (which is the default lifecycle mode), the {@code TestWatcher} will
+ * not be invoked with events for {@code @TestTemplate} methods
+ * (such as {@code @RepeatedTest} and {@code @ParameterizedTest}). To ensure that
+ * a {@code TestWatcher} is invoked for all test methods in a given class, it is
+ * therefore recommended that the {@code TestWatcher} be registered at the class
+ * level with {@link ExtendWith @ExtendWith} or via a {@code static} field with
+ * {@code @RegisterExtension} or {@code @ExtendWith}.
*
*
Exception Handling
*
diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertSameAssertionsTests.java b/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertSameAssertionsTests.java
index e9ab08de1a49..c1299d8f5d04 100644
--- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertSameAssertionsTests.java
+++ b/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertSameAssertionsTests.java
@@ -12,6 +12,7 @@
import static org.junit.jupiter.api.AssertionTestUtils.assertExpectedAndActualValues;
import static org.junit.jupiter.api.AssertionTestUtils.assertMessageContains;
+import static org.junit.jupiter.api.AssertionTestUtils.assertMessageMatches;
import static org.junit.jupiter.api.AssertionTestUtils.assertMessageStartsWith;
import static org.junit.jupiter.api.AssertionTestUtils.expectAssertionFailedError;
import static org.junit.jupiter.api.Assertions.assertSame;
@@ -82,6 +83,20 @@ void assertSameWithDifferentObjects() {
}
}
+ @Test
+ void assertSameWithEqualPrimitivesAutoboxedToDifferentWrappers() {
+ try {
+ int i = 999;
+ assertSame(i, i);
+ expectAssertionFailedError();
+ }
+ catch (AssertionFailedError ex) {
+ assertMessageMatches(ex,
+ "expected: java\\.lang\\.Integer@.+?<999> but was: java\\.lang\\.Integer@.+?<999>");
+ assertExpectedAndActualValues(ex, 999, 999);
+ }
+ }
+
@Test
void assertSameWithEquivalentStringsAndMessageSupplier() {
String expected = new String("foo");
diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestWatcherTests.java b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestWatcherTests.java
index 4c446f6fa094..6ada22bf2527 100644
--- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestWatcherTests.java
+++ b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestWatcherTests.java
@@ -33,12 +33,18 @@
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.DynamicTest;
+import org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.RepeatedTest;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestFactory;
+import org.junit.jupiter.api.TestInstance;
+import org.junit.jupiter.api.TestInstance.Lifecycle;
+import org.junit.jupiter.api.TestMethodOrder;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.extension.ExtensionContext;
+import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.jupiter.api.extension.TestWatcher;
import org.junit.jupiter.api.fixtures.TrackLogRecords;
import org.junit.jupiter.engine.AbstractJupiterTestEngineTests;
@@ -68,7 +74,7 @@ void clearResults() {
void testWatcherIsInvokedForTestMethodsInTopLevelAndNestedTestClasses() {
assertCommonStatistics(executeTestsForClass(TrackingTestWatcherTestMethodsTestCase.class));
assertThat(TrackingTestWatcher.results.keySet()).containsAll(testWatcherMethodNames);
- TrackingTestWatcher.results.values().forEach(uidList -> assertEquals(2, uidList.size()));
+ TrackingTestWatcher.results.values().forEach(testMethodNames -> assertEquals(2, testMethodNames.size()));
}
@Test
@@ -80,12 +86,11 @@ void testWatcherIsInvokedForRepeatedTestMethods() {
results.testEvents().assertStatistics(
stats -> stats.dynamicallyRegistered(6).skipped(0).started(6).succeeded(2).aborted(2).failed(2));
- ArrayList expectedMethods = new ArrayList<>(testWatcherMethodNames);
// Since the @RepeatedTest container is disabled, the individual invocations never occur.
- assertThat(TrackingTestWatcher.results.keySet()).containsAll(expectedMethods);
+ assertThat(TrackingTestWatcher.results.keySet()).containsAll(testWatcherMethodNames);
// 2 => number of iterations declared in @RepeatedTest(2).
- TrackingTestWatcher.results.forEach(
- (methodName, uidList) -> assertEquals("testDisabled".endsWith(methodName) ? 1 : 2, uidList.size()));
+ TrackingTestWatcher.results.forEach((testWatcherMethod, testMethodNames) -> assertEquals(
+ "testDisabled".equals(testWatcherMethod) ? 1 : 2, testMethodNames.size()));
}
@Test
@@ -109,8 +114,9 @@ void testWatcherExceptionsAreLoggedAndSwallowed(LogRecordListener logRecordListe
// @formatter:off
long exceptionCount = logRecordListener.stream(MethodBasedTestDescriptor.class, Level.WARNING)
.map(LogRecord::getThrown)
- .filter(throwable -> throwable instanceof JUnitException)
- .filter(throwable -> testWatcherMethodNames.contains(throwable.getStackTrace()[0].getMethodName()))
+ .filter(JUnitException.class::isInstance)
+ .map(throwable -> throwable.getStackTrace()[0].getMethodName())
+ .filter(testWatcherMethodNames::contains)
.count();
// @formatter:on
@@ -118,18 +124,67 @@ void testWatcherExceptionsAreLoggedAndSwallowed(LogRecordListener logRecordListe
}
@Test
- void testWatcherInvokedForTestMethodsInTestCaseWithProblematicConstructor() {
+ void testWatcherIsInvokedForTestMethodsInTestCaseWithProblematicConstructor() {
EngineExecutionResults results = executeTestsForClass(ProblematicConstructorTestCase.class);
results.testEvents().assertStatistics(stats -> stats.skipped(0).started(8).succeeded(0).aborted(0).failed(8));
assertThat(TrackingTestWatcher.results.keySet()).containsExactly("testFailed");
assertThat(TrackingTestWatcher.results.get("testFailed")).hasSize(8);
}
+ @Test
+ void testWatcherSemanticsWhenRegisteredAtClassLevel() {
+ Class> testClass = ClassLevelTestWatcherTestCase.class;
+ assertStatsForAbstractDisabledMethodsTestCase(testClass);
+
+ // We get "testDisabled" events for the @Test method and the @RepeatedTest container.
+ assertThat(TrackingTestWatcher.results.get("testDisabled")).containsExactly("test", "repeatedTest");
+ }
+
+ @Test
+ void testWatcherSemanticsWhenRegisteredAtInstanceLevelWithTestInstanceLifecyclePerClass() {
+ Class> testClass = TestInstancePerClassInstanceLevelTestWatcherTestCase.class;
+ assertStatsForAbstractDisabledMethodsTestCase(testClass);
+
+ // We get "testDisabled" events for the @Test method and the @RepeatedTest container.
+ assertThat(TrackingTestWatcher.results.get("testDisabled")).containsExactly("test", "repeatedTest");
+ }
+
+ @Test
+ void testWatcherSemanticsWhenRegisteredAtInstanceLevelWithTestInstanceLifecyclePerMethod() {
+ Class> testClass = TestInstancePerMethodInstanceLevelTestWatcherTestCase.class;
+ assertStatsForAbstractDisabledMethodsTestCase(testClass);
+
+ // Since the TestWatcher is registered at the instance level with test instance
+ // lifecycle per-method semantics, we get a "testDisabled" event only for the @Test
+ // method and NOT for the @RepeatedTest container.
+ assertThat(TrackingTestWatcher.results.get("testDisabled")).containsExactly("test");
+ }
+
+ @Test
+ void testWatcherSemanticsWhenRegisteredAtMethodLevel() {
+ Class> testClass = MethodLevelTestWatcherTestCase.class;
+ assertStatsForAbstractDisabledMethodsTestCase(testClass);
+
+ // We get "testDisabled" events for the @Test method and the @RepeatedTest container.
+ assertThat(TrackingTestWatcher.results.get("testDisabled")).containsExactly("test", "repeatedTest");
+ }
+
private void assertCommonStatistics(EngineExecutionResults results) {
results.containerEvents().assertStatistics(stats -> stats.started(3).succeeded(3).failed(0));
results.testEvents().assertStatistics(stats -> stats.skipped(2).started(6).succeeded(2).aborted(2).failed(2));
}
+ private void assertStatsForAbstractDisabledMethodsTestCase(Class> testClass) {
+ EngineExecutionResults results = executeTestsForClass(testClass);
+
+ results.containerEvents().assertStatistics(//
+ stats -> stats.skipped(1).started(2).succeeded(2).aborted(0).failed(0));
+ results.testEvents().assertStatistics(//
+ stats -> stats.skipped(1).started(0).succeeded(0).aborted(0).failed(0));
+
+ assertThat(TrackingTestWatcher.results.keySet()).containsExactly("testDisabled");
+ }
+
// -------------------------------------------------------------------------
private static abstract class AbstractTestCase {
@@ -251,32 +306,88 @@ static class ProblematicConstructorTestCase extends AbstractTestCase {
}
}
+ @TestMethodOrder(OrderAnnotation.class)
+ private static abstract class AbstractDisabledMethodsTestCase {
+
+ @Disabled
+ @Test
+ @Order(1)
+ void test() {
+ }
+
+ @Disabled
+ @RepeatedTest(2)
+ @Order(2)
+ void repeatedTest() {
+ }
+ }
+
+ static class ClassLevelTestWatcherTestCase extends AbstractDisabledMethodsTestCase {
+
+ @RegisterExtension
+ static TestWatcher watcher = new TrackingTestWatcher();
+ }
+
+ @TestInstance(Lifecycle.PER_CLASS)
+ static class TestInstancePerClassInstanceLevelTestWatcherTestCase extends AbstractDisabledMethodsTestCase {
+
+ @RegisterExtension
+ TestWatcher watcher = new TrackingTestWatcher();
+ }
+
+ @TestInstance(Lifecycle.PER_METHOD)
+ static class TestInstancePerMethodInstanceLevelTestWatcherTestCase extends AbstractDisabledMethodsTestCase {
+
+ @RegisterExtension
+ TestWatcher watcher = new TrackingTestWatcher();
+ }
+
+ static class MethodLevelTestWatcherTestCase extends AbstractDisabledMethodsTestCase {
+
+ @Override
+ @Disabled
+ @Test
+ @Order(1)
+ @ExtendWith(TrackingTestWatcher.class)
+ void test() {
+ }
+
+ @Override
+ @Disabled
+ @RepeatedTest(1)
+ @Order(2)
+ @ExtendWith(TrackingTestWatcher.class)
+ void repeatedTest() {
+ }
+ }
+
private static class TrackingTestWatcher implements TestWatcher {
private static final Map> results = new HashMap<>();
@Override
public void testSuccessful(ExtensionContext context) {
- trackResult("testSuccessful", context.getUniqueId());
+ trackResult("testSuccessful", context);
}
@Override
public void testAborted(ExtensionContext context, Throwable cause) {
- trackResult("testAborted", context.getUniqueId());
+ trackResult("testAborted", context);
}
@Override
public void testFailed(ExtensionContext context, Throwable cause) {
- trackResult("testFailed", context.getUniqueId());
+ trackResult("testFailed", context);
}
@Override
public void testDisabled(ExtensionContext context, Optional reason) {
- trackResult("testDisabled", context.getUniqueId());
+ trackResult("testDisabled", context);
}
- protected void trackResult(String status, String uid) {
- results.computeIfAbsent(status, k -> new ArrayList<>()).add(uid);
+ protected void trackResult(String testWatcherMethod, ExtensionContext context) {
+ String testMethod = context.getRequiredTestMethod().getName();
+ results.computeIfAbsent(testWatcherMethod, k -> new ArrayList<>()).add(testMethod);
}
}
diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/AnnotationSupport.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/AnnotationSupport.java
index f32dc57a2965..8764d2d70670 100644
--- a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/AnnotationSupport.java
+++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/AnnotationSupport.java
@@ -285,7 +285,36 @@ public static List findPublicAnnotatedFields(Class> clazz, Class> fie
*/
@API(status = MAINTAINED, since = "1.4")
public static List findAnnotatedFields(Class> clazz, Class extends Annotation> annotationType) {
- return AnnotationUtils.findAnnotatedFields(clazz, annotationType, field -> true);
+ return findAnnotatedFields(clazz, annotationType, field -> true);
+ }
+
+ /**
+ * Find all {@linkplain Field fields} of the supplied class or interface
+ * that are annotated or meta-annotated with the specified
+ * {@code annotationType} and match the specified {@code predicate}, using
+ * top-down search semantics within the type hierarchy.
+ *
+ * Fields declared in the same class or interface will be ordered using
+ * an algorithm that is deterministic but intentionally nonobvious.
+ *
+ *
The results will not contain fields that are hidden or
+ * {@linkplain Field#isSynthetic() synthetic}.
+ *
+ * @param clazz the class or interface in which to find the fields; never {@code null}
+ * @param annotationType the annotation type to search for; never {@code null}
+ * @param predicate the field filter; never {@code null}
+ * @return the list of all such fields found; neither {@code null} nor mutable
+ * @since 1.10
+ * @see Class#getDeclaredFields()
+ * @see #findPublicAnnotatedFields(Class, Class, Class)
+ * @see #findAnnotatedFields(Class, Class, Predicate, HierarchyTraversalMode)
+ * @see ReflectionSupport#findFields(Class, Predicate, HierarchyTraversalMode)
+ * @see ReflectionSupport#tryToReadFieldValue(Field, Object)
+ */
+ @API(status = MAINTAINED, since = "1.10")
+ public static List findAnnotatedFields(Class> clazz, Class extends Annotation> annotationType,
+ Predicate predicate) {
+ return AnnotationUtils.findAnnotatedFields(clazz, annotationType, predicate);
}
/**
diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/AnnotationUtils.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/AnnotationUtils.java
index 9da0cb5650d6..2cd1bbcee225 100644
--- a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/AnnotationUtils.java
+++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/AnnotationUtils.java
@@ -455,11 +455,7 @@ public static List findPublicAnnotatedFields(Class> clazz, Class> fie
}
/**
- * Find all {@linkplain Field fields} of the supplied class or interface
- * that are annotated or meta-annotated with the specified
- * {@code annotationType} and match the specified {@code predicate}, using
- * top-down search semantics within the type hierarchy.
- *
+ * @see org.junit.platform.commons.support.AnnotationSupport#findAnnotatedFields(Class, Class, Predicate)
* @see #findAnnotatedFields(Class, Class, Predicate, HierarchyTraversalMode)
*/
public static List findAnnotatedFields(Class> clazz, Class extends Annotation> annotationType,
diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ReflectionUtils.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ReflectionUtils.java
index 74be92f77a3a..816b7fadd104 100644
--- a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ReflectionUtils.java
+++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ReflectionUtils.java
@@ -1361,7 +1361,8 @@ public static Optional findMethod(Class> clazz, String methodName, Str
return findMethod(clazz, methodName, resolveParameterTypes(clazz, methodName, parameterTypeNames));
}
- private static Class>[] resolveParameterTypes(Class> clazz, String methodName, String parameterTypeNames) {
+ @API(status = INTERNAL, since = "1.10")
+ public static Class>[] resolveParameterTypes(Class> clazz, String methodName, String parameterTypeNames) {
if (StringUtils.isBlank(parameterTypeNames)) {
return EMPTY_CLASS_ARRAY;
}
diff --git a/junit-platform-commons/src/testFixtures/java/org/junit/platform/commons/test/TestClassLoader.java b/junit-platform-commons/src/testFixtures/java/org/junit/platform/commons/test/TestClassLoader.java
index 5ec1970b4dc8..e408068352b1 100644
--- a/junit-platform-commons/src/testFixtures/java/org/junit/platform/commons/test/TestClassLoader.java
+++ b/junit-platform-commons/src/testFixtures/java/org/junit/platform/commons/test/TestClassLoader.java
@@ -28,7 +28,7 @@
* This class loader is only suitable for specific testing scenarios, where
* you need to load particular classes from a different class loader.
*
- * @since 5.10
+ * @since 1.10
*/
public class TestClassLoader extends URLClassLoader {
diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/DiscoverySelectors.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/DiscoverySelectors.java
index 42553214d695..3439d20d45e3 100644
--- a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/DiscoverySelectors.java
+++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/DiscoverySelectors.java
@@ -504,9 +504,7 @@ public static MethodSelector selectMethod(String className, String methodName) {
*/
@API(status = EXPERIMENTAL, since = "1.10")
public static MethodSelector selectMethod(ClassLoader classLoader, String className, String methodName) {
- Preconditions.notBlank(className, "Class name must not be null or blank");
- Preconditions.notBlank(methodName, "Method name must not be null or blank");
- return new MethodSelector(classLoader, className, methodName, "");
+ return selectMethod(classLoader, className, methodName, "");
}
/**
@@ -592,6 +590,48 @@ public static MethodSelector selectMethod(Class> javaClass, String methodName,
return new MethodSelector(javaClass, methodName, parameterTypeNames.trim());
}
+ /**
+ * Create a {@code MethodSelector} for the supplied class name, method name,
+ * and parameter types.
+ *
+ * @param className the fully qualified name of the class in which the method
+ * is declared, or a subclass thereof; never {@code null} or blank
+ * @param methodName the name of the method to select; never {@code null} or blank
+ * @param parameterTypes the formal parameter types of the method; never
+ * {@code null} though potentially empty if the method does not declare parameters
+ * @since 1.10
+ * @see MethodSelector
+ */
+ @API(status = EXPERIMENTAL, since = "1.10")
+ public static MethodSelector selectMethod(String className, String methodName, Class>... parameterTypes) {
+ Preconditions.notBlank(className, "Class name must not be null or blank");
+ Preconditions.notBlank(methodName, "Method name must not be null or blank");
+ Preconditions.notNull(parameterTypes, "Parameter types array must not be null");
+ Preconditions.containsNoNullElements(parameterTypes, "Parameter types array must not contain null elements");
+ return new MethodSelector((ClassLoader) null, className, methodName, parameterTypes);
+ }
+
+ /**
+ * Create a {@code MethodSelector} for the supplied {@link Class}, method name,
+ * and parameter types.
+ *
+ * @param javaClass the class in which the method is declared, or a subclass thereof;
+ * never {@code null}
+ * @param methodName the name of the method to select; never {@code null} or blank
+ * @param parameterTypes the formal parameter types of the method; never
+ * {@code null} though potentially empty if the method does not declare parameters
+ * @since 1.10
+ * @see MethodSelector
+ */
+ @API(status = EXPERIMENTAL, since = "1.10")
+ public static MethodSelector selectMethod(Class> javaClass, String methodName, Class>... parameterTypes) {
+ Preconditions.notNull(javaClass, "Class must not be null");
+ Preconditions.notBlank(methodName, "Method name must not be null or blank");
+ Preconditions.notNull(parameterTypes, "Parameter types array must not be null");
+ Preconditions.containsNoNullElements(parameterTypes, "Parameter types array must not contain null elements");
+ return new MethodSelector(javaClass, methodName, parameterTypes);
+ }
+
/**
* Create a {@code MethodSelector} for the supplied {@link Class} and {@link Method}.
*
@@ -744,6 +784,33 @@ public static NestedMethodSelector selectNestedMethod(ClassLoader classLoader, L
parameterTypeNames.trim());
}
+ /**
+ * Create a {@code NestedMethodSelector} for the supplied enclosing class names,
+ * nested class name, method name, and parameter types.
+ *
+ * @param enclosingClassNames the names of the enclosing classes; never {@code null}
+ * or empty
+ * @param nestedClassName the name of the nested class to select; never {@code null}
+ * or blank
+ * @param methodName the name of the method to select; never {@code null} or blank
+ * @param parameterTypes the formal parameter types of the method; never {@code null}
+ * though potentially empty if the method does not declare parameters
+ * @since 1.10
+ * @see NestedMethodSelector
+ */
+ @API(status = EXPERIMENTAL, since = "1.10")
+ public static NestedMethodSelector selectNestedMethod(List enclosingClassNames, String nestedClassName,
+ String methodName, Class>... parameterTypes) {
+
+ Preconditions.notEmpty(enclosingClassNames, "Enclosing class names must not be null or empty");
+ Preconditions.notBlank(nestedClassName, "Nested class name must not be null or blank");
+ Preconditions.notBlank(methodName, "Method name must not be null or blank");
+ Preconditions.notNull(parameterTypes, "Parameter types array must not be null");
+ Preconditions.containsNoNullElements(parameterTypes, "Parameter types array must not contain null elements");
+ return new NestedMethodSelector((ClassLoader) null, enclosingClassNames, nestedClassName, methodName,
+ parameterTypes);
+ }
+
/**
* Create a {@code NestedMethodSelector} for the supplied nested {@link Class} and method name.
*
@@ -788,6 +855,31 @@ public static NestedMethodSelector selectNestedMethod(List> enclosingCl
return new NestedMethodSelector(enclosingClasses, nestedClass, methodName, parameterTypeNames.trim());
}
+ /**
+ * Create a {@code NestedMethodSelector} for the supplied enclosing classes,
+ * nested class, method name, and parameter types.
+ *
+ * @param enclosingClasses the path to the nested class to select; never {@code null}
+ * or empty
+ * @param nestedClass the nested class to select; never {@code null}
+ * @param methodName the name of the method to select; never {@code null} or blank
+ * @param parameterTypes the formal parameter types of the method; never {@code null}
+ * though potentially empty if the method does not declare parameters
+ * @since 1.10
+ * @see NestedMethodSelector
+ */
+ @API(status = EXPERIMENTAL, since = "1.10")
+ public static NestedMethodSelector selectNestedMethod(List> enclosingClasses, Class> nestedClass,
+ String methodName, Class>... parameterTypes) {
+
+ Preconditions.notEmpty(enclosingClasses, "Enclosing classes must not be null or empty");
+ Preconditions.notNull(nestedClass, "Nested class must not be null");
+ Preconditions.notBlank(methodName, "Method name must not be null or blank");
+ Preconditions.notNull(parameterTypes, "Parameter types array must not be null");
+ Preconditions.containsNoNullElements(parameterTypes, "Parameter types array must not contain null elements");
+ return new NestedMethodSelector(enclosingClasses, nestedClass, methodName, parameterTypes);
+ }
+
/**
* Create a {@code NestedMethodSelector} for the supplied nested {@link Class} and {@link Method}.
*
diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/MethodSelector.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/MethodSelector.java
index c1d9ad28deb8..c1055f68b743 100644
--- a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/MethodSelector.java
+++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/MethodSelector.java
@@ -18,11 +18,11 @@
import java.util.Objects;
import org.apiguardian.api.API;
+import org.junit.platform.commons.JUnitException;
import org.junit.platform.commons.PreconditionViolationException;
import org.junit.platform.commons.function.Try;
import org.junit.platform.commons.util.ClassUtils;
import org.junit.platform.commons.util.ReflectionUtils;
-import org.junit.platform.commons.util.StringUtils;
import org.junit.platform.commons.util.ToStringBuilder;
import org.junit.platform.engine.DiscoverySelector;
@@ -36,9 +36,9 @@
* {@linkplain #getJavaMethod() method} and its method name, class name, and
* parameter types accordingly. If a {@link Class} and method name, a class name
* and method name, or a fully qualified method name is provided,
- * this selector will only attempt to lazily load the {@link Class} or
- * {@link Method} if {@link #getJavaClass()} or {@link #getJavaMethod()} is
- * invoked.
+ * this selector will only attempt to lazily load the class, method, or parameter
+ * types if {@link #getJavaClass()}, {@link #getJavaMethod()}, or
+ * {@link #getParameterTypes()} is invoked.
*
* In this context, a Java {@code Method} means anything that can be referenced
* as a {@link Method} on the JVM — for example, methods from Java classes
@@ -63,6 +63,7 @@ public class MethodSelector implements DiscoverySelector {
private volatile Class> javaClass;
private volatile Method javaMethod;
+ private volatile Class>[] parameterTypes;
/**
* @since 1.10
@@ -82,13 +83,37 @@ public class MethodSelector implements DiscoverySelector {
this.parameterTypeNames = parameterTypeNames;
}
+ /**
+ * @since 1.10
+ */
+ MethodSelector(ClassLoader classLoader, String className, String methodName, Class>... parameterTypes) {
+ this.classLoader = classLoader;
+ this.className = className;
+ this.methodName = methodName;
+ this.parameterTypes = parameterTypes.clone();
+ this.parameterTypeNames = ClassUtils.nullSafeToString(Class::getTypeName, this.parameterTypes);
+ }
+
+ /**
+ * @since 1.10
+ */
+ MethodSelector(Class> javaClass, String methodName, Class>... parameterTypes) {
+ this.classLoader = javaClass.getClassLoader();
+ this.javaClass = javaClass;
+ this.className = javaClass.getName();
+ this.methodName = methodName;
+ this.parameterTypes = parameterTypes.clone();
+ this.parameterTypeNames = ClassUtils.nullSafeToString(Class::getTypeName, this.parameterTypes);
+ }
+
MethodSelector(Class> javaClass, Method method) {
this.classLoader = javaClass.getClassLoader();
this.javaClass = javaClass;
this.className = javaClass.getName();
this.javaMethod = method;
this.methodName = method.getName();
- this.parameterTypeNames = ClassUtils.nullSafeToString(method.getParameterTypes());
+ this.parameterTypes = method.getParameterTypes();
+ this.parameterTypeNames = ClassUtils.nullSafeToString(Class::getTypeName, this.parameterTypes);
}
/**
@@ -121,10 +146,10 @@ public String getMethodName() {
*
*
See {@link #getParameterTypeNames()} for details.
*
- * @return the names of parameter types supplied to this {@code MethodSelector}
- * via a constructor or deduced from a {@code Method} or parameter types supplied
- * via a constructor; never {@code null} but potentially an empty string
+ * @return the names of parameter types
* @since 1.0
+ * @see #getParameterTypeNames()
+ * @see #getParameterTypes()
* @deprecated since 1.10 in favor of {@link #getParameterTypeNames()}
*/
@Deprecated
@@ -144,9 +169,10 @@ public String getMethodParameterTypes() {
* the caller of this method to determine how to parse the returned string.
*
* @return the names of parameter types supplied to this {@code MethodSelector}
- * via a constructor or deduced from a {@code Method} supplied via a constructor;
- * never {@code null} but potentially an empty string
+ * via a constructor or deduced from a {@code Method} or parameter types supplied
+ * via a constructor; never {@code null} but potentially an empty string
* @since 1.10
+ * @see #getParameterTypes()
*/
@API(status = STABLE, since = "1.10")
public String getParameterTypeNames() {
@@ -182,6 +208,27 @@ public Method getJavaMethod() {
return this.javaMethod;
}
+ /**
+ * Get the parameter types for the selected method.
+ *
+ *
If the parameter types were not provided as {@link Class} references
+ * (or could not be deduced as {@code Class} references in the constructor),
+ * this method attempts to lazily load the class reference for each parameter
+ * type based on its name and throws a {@link JUnitException} if the class
+ * cannot be loaded.
+ *
+ * @return the method's parameter types; never {@code null} but potentially
+ * an empty array if the selected method does not declare parameters
+ * @since 1.10
+ * @see #getParameterTypeNames()
+ * @see Method#getParameterTypes()
+ */
+ @API(status = EXPERIMENTAL, since = "1.10")
+ public Class>[] getParameterTypes() {
+ lazyLoadParameterTypes();
+ return this.parameterTypes.clone();
+ }
+
private void lazyLoadJavaClass() {
// @formatter:off
if (this.javaClass == null) {
@@ -197,9 +244,10 @@ private void lazyLoadJavaClass() {
private void lazyLoadJavaMethod() {
if (this.javaMethod == null) {
lazyLoadJavaClass();
- if (StringUtils.isNotBlank(this.parameterTypeNames)) {
+ lazyLoadParameterTypes();
+ if (this.parameterTypes.length > 0) {
this.javaMethod = ReflectionUtils.findMethod(this.javaClass, this.methodName,
- this.parameterTypeNames).orElseThrow(
+ this.parameterTypes).orElseThrow(
() -> new PreconditionViolationException(String.format(
"Could not find method with name [%s] and parameter types [%s] in class [%s].",
this.methodName, this.parameterTypeNames, this.javaClass.getName())));
@@ -213,6 +261,14 @@ private void lazyLoadJavaMethod() {
}
}
+ private void lazyLoadParameterTypes() {
+ if (this.parameterTypes == null) {
+ lazyLoadJavaClass();
+ this.parameterTypes = ReflectionUtils.resolveParameterTypes(this.javaClass, this.methodName,
+ this.parameterTypeNames);
+ }
+ }
+
/**
* @since 1.3
*/
diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/NestedMethodSelector.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/NestedMethodSelector.java
index 0d4ebb51a6c4..22e1ffdef440 100644
--- a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/NestedMethodSelector.java
+++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/NestedMethodSelector.java
@@ -33,9 +33,9 @@
*
If a Java {@link Method} is provided, the selector will return that
* {@linkplain #getMethod() method} and its method name, class name, enclosing
* classes names, and parameter types accordingly. If class names or method names
- * are provided, this selector will only attempt to lazily load the {@link Class}
- * or {@link Method} if {@link #getEnclosingClasses()},
- * {@link #getNestedClass()}, or {@link #getMethod()} is invoked.
+ * are provided, this selector will only attempt to lazily load a class or method
+ * if {@link #getEnclosingClasses()}, {@link #getNestedClass()},
+ * {@link #getMethod()}, or {@link #getParameterTypes()} is invoked.
*
*
In this context, a Java {@code Method} means anything that can be referenced
* as a {@link Method} on the JVM — for example, methods from Java classes
@@ -63,12 +63,30 @@ public class NestedMethodSelector implements DiscoverySelector {
this.methodSelector = new MethodSelector(classLoader, nestedClassName, methodName, parameterTypeNames);
}
+ /**
+ * @since 1.10
+ */
+ NestedMethodSelector(ClassLoader classLoader, List enclosingClassNames, String nestedClassName,
+ String methodName, Class>... parameterTypes) {
+ this.nestedClassSelector = new NestedClassSelector(classLoader, enclosingClassNames, nestedClassName);
+ this.methodSelector = new MethodSelector(classLoader, nestedClassName, methodName, parameterTypes);
+ }
+
NestedMethodSelector(List> enclosingClasses, Class> nestedClass, String methodName,
String parameterTypeNames) {
this.nestedClassSelector = new NestedClassSelector(enclosingClasses, nestedClass);
this.methodSelector = new MethodSelector(nestedClass, methodName, parameterTypeNames);
}
+ /**
+ * @since 1.10
+ */
+ NestedMethodSelector(List> enclosingClasses, Class> nestedClass, String methodName,
+ Class>... parameterTypes) {
+ this.nestedClassSelector = new NestedClassSelector(enclosingClasses, nestedClass);
+ this.methodSelector = new MethodSelector(nestedClass, methodName, parameterTypes);
+ }
+
NestedMethodSelector(List> enclosingClasses, Class> nestedClass, Method method) {
this.nestedClassSelector = new NestedClassSelector(enclosingClasses, nestedClass);
this.methodSelector = new MethodSelector(nestedClass, method);
@@ -147,10 +165,10 @@ public Method getMethod() {
*
* See {@link #getParameterTypeNames()} for details.
*
- * @return the names of parameter types supplied to this {@code NestedMethodSelector}
- * via a constructor or deduced from a {@code Method} supplied via a constructor;
- * never {@code null} but potentially an empty string
+ * @return the names of parameter types
* @since 1.6
+ * @see #getParameterTypeNames()
+ * @see #getParameterTypes()
* @deprecated since 1.10 in favor or {@link #getParameterTypeNames()}
*/
@Deprecated
@@ -160,25 +178,38 @@ public String getMethodParameterTypes() {
}
/**
- * Get the names of parameter types for the selected method as a {@link String},
- * typically a comma-separated list of primitive types, fully qualified
- * class names, or array types.
+ * Get the names of parameter types for the selected method as a {@link String}.
*
- *
Note: the names of parameter types are provided as a single string instead
- * of a collection in order to allow this selector to be used in a generic
- * fashion by various test engines. It is therefore the responsibility of
- * the caller of this method to determine how to parse the returned string.
+ *
See {@link MethodSelector#getParameterTypeNames()} for details.
*
* @return the names of parameter types supplied to this {@code NestedMethodSelector}
- * via a constructor or deduced from a {@code Method} supplied via a constructor;
- * never {@code null} but potentially an empty string
+ * via a constructor or deduced from a {@code Method} or parameter types supplied
+ * via a constructor; never {@code null} but potentially an empty string
* @since 1.10
+ * @see MethodSelector#getParameterTypeNames()
+ *
*/
@API(status = STABLE, since = "1.10")
public String getParameterTypeNames() {
return this.methodSelector.getParameterTypeNames();
}
+ /**
+ * Get the parameter types for the selected method.
+ *
+ *
See {@link MethodSelector#getParameterTypes()} for details.
+ *
+ * @return the method's parameter types; never {@code null} but potentially
+ * an empty array if the selected method does not declare parameters
+ * @since 1.10
+ * @see #getParameterTypeNames()
+ * @see MethodSelector#getParameterTypes()
+ */
+ @API(status = EXPERIMENTAL, since = "1.10")
+ public Class>[] getParameterTypes() {
+ return this.methodSelector.getParameterTypes();
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) {
diff --git a/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectMethod.java b/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectMethod.java
index b83fd420405a..a1313a343398 100644
--- a/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectMethod.java
+++ b/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectMethod.java
@@ -30,7 +30,11 @@
* @since 1.10
* @see Suite
* @see org.junit.platform.runner.JUnitPlatform
+ * @see org.junit.platform.engine.discovery.DiscoverySelectors#selectMethod(String)
+ * @see org.junit.platform.engine.discovery.DiscoverySelectors#selectMethod(String, String, String)
+ * @see org.junit.platform.engine.discovery.DiscoverySelectors#selectMethod(String, String, Class...)
* @see org.junit.platform.engine.discovery.DiscoverySelectors#selectMethod(Class, String, String)
+ * @see org.junit.platform.engine.discovery.DiscoverySelectors#selectMethod(Class, String, Class...)
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@@ -40,15 +44,74 @@
@Repeatable(SelectMethods.class)
public @interface SelectMethod {
+ /**
+ * The fully qualified method name of the method to select.
+ *
+ *
The following formats are supported.
+ *
+ *
+ * - {@code [fully qualified class name]#[methodName]}
+ * - {@code [fully qualified class name]#[methodName](parameter type list)}
+ *
+ *
+ * The parameter type list is a comma-separated list of primitive
+ * names or fully qualified class names for the types of parameters accepted
+ * by the method.
+ *
+ *
Array parameter types may be specified using either the JVM's internal
+ * String representation (e.g., {@code [[I} for {@code int[][]},
+ * {@code [Ljava.lang.String;} for {@code java.lang.String[]}, etc.) or
+ * source code syntax (e.g., {@code int[][]}, {@code java.lang.String[]},
+ * etc.).
+ *
+ *
+ * Examples
+ * Method | Fully Qualified Method Name |
+ * {@code java.lang.String.chars()} | {@code java.lang.String#chars} |
+ * {@code java.lang.String.chars()} | {@code java.lang.String#chars()} |
+ * {@code java.lang.String.equalsIgnoreCase(String)} | {@code java.lang.String#equalsIgnoreCase(java.lang.String)} |
+ * {@code java.lang.String.substring(int, int)} | {@code java.lang.String#substring(int, int)} |
+ * {@code example.Calc.avg(int[])} | {@code example.Calc#avg([I)} |
+ * {@code example.Calc.avg(int[])} | {@code example.Calc#avg(int[])} |
+ * {@code example.Matrix.multiply(double[][])} | {@code example.Matrix#multiply([[D)} |
+ * {@code example.Matrix.multiply(double[][])} | {@code example.Matrix#multiply(double[][])} |
+ * {@code example.Service.process(String[])} | {@code example.Service#process([Ljava.lang.String;)} |
+ * {@code example.Service.process(String[])} | {@code example.Service#process(java.lang.String[])} |
+ * {@code example.Service.process(String[][])} | {@code example.Service#process([[Ljava.lang.String;)} |
+ * {@code example.Service.process(String[][])} | {@code example.Service#process(java.lang.String[][])} |
+ *
+ *
+ * Cannot be combined with any other attribute.
+ *
+ * @see org.junit.platform.engine.discovery.DiscoverySelectors#selectMethod(String)
+ */
+ String value() default "";
+
/**
* The class in which the method is declared, or a subclass thereof.
+ *
+ *
Cannot be used in conjunction with {@link #typeName()}.
+ */
+ Class> type() default Class.class;
+
+ /**
+ * The fully qualified class name in which the method is declared, or a subclass thereof.
+ *
+ *
Cannot be used in conjunction with {@link #type()}.
*/
- Class> clazz();
+ String typeName() default "";
/**
- * The name of the method to select; never blank.
+ * The name of the method to select; never blank unless {@link #value()} is used.
*/
- String name();
+ String name() default "";
+
+ /**
+ * The parameter types of the method to select.
+ *
+ *
Cannot be used in conjunction with {@link #parameterTypeNames()}.
+ */
+ Class>[] parameterTypes() default {};
/**
* The parameter types of the method to select.
@@ -81,7 +144,9 @@
*
{@code example.Service.process(String[][])} | {@code [[Ljava.lang.String;} |
* {@code example.Service.process(String[][])} | {@code java.lang.String[][]} |
*
+ *
+ * Cannot be used in conjunction with {@link #parameterTypes()}.
*/
- String parameters() default "";
+ String parameterTypeNames() default "";
}
diff --git a/junit-platform-suite-commons/src/main/java/org/junit/platform/suite/commons/AdditionalDiscoverySelectors.java b/junit-platform-suite-commons/src/main/java/org/junit/platform/suite/commons/AdditionalDiscoverySelectors.java
index 188385080855..65e6c3504190 100644
--- a/junit-platform-suite-commons/src/main/java/org/junit/platform/suite/commons/AdditionalDiscoverySelectors.java
+++ b/junit-platform-suite-commons/src/main/java/org/junit/platform/suite/commons/AdditionalDiscoverySelectors.java
@@ -23,7 +23,6 @@
import org.junit.platform.engine.discovery.DiscoverySelectors;
import org.junit.platform.engine.discovery.FilePosition;
import org.junit.platform.engine.discovery.FileSelector;
-import org.junit.platform.engine.discovery.MethodSelector;
import org.junit.platform.engine.discovery.ModuleSelector;
import org.junit.platform.engine.discovery.PackageSelector;
import org.junit.platform.engine.discovery.UriSelector;
@@ -82,12 +81,6 @@ static Stream selectClasses(String... classNames) {
return uniqueStreamOf(classNames).map(DiscoverySelectors::selectClass);
}
- static MethodSelector selectMethod(Class> clazz, String name, String parameterTypeNames) {
- Preconditions.notBlank(name, "Method name must not be null or blank");
- Preconditions.notNull(parameterTypeNames, "parameter type names must not be null");
- return DiscoverySelectors.selectMethod(clazz, name, parameterTypeNames);
- }
-
static List selectModules(String... moduleNames) {
Preconditions.notNull(moduleNames, "Module names must not be null");
Preconditions.containsNoNullElements(moduleNames, "Individual module names must not be null");
diff --git a/junit-platform-suite-commons/src/main/java/org/junit/platform/suite/commons/SuiteLauncherDiscoveryRequestBuilder.java b/junit-platform-suite-commons/src/main/java/org/junit/platform/suite/commons/SuiteLauncherDiscoveryRequestBuilder.java
index 08cb4aea20fa..c94b8009aaf8 100644
--- a/junit-platform-suite-commons/src/main/java/org/junit/platform/suite/commons/SuiteLauncherDiscoveryRequestBuilder.java
+++ b/junit-platform-suite-commons/src/main/java/org/junit/platform/suite/commons/SuiteLauncherDiscoveryRequestBuilder.java
@@ -38,6 +38,7 @@
import org.junit.platform.engine.Filter;
import org.junit.platform.engine.discovery.ClassNameFilter;
import org.junit.platform.engine.discovery.ClassSelector;
+import org.junit.platform.engine.discovery.DiscoverySelectors;
import org.junit.platform.engine.discovery.MethodSelector;
import org.junit.platform.engine.discovery.PackageNameFilter;
import org.junit.platform.launcher.EngineFilter;
@@ -155,7 +156,7 @@ public SuiteLauncherDiscoveryRequestBuilder suite(Class> suiteClass) {
.ifPresent(this::selectors);
findRepeatableAnnotations(suiteClass, SelectMethod.class)
.stream()
- .map(annotation -> selectMethod(annotation.clazz(), annotation.name(), annotation.parameters()))
+ .map(annotation -> selectMethod(suiteClass, annotation))
.forEach(this::selectors);
findAnnotationValues(suiteClass, IncludeClassNamePatterns.class, IncludeClassNamePatterns::value)
.flatMap(SuiteLauncherDiscoveryRequestBuilder::trimmed)
@@ -226,9 +227,68 @@ private static Stream toClassSelectors(Class> suiteClass, Selec
);
}
- private MethodSelector selectMethod(Class> clazz, String name, String parameters) {
- selectedClassNames.add(clazz.getName());
- return AdditionalDiscoverySelectors.selectMethod(clazz, name, parameters);
+ private MethodSelector selectMethod(Class> suiteClass, SelectMethod annotation) {
+ MethodSelector methodSelector = toMethodSelector(suiteClass, annotation);
+ selectedClassNames.add(methodSelector.getClassName());
+ return methodSelector;
+ }
+
+ private MethodSelector toMethodSelector(Class> suiteClass, SelectMethod annotation) {
+ if (!annotation.value().isEmpty()) {
+ Preconditions.condition(annotation.type() == Class.class,
+ () -> prefixErrorMessageForInvalidSelectMethodUsage(suiteClass,
+ "type must not be set in conjunction with fully qualified method name"));
+ Preconditions.condition(annotation.typeName().isEmpty(),
+ () -> prefixErrorMessageForInvalidSelectMethodUsage(suiteClass,
+ "type name must not be set in conjunction with fully qualified method name"));
+ Preconditions.condition(annotation.name().isEmpty(),
+ () -> prefixErrorMessageForInvalidSelectMethodUsage(suiteClass,
+ "method name must not be set in conjunction with fully qualified method name"));
+ Preconditions.condition(annotation.parameterTypes().length == 0,
+ () -> prefixErrorMessageForInvalidSelectMethodUsage(suiteClass,
+ "parameter types must not be set in conjunction with fully qualified method name"));
+ Preconditions.condition(annotation.parameterTypeNames().isEmpty(),
+ () -> prefixErrorMessageForInvalidSelectMethodUsage(suiteClass,
+ "parameter type names must not be set in conjunction with fully qualified method name"));
+
+ return DiscoverySelectors.selectMethod(annotation.value());
+ }
+
+ Class> type = annotation.type() == Class.class ? null : annotation.type();
+ String typeName = annotation.typeName().isEmpty() ? null : annotation.typeName().trim();
+ String methodName = Preconditions.notBlank(annotation.name(),
+ () -> prefixErrorMessageForInvalidSelectMethodUsage(suiteClass, "method name must not be blank"));
+ Class>[] parameterTypes = annotation.parameterTypes().length == 0 ? null : annotation.parameterTypes();
+ String parameterTypeNames = annotation.parameterTypeNames().trim();
+ if (parameterTypes != null) {
+ Preconditions.condition(parameterTypeNames.isEmpty(),
+ () -> prefixErrorMessageForInvalidSelectMethodUsage(suiteClass,
+ "either parameter type names or parameter types must be set but not both"));
+ }
+ if (type == null) {
+ Preconditions.notBlank(typeName, () -> prefixErrorMessageForInvalidSelectMethodUsage(suiteClass,
+ "type must be set or type name must not be blank"));
+ if (parameterTypes == null) {
+ return DiscoverySelectors.selectMethod(typeName, methodName, parameterTypeNames);
+ }
+ else {
+ return DiscoverySelectors.selectMethod(typeName, methodName, parameterTypes);
+ }
+ }
+ else {
+ Preconditions.condition(typeName == null, () -> prefixErrorMessageForInvalidSelectMethodUsage(suiteClass,
+ "either type name or type must be set but not both"));
+ if (parameterTypes == null) {
+ return DiscoverySelectors.selectMethod(type, methodName, parameterTypeNames);
+ }
+ else {
+ return DiscoverySelectors.selectMethod(type, methodName, parameterTypes);
+ }
+ }
+ }
+
+ private static String prefixErrorMessageForInvalidSelectMethodUsage(Class> suiteClass, String detailMessage) {
+ return String.format("@SelectMethod on class [%s]: %s", suiteClass.getName(), detailMessage);
}
private ClassNameFilter createIncludeClassNameFilter(String... patterns) {
diff --git a/platform-tests/src/test/java/org/junit/platform/commons/util/ExceptionUtilsTests.java b/platform-tests/src/test/java/org/junit/platform/commons/util/ExceptionUtilsTests.java
index 4d04fa970301..1c96ca1b7e9b 100644
--- a/platform-tests/src/test/java/org/junit/platform/commons/util/ExceptionUtilsTests.java
+++ b/platform-tests/src/test/java/org/junit/platform/commons/util/ExceptionUtilsTests.java
@@ -11,7 +11,6 @@
package org.junit.platform.commons.util;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.jupiter.api.Assertions.assertLinesMatch;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.platform.commons.util.ExceptionUtils.findNestedThrowables;
import static org.junit.platform.commons.util.ExceptionUtils.pruneStackTrace;
@@ -19,9 +18,6 @@
import static org.junit.platform.commons.util.ExceptionUtils.throwAsUncheckedException;
import java.io.IOException;
-import java.util.Arrays;
-import java.util.List;
-import java.util.stream.Collectors;
import org.junit.jupiter.api.Test;
import org.junit.platform.commons.JUnitException;
@@ -137,11 +133,4 @@ void avoidCyclesWhileSearchingForNestedThrowables() {
assertThat(findNestedThrowables(t1)).hasSize(3);
}
- private static void assertStackTraceMatch(StackTraceElement[] stackTrace, String expectedLines) {
- List stackStraceAsLines = Arrays.stream(stackTrace) //
- .map(StackTraceElement::toString) //
- .collect(Collectors.toList());
- assertLinesMatch(expectedLines.lines().toList(), stackStraceAsLines);
- }
-
}
diff --git a/platform-tests/src/test/java/org/junit/platform/engine/discovery/DiscoverySelectorsTests.java b/platform-tests/src/test/java/org/junit/platform/engine/discovery/DiscoverySelectorsTests.java
index a2e4af2113f2..dfb967afd9fd 100644
--- a/platform-tests/src/test/java/org/junit/platform/engine/discovery/DiscoverySelectorsTests.java
+++ b/platform-tests/src/test/java/org/junit/platform/engine/discovery/DiscoverySelectorsTests.java
@@ -314,7 +314,7 @@ void selectClassByName() {
}
@Test
- void selectClassByNameAndClassLoader() throws Exception {
+ void selectClassByNameWithExplicitClassLoader() throws Exception {
try (var testClassLoader = TestClassLoader.forClasses(getClass())) {
var selector = selectClass(testClassLoader, getClass().getName());
@@ -353,6 +353,19 @@ void selectMethodByClassNameMethodNameAndParameterTypeNamesPreconditions() {
assertViolatesPrecondition(() -> selectMethod("TestClass", "method", (String) null));
}
+ @Test
+ @DisplayName("Preconditions: selectMethod(className, methodName, parameterTypes)")
+ void selectMethodByClassNameMethodNameAndParameterTypesPreconditions() {
+ assertViolatesPrecondition(() -> selectMethod("TestClass", null, int.class));
+ assertViolatesPrecondition(() -> selectMethod("TestClass", "", int.class));
+ assertViolatesPrecondition(() -> selectMethod("TestClass", " ", int.class));
+ assertViolatesPrecondition(() -> selectMethod((String) null, "method", int.class));
+ assertViolatesPrecondition(() -> selectMethod("", "method", int.class));
+ assertViolatesPrecondition(() -> selectMethod(" ", "method", int.class));
+ assertViolatesPrecondition(() -> selectMethod("TestClass", "method", (Class>) null));
+ assertViolatesPrecondition(() -> selectMethod("TestClass", "method", new Class>[] { int.class, null }));
+ }
+
@Test
@DisplayName("Preconditions: selectMethod(class, methodName)")
void selectMethodByClassAndMethodNamePreconditions() {
@@ -413,7 +426,7 @@ void selectMethodByFullyQualifiedName() throws Exception {
}
@Test
- void selectMethodByFullyQualifiedNameAndClassLoader() throws Exception {
+ void selectMethodByFullyQualifiedNameWithExplicitClassLoader() throws Exception {
try (var testClassLoader = TestClassLoader.forClasses(testClass())) {
var clazz = testClassLoader.loadClass(testClass().getName());
assertThat(clazz).isNotEqualTo(testClass());
@@ -690,6 +703,58 @@ void selectMethodByClassMethodNameAndParameterTypeNames() throws Exception {
assertThat(selector.getMethodName()).isEqualTo("myTest");
assertThat(selector.getJavaMethod()).isEqualTo(method);
assertThat(selector.getParameterTypeNames()).isEqualTo("java.lang.String, boolean[]");
+ assertThat(selector.getParameterTypes()).containsExactly(String.class, boolean[].class);
+ }
+
+ @Test
+ void selectMethodByClassNameMethodNameAndParameterTypes() throws Exception {
+ var testClass = testClass();
+ var method = testClass.getDeclaredMethod("myTest", String.class, boolean[].class);
+
+ var selector = selectMethod(testClass.getName(), "myTest", String.class, boolean[].class);
+
+ assertThat(selector.getClassName()).isEqualTo(testClass.getName());
+ assertThat(selector.getJavaClass()).isEqualTo(testClass);
+ assertThat(selector.getMethodName()).isEqualTo("myTest");
+ assertThat(selector.getJavaMethod()).isEqualTo(method);
+ assertThat(selector.getParameterTypeNames()).isEqualTo("java.lang.String, boolean[]");
+ assertThat(selector.getParameterTypes()).containsExactly(String.class, boolean[].class);
+ }
+
+ @Test
+ void selectMethodByClassNameMethodNameAndParameterTypeNamesWithExplicitClassLoader() throws Exception {
+ var testClass = testClass();
+
+ try (var testClassLoader = TestClassLoader.forClasses(testClass)) {
+ var clazz = testClassLoader.loadClass(testClass.getName());
+ assertThat(clazz).isNotEqualTo(testClass);
+
+ var method = clazz.getDeclaredMethod("myTest", String.class, boolean[].class);
+ var selector = selectMethod(testClassLoader, testClass.getName(), "myTest",
+ "java.lang.String, boolean[]");
+
+ assertThat(selector.getClassName()).isEqualTo(clazz.getName());
+ assertThat(selector.getJavaClass()).isEqualTo(clazz);
+ assertThat(selector.getMethodName()).isEqualTo(method.getName());
+ assertThat(selector.getJavaMethod()).isEqualTo(method);
+ assertThat(selector.getParameterTypeNames()).isEqualTo("java.lang.String, boolean[]");
+ assertThat(selector.getParameterTypes()).containsExactly(String.class, boolean[].class);
+ }
+ }
+
+ @Test
+ void selectMethodByClassMethodNameAndParameterTypes() throws Exception {
+ var testClass = testClass();
+ var method = testClass.getDeclaredMethod("myTest", String.class, boolean[].class);
+
+ var selector = selectMethod(testClass, "myTest", String.class, boolean[].class);
+
+ assertThat(selector.getClassName()).isEqualTo(testClass.getName());
+ assertThat(selector.getJavaClass()).isEqualTo(testClass);
+ assertThat(selector.getMethodName()).isEqualTo("myTest");
+ assertThat(selector.getJavaMethod()).isEqualTo(method);
+ assertThat(selector.getParameterTypeNames()).isEqualTo("java.lang.String, boolean[]");
+ assertThat(selector.getParameterTypes()).containsExactly(String.class, boolean[].class);
}
@Test
@@ -703,7 +768,8 @@ void selectMethodWithParametersByMethodReference() throws Exception {
assertThat(selector.getJavaClass()).isEqualTo(testClass);
assertThat(selector.getMethodName()).isEqualTo("myTest");
assertThat(selector.getJavaMethod()).isEqualTo(method);
- assertThat(selector.getParameterTypeNames()).isEqualTo("java.lang.String, [Z");
+ assertThat(selector.getParameterTypeNames()).isEqualTo("java.lang.String, boolean[]");
+ assertThat(selector.getParameterTypes()).containsExactly(String.class, boolean[].class);
}
@Test
@@ -734,7 +800,7 @@ private static Class> testClass() {
class SelectNestedClassAndSelectNestedMethodTests {
private final String enclosingClassName = getClass().getName() + "$ClassWithNestedInnerClass";
- private final String nestedClassName = getClass().getName() + "$AbstractClassWithNestedInnerClass$NestedClass";
+ private final String nestedClassName = getClass().getName() + "$ClassWithNestedInnerClass$NestedClass";
private final String doubleNestedClassName = nestedClassName + "$DoubleNestedClass";
private final String methodName = "nestedTest";
@@ -743,23 +809,22 @@ void selectNestedClassByClassNames() {
var selector = selectNestedClass(List.of(enclosingClassName), nestedClassName);
assertThat(selector.getEnclosingClasses()).containsOnly(ClassWithNestedInnerClass.class);
- assertThat(selector.getNestedClass()).isEqualTo(AbstractClassWithNestedInnerClass.NestedClass.class);
+ assertThat(selector.getNestedClass()).isEqualTo(ClassWithNestedInnerClass.NestedClass.class);
assertThat(selector.getEnclosingClassNames()).containsOnly(enclosingClassName);
assertThat(selector.getNestedClassName()).isEqualTo(nestedClassName);
}
@Test
- void selectNestedClassByClassNamesAndClassLoader() throws Exception {
- var testClasses = List.of(AbstractClassWithNestedInnerClass.class, ClassWithNestedInnerClass.class,
- AbstractClassWithNestedInnerClass.NestedClass.class);
+ void selectNestedClassByClassNamesWithExplicitClassLoader() throws Exception {
+ var testClasses = List.of(ClassWithNestedInnerClass.class, ClassWithNestedInnerClass.NestedClass.class);
try (var testClassLoader = TestClassLoader.forClasses(testClasses)) {
var selector = selectNestedClass(testClassLoader, List.of(enclosingClassName), nestedClassName);
assertThat(selector.getEnclosingClasses()).doesNotContain(ClassWithNestedInnerClass.class);
assertThat(selector.getEnclosingClasses()).extracting(Class::getName).containsOnly(enclosingClassName);
- assertThat(selector.getNestedClass()).isNotEqualTo(AbstractClassWithNestedInnerClass.NestedClass.class);
+ assertThat(selector.getNestedClass()).isNotEqualTo(ClassWithNestedInnerClass.NestedClass.class);
assertThat(selector.getNestedClass().getName()).isEqualTo(nestedClassName);
assertThat(selector.getClassLoader()).isSameAs(testClassLoader);
@@ -774,9 +839,9 @@ void selectDoubleNestedClassByClassNames() {
var selector = selectNestedClass(List.of(enclosingClassName, nestedClassName), doubleNestedClassName);
assertThat(selector.getEnclosingClasses()).containsExactly(ClassWithNestedInnerClass.class,
- AbstractClassWithNestedInnerClass.NestedClass.class);
+ ClassWithNestedInnerClass.NestedClass.class);
assertThat(selector.getNestedClass()).isEqualTo(
- AbstractClassWithNestedInnerClass.NestedClass.DoubleNestedClass.class);
+ ClassWithNestedInnerClass.NestedClass.DoubleNestedClass.class);
assertThat(selector.getEnclosingClassNames()).containsExactly(enclosingClassName, nestedClassName);
assertThat(selector.getNestedClassName()).isEqualTo(doubleNestedClassName);
@@ -796,7 +861,7 @@ void selectNestedMethodByEnclosingClassNamesAndMethodName() throws Exception {
var selector = selectNestedMethod(List.of(enclosingClassName), nestedClassName, methodName);
assertThat(selector.getEnclosingClasses()).containsOnly(ClassWithNestedInnerClass.class);
- assertThat(selector.getNestedClass()).isEqualTo(AbstractClassWithNestedInnerClass.NestedClass.class);
+ assertThat(selector.getNestedClass()).isEqualTo(ClassWithNestedInnerClass.NestedClass.class);
assertThat(selector.getMethod()).isEqualTo(selector.getNestedClass().getDeclaredMethod(methodName));
assertThat(selector.getEnclosingClassNames()).containsOnly(enclosingClassName);
@@ -805,9 +870,8 @@ void selectNestedMethodByEnclosingClassNamesAndMethodName() throws Exception {
}
@Test
- void selectNestedMethodByEnclosingClassNamesAndMethodNameAndClassLoader() throws Exception {
- var testClasses = List.of(AbstractClassWithNestedInnerClass.class, ClassWithNestedInnerClass.class,
- AbstractClassWithNestedInnerClass.NestedClass.class);
+ void selectNestedMethodByEnclosingClassNamesAndMethodNameWithExplicitClassLoader() throws Exception {
+ var testClasses = List.of(ClassWithNestedInnerClass.class, ClassWithNestedInnerClass.NestedClass.class);
try (var testClassLoader = TestClassLoader.forClasses(testClasses)) {
var selector = selectNestedMethod(testClassLoader, List.of(enclosingClassName), nestedClassName,
@@ -815,7 +879,7 @@ void selectNestedMethodByEnclosingClassNamesAndMethodNameAndClassLoader() throws
assertThat(selector.getEnclosingClasses()).doesNotContain(ClassWithNestedInnerClass.class);
assertThat(selector.getEnclosingClasses()).extracting(Class::getName).containsOnly(enclosingClassName);
- assertThat(selector.getNestedClass()).isNotEqualTo(AbstractClassWithNestedInnerClass.NestedClass.class);
+ assertThat(selector.getNestedClass()).isNotEqualTo(ClassWithNestedInnerClass.NestedClass.class);
assertThat(selector.getNestedClass().getName()).isEqualTo(nestedClassName);
assertThat(selector.getClassLoader()).isSameAs(testClassLoader);
@@ -830,10 +894,10 @@ void selectNestedMethodByEnclosingClassNamesAndMethodNameAndClassLoader() throws
@Test
void selectNestedMethodByEnclosingClassesAndMethodName() throws Exception {
var selector = selectNestedMethod(List.of(ClassWithNestedInnerClass.class),
- AbstractClassWithNestedInnerClass.NestedClass.class, methodName);
+ ClassWithNestedInnerClass.NestedClass.class, methodName);
assertThat(selector.getEnclosingClasses()).containsOnly(ClassWithNestedInnerClass.class);
- assertThat(selector.getNestedClass()).isEqualTo(AbstractClassWithNestedInnerClass.NestedClass.class);
+ assertThat(selector.getNestedClass()).isEqualTo(ClassWithNestedInnerClass.NestedClass.class);
assertThat(selector.getMethod()).isEqualTo(selector.getNestedClass().getDeclaredMethod(methodName));
assertThat(selector.getEnclosingClassNames()).containsOnly(enclosingClassName);
@@ -842,12 +906,12 @@ void selectNestedMethodByEnclosingClassesAndMethodName() throws Exception {
}
@Test
- void selectNestedMethodByEnclosingClassNamesAndMethodNameWithParameterTypes() throws Exception {
+ void selectNestedMethodByEnclosingClassNamesMethodNameAndParameterTypeNames() throws Exception {
var selector = selectNestedMethod(List.of(enclosingClassName), nestedClassName, methodName,
String.class.getName());
assertThat(selector.getEnclosingClasses()).containsOnly(ClassWithNestedInnerClass.class);
- assertThat(selector.getNestedClass()).isEqualTo(AbstractClassWithNestedInnerClass.NestedClass.class);
+ assertThat(selector.getNestedClass()).isEqualTo(ClassWithNestedInnerClass.NestedClass.class);
assertThat(selector.getMethod()).isEqualTo(
selector.getNestedClass().getDeclaredMethod(methodName, String.class));
@@ -856,18 +920,42 @@ void selectNestedMethodByEnclosingClassNamesAndMethodNameWithParameterTypes() th
assertThat(selector.getMethodName()).isEqualTo(methodName);
}
+ /**
+ * @since 1.0
+ */
@Test
- void selectNestedMethodByEnclosingClassNamesAndMethodNameWithParameterTypesAndClassLoader() throws Exception {
- var testClasses = List.of(AbstractClassWithNestedInnerClass.class, ClassWithNestedInnerClass.class,
- AbstractClassWithNestedInnerClass.NestedClass.class);
+ void selectNestedMethodByEnclosingClassNamesMethodNameAndParameterTypes() throws Exception {
+ var selector = selectNestedMethod(List.of(enclosingClassName), nestedClassName, methodName, String.class);
- try (var testClassLoader = TestClassLoader.forClasses(testClasses.toArray(Class[]::new))) {
+ assertThat(selector.getEnclosingClasses()).containsOnly(ClassWithNestedInnerClass.class);
+ assertThat(selector.getNestedClass()).isEqualTo(ClassWithNestedInnerClass.NestedClass.class);
+ assertThat(selector.getMethod()).isEqualTo(
+ selector.getNestedClass().getDeclaredMethod(methodName, String.class));
+ assertThat(selector.getParameterTypes()).containsExactly(String.class);
+
+ assertThat(selector.getEnclosingClassNames()).containsOnly(enclosingClassName);
+ assertThat(selector.getNestedClassName()).isEqualTo(nestedClassName);
+ assertThat(selector.getMethodName()).isEqualTo(methodName);
+ assertThat(selector.getParameterTypeNames()).isEqualTo("java.lang.String");
+ }
+
+ /**
+ * @since 1.10
+ */
+ @Test
+ void selectNestedMethodByEnclosingClassNamesMethodNameAndParameterTypeNamesWithExplicitClassLoader()
+ throws Exception {
+
+ var enclosingClass = ClassWithNestedInnerClass.class;
+ var nestedClass = ClassWithNestedInnerClass.NestedClass.class;
+
+ try (var testClassLoader = TestClassLoader.forClasses(enclosingClass, nestedClass)) {
var selector = selectNestedMethod(testClassLoader, List.of(enclosingClassName), nestedClassName,
methodName, String.class.getName());
- assertThat(selector.getEnclosingClasses()).doesNotContain(ClassWithNestedInnerClass.class);
+ assertThat(selector.getEnclosingClasses()).doesNotContain(enclosingClass);
assertThat(selector.getEnclosingClasses()).extracting(Class::getName).containsOnly(enclosingClassName);
- assertThat(selector.getNestedClass()).isNotEqualTo(AbstractClassWithNestedInnerClass.NestedClass.class);
+ assertThat(selector.getNestedClass()).isNotEqualTo(nestedClass);
assertThat(selector.getNestedClass().getName()).isEqualTo(nestedClassName);
assertThat(selector.getClassLoader()).isSameAs(testClassLoader);
@@ -880,6 +968,26 @@ void selectNestedMethodByEnclosingClassNamesAndMethodNameWithParameterTypesAndCl
}
}
+ /**
+ * @since 1.10
+ */
+ @Test
+ void selectNestedMethodByEnclosingClassesMethodNameAndParameterTypes() throws Exception {
+ var selector = selectNestedMethod(List.of(ClassWithNestedInnerClass.class),
+ ClassWithNestedInnerClass.NestedClass.class, methodName, String.class);
+
+ assertThat(selector.getEnclosingClasses()).containsOnly(ClassWithNestedInnerClass.class);
+ assertThat(selector.getNestedClass()).isEqualTo(ClassWithNestedInnerClass.NestedClass.class);
+ assertThat(selector.getMethod()).isEqualTo(
+ selector.getNestedClass().getDeclaredMethod(methodName, String.class));
+ assertThat(selector.getParameterTypes()).containsExactly(String.class);
+
+ assertThat(selector.getEnclosingClassNames()).containsOnly(enclosingClassName);
+ assertThat(selector.getNestedClassName()).isEqualTo(nestedClassName);
+ assertThat(selector.getMethodName()).isEqualTo(methodName);
+ assertThat(selector.getParameterTypeNames()).isEqualTo("java.lang.String");
+ }
+
@Test
void selectDoubleNestedMethodByEnclosingClassNamesAndMethodName() throws Exception {
var doubleNestedMethodName = "doubleNestedTest";
@@ -887,9 +995,9 @@ void selectDoubleNestedMethodByEnclosingClassNamesAndMethodName() throws Excepti
doubleNestedMethodName);
assertThat(selector.getEnclosingClasses()).containsExactly(ClassWithNestedInnerClass.class,
- AbstractClassWithNestedInnerClass.NestedClass.class);
+ ClassWithNestedInnerClass.NestedClass.class);
assertThat(selector.getNestedClass()).isEqualTo(
- AbstractClassWithNestedInnerClass.NestedClass.DoubleNestedClass.class);
+ ClassWithNestedInnerClass.NestedClass.DoubleNestedClass.class);
assertThat(selector.getMethod()).isEqualTo(
selector.getNestedClass().getDeclaredMethod(doubleNestedMethodName));
@@ -899,53 +1007,88 @@ void selectDoubleNestedMethodByEnclosingClassNamesAndMethodName() throws Excepti
}
@Test
- void selectNestedMethodPreconditions() {
+ @DisplayName("Preconditions: selectNestedMethod(enclosingClassNames, nestedClassName, methodName)")
+ void selectNestedMethodByEnclosingClassNamesAndMethodNamePreconditions() {
assertViolatesPrecondition(() -> selectNestedMethod(null, "ClassName", "methodName"));
- assertViolatesPrecondition(() -> selectNestedMethod(null, "ClassName", "methodName", "int"));
assertViolatesPrecondition(() -> selectNestedMethod(List.of(), "ClassName", "methodName"));
- assertViolatesPrecondition(() -> selectNestedMethod(List.of(), "ClassName", "methodName", "int"));
assertViolatesPrecondition(() -> selectNestedMethod(List.of("ClassName"), null, "methodName"));
- assertViolatesPrecondition(() -> selectNestedMethod(List.of("ClassName"), null, "methodName", "int"));
assertViolatesPrecondition(() -> selectNestedMethod(List.of("ClassName"), " ", "methodName"));
- assertViolatesPrecondition(() -> selectNestedMethod(List.of("ClassName"), " ", "methodName", "int"));
assertViolatesPrecondition(() -> selectNestedMethod(List.of("ClassName"), "ClassName", null));
- assertViolatesPrecondition(() -> selectNestedMethod(List.of("ClassName"), "ClassName", null, "int"));
assertViolatesPrecondition(() -> selectNestedMethod(List.of("ClassName"), "ClassName", " "));
+ }
+
+ @Test
+ @DisplayName("Preconditions: selectNestedMethod(enclosingClassNames, nestedClassName, methodName, parameterTypeNames)")
+ void selectNestedMethodByEnclosingClassNamesMethodNameAndParameterTypeNamesPreconditions() {
+ assertViolatesPrecondition(() -> selectNestedMethod(null, "ClassName", "methodName", "int"));
+ assertViolatesPrecondition(() -> selectNestedMethod(List.of(), "ClassName", "methodName", "int"));
+ assertViolatesPrecondition(() -> selectNestedMethod(List.of("ClassName"), null, "methodName", "int"));
+ assertViolatesPrecondition(() -> selectNestedMethod(List.of("ClassName"), " ", "methodName", "int"));
+ assertViolatesPrecondition(() -> selectNestedMethod(List.of("ClassName"), "ClassName", null, "int"));
assertViolatesPrecondition(() -> selectNestedMethod(List.of("ClassName"), "ClassName", " ", "int"));
assertViolatesPrecondition(
() -> selectNestedMethod(List.of("ClassName"), "ClassName", "methodName", (String) null));
}
- abstract class AbstractClassWithNestedInnerClass {
+ /**
+ * @since 1.10
+ */
+ @Test
+ @DisplayName("Preconditions: selectNestedMethod(enclosingClassNames, nestedClassName, methodName, parameterTypes)")
+ void selectNestedMethodByEnclosingClassNamesMethodNameAndParameterTypesPreconditions() {
+ assertViolatesPrecondition(() -> selectNestedMethod(null, "ClassName", "methodName", int.class));
+ assertViolatesPrecondition(() -> selectNestedMethod(List.of(), "ClassName", "methodName", int.class));
+ assertViolatesPrecondition(() -> selectNestedMethod(List.of("ClassName"), null, "methodName", int.class));
+ assertViolatesPrecondition(() -> selectNestedMethod(List.of("ClassName"), " ", "methodName", int.class));
+ assertViolatesPrecondition(() -> selectNestedMethod(List.of("ClassName"), "ClassName", null, int.class));
+ assertViolatesPrecondition(() -> selectNestedMethod(List.of("ClassName"), "ClassName", " ", int.class));
+ assertViolatesPrecondition(
+ () -> selectNestedMethod(List.of("ClassName"), "ClassName", "methodName", (Class>) null));
+ assertViolatesPrecondition(() -> selectNestedMethod(List.of("ClassName"), "ClassName", "methodName",
+ new Class>[] { int.class, null }));
+ }
+
+ /**
+ * @since 1.10
+ */
+ @Test
+ @DisplayName("Preconditions: selectNestedMethod(enclosingClasses, nestedClass, methodName, parameterTypes)")
+ void selectNestedMethodByEnclosingClassesClassMethodNameAndParameterTypesPreconditions() {
+ List> enclosingClassList = List.of(ClassWithNestedInnerClass.class);
+ Class> nestedClass = ClassWithNestedInnerClass.NestedClass.class;
+
+ assertViolatesPrecondition(() -> selectNestedMethod(null, nestedClass, "methodName", int.class));
+ assertViolatesPrecondition(() -> selectNestedMethod(List.of(), nestedClass, "methodName", int.class));
+ assertViolatesPrecondition(() -> selectNestedMethod(enclosingClassList, null, "methodName", int.class));
+ assertViolatesPrecondition(() -> selectNestedMethod(enclosingClassList, nestedClass, null, int.class));
+ assertViolatesPrecondition(() -> selectNestedMethod(enclosingClassList, nestedClass, " ", int.class));
+ assertViolatesPrecondition(
+ () -> selectNestedMethod(enclosingClassList, nestedClass, "methodName", (Class>) null));
+ assertViolatesPrecondition(() -> selectNestedMethod(enclosingClassList, nestedClass, "methodName",
+ new Class>[] { int.class, null }));
+ }
+
+ static class ClassWithNestedInnerClass {
- @Nested
+ // @Nested
class NestedClass {
- @Test
+ // @Test
void nestedTest() {
}
- @Test
+ // @Test
void nestedTest(String parameter) {
}
- @Nested
+ // @Nested
class DoubleNestedClass {
- @Test
+ // @Test
void doubleNestedTest() {
}
-
}
-
}
-
- }
-
- class ClassWithNestedInnerClass extends AbstractClassWithNestedInnerClass {
- }
-
- class OtherClassWithNestedInnerClass extends AbstractClassWithNestedInnerClass {
}
}
diff --git a/platform-tests/src/test/java/org/junit/platform/engine/discovery/MethodSelectorTests.java b/platform-tests/src/test/java/org/junit/platform/engine/discovery/MethodSelectorTests.java
index 9f314ef7d8ed..b6d890ba0861 100644
--- a/platform-tests/src/test/java/org/junit/platform/engine/discovery/MethodSelectorTests.java
+++ b/platform-tests/src/test/java/org/junit/platform/engine/discovery/MethodSelectorTests.java
@@ -35,8 +35,9 @@ void equalsAndHashCode() {
var selector1 = new MethodSelector(null, TEST_CASE_NAME, "method", "int, boolean");
var selector2 = new MethodSelector(null, TEST_CASE_NAME, "method", "int, boolean");
var selector3 = new MethodSelector(TestCase.class, "method", "int, boolean");
+ var selector4 = new MethodSelector(TestCase.class, "method", int.class, boolean.class);
- Stream.of(selector2, selector3).forEach(selector -> {
+ Stream.of(selector2, selector3, selector4).forEach(selector -> {
assertEqualsAndHashCode(selector1, selector, new MethodSelector(null, TEST_CASE_NAME, "method", "int"));
assertEqualsAndHashCode(selector1, selector,
new MethodSelector((ClassLoader) null, TEST_CASE_NAME, "method", ""));
diff --git a/platform-tests/src/test/java/org/junit/platform/suite/commons/SuiteLauncherDiscoveryRequestBuilderTests.java b/platform-tests/src/test/java/org/junit/platform/suite/commons/SuiteLauncherDiscoveryRequestBuilderTests.java
index 1848163bc6ce..55aa8d6eaa04 100644
--- a/platform-tests/src/test/java/org/junit/platform/suite/commons/SuiteLauncherDiscoveryRequestBuilderTests.java
+++ b/platform-tests/src/test/java/org/junit/platform/suite/commons/SuiteLauncherDiscoveryRequestBuilderTests.java
@@ -10,11 +10,14 @@
package org.junit.platform.suite.commons;
+import static java.util.Map.entry;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.DynamicTest.dynamicTest;
+import static org.junit.platform.suite.commons.SuiteLauncherDiscoveryRequestBuilder.request;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -26,8 +29,11 @@
import java.util.Optional;
import java.util.Set;
import java.util.regex.Pattern;
+import java.util.stream.Stream;
+import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestFactory;
import org.junit.jupiter.engine.JupiterTestEngine;
import org.junit.platform.commons.PreconditionViolationException;
import org.junit.platform.commons.util.CollectionUtils;
@@ -72,7 +78,7 @@
class SuiteLauncherDiscoveryRequestBuilderTests {
- SuiteLauncherDiscoveryRequestBuilder builder = SuiteLauncherDiscoveryRequestBuilder.request();
+ SuiteLauncherDiscoveryRequestBuilder builder = request();
@Test
void configurationParameter() {
@@ -249,38 +255,69 @@ class Suite {
static class NonLocalTestCase {
}
- @Test
- void selectOneMethodWithNoParameters() {
- class TestClass {
- void testMethod() {
- }
+ @TestFactory
+ Stream selectOneMethodWithNoParameters() {
+ @SelectMethod("org.junit.platform.suite.commons.SuiteLauncherDiscoveryRequestBuilderTests$NoParameterTestCase#testMethod")
+ class SuiteA {
}
- @SelectMethod(clazz = TestClass.class, name = "testMethod")
- class Suite {
+ @SelectMethod(type = NoParameterTestCase.class, name = "testMethod")
+ class SuiteB {
+ }
+ @SelectMethod(typeName = "org.junit.platform.suite.commons.SuiteLauncherDiscoveryRequestBuilderTests$NoParameterTestCase", name = "testMethod")
+ class SuiteC {
}
- LauncherDiscoveryRequest request = builder.suite(Suite.class).build();
- List selectors = request.getSelectorsByType(MethodSelector.class);
- assertEquals(DiscoverySelectors.selectMethod(TestClass.class, "testMethod"), exactlyOne(selectors));
+ return Stream.of(SuiteA.class, SuiteB.class, SuiteC.class) //
+ .map(suiteClass -> dynamicTest(suiteClass.getSimpleName(), () -> {
+ LauncherDiscoveryRequest request = request().suite(suiteClass).build();
+ List selectors = request.getSelectorsByType(MethodSelector.class);
+ assertEquals(DiscoverySelectors.selectMethod(NoParameterTestCase.class, "testMethod"),
+ exactlyOne(selectors));
+ }));
}
- @Test
- void selectOneMethodWithOneParameters() {
- class TestClass {
- void testMethod(int i) {
- }
+ static class NoParameterTestCase {
+ @SuppressWarnings("unused")
+ void testMethod() {
}
- @SelectMethod(clazz = TestClass.class, name = "testMethod", parameters = "int")
- class Suite {
+ }
+
+ @TestFactory
+ Stream selectOneMethodWithOneParameter() {
+ @SelectMethod("org.junit.platform.suite.commons.SuiteLauncherDiscoveryRequestBuilderTests$OneParameterTestCase#testMethod(int)")
+ class SuiteA {
+ }
+ @SelectMethod(type = OneParameterTestCase.class, name = "testMethod", parameterTypeNames = "int")
+ class SuiteB {
+ }
+ @SelectMethod(type = OneParameterTestCase.class, name = "testMethod", parameterTypes = int.class)
+ class SuiteC {
+ }
+ @SelectMethod(typeName = "org.junit.platform.suite.commons.SuiteLauncherDiscoveryRequestBuilderTests$OneParameterTestCase", name = "testMethod", parameterTypeNames = "int")
+ class SuiteD {
+ }
+ @SelectMethod(typeName = "org.junit.platform.suite.commons.SuiteLauncherDiscoveryRequestBuilderTests$OneParameterTestCase", name = "testMethod", parameterTypes = int.class)
+ class SuiteE {
}
- LauncherDiscoveryRequest request = builder.suite(Suite.class).build();
- List selectors = request.getSelectorsByType(MethodSelector.class);
- assertEquals(DiscoverySelectors.selectMethod(TestClass.class, "testMethod", "int"), exactlyOne(selectors));
+ return Stream.of(SuiteA.class, SuiteB.class, SuiteC.class, SuiteD.class, SuiteE.class) //
+ .map(suiteClass -> dynamicTest(suiteClass.getSimpleName(), () -> {
+ LauncherDiscoveryRequest request = request().suite(suiteClass).build();
+ List selectors = request.getSelectorsByType(MethodSelector.class);
+ assertEquals(DiscoverySelectors.selectMethod(OneParameterTestCase.class, "testMethod", "int"),
+ exactlyOne(selectors));
+ }));
+ }
+
+ static class OneParameterTestCase {
+ @SuppressWarnings("unused")
+ void testMethod(int i) {
+ }
}
@Test
- void selectTwoMethodWithTwoParameters() {
+ void selectTwoMethodsWithTwoParameters() {
+ @SuppressWarnings("unused")
class TestClass {
void firstTestMethod(int i, String j) {
}
@@ -288,8 +325,9 @@ void firstTestMethod(int i, String j) {
void secondTestMethod(boolean i, float j) {
}
}
- @SelectMethod(clazz = TestClass.class, name = "firstTestMethod", parameters = "int, java.lang.String")
- @SelectMethod(clazz = TestClass.class, name = "secondTestMethod", parameters = "boolean, float")
+ @SelectMethod(type = TestClass.class, name = "firstTestMethod", parameterTypeNames = "int, java.lang.String")
+ @SelectMethod(type = TestClass.class, name = "secondTestMethod", parameterTypes = { boolean.class,
+ float.class })
class Suite {
}
@@ -302,6 +340,61 @@ class Suite {
selectors.get(1));
}
+ @TestFactory
+ Stream selectMethodCausesExceptionOnInvalidUsage() {
+ @SelectMethod(value = "irrelevant", type = NoParameterTestCase.class)
+ class ValueAndType {
+ }
+ @SelectMethod(value = "SomeClass#someMethod", typeName = "org.junit.platform.suite.commons.SuiteLauncherDiscoveryRequestBuilderTests$NoParameterTestCase")
+ class ValueAndTypeName {
+ }
+ @SelectMethod(value = "SomeClass#someMethod", name = "testMethod")
+ class ValueAndMethodName {
+ }
+ @SelectMethod(value = "SomeClass#someMethod", parameterTypes = int.class)
+ class ValueAndParameterTypes {
+ }
+ @SelectMethod(value = "SomeClass#someMethod", parameterTypeNames = "int")
+ class ValueAndParameterTypeNames {
+ }
+ @SelectMethod(type = NoParameterTestCase.class)
+ class MissingMethodName {
+ }
+ @SelectMethod(name = "testMethod", type = NoParameterTestCase.class, typeName = "org.junit.platform.suite.commons.SuiteLauncherDiscoveryRequestBuilderTests$NoParameterTestCase")
+ class TypeAndTypeName {
+ }
+ @SelectMethod(name = "testMethod", parameterTypes = int.class, parameterTypeNames = "int")
+ class ParameterTypesAndParameterTypeNames {
+ }
+
+ var expectedFailureMessages = Map.ofEntries( //
+ entry(ValueAndType.class, "type must not be set in conjunction with fully qualified method name"), //
+ entry(ValueAndTypeName.class, "type name must not be set in conjunction with fully qualified method name"), //
+ entry(ValueAndMethodName.class,
+ "method name must not be set in conjunction with fully qualified method name"), //
+ entry(ValueAndParameterTypes.class,
+ "parameter types must not be set in conjunction with fully qualified method name"), //
+ entry(ValueAndParameterTypeNames.class,
+ "parameter type names must not be set in conjunction with fully qualified method name"), //
+ entry(MissingMethodName.class, "method name must not be blank"), //
+ entry(TypeAndTypeName.class, "either type name or type must be set but not both"), //
+ entry(ParameterTypesAndParameterTypeNames.class,
+ "either parameter type names or parameter types must be set but not both") //
+ );
+ return expectedFailureMessages.entrySet().stream() //
+ .map(entry -> {
+ Class> suiteClassName = entry.getKey();
+ var expectedFailureMessage = entry.getValue();
+ return dynamicTest(suiteClassName.getSimpleName(), () -> {
+ var ex = assertThrows(PreconditionViolationException.class,
+ () -> request().suite(suiteClassName));
+ assertEquals(
+ "@SelectMethod on class [" + suiteClassName.getName() + "]: " + expectedFailureMessage,
+ ex.getMessage());
+ });
+ });
+ }
+
@Test
void selectClasspathResource() {
@SelectClasspathResource("com.example.testcases")
diff --git a/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/SelectMethodsSuite.java b/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/SelectMethodsSuite.java
index 1e9712e5e156..49184c267e23 100644
--- a/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/SelectMethodsSuite.java
+++ b/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/SelectMethodsSuite.java
@@ -18,6 +18,6 @@
* @since 1.10
*/
@Suite
-@SelectMethod(clazz = MultipleTestsTestCase.class, name = "test")
+@SelectMethod(type = MultipleTestsTestCase.class, name = "test")
public class SelectMethodsSuite {
}