The various {@code assertTimeoutPreemptively()} methods in this class
- * execute the provided {@code executable} or {@code supplier} in a different
- * thread than that of the calling code. This behavior can lead to undesirable
- * side effects if the code that is executed within the {@code executable} or
- * {@code supplier} relies on {@link ThreadLocal} storage.
+ * execute the provided callback ({@code executable} or {@code supplier}) in a
+ * different thread than that of the calling code. If the timeout is exceeded,
+ * an attempt will be made to preemptively abort execution of the callback by
+ * {@linkplain Thread#interrupt() interrupting} the callback's thread. If the
+ * callback's thread does not return when interrupted, the thread will continue
+ * to run in the background after the {@code assertTimeoutPreemptively()} method
+ * has returned.
*
- *
One common example of this is the transactional testing support in the Spring
- * Framework. Specifically, Spring's testing support binds transaction state to
- * the current thread (via a {@code ThreadLocal}) before a test method is invoked.
- * Consequently, if an {@code executable} or {@code supplier} provided to
- * {@code assertTimeoutPreemptively()} invokes Spring-managed components that
- * participate in transactions, any actions taken by those components will not be
- * rolled back with the test-managed transaction. On the contrary, such actions
- * will be committed to the persistent store (e.g., relational database) even
- * though the test-managed transaction is rolled back.
- *
- *
Similar side effects may be encountered with other frameworks that rely on
+ *
Furthermore, the behavior of {@code assertTimeoutPreemptively()} methods
+ * can lead to undesirable side effects if the code that is executed within the
+ * callback relies on {@link ThreadLocal} storage. One common example of this is
+ * the transactional testing support in the Spring Framework. Specifically, Spring's
+ * testing support binds transaction state to the current thread (via a
+ * {@code ThreadLocal}) before a test method is invoked. Consequently, if a
+ * callback provided to {@code assertTimeoutPreemptively()} invokes Spring-managed
+ * components that participate in transactions, any actions taken by those
+ * components will not be rolled back with the test-managed transaction. On the
+ * contrary, such actions will be committed to the persistent store (e.g.,
+ * relational database) even though the test-managed transaction is rolled back.
+ * Similar side effects may be encountered with other frameworks that rely on
* {@code ThreadLocal} storage.
*
*
Extensibility
@@ -3410,11 +3414,8 @@ public static T assertTimeout(Duration timeout, ThrowingSupplier supplier
* Assert that execution of the supplied {@code executable}
* completes before the given {@code timeout} is exceeded.
*
- *
Note: the {@code executable} will be executed in a different thread than
- * that of the calling code. Furthermore, execution of the {@code executable} will
- * be preemptively aborted if the timeout is exceeded. See the
- * {@linkplain Assertions Preemptive Timeouts} section of the class-level
- * Javadoc for a discussion of possible undesirable side effects.
+ *
See the {@linkplain Assertions Preemptive Timeouts} section of the
+ * class-level Javadoc for further details.
*
* @see #assertTimeoutPreemptively(Duration, Executable, String)
* @see #assertTimeoutPreemptively(Duration, Executable, Supplier)
@@ -3431,11 +3432,8 @@ public static void assertTimeoutPreemptively(Duration timeout, Executable execut
* Assert that execution of the supplied {@code executable}
* completes before the given {@code timeout} is exceeded.
*
- *
Note: the {@code executable} will be executed in a different thread than
- * that of the calling code. Furthermore, execution of the {@code executable} will
- * be preemptively aborted if the timeout is exceeded. See the
- * {@linkplain Assertions Preemptive Timeouts} section of the class-level
- * Javadoc for a discussion of possible undesirable side effects.
+ *
See the {@linkplain Assertions Preemptive Timeouts} section of the
+ * class-level Javadoc for further details.
*
*
Fails with the supplied failure {@code message}.
*
@@ -3454,11 +3452,8 @@ public static void assertTimeoutPreemptively(Duration timeout, Executable execut
* Assert that execution of the supplied {@code executable}
* completes before the given {@code timeout} is exceeded.
*
- *
Note: the {@code executable} will be executed in a different thread than
- * that of the calling code. Furthermore, execution of the {@code executable} will
- * be preemptively aborted if the timeout is exceeded. See the
- * {@linkplain Assertions Preemptive Timeouts} section of the class-level
- * Javadoc for a discussion of possible undesirable side effects.
+ *
See the {@linkplain Assertions Preemptive Timeouts} section of the
+ * class-level Javadoc for further details.
*
*
If necessary, the failure message will be retrieved lazily from the
* supplied {@code messageSupplier}.
@@ -3481,13 +3476,10 @@ public static void assertTimeoutPreemptively(Duration timeout, Executable execut
* Assert that execution of the supplied {@code supplier}
* completes before the given {@code timeout} is exceeded.
*
- *
If the assertion passes then the {@code supplier}'s result is returned.
+ *
See the {@linkplain Assertions Preemptive Timeouts} section of the
+ * class-level Javadoc for further details.
*
- *
Note: the {@code supplier} will be executed in a different thread than
- * that of the calling code. Furthermore, execution of the {@code supplier} will
- * be preemptively aborted if the timeout is exceeded. See the
- * {@linkplain Assertions Preemptive Timeouts} section of the class-level
- * Javadoc for a discussion of possible undesirable side effects.
+ *
If the assertion passes then the {@code supplier}'s result is returned.
*
* @see #assertTimeoutPreemptively(Duration, Executable)
* @see #assertTimeoutPreemptively(Duration, Executable, String)
@@ -3504,13 +3496,10 @@ public static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier
* Assert that execution of the supplied {@code supplier}
* completes before the given {@code timeout} is exceeded.
*
- *
If the assertion passes then the {@code supplier}'s result is returned.
+ *
See the {@linkplain Assertions Preemptive Timeouts} section of the
+ * class-level Javadoc for further details.
*
- *
Note: the {@code supplier} will be executed in a different thread than
- * that of the calling code. Furthermore, execution of the {@code supplier} will
- * be preemptively aborted if the timeout is exceeded. See the
- * {@linkplain Assertions Preemptive Timeouts} section of the class-level
- * Javadoc for a discussion of possible undesirable side effects.
+ *
If the assertion passes then the {@code supplier}'s result is returned.
*
*
Fails with the supplied failure {@code message}.
*
@@ -3529,13 +3518,10 @@ public static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier
* Assert that execution of the supplied {@code supplier}
* completes before the given {@code timeout} is exceeded.
*
- *
If the assertion passes then the {@code supplier}'s result is returned.
+ *
See the {@linkplain Assertions Preemptive Timeouts} section of the
+ * class-level Javadoc for further details.
*
- *
Note: the {@code supplier} will be executed in a different thread than
- * that of the calling code. Furthermore, execution of the {@code supplier} will
- * be preemptively aborted if the timeout is exceeded. See the
- * {@linkplain Assertions Preemptive Timeouts} section of the class-level
- * Javadoc for a discussion of possible undesirable side effects.
+ *
If the assertion passes then the {@code supplier}'s result is returned.
*
*
If necessary, the failure message will be retrieved lazily from the
* supplied {@code messageSupplier}.
@@ -3556,18 +3542,15 @@ public static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier
* Assert that execution of the supplied {@code supplier}
* completes before the given {@code timeout} is exceeded.
*
+ *
See the {@linkplain Assertions Preemptive Timeouts} section of the
+ * class-level Javadoc for further details.
+ *
*
If the assertion passes then the {@code supplier}'s result is returned.
*
*
In the case the assertion does not pass, the supplied
* {@link TimeoutFailureFactory} is invoked to create an exception which is
* then thrown.
*
- *
Note: the {@code supplier} will be executed in a different thread than
- * that of the calling code. Furthermore, execution of the {@code supplier} will
- * be preemptively aborted if the timeout is exceeded. See the
- * {@linkplain Assertions Preemptive Timeouts} section of the class-level
- * Javadoc for a discussion of possible undesirable side effects.
- *
*
If necessary, the failure message will be retrieved lazily from the
* supplied {@code messageSupplier}.
*
diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Disabled.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Disabled.java
index 9a7717053491..6394a84634c3 100644
--- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Disabled.java
+++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Disabled.java
@@ -30,6 +30,10 @@
*
When applied at the class level, all test methods within that class
* are automatically disabled as well.
*
+ *
This annotation is not {@link java.lang.annotation.Inherited @Inherited}.
+ * Consequently, if you wish to apply the same semantics to a subclass, this
+ * annotation must be redeclared on the subclass.
+ *
*
When applied at the method level, the presence of this annotation does not
* prevent the test class from being instantiated. Rather, it prevents the
* execution of the test method and method-level lifecycle callbacks such as
diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledForJreRange.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledForJreRange.java
index a3b0762e0de5..1b175ed34d91 100644
--- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledForJreRange.java
+++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledForJreRange.java
@@ -22,13 +22,17 @@
import org.junit.jupiter.api.extension.ExtendWith;
/**
- * {@code @DisabledForJreRange} is used to signal that the annotated test class or
- * test method is only disabled for a specific range of Java Runtime
+ * {@code @DisabledForJreRange} is used to signal that the annotated test class
+ * or test method is disabled for a specific range of Java Runtime
* Environment (JRE) versions from {@link #min} to {@link #max}.
*
*
When applied at the class level, all test methods within that class will
* be disabled on the same specified JRE versions.
*
+ *
This annotation is not {@link java.lang.annotation.Inherited @Inherited}.
+ * Consequently, if you wish to apply the same semantics to a subclass, this
+ * annotation must be redeclared on the subclass.
+ *
*
If a test method is disabled via this annotation, that does not prevent
* the test class from being instantiated. Rather, it prevents the execution of
* the test method and method-level lifecycle callbacks such as {@code @BeforeEach}
diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIf.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIf.java
index 06bdd569048a..1d3f119662da 100644
--- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIf.java
+++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIf.java
@@ -23,12 +23,16 @@
/**
* {@code @DisabledIf} is used to signal that the annotated test class or test
- * method is disabled only if the provided
- * {@linkplain #value() condition} evaluates to {@code true}.
+ * method is disabled if the provided {@linkplain #value() condition}
+ * evaluates to {@code true}.
*
*
When applied at the class level, all test methods within that class will
* be disabled on the same condition.
*
+ *
This annotation is not {@link java.lang.annotation.Inherited @Inherited}.
+ * Consequently, if you wish to apply the same semantics to a subclass, this
+ * annotation must be redeclared on the subclass.
+ *
*
If a test method is disabled via this annotation, that does not prevent
* the test class from being instantiated. Rather, it prevents the execution of
* the test method and method-level lifecycle callbacks such as {@code @BeforeEach}
diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariable.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariable.java
index 0cc90eec72a4..ea4f0a147a67 100644
--- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariable.java
+++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariable.java
@@ -31,6 +31,10 @@
*
When declared at the class level, the result will apply to all test methods
* within that class as well.
*
+ *
This annotation is not {@link java.lang.annotation.Inherited @Inherited}.
+ * Consequently, if you wish to apply the same semantics to a subclass, this
+ * annotation must be redeclared on the subclass.
+ *
*
If a test method is disabled via this annotation, that does not prevent
* the test class from being instantiated. Rather, it prevents the execution of
* the test method and method-level lifecycle callbacks such as {@code @BeforeEach}
diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariables.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariables.java
index deef2a4a8fa0..6b473d684cff 100644
--- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariables.java
+++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariables.java
@@ -28,6 +28,10 @@
* is completely optional since {@code @DisabledIfEnvironmentVariable} is a {@linkplain
* java.lang.annotation.Repeatable repeatable} annotation.
*
+ *
This annotation is not {@link java.lang.annotation.Inherited @Inherited}.
+ * Consequently, if you wish to apply the same semantics to a subclass, this
+ * annotation must be redeclared on the subclass.
+ *
* @since 5.6
* @see DisabledIfEnvironmentVariable
* @see java.lang.annotation.Repeatable
diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfSystemProperties.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfSystemProperties.java
index ff6877bf1a06..eb351cb2ea14 100644
--- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfSystemProperties.java
+++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfSystemProperties.java
@@ -28,6 +28,10 @@
* is completely optional since {@code @DisabledIfSystemProperty} is a {@linkplain
* java.lang.annotation.Repeatable repeatable} annotation.
*
+ *
This annotation is not {@link java.lang.annotation.Inherited @Inherited}.
+ * Consequently, if you wish to apply the same semantics to a subclass, this
+ * annotation must be redeclared on the subclass.
+ *
* @since 5.6
* @see DisabledIfSystemProperty
* @see java.lang.annotation.Repeatable
diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfSystemProperty.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfSystemProperty.java
index ac7d0937d848..ce767e6afa95 100644
--- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfSystemProperty.java
+++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfSystemProperty.java
@@ -31,6 +31,10 @@
*
When declared at the class level, the result will apply to all test methods
* within that class as well.
*
+ *
This annotation is not {@link java.lang.annotation.Inherited @Inherited}.
+ * Consequently, if you wish to apply the same semantics to a subclass, this
+ * annotation must be redeclared on the subclass.
+ *
*
If a test method is disabled via this annotation, that does not prevent
* the test class from being instantiated. Rather, it prevents the execution of
* the test method and method-level lifecycle callbacks such as {@code @BeforeEach}
diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledInNativeImage.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledInNativeImage.java
index a96399e799be..06ea015ebf88 100644
--- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledInNativeImage.java
+++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledInNativeImage.java
@@ -22,12 +22,16 @@
/**
* {@code @DisabledInNativeImage} is used to signal that the annotated test class
- * or test method is only disabled when executing within a GraalVM native
+ * or test method is disabled when executing within a GraalVM native
* image.
*
*
When applied at the class level, all test methods within that class will
* be disabled within a native image.
*
+ *
This annotation is not {@link java.lang.annotation.Inherited @Inherited}.
+ * Consequently, if you wish to apply the same semantics to a subclass, this
+ * annotation must be redeclared on the subclass.
+ *
*
If a test method is disabled via this annotation, that does not prevent
* the test class from being instantiated. Rather, it prevents the execution of
* the test method and method-level lifecycle callbacks such as {@code @BeforeEach}
diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledOnJre.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledOnJre.java
index 2aa7f012c947..5d66386056cd 100644
--- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledOnJre.java
+++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledOnJre.java
@@ -23,12 +23,16 @@
/**
* {@code @DisabledOnJre} is used to signal that the annotated test class or
- * test method is disabled on one or more specified Java
- * Runtime Environment (JRE) {@linkplain #value versions}.
+ * test method is disabled on one or more specified Java Runtime
+ * Environment (JRE) {@linkplain #value versions}.
*
*
When applied at the class level, all test methods within that class
* will be disabled on the same specified JRE versions.
*
+ *
This annotation is not {@link java.lang.annotation.Inherited @Inherited}.
+ * Consequently, if you wish to apply the same semantics to a subclass, this
+ * annotation must be redeclared on the subclass.
+ *
*
If a test method is disabled via this annotation, that does not prevent
* the test class from being instantiated. Rather, it prevents the execution of
* the test method and method-level lifecycle callbacks such as {@code @BeforeEach}
diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledOnOs.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledOnOs.java
index f067c2290083..3e843a545387 100644
--- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledOnOs.java
+++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledOnOs.java
@@ -34,6 +34,10 @@
* will be disabled on the same specified operating systems, architectures, or
* the specified combinations of both.
*
+ *
This annotation is not {@link java.lang.annotation.Inherited @Inherited}.
+ * Consequently, if you wish to apply the same semantics to a subclass, this
+ * annotation must be redeclared on the subclass.
+ *
*
If a test method is disabled via this annotation, that does not prevent
* the test class from being instantiated. Rather, it prevents the execution of
* the test method and method-level lifecycle callbacks such as {@code @BeforeEach}
diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledForJreRange.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledForJreRange.java
index b1efb7c88e0d..ed1a74b06e09 100644
--- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledForJreRange.java
+++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledForJreRange.java
@@ -29,6 +29,10 @@
*
When applied at the class level, all test methods within that class will
* be enabled on the same specified JRE versions.
*
+ *
This annotation is not {@link java.lang.annotation.Inherited @Inherited}.
+ * Consequently, if you wish to apply the same semantics to a subclass, this
+ * annotation must be redeclared on the subclass.
+ *
*
If a test method is disabled via this annotation, that does not prevent
* the test class from being instantiated. Rather, it prevents the execution of
* the test method and method-level lifecycle callbacks such as {@code @BeforeEach}
diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIf.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIf.java
index 5bd4b3df30e7..f28b1f8d2a1a 100644
--- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIf.java
+++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIf.java
@@ -23,12 +23,16 @@
/**
* {@code @EnabledIf} is used to signal that the annotated test class or test
- * method is enabled only if the provided
- * {@linkplain #value() condition} evaluates to {@code true}.
+ * method is only enabled if the provided {@linkplain #value() condition}
+ * evaluates to {@code true}.
*
*
When applied at the class level, all test methods within that class will
* be enabled on the same condition.
*
+ *
This annotation is not {@link java.lang.annotation.Inherited @Inherited}.
+ * Consequently, if you wish to apply the same semantics to a subclass, this
+ * annotation must be redeclared on the subclass.
+ *
*
If a test method is disabled via this annotation, that does not prevent
* the test class from being instantiated. Rather, it prevents the execution of
* the test method and method-level lifecycle callbacks such as {@code @BeforeEach}
diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariable.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariable.java
index 44cf3bf5940f..11c48fdf369a 100644
--- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariable.java
+++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariable.java
@@ -31,6 +31,10 @@
*
When declared at the class level, the result will apply to all test methods
* within that class as well.
*
+ *
This annotation is not {@link java.lang.annotation.Inherited @Inherited}.
+ * Consequently, if you wish to apply the same semantics to a subclass, this
+ * annotation must be redeclared on the subclass.
+ *
*
If a test method is disabled via this annotation, that does not prevent
* the test class from being instantiated. Rather, it prevents the execution of
* the test method and method-level lifecycle callbacks such as {@code @BeforeEach}
diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariables.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariables.java
index 3589ed58b217..928c8582dd80 100644
--- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariables.java
+++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariables.java
@@ -28,6 +28,10 @@
* is completely optional since {@code @EnabledIfEnvironmentVariable} is a {@linkplain
* java.lang.annotation.Repeatable repeatable} annotation.
*
+ *
This annotation is not {@link java.lang.annotation.Inherited @Inherited}.
+ * Consequently, if you wish to apply the same semantics to a subclass, this
+ * annotation must be redeclared on the subclass.
+ *
* @since 5.6
* @see EnabledIfEnvironmentVariable
* @see java.lang.annotation.Repeatable
diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfSystemProperties.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfSystemProperties.java
index f33bdfae4790..2a3a32c6f6c5 100644
--- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfSystemProperties.java
+++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfSystemProperties.java
@@ -28,6 +28,10 @@
* is completely optional since {@code @EnabledIfSystemProperty} is a {@linkplain
* java.lang.annotation.Repeatable repeatable} annotation.
*
+ *
This annotation is not {@link java.lang.annotation.Inherited @Inherited}.
+ * Consequently, if you wish to apply the same semantics to a subclass, this
+ * annotation must be redeclared on the subclass.
+ *
* @since 5.6
* @see EnabledIfSystemProperty
* @see java.lang.annotation.Repeatable
diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfSystemProperty.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfSystemProperty.java
index 26cbcc10743a..99587b7085d5 100644
--- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfSystemProperty.java
+++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfSystemProperty.java
@@ -31,6 +31,10 @@
*
When declared at the class level, the result will apply to all test methods
* within that class as well.
*
+ *
This annotation is not {@link java.lang.annotation.Inherited @Inherited}.
+ * Consequently, if you wish to apply the same semantics to a subclass, this
+ * annotation must be redeclared on the subclass.
+ *
*
If a test method is disabled via this annotation, that does not prevent
* the test class from being instantiated. Rather, it prevents the execution of
* the test method and method-level lifecycle callbacks such as {@code @BeforeEach}
diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledInNativeImage.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledInNativeImage.java
index 33641509e7a0..98504f74f653 100644
--- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledInNativeImage.java
+++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledInNativeImage.java
@@ -28,6 +28,10 @@
*
When applied at the class level, all test methods within that class will
* be enabled within a native image.
*
+ *
This annotation is not {@link java.lang.annotation.Inherited @Inherited}.
+ * Consequently, if you wish to apply the same semantics to a subclass, this
+ * annotation must be redeclared on the subclass.
+ *
*
If a test method is disabled via this annotation, that does not prevent
* the test class from being instantiated. Rather, it prevents the execution of
* the test method and method-level lifecycle callbacks such as {@code @BeforeEach}
diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledOnJre.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledOnJre.java
index ab6e18a193e4..9b454a5744ba 100644
--- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledOnJre.java
+++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledOnJre.java
@@ -29,6 +29,10 @@
*
When applied at the class level, all test methods within that class
* will be enabled on the same specified JRE versions.
*
+ *
This annotation is not {@link java.lang.annotation.Inherited @Inherited}.
+ * Consequently, if you wish to apply the same semantics to a subclass, this
+ * annotation must be redeclared on the subclass.
+ *
*
If a test method is disabled via this annotation, that does not prevent
* the test class from being instantiated. Rather, it prevents the execution of
* the test method and method-level lifecycle callbacks such as {@code @BeforeEach}
diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledOnOs.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledOnOs.java
index de284cd68654..838579e4251a 100644
--- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledOnOs.java
+++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledOnOs.java
@@ -34,6 +34,10 @@
* will be enabled on the same specified operating systems, architectures, or
* the specified combinations of both.
*
+ *
This annotation is not {@link java.lang.annotation.Inherited @Inherited}.
+ * Consequently, if you wish to apply the same semantics to a subclass, this
+ * annotation must be redeclared on the subclass.
+ *
*
Such methods must not be {@code private} or {@code static}.
*
- *
Argument Providers and Sources
+ *
Arguments Providers and Sources
*
*
{@code @ParameterizedTest} methods must specify at least one
* {@link org.junit.jupiter.params.provider.ArgumentsProvider ArgumentsProvider}
diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/DefaultArgumentConverter.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/DefaultArgumentConverter.java
index fe697c5696e3..adbb606da691 100644
--- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/DefaultArgumentConverter.java
+++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/DefaultArgumentConverter.java
@@ -10,10 +10,7 @@
package org.junit.jupiter.params.converter;
-import static java.util.Arrays.asList;
-import static java.util.Collections.unmodifiableList;
import static org.apiguardian.api.API.Status.INTERNAL;
-import static org.junit.platform.commons.util.ReflectionUtils.getWrapperType;
import java.io.File;
import java.math.BigDecimal;
@@ -21,13 +18,13 @@
import java.net.URI;
import java.net.URL;
import java.util.Currency;
-import java.util.List;
import java.util.Locale;
-import java.util.Optional;
import java.util.UUID;
import org.apiguardian.api.API;
import org.junit.jupiter.api.extension.ParameterContext;
+import org.junit.platform.commons.support.conversion.ConversionException;
+import org.junit.platform.commons.support.conversion.StringConversionSupport;
import org.junit.platform.commons.util.ClassLoaderUtils;
import org.junit.platform.commons.util.ReflectionUtils;
@@ -47,23 +44,13 @@
*
* @since 5.0
* @see org.junit.jupiter.params.converter.ArgumentConverter
+ * @see org.junit.platform.commons.support.conversion.StringConversionSupport
*/
@API(status = INTERNAL, since = "5.0")
public class DefaultArgumentConverter implements ArgumentConverter {
public static final DefaultArgumentConverter INSTANCE = new DefaultArgumentConverter();
- private static final List stringToObjectConverters = unmodifiableList(asList( //
- new StringToBooleanConverter(), //
- new StringToCharacterConverter(), //
- new StringToNumberConverter(), //
- new StringToClassConverter(), //
- new StringToEnumConverter(), //
- new StringToJavaTimeConverter(), //
- new StringToCommonJavaTypesConverter(), //
- new FallbackStringToObjectConverter() //
- ));
-
private DefaultArgumentConverter() {
// nothing to initialize
}
@@ -88,34 +75,19 @@ public final Object convert(Object source, Class> targetType, ParameterContext
}
if (source instanceof String) {
- Class> targetTypeToUse = toWrapperType(targetType);
- Optional converter = stringToObjectConverters.stream().filter(
- candidate -> candidate.canConvert(targetTypeToUse)).findFirst();
- if (converter.isPresent()) {
- Class> declaringClass = context.getDeclaringExecutable().getDeclaringClass();
- ClassLoader classLoader = ClassLoaderUtils.getClassLoader(declaringClass);
- try {
- return converter.get().convert((String) source, targetTypeToUse, classLoader);
- }
- catch (Exception ex) {
- if (ex instanceof ArgumentConversionException) {
- // simply rethrow it
- throw (ArgumentConversionException) ex;
- }
- // else
- throw new ArgumentConversionException(
- "Failed to convert String \"" + source + "\" to type " + targetType.getTypeName(), ex);
- }
+ Class> declaringClass = context.getDeclaringExecutable().getDeclaringClass();
+ ClassLoader classLoader = ClassLoaderUtils.getClassLoader(declaringClass);
+ try {
+ return StringConversionSupport.convert((String) source, targetType, classLoader);
+ }
+ catch (ConversionException ex) {
+ throw new ArgumentConversionException(ex.getMessage(), ex);
}
}
+
throw new ArgumentConversionException(
String.format("No built-in converter for source type %s and target type %s",
source.getClass().getTypeName(), targetType.getTypeName()));
}
- private static Class> toWrapperType(Class> targetType) {
- Class> wrapperType = getWrapperType(targetType);
- return wrapperType != null ? wrapperType : targetType;
- }
-
}
diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ArgumentsSource.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ArgumentsSource.java
index 04f7849d91e0..aa2eb1ced01a 100644
--- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ArgumentsSource.java
+++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ArgumentsSource.java
@@ -23,7 +23,7 @@
/**
* {@code @ArgumentsSource} is a {@linkplain Repeatable repeatable} annotation
- * that is used to register {@linkplain ArgumentsProvider argument providers}
+ * that is used to register {@linkplain ArgumentsProvider arguments providers}
* for the annotated test method.
*
*
{@code @ArgumentsSource} may also be used as a meta-annotation in order to
diff --git a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestIntegrationTests.java b/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestIntegrationTests.java
index 2f248bfdad42..7ccc5250604b 100644
--- a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestIntegrationTests.java
+++ b/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestIntegrationTests.java
@@ -111,6 +111,40 @@
*/
class ParameterizedTestIntegrationTests {
+ @ParameterizedTest
+ @CsvSource(textBlock = """
+ apple, True
+ banana, true
+ lemon, false
+ kumquat, FALSE
+ """)
+ void sweetFruit(String fruit, Boolean sweet) {
+ switch (fruit) {
+ case "apple" -> assertThat(sweet).isTrue();
+ case "banana" -> assertThat(sweet).isTrue();
+ case "lemon" -> assertThat(sweet).isFalse();
+ case "kumquat" -> assertThat(sweet).isFalse();
+ default -> fail("Unexpected fruit : " + fruit);
+ }
+ }
+
+ @ParameterizedTest
+ @CsvSource(nullValues = "null", textBlock = """
+ apple, True
+ banana, true
+ lemon, false
+ kumquat, null
+ """)
+ void sweetFruitWithNullableBoolean(String fruit, Boolean sweet) {
+ switch (fruit) {
+ case "apple" -> assertThat(sweet).isTrue();
+ case "banana" -> assertThat(sweet).isTrue();
+ case "lemon" -> assertThat(sweet).isFalse();
+ case "kumquat" -> assertThat(sweet).isNull(); // null --> null
+ default -> fail("Unexpected fruit : " + fruit);
+ }
+ }
+
@ParameterizedTest
@CsvSource(quoteCharacter = '"', textBlock = """
diff --git a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/converter/DefaultArgumentConverterTests.java b/junit-jupiter-params/src/test/java/org/junit/jupiter/params/converter/DefaultArgumentConverterTests.java
index 90d8fb062792..b2b1e7667722 100644
--- a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/converter/DefaultArgumentConverterTests.java
+++ b/junit-jupiter-params/src/test/java/org/junit/jupiter/params/converter/DefaultArgumentConverterTests.java
@@ -47,6 +47,8 @@
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ParameterContext;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
import org.junit.platform.commons.test.TestClassLoader;
import org.junit.platform.commons.util.ReflectionUtils;
@@ -61,11 +63,13 @@ class DefaultArgumentConverterTests {
void isAwareOfNull() {
assertConverts(null, Object.class, null);
assertConverts(null, String.class, null);
+ assertConverts(null, Boolean.class, null);
}
@Test
void isAwareOfWrapperTypesForPrimitiveTypes() {
assertConverts(true, boolean.class, true);
+ assertConverts(false, boolean.class, false);
assertConverts((byte) 1, byte.class, (byte) 1);
assertConverts('o', char.class, 'o');
assertConverts((short) 1, short.class, (short) 1);
@@ -91,6 +95,7 @@ void isAwareOfWideningConversions() {
@Test
void convertsStringsToPrimitiveTypes() {
assertConverts("true", boolean.class, true);
+ assertConverts("false", boolean.class, false);
assertConverts("o", char.class, 'o');
assertConverts("1", byte.class, (byte) 1);
assertConverts("1_0", byte.class, (byte) 10);
@@ -106,19 +111,75 @@ void convertsStringsToPrimitiveTypes() {
assertConverts("42.2_3", double.class, 42.23);
}
+ @Test
+ void convertsStringsToPrimitiveWrapperTypes() {
+ assertConverts("true", Boolean.class, true);
+ assertConverts("false", Boolean.class, false);
+ assertConverts("o", Character.class, 'o');
+ assertConverts("1", Byte.class, (byte) 1);
+ assertConverts("1_0", Byte.class, (byte) 10);
+ assertConverts("1", Short.class, (short) 1);
+ assertConverts("1_2", Short.class, (short) 12);
+ assertConverts("42", Integer.class, 42);
+ assertConverts("700_050_000", Integer.class, 700_050_000);
+ assertConverts("42", Long.class, 42L);
+ assertConverts("4_2", Long.class, 42L);
+ assertConverts("42.23", Float.class, 42.23f);
+ assertConverts("42.2_3", Float.class, 42.23f);
+ assertConverts("42.23", Double.class, 42.23);
+ assertConverts("42.2_3", Double.class, 42.23);
+ }
+
+ @ParameterizedTest(name = "[{index}] {0}")
+ @ValueSource(classes = { char.class, boolean.class, short.class, byte.class, int.class, long.class, float.class,
+ double.class })
+ void throwsExceptionForNullToPrimitiveTypeConversion(Class> type) {
+ assertThatExceptionOfType(ArgumentConversionException.class) //
+ .isThrownBy(() -> convert(null, type)) //
+ .withMessage("Cannot convert null to primitive value of type " + type.getCanonicalName());
+ }
+
+ @ParameterizedTest(name = "[{index}] {0}")
+ @ValueSource(classes = { Boolean.class, Character.class, Short.class, Byte.class, Integer.class, Long.class,
+ Float.class, Double.class })
+ void throwsExceptionWhenConvertingTheWordNullToPrimitiveWrapperType(Class> type) {
+ assertThatExceptionOfType(ArgumentConversionException.class) //
+ .isThrownBy(() -> convert("null", type)) //
+ .withMessage("Failed to convert String \"null\" to type " + type.getCanonicalName());
+ assertThatExceptionOfType(ArgumentConversionException.class) //
+ .isThrownBy(() -> convert("NULL", type)) //
+ .withMessage("Failed to convert String \"NULL\" to type " + type.getCanonicalName());
+ }
+
@Test
void throwsExceptionOnInvalidStringForPrimitiveTypes() {
assertThatExceptionOfType(ArgumentConversionException.class) //
.isThrownBy(() -> convert("ab", char.class)) //
.withMessage("Failed to convert String \"ab\" to type char") //
.havingCause() //
+ .havingCause() //
.withMessage("String must have length of 1: ab");
assertThatExceptionOfType(ArgumentConversionException.class) //
.isThrownBy(() -> convert("tru", boolean.class)) //
.withMessage("Failed to convert String \"tru\" to type boolean") //
.havingCause() //
+ .havingCause() //
.withMessage("String must be 'true' or 'false' (ignoring case): tru");
+
+ assertThatExceptionOfType(ArgumentConversionException.class) //
+ .isThrownBy(() -> convert("null", boolean.class)) //
+ .withMessage("Failed to convert String \"null\" to type boolean") //
+ .havingCause() //
+ .havingCause() //
+ .withMessage("String must be 'true' or 'false' (ignoring case): null");
+
+ assertThatExceptionOfType(ArgumentConversionException.class) //
+ .isThrownBy(() -> convert("NULL", boolean.class)) //
+ .withMessage("Failed to convert String \"NULL\" to type boolean") //
+ .havingCause() //
+ .havingCause() //
+ .withMessage("String must be 'true' or 'false' (ignoring case): NULL");
}
@Test
@@ -281,6 +342,7 @@ void convertsStringToCurrency() {
}
@Test
+ @SuppressWarnings("deprecation")
void convertsStringToLocale() {
assertConverts("en", Locale.class, Locale.ENGLISH);
assertConverts("en_us", Locale.class, new Locale(Locale.US.toString()));
diff --git a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/CsvArgumentsProviderTests.java b/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/CsvArgumentsProviderTests.java
index 767768ce6bac..e932451fa685 100644
--- a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/CsvArgumentsProviderTests.java
+++ b/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/CsvArgumentsProviderTests.java
@@ -248,11 +248,12 @@ void customEmptyValueAndDefaultNullValue() {
@Test
void customNullValues() {
- var annotation = csvSource().nullValues("N/A", "NIL").lines("apple, , NIL, '', N/A, banana").build();
+ var annotation = csvSource().nullValues("N/A", "NIL", "null")//
+ .lines("apple, , NIL, '', N/A, banana, null").build();
var arguments = provideArguments(annotation);
- assertThat(arguments).containsExactly(array("apple", null, null, "", null, "banana"));
+ assertThat(arguments).containsExactly(array("apple", null, null, "", null, "banana", null));
}
@Test
diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/ConversionException.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/ConversionException.java
new file mode 100644
index 000000000000..439334635776
--- /dev/null
+++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/ConversionException.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2015-2023 the original author or authors.
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v2.0 which
+ * accompanies this distribution and is available at
+ *
+ * https://www.eclipse.org/legal/epl-v20.html
+ */
+
+package org.junit.platform.commons.support.conversion;
+
+import static org.apiguardian.api.API.Status.EXPERIMENTAL;
+
+import org.apiguardian.api.API;
+import org.junit.platform.commons.JUnitException;
+
+/**
+ * {@code ConversionException} is an exception that can occur when an
+ * object is converted to another object.
+ *
+ * @since 1.11
+ */
+@API(status = EXPERIMENTAL, since = "1.11")
+public class ConversionException extends JUnitException {
+
+ private static final long serialVersionUID = 1L;
+
+ public ConversionException(String message) {
+ super(message);
+ }
+
+ public ConversionException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+}
diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/FallbackStringToObjectConverter.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/FallbackStringToObjectConverter.java
similarity index 98%
rename from junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/FallbackStringToObjectConverter.java
rename to junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/FallbackStringToObjectConverter.java
index 79f0028e58a6..06dc29153e31 100644
--- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/FallbackStringToObjectConverter.java
+++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/FallbackStringToObjectConverter.java
@@ -8,7 +8,7 @@
* https://www.eclipse.org/legal/epl-v20.html
*/
-package org.junit.jupiter.params.converter;
+package org.junit.platform.commons.support.conversion;
import static org.junit.platform.commons.util.ReflectionUtils.HierarchyTraversalMode.BOTTOM_UP;
import static org.junit.platform.commons.util.ReflectionUtils.findConstructors;
@@ -48,8 +48,8 @@
* If neither a single factory method nor a single constructor is found, this
* converter acts as a no-op.
*
- * @since 5.1
- * @see DefaultArgumentConverter
+ * @since 1.11
+ * @see StringConversionSupport
*/
class FallbackStringToObjectConverter implements StringToObjectConverter {
diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringConversionSupport.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringConversionSupport.java
new file mode 100644
index 000000000000..2ac35dc67461
--- /dev/null
+++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringConversionSupport.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2015-2023 the original author or authors.
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v2.0 which
+ * accompanies this distribution and is available at
+ *
+ * https://www.eclipse.org/legal/epl-v20.html
+ */
+
+package org.junit.platform.commons.support.conversion;
+
+import static java.util.Arrays.asList;
+import static java.util.Collections.unmodifiableList;
+import static org.apiguardian.api.API.Status.EXPERIMENTAL;
+import static org.junit.platform.commons.util.ReflectionUtils.getWrapperType;
+
+import java.io.File;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.net.URI;
+import java.net.URL;
+import java.util.Currency;
+import java.util.List;
+import java.util.Locale;
+import java.util.Optional;
+import java.util.UUID;
+
+import org.apiguardian.api.API;
+import org.junit.platform.commons.util.ClassLoaderUtils;
+
+/**
+ * {@code StringConversionSupport} is able to convert from strings to a number
+ * of primitive types and their corresponding wrapper types (Byte, Short,
+ * Integer, Long, Float, and Double), date and time types from the
+ * {@code java.time} package, and some additional common Java types such as
+ * {@link File}, {@link BigDecimal}, {@link BigInteger}, {@link Currency},
+ * {@link Locale}, {@link URI}, {@link URL}, {@link UUID}, etc.
+ *
+ *
If the target type is {@code String} the source {@code String} will not
+ * be modified.
+ *
+ * @since 1.11
+ */
+@API(status = EXPERIMENTAL, since = "1.11")
+public final class StringConversionSupport {
+
+ private static final List stringToObjectConverters = unmodifiableList(asList( //
+ new StringToBooleanConverter(), //
+ new StringToCharacterConverter(), //
+ new StringToNumberConverter(), //
+ new StringToClassConverter(), //
+ new StringToEnumConverter(), //
+ new StringToJavaTimeConverter(), //
+ new StringToCommonJavaTypesConverter(), //
+ new FallbackStringToObjectConverter() //
+ ));
+
+ private StringConversionSupport() {
+ /* no-op */
+ }
+
+ /**
+ * Convert a {@code String} into an object of the supplied type.
+ *
+ *
Some underlying converters can require a {@code ClassLoader}.
+ * If none is provided, the default one given by
+ * {@link ClassLoaderUtils#getDefaultClassLoader()} will be used.
+ *
+ * @param source the source {@code String} to convert; may be {@code null}
+ * @param targetType the target type the source should be converted into;
+ * never {@code null}
+ * @param classLoader the {@code ClassLoader} to use; may be {@code null}
+ * @param the type of the target
+ * @return the converted object; may be {@code null} but only if the target
+ * type is a reference type
+ *
+ * @since 1.11
+ */
+ @SuppressWarnings("unchecked")
+ public static T convert(String source, Class targetType, ClassLoader classLoader) {
+ if (source == null) {
+ if (targetType.isPrimitive()) {
+ throw new ConversionException(
+ "Cannot convert null to primitive value of type " + targetType.getTypeName());
+ }
+ return null;
+ }
+
+ if (String.class.equals(targetType)) {
+ return (T) source;
+ }
+
+ Class> targetTypeToUse = toWrapperType(targetType);
+ Optional converter = stringToObjectConverters.stream().filter(
+ candidate -> candidate.canConvert(targetTypeToUse)).findFirst();
+ if (converter.isPresent()) {
+ try {
+ ClassLoader classLoaderToUse = classLoader != null ? classLoader
+ : ClassLoaderUtils.getDefaultClassLoader();
+ return (T) converter.get().convert(source, targetTypeToUse, classLoaderToUse);
+ }
+ catch (Exception ex) {
+ if (ex instanceof ConversionException) {
+ // simply rethrow it
+ throw (ConversionException) ex;
+ }
+ // else
+ throw new ConversionException(
+ String.format("Failed to convert String \"%s\" to type %s", source, targetType.getTypeName()), ex);
+ }
+ }
+
+ throw new ConversionException(
+ "No built-in converter for source type java.lang.String and target type " + targetType.getTypeName());
+ }
+
+ private static Class> toWrapperType(Class> targetType) {
+ Class> wrapperType = getWrapperType(targetType);
+ return wrapperType != null ? wrapperType : targetType;
+ }
+
+}
diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/StringToBooleanConverter.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToBooleanConverter.java
similarity index 93%
rename from junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/StringToBooleanConverter.java
rename to junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToBooleanConverter.java
index 9d911825809b..2bde9ac323c2 100644
--- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/StringToBooleanConverter.java
+++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToBooleanConverter.java
@@ -8,7 +8,7 @@
* https://www.eclipse.org/legal/epl-v20.html
*/
-package org.junit.jupiter.params.converter;
+package org.junit.platform.commons.support.conversion;
import org.junit.platform.commons.util.Preconditions;
diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/StringToCharacterConverter.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToCharacterConverter.java
similarity index 93%
rename from junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/StringToCharacterConverter.java
rename to junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToCharacterConverter.java
index b3849051e45d..925acdfe62d5 100644
--- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/StringToCharacterConverter.java
+++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToCharacterConverter.java
@@ -8,7 +8,7 @@
* https://www.eclipse.org/legal/epl-v20.html
*/
-package org.junit.jupiter.params.converter;
+package org.junit.platform.commons.support.conversion;
import org.junit.platform.commons.util.Preconditions;
diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/StringToClassConverter.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToClassConverter.java
similarity index 90%
rename from junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/StringToClassConverter.java
rename to junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToClassConverter.java
index debefc342ed0..df2b0164ad5c 100644
--- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/StringToClassConverter.java
+++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToClassConverter.java
@@ -8,7 +8,7 @@
* https://www.eclipse.org/legal/epl-v20.html
*/
-package org.junit.jupiter.params.converter;
+package org.junit.platform.commons.support.conversion;
import org.junit.platform.commons.util.ReflectionUtils;
@@ -28,7 +28,7 @@ public Object convert(String source, Class> targetType) throws Exception {
public Object convert(String className, Class> targetType, ClassLoader classLoader) throws Exception {
// @formatter:off
return ReflectionUtils.tryToLoadClass(className, classLoader)
- .getOrThrow(cause -> new ArgumentConversionException(
+ .getOrThrow(cause -> new ConversionException(
"Failed to convert String \"" + className + "\" to type java.lang.Class", cause));
// @formatter:on
}
diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/StringToCommonJavaTypesConverter.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToCommonJavaTypesConverter.java
similarity index 92%
rename from junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/StringToCommonJavaTypesConverter.java
rename to junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToCommonJavaTypesConverter.java
index b9ac124aded8..2988714318e3 100644
--- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/StringToCommonJavaTypesConverter.java
+++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToCommonJavaTypesConverter.java
@@ -8,7 +8,7 @@
* https://www.eclipse.org/legal/epl-v20.html
*/
-package org.junit.jupiter.params.converter;
+package org.junit.platform.commons.support.conversion;
import static java.util.Collections.unmodifiableMap;
@@ -63,7 +63,7 @@ private static URL toURL(String url) {
return URI.create(url).toURL();
}
catch (MalformedURLException ex) {
- throw new ArgumentConversionException("Failed to convert String \"" + url + "\" to type java.net.URL", ex);
+ throw new ConversionException("Failed to convert String \"" + url + "\" to type java.net.URL", ex);
}
}
diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/StringToEnumConverter.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToEnumConverter.java
similarity index 92%
rename from junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/StringToEnumConverter.java
rename to junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToEnumConverter.java
index f20d1487d5ee..48c07fa59eb7 100644
--- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/StringToEnumConverter.java
+++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToEnumConverter.java
@@ -8,7 +8,7 @@
* https://www.eclipse.org/legal/epl-v20.html
*/
-package org.junit.jupiter.params.converter;
+package org.junit.platform.commons.support.conversion;
class StringToEnumConverter implements StringToObjectConverter {
diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/StringToJavaTimeConverter.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToJavaTimeConverter.java
similarity index 97%
rename from junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/StringToJavaTimeConverter.java
rename to junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToJavaTimeConverter.java
index b1851ecc323d..6ecbf84b25e5 100644
--- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/StringToJavaTimeConverter.java
+++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToJavaTimeConverter.java
@@ -8,7 +8,7 @@
* https://www.eclipse.org/legal/epl-v20.html
*/
-package org.junit.jupiter.params.converter;
+package org.junit.platform.commons.support.conversion;
import static java.util.Collections.unmodifiableMap;
diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/StringToNumberConverter.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToNumberConverter.java
similarity index 96%
rename from junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/StringToNumberConverter.java
rename to junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToNumberConverter.java
index ca278019a488..b8cd6e7d3e4f 100644
--- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/StringToNumberConverter.java
+++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToNumberConverter.java
@@ -8,7 +8,7 @@
* https://www.eclipse.org/legal/epl-v20.html
*/
-package org.junit.jupiter.params.converter;
+package org.junit.platform.commons.support.conversion;
import static java.util.Collections.unmodifiableMap;
diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/StringToObjectConverter.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToObjectConverter.java
similarity index 96%
rename from junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/StringToObjectConverter.java
rename to junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToObjectConverter.java
index 2a60202dba81..243bfeec4afc 100644
--- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/StringToObjectConverter.java
+++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToObjectConverter.java
@@ -8,7 +8,7 @@
* https://www.eclipse.org/legal/epl-v20.html
*/
-package org.junit.jupiter.params.converter;
+package org.junit.platform.commons.support.conversion;
/**
* Internal API for converting arguments of type {@link String} to a specified
diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/package-info.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/package-info.java
new file mode 100644
index 000000000000..e51977179941
--- /dev/null
+++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/package-info.java
@@ -0,0 +1,5 @@
+/**
+ * Maintained conversion APIs provided by the JUnit Platform.
+ */
+
+package org.junit.platform.commons.support.conversion;
diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/CloseablePath.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/CloseablePath.java
index e6bb3ddd27df..3f7af1ddd407 100644
--- a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/CloseablePath.java
+++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/CloseablePath.java
@@ -23,6 +23,7 @@
import java.nio.file.Paths;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
@@ -32,7 +33,7 @@
final class CloseablePath implements Closeable {
private static final String FILE_URI_SCHEME = "file";
- private static final String JAR_URI_SCHEME = "jar";
+ static final String JAR_URI_SCHEME = "jar";
private static final String JAR_FILE_EXTENSION = ".jar";
private static final String JAR_URI_SEPARATOR = "!";
@@ -41,26 +42,34 @@ final class CloseablePath implements Closeable {
private static final ConcurrentMap MANAGED_FILE_SYSTEMS = new ConcurrentHashMap<>();
+ private final AtomicBoolean closed = new AtomicBoolean();
+
private final Path path;
private final Closeable delegate;
static CloseablePath create(URI uri) throws URISyntaxException {
+ return create(uri, it -> FileSystems.newFileSystem(it, emptyMap()));
+ }
+
+ static CloseablePath create(URI uri, FileSystemProvider fileSystemProvider) throws URISyntaxException {
if (JAR_URI_SCHEME.equals(uri.getScheme())) {
String[] parts = uri.toString().split(JAR_URI_SEPARATOR);
String jarUri = parts[0];
String jarEntry = parts[1];
- return createForJarFileSystem(new URI(jarUri), fileSystem -> fileSystem.getPath(jarEntry));
+ return createForJarFileSystem(new URI(jarUri), fileSystem -> fileSystem.getPath(jarEntry),
+ fileSystemProvider);
}
- if (uri.getScheme().equals(FILE_URI_SCHEME) && uri.getPath().endsWith(JAR_FILE_EXTENSION)) {
+ if (FILE_URI_SCHEME.equals(uri.getScheme()) && uri.getPath().endsWith(JAR_FILE_EXTENSION)) {
return createForJarFileSystem(new URI(JAR_URI_SCHEME + ':' + uri),
- fileSystem -> fileSystem.getRootDirectories().iterator().next());
+ fileSystem -> fileSystem.getRootDirectories().iterator().next(), fileSystemProvider);
}
return new CloseablePath(Paths.get(uri), NULL_CLOSEABLE);
}
- private static CloseablePath createForJarFileSystem(URI jarUri, Function pathProvider) {
+ private static CloseablePath createForJarFileSystem(URI jarUri, Function pathProvider,
+ FileSystemProvider fileSystemProvider) {
ManagedFileSystem managedFileSystem = MANAGED_FILE_SYSTEMS.compute(jarUri,
- (__, oldValue) -> oldValue == null ? new ManagedFileSystem(jarUri) : oldValue.retain());
+ (__, oldValue) -> oldValue == null ? new ManagedFileSystem(jarUri, fileSystemProvider) : oldValue.retain());
Path path = pathProvider.apply(managedFileSystem.fileSystem);
return new CloseablePath(path,
() -> MANAGED_FILE_SYSTEMS.compute(jarUri, (__, ___) -> managedFileSystem.release()));
@@ -77,7 +86,9 @@ public Path getPath() {
@Override
public void close() throws IOException {
- delegate.close();
+ if (closed.compareAndSet(false, true)) {
+ delegate.close();
+ }
}
private static class ManagedFileSystem {
@@ -86,10 +97,10 @@ private static class ManagedFileSystem {
private final FileSystem fileSystem;
private final URI jarUri;
- ManagedFileSystem(URI jarUri) {
+ ManagedFileSystem(URI jarUri, FileSystemProvider fileSystemProvider) {
this.jarUri = jarUri;
try {
- fileSystem = FileSystems.newFileSystem(jarUri, emptyMap());
+ fileSystem = fileSystemProvider.newFileSystem(jarUri);
}
catch (IOException e) {
throw new UncheckedIOException("Failed to create file system for " + jarUri, e);
@@ -118,4 +129,8 @@ private void close() {
}
}
}
+
+ interface FileSystemProvider {
+ FileSystem newFileSystem(URI uri) throws IOException;
+ }
}
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 816b7fadd104..b7e45171dbaf 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
@@ -927,57 +927,6 @@ else if (methodPart.endsWith(")")) {
return new String[] { className, methodName, methodParameters };
}
- /**
- * Get the outermost instance of the required type, searching recursively
- * through enclosing instances.
- *
- *
If the supplied inner object is of the required type, it will be
- * returned.
- *
- * @param inner the inner object from which to begin the search; never {@code null}
- * @param requiredType the required type of the outermost instance; never {@code null}
- * @return an {@code Optional} containing the outermost instance; never {@code null}
- * but potentially empty
- * @deprecated Please discontinue use of this method since it relies on internal
- * implementation details of the JDK that may not work in the future.
- */
- @API(status = DEPRECATED, since = "1.4")
- @Deprecated
- public static Optional