From 02bba2d1d8b1e1f97a034720ed699f0b6aebc34a Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Sun, 23 Jul 2023 15:04:17 +0200 Subject: [PATCH 01/45] Back to snapshots for further development --- gradle.properties | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gradle.properties b/gradle.properties index 8bbcba21a037..c1f5d184ea40 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,13 +1,13 @@ group = org.junit -version = 5.10.0 +version = 5.10.1-SNAPSHOT jupiterGroup = org.junit.jupiter platformGroup = org.junit.platform -platformVersion = 1.10.0 +platformVersion = 1.10.1-SNAPSHOT vintageGroup = org.junit.vintage -vintageVersion = 5.10.0 +vintageVersion = 5.10.1-SNAPSHOT defaultBuiltBy = JUnit Team From d6587cfeb0a1a77a29c89a7ba9347d6b417f86b2 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Sun, 23 Jul 2023 15:25:05 +0200 Subject: [PATCH 02/45] Fix createCurrentDocsFolder task --- documentation/documentation.gradle.kts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/documentation/documentation.gradle.kts b/documentation/documentation.gradle.kts index 4cc640563170..fd4877cdd6fc 100644 --- a/documentation/documentation.gradle.kts +++ b/documentation/documentation.gradle.kts @@ -447,11 +447,10 @@ tasks { val createCurrentDocsFolder by registering(Copy::class) { dependsOn(prepareDocsForUploadToGhPages) - outputs.dir("$docsDir/current") onlyIf { replaceCurrentDocs } - from("$docsDir/$docsVersion") - into("$docsDir/current") + from(docsDir.map { it.dir(docsVersion.toString()) }) + into(docsDir.map { it.dir("current") }) } val configureGitAuthor by registering { From 7baad913503f00962813d6f58e563c2256d5cdf8 Mon Sep 17 00:00:00 2001 From: Stefano Cordio Date: Sat, 29 Jul 2023 18:38:23 +0200 Subject: [PATCH 03/45] Fix expected value in assertion Closes #3406 --- .../jupiter/engine/config/DefaultJupiterConfigurationTests.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/config/DefaultJupiterConfigurationTests.java b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/config/DefaultJupiterConfigurationTests.java index 44d05998be02..e739a40039d0 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/config/DefaultJupiterConfigurationTests.java +++ b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/config/DefaultJupiterConfigurationTests.java @@ -122,7 +122,7 @@ void shouldGetDefaultTempDirFactorySupplierWithConfigParamSet() { Supplier supplier = configuration.getDefaultTempDirFactorySupplier(); - assertThat(supplier.get()).isInstanceOf(TempDirFactory.class); + assertThat(supplier.get()).isInstanceOf(CustomFactory.class); } private static class CustomFactory implements TempDirFactory { From dbf861593a4903b6868310a619f03989c1813c76 Mon Sep 17 00:00:00 2001 From: Andrei Rybak Date: Sun, 6 Aug 2023 12:37:15 +0200 Subject: [PATCH 04/45] Fix broken links to Gradle docs about BOM URL: https://docs.gradle.org/current/userguide/managing_transitive_dependencies.html#sec:bom_import returns HTTP error status 404. This page is present in documentation for Gradle 5.6.4 [1], but doesn't exist in documentation of Gradle 6.0 and later versions [2]. Documentation file "userguide/managing_transitive_dependencies.adoc" got renamed to "dep-man/03-controlling-transitive-dependencies/controlling-transitive-dependencies.adoc" in commit [3]. Replace links to this page in JUnit documentation with up-to-date URL: https://docs.gradle.org/current/userguide/platforms.html#sub:bom_import [1] https://docs.gradle.org/5.6.4/userguide/managing_transitive_dependencies.html#sec:bom_import [2] https://docs.gradle.org/6.0/userguide/managing_transitive_dependencies.html#sec:bom_import [3] 0177e18c63e ("Reshuffle dependency management docs", 2019-05-15) https://github.com/gradle/gradle/commit/0177e18c63e8e9a3caaf8487158268318d73844d#diff-7c6cad628253d79a589fdfdee343caf27bc7baf9212f28ba2a8a43a73ced6600 Closes #3414 --- documentation/src/docs/asciidoc/user-guide/appendix.adoc | 2 +- junit-bom/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/documentation/src/docs/asciidoc/user-guide/appendix.adoc b/documentation/src/docs/asciidoc/user-guide/appendix.adoc index d92194fd2b79..a5d8290b5d75 100644 --- a/documentation/src/docs/asciidoc/user-guide/appendix.adoc +++ b/documentation/src/docs/asciidoc/user-guide/appendix.adoc @@ -107,7 +107,7 @@ artifacts are deployed to Sonatype's {snapshot-repo}[snapshots repository] under The _Bill of Materials_ POM provided under the following Maven coordinates can be used to ease dependency management when referencing multiple of the above artifacts using https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html#Importing_Dependencies[Maven] -or https://docs.gradle.org/current/userguide/managing_transitive_dependencies.html#sec:bom_import[Gradle]. +or https://docs.gradle.org/current/userguide/platforms.html#sub:bom_import[Gradle]. * *Group ID*: `org.junit` * *Artifact ID*: `junit-bom` diff --git a/junit-bom/README.md b/junit-bom/README.md index 907a361c665a..e3c52f7f76d1 100644 --- a/junit-bom/README.md +++ b/junit-bom/README.md @@ -4,5 +4,5 @@ This module provides a Bill of Materials POM to ease dependency management using or [Gradle]. Please refer to the [User Guide] for details. [Maven]: https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html#Importing_Dependencies -[Gradle]: https://docs.gradle.org/current/userguide/managing_transitive_dependencies.html#sec:bom_import +[Gradle]: https://docs.gradle.org/current/userguide/platforms.html#sub:bom_import [User Guide]: https://junit.org/junit5/docs/current/user-guide/#dependency-metadata-junit-bom From 3ce6706bd1d39451e9670cf7bf720ac799fdd491 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Sat, 12 Aug 2023 12:45:59 +0200 Subject: [PATCH 05/45] Improve Javadoc for assertTimeoutPreemptively regarding thread interrupt Closes #3424 --- .../org/junit/jupiter/api/Assertions.java | 91 ++++++++----------- 1 file changed, 37 insertions(+), 54 deletions(-) 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 4be1561689dc..670f1373fb8d 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 @@ -59,22 +59,26 @@ *

Preemptive Timeouts

* *

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}. * From beb4f04ad64944979e646309956f8b9277e81bbd Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Fri, 25 Aug 2023 10:54:46 +0200 Subject: [PATCH 06/45] Fix implementation of RandomNumberExtension in the User Guide Closes #3433 --- .../src/test/java/example/extensions/RandomNumberExtension.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/src/test/java/example/extensions/RandomNumberExtension.java b/documentation/src/test/java/example/extensions/RandomNumberExtension.java index 3bbece08e206..b2a900dc5ea5 100644 --- a/documentation/src/test/java/example/extensions/RandomNumberExtension.java +++ b/documentation/src/test/java/example/extensions/RandomNumberExtension.java @@ -86,7 +86,7 @@ private void injectFields(Class testClass, Object testInstance, } private static boolean isInteger(Class type) { - return int.class.isAssignableFrom(type); + return type == Integer.class || type == int.class; } } From 3b7c2fdd4f6d105d094b13e7f8951ebe9b9423ef Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Tue, 5 Sep 2023 13:50:55 +0200 Subject: [PATCH 07/45] Create empty release notes for 5.10.1 from template --- .../docs/asciidoc/release-notes/index.adoc | 2 + .../release-notes/release-notes-5.10.1.adoc | 58 +++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 documentation/src/docs/asciidoc/release-notes/release-notes-5.10.1.adoc diff --git a/documentation/src/docs/asciidoc/release-notes/index.adoc b/documentation/src/docs/asciidoc/release-notes/index.adoc index ec787091b41e..f6c89ae8dc6a 100644 --- a/documentation/src/docs/asciidoc/release-notes/index.adoc +++ b/documentation/src/docs/asciidoc/release-notes/index.adoc @@ -16,6 +16,8 @@ authors as well as build tool and IDE vendors. include::{includedir}/link-attributes.adoc[] +include::{basedir}/release-notes-5.10.1.adoc[] + include::{basedir}/release-notes-5.10.0.adoc[] include::{basedir}/release-notes-5.9.3.adoc[] diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.10.1.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.10.1.adoc new file mode 100644 index 000000000000..f9e994b0bd79 --- /dev/null +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.10.1.adoc @@ -0,0 +1,58 @@ +[[release-notes-5.10.1]] +== 5.10.1 + +*Date of Release:* ❓ + +*Scope:* ❓ + +For a complete list of all _closed_ issues and pull requests for this release, consult the +link:{junit5-repo}+/milestone/72?closed=1+[5.10.1] milestone page in the +JUnit repository on GitHub. + + +[[release-notes-5.10.1-junit-platform]] +=== JUnit Platform + +==== Bug Fixes + +* ❓ + +==== Deprecations and Breaking Changes + +* ❓ + +==== New Features and Improvements + +* ❓ + + +[[release-notes-5.10.1-junit-jupiter]] +=== JUnit Jupiter + +==== Bug Fixes + +* ❓ + +==== Deprecations and Breaking Changes + +* ❓ + +==== New Features and Improvements + +* ❓ + + +[[release-notes-5.10.1-junit-vintage]] +=== JUnit Vintage + +==== Bug Fixes + +* ❓ + +==== Deprecations and Breaking Changes + +* ❓ + +==== New Features and Improvements + +* ❓ From b8bf2956b79836a1f5e832c7cca95df11eb45a6d Mon Sep 17 00:00:00 2001 From: Pavlo Shevchenko Date: Tue, 5 Sep 2023 15:26:27 +0200 Subject: [PATCH 08/45] Fix reporting of ignored JUnit 3 test classes (#3427) Prior to this commit, `@Ignore`-annotated JUnit 3 test classes where reported as started and then as skipped due to their `Description` not including class-level annotations in some case. Co-authored-by: Marc Philipp --- .../release-notes/release-notes-5.10.1.adoc | 2 +- .../descriptor/RunnerTestDescriptor.java | 8 +++++- .../discovery/ClassSelectorResolver.java | 5 ++-- ...fensiveAllDefaultPossibilitiesBuilder.java | 6 +++- .../engine/execution/RunListenerAdapter.java | 25 +++++++++++++---- .../VintageTestEngineExecutionTests.java | 24 ++++++++++++++++ .../engine/execution/TestRunTests.java | 6 ++-- .../samples/junit3/IgnoredJUnit3TestCase.java | 28 +++++++++++++++++++ .../JUnit4SuiteWithIgnoredJUnit3TestCase.java | 20 +++++++++++++ 9 files changed, 111 insertions(+), 13 deletions(-) create mode 100644 junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/IgnoredJUnit3TestCase.java create mode 100644 junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/JUnit4SuiteWithIgnoredJUnit3TestCase.java diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.10.1.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.10.1.adoc index f9e994b0bd79..f3186db2b66a 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-5.10.1.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.10.1.adoc @@ -47,7 +47,7 @@ JUnit repository on GitHub. ==== Bug Fixes -* ❓ +* Fix reporting of JUnit 3 test classes with `@Ignored` annotation ==== Deprecations and Breaking Changes diff --git a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/RunnerTestDescriptor.java b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/RunnerTestDescriptor.java index 0ad3c5f9b6a0..03ae08dd0a6c 100644 --- a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/RunnerTestDescriptor.java +++ b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/RunnerTestDescriptor.java @@ -43,12 +43,14 @@ public class RunnerTestDescriptor extends VintageTestDescriptor { private final Set rejectedExclusions = new HashSet<>(); private Runner runner; + private final boolean ignored; private boolean wasFiltered; private List filters = new ArrayList<>(); - public RunnerTestDescriptor(UniqueId uniqueId, Class testClass, Runner runner) { + public RunnerTestDescriptor(UniqueId uniqueId, Class testClass, Runner runner, boolean ignored) { super(uniqueId, runner.getDescription(), testClass.getSimpleName(), ClassSource.from(testClass)); this.runner = runner; + this.ignored = ignored; } @Override @@ -155,6 +157,10 @@ private Runner getRunnerToReport() { return (runner instanceof RunnerDecorator) ? ((RunnerDecorator) runner).getDecoratedRunner() : runner; } + public boolean isIgnored() { + return ignored; + } + private static class ExcludeDescriptionFilter extends Filter { private final Description description; diff --git a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/ClassSelectorResolver.java b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/ClassSelectorResolver.java index a2d1e7d4c80c..14445f155b4e 100644 --- a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/ClassSelectorResolver.java +++ b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/ClassSelectorResolver.java @@ -26,7 +26,6 @@ import org.junit.platform.engine.discovery.UniqueIdSelector; import org.junit.platform.engine.support.discovery.SelectorResolver; import org.junit.runner.Runner; -import org.junit.runners.model.RunnerBuilder; import org.junit.vintage.engine.descriptor.RunnerTestDescriptor; /** @@ -34,7 +33,7 @@ */ class ClassSelectorResolver implements SelectorResolver { - private static final RunnerBuilder RUNNER_BUILDER = new DefensiveAllDefaultPossibilitiesBuilder(); + private static final DefensiveAllDefaultPossibilitiesBuilder RUNNER_BUILDER = new DefensiveAllDefaultPossibilitiesBuilder(); private final ClassFilter classFilter; @@ -76,7 +75,7 @@ private Resolution resolveTestClass(Class testClass, Context context) { private RunnerTestDescriptor createRunnerTestDescriptor(TestDescriptor parent, Class testClass, Runner runner) { UniqueId uniqueId = parent.getUniqueId().append(SEGMENT_TYPE_RUNNER, testClass.getName()); - return new RunnerTestDescriptor(uniqueId, testClass, runner); + return new RunnerTestDescriptor(uniqueId, testClass, runner, RUNNER_BUILDER.isIgnored(runner)); } } diff --git a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/DefensiveAllDefaultPossibilitiesBuilder.java b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/DefensiveAllDefaultPossibilitiesBuilder.java index 0da94f689fde..b35dc20077c4 100644 --- a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/DefensiveAllDefaultPossibilitiesBuilder.java +++ b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/DefensiveAllDefaultPossibilitiesBuilder.java @@ -64,6 +64,10 @@ public Runner runnerForClass(Class testClass) throws Throwable { return runner; } + boolean isIgnored(Runner runner) { + return runner instanceof IgnoredClassRunner || runner instanceof IgnoringRunnerDecorator; + } + /** * Instead of checking for the {@link Ignore} annotation and returning an * {@link IgnoredClassRunner} from {@link IgnoredBuilder}, we've let the @@ -72,7 +76,7 @@ public Runner runnerForClass(Class testClass) throws Throwable { * override its runtime behavior (i.e. skip execution) but return its * regular {@link org.junit.runner.Description}. */ - private Runner decorateIgnoredTestClass(Runner runner) { + private IgnoringRunnerDecorator decorateIgnoredTestClass(Runner runner) { if (runner instanceof Filterable) { return new FilterableIgnoringRunnerDecorator(runner); } diff --git a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/RunListenerAdapter.java b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/RunListenerAdapter.java index 9cefb8faee30..58144877d04a 100644 --- a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/RunListenerAdapter.java +++ b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/RunListenerAdapter.java @@ -20,6 +20,7 @@ import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.support.descriptor.ClassSource; import org.junit.runner.Description; import org.junit.runner.Result; import org.junit.runner.notification.Failure; @@ -49,7 +50,7 @@ class RunListenerAdapter extends RunListener { @Override public void testRunStarted(Description description) { - if (description.isSuite() && description.getAnnotation(Ignore.class) == null) { + if (description.isSuite() && !testRun.getRunnerTestDescriptor().isIgnored()) { fireExecutionStarted(testRun.getRunnerTestDescriptor(), EventType.REPORTED); } } @@ -65,7 +66,9 @@ public void testSuiteStarted(Description description) { @Override public void testIgnored(Description description) { - testIgnored(lookupOrRegisterNextTestDescriptor(description), determineReasonForIgnoredTest(description)); + TestDescriptor testDescriptor = lookupOrRegisterNextTestDescriptor(description); + String reason = determineReasonForIgnoredTest(testDescriptor, description).orElse(""); + testIgnored(testDescriptor, reason); } @Override @@ -176,9 +179,21 @@ private void testIgnored(TestDescriptor testDescriptor, String reason) { fireExecutionSkipped(testDescriptor, reason); } - private String determineReasonForIgnoredTest(Description description) { - Ignore ignoreAnnotation = description.getAnnotation(Ignore.class); - return Optional.ofNullable(ignoreAnnotation).map(Ignore::value).orElse(""); + private Optional determineReasonForIgnoredTest(TestDescriptor testDescriptor, Description description) { + Optional reason = getReason(description.getAnnotation(Ignore.class)); + if (reason.isPresent()) { + return reason; + } + // Workaround for some runners (e.g. JUnit38ClassRunner) don't include the @Ignore annotation + // in the description, so we read it from the test class directly + return testDescriptor.getSource() // + .filter(ClassSource.class::isInstance) // + .map(source -> ((ClassSource) source).getJavaClass()) // + .flatMap(testClass -> getReason(testClass.getAnnotation(Ignore.class))); + } + + private static Optional getReason(Ignore annotation) { + return Optional.ofNullable(annotation).map(Ignore::value); } private void dynamicTestRegistered(TestDescriptor testDescriptor) { diff --git a/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineExecutionTests.java b/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineExecutionTests.java index 66af815aff99..cd33b52e2269 100644 --- a/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineExecutionTests.java +++ b/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineExecutionTests.java @@ -10,6 +10,7 @@ package org.junit.vintage.engine; +import static java.util.function.Predicate.isEqual; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assumptions.assumeTrue; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; @@ -56,8 +57,10 @@ import org.junit.runner.notification.RunNotifier; import org.junit.runners.Suite; import org.junit.runners.Suite.SuiteClasses; +import org.junit.vintage.engine.samples.junit3.IgnoredJUnit3TestCase; import org.junit.vintage.engine.samples.junit3.JUnit3ParallelSuiteWithSubsuites; import org.junit.vintage.engine.samples.junit3.JUnit3SuiteWithSubsuites; +import org.junit.vintage.engine.samples.junit3.JUnit4SuiteWithIgnoredJUnit3TestCase; import org.junit.vintage.engine.samples.junit3.PlainJUnit3TestCaseWithSingleTestWhichFails; import org.junit.vintage.engine.samples.junit4.CompletelyDynamicTestCase; import org.junit.vintage.engine.samples.junit4.EmptyIgnoredTestCase; @@ -896,6 +899,27 @@ void executesRegularSpockFeatureMethod() { event(engine(), finishedSuccessfully())); } + @Test + void executesIgnoredJUnit3TestCase() { + var suiteClass = IgnoredJUnit3TestCase.class; + execute(suiteClass).allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(suiteClass), skippedWithReason(isEqual("testing"))), // + event(engine(), finishedSuccessfully())); + } + + @Test + void executesJUnit4SuiteWithIgnoredJUnit3TestCase() { + var suiteClass = JUnit4SuiteWithIgnoredJUnit3TestCase.class; + var testClass = IgnoredJUnit3TestCase.class; + execute(suiteClass).allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(suiteClass), started()), // + event(container(testClass), skippedWithReason(isEqual("testing"))), // + event(container(suiteClass), finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); + } + private static EngineExecutionResults execute(Class testClass) { return execute(request(testClass)); } diff --git a/junit-vintage-engine/src/test/java/org/junit/vintage/engine/execution/TestRunTests.java b/junit-vintage-engine/src/test/java/org/junit/vintage/engine/execution/TestRunTests.java index 69272fd511db..918e32dffa96 100644 --- a/junit-vintage-engine/src/test/java/org/junit/vintage/engine/execution/TestRunTests.java +++ b/junit-vintage-engine/src/test/java/org/junit/vintage/engine/execution/TestRunTests.java @@ -32,7 +32,8 @@ class TestRunTests { void returnsEmptyOptionalForUnknownDescriptions() throws Exception { Class testClass = PlainJUnit4TestCaseWithSingleTestWhichFails.class; var runnerId = engineId().append(SEGMENT_TYPE_RUNNER, testClass.getName()); - var runnerTestDescriptor = new RunnerTestDescriptor(runnerId, testClass, new BlockJUnit4ClassRunner(testClass)); + var runnerTestDescriptor = new RunnerTestDescriptor(runnerId, testClass, new BlockJUnit4ClassRunner(testClass), + false); var unknownDescription = createTestDescription(testClass, "dynamicTest"); var testRun = new TestRun(runnerTestDescriptor); @@ -45,7 +46,8 @@ void returnsEmptyOptionalForUnknownDescriptions() throws Exception { void registersDynamicTestDescriptors() throws Exception { Class testClass = PlainJUnit4TestCaseWithSingleTestWhichFails.class; var runnerId = engineId().append(SEGMENT_TYPE_RUNNER, testClass.getName()); - var runnerTestDescriptor = new RunnerTestDescriptor(runnerId, testClass, new BlockJUnit4ClassRunner(testClass)); + var runnerTestDescriptor = new RunnerTestDescriptor(runnerId, testClass, new BlockJUnit4ClassRunner(testClass), + false); var dynamicTestId = runnerId.append(SEGMENT_TYPE_DYNAMIC, "dynamicTest"); var dynamicDescription = createTestDescription(testClass, "dynamicTest"); var dynamicTestDescriptor = new VintageTestDescriptor(dynamicTestId, dynamicDescription, null); diff --git a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/IgnoredJUnit3TestCase.java b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/IgnoredJUnit3TestCase.java new file mode 100644 index 000000000000..64015bf57d52 --- /dev/null +++ b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/IgnoredJUnit3TestCase.java @@ -0,0 +1,28 @@ +/* + * 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.vintage.engine.samples.junit3; + +import junit.framework.TestCase; + +import org.junit.Assert; +import org.junit.Ignore; + +/** + * @since 4.12 + */ +@Ignore("testing") +public class IgnoredJUnit3TestCase extends TestCase { + + public void test() { + Assert.fail("this test should be ignored"); + } + +} diff --git a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/JUnit4SuiteWithIgnoredJUnit3TestCase.java b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/JUnit4SuiteWithIgnoredJUnit3TestCase.java new file mode 100644 index 000000000000..ba89153ace0c --- /dev/null +++ b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/JUnit4SuiteWithIgnoredJUnit3TestCase.java @@ -0,0 +1,20 @@ +/* + * 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.vintage.engine.samples.junit3; + +import org.junit.runner.RunWith; +import org.junit.runners.Suite; +import org.junit.runners.Suite.SuiteClasses; + +@RunWith(Suite.class) +@SuiteClasses({ IgnoredJUnit3TestCase.class }) +public class JUnit4SuiteWithIgnoredJUnit3TestCase { +} From 40ab3b7aa08f97a7b1f085765432f7d3cc2d9a3b Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Fri, 15 Sep 2023 10:17:56 +0200 Subject: [PATCH 09/45] =?UTF-8?q?Document=20that=20@=E2=81=A0Disabled=20&?= =?UTF-8?q?=20conditional=20annotations=20are=20not=20inherited?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #3462 --- .../asciidoc/user-guide/writing-tests.adoc | 19 ++++++++++++++++++- .../java/org/junit/jupiter/api/Disabled.java | 4 ++++ .../api/condition/DisabledForJreRange.java | 4 ++++ .../jupiter/api/condition/DisabledIf.java | 4 ++++ .../DisabledIfEnvironmentVariable.java | 4 ++++ .../DisabledIfEnvironmentVariables.java | 4 ++++ .../condition/DisabledIfSystemProperties.java | 4 ++++ .../condition/DisabledIfSystemProperty.java | 4 ++++ .../api/condition/DisabledInNativeImage.java | 4 ++++ .../jupiter/api/condition/DisabledOnJre.java | 4 ++++ .../jupiter/api/condition/DisabledOnOs.java | 4 ++++ .../api/condition/EnabledForJreRange.java | 4 ++++ .../jupiter/api/condition/EnabledIf.java | 4 ++++ .../EnabledIfEnvironmentVariable.java | 4 ++++ .../EnabledIfEnvironmentVariables.java | 4 ++++ .../condition/EnabledIfSystemProperties.java | 4 ++++ .../condition/EnabledIfSystemProperty.java | 4 ++++ .../api/condition/EnabledInNativeImage.java | 4 ++++ .../jupiter/api/condition/EnabledOnJre.java | 4 ++++ .../jupiter/api/condition/EnabledOnOs.java | 4 ++++ 20 files changed, 94 insertions(+), 1 deletion(-) diff --git a/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc b/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc index 0a0862b6f2b8..4f8d59925fbf 100644 --- a/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc +++ b/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc @@ -371,12 +371,22 @@ And here's a test class that contains a `@Disabled` test method. include::{testDir}/example/DisabledTestsDemo.java[tags=user_guide] ---- -NOTE: `@Disabled` may be declared without providing a _reason_; however, the JUnit team +[TIP] +==== +`@Disabled` may be declared without providing a _reason_; however, the JUnit team recommends that developers provide a short explanation for why a test class or test method has been disabled. Consequently, the above examples both show the use of a reason -- for example, `@Disabled("Disabled until bug #42 has been resolved")`. Some development teams even require the presence of issue tracking numbers in the _reason_ for automated traceability, etc. +==== + +[NOTE] +==== +`@Disabled` is not `@Inherited`. Consequently, if you wish to disable a class whose +superclass is `@Disabled`, you must redeclare `@Disabled` on the subclass. +==== + [[writing-tests-conditional-execution]] === Conditional Test Execution @@ -406,6 +416,13 @@ example, the `@TestOnMac` annotation in the combine `@Test` and `@EnabledOnOs` in a single, reusable annotation. ==== +[NOTE] +==== +_Conditional_ annotations in JUnit Jupiter are not `@Inherited`. Consequently, if you wish +to apply the same semantics to subclasses, each conditional annotation must be redeclared +on each subclass. +==== + [WARNING] ==== Unless otherwise stated, each of the _conditional_ annotations listed in the following 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..47d5af673cd2 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 @@ -29,6 +29,10 @@ *

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..88e3f8e329fd 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 @@ -29,6 +29,10 @@ *

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..d996b71705d8 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 @@ -28,6 +28,10 @@ *

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..e707c0f74a99 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 @@ -29,6 +29,10 @@ *

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..2087c1ca1de6 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 @@ -29,6 +29,10 @@ *

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. + * *

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} From 4243fc612f5ba158bb612980aa128df9ca3079d9 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Sat, 16 Sep 2023 14:54:24 +0200 Subject: [PATCH 10/45] Fix implementation of RandomNumberExtension in the User Guide With this commit, the RandomNumberExtension example in the User Guide now properly supports non-static field injection. See #3437 Closes #3433 --- .../release-notes/release-notes-5.10.1.adoc | 4 +++- .../docs/asciidoc/user-guide/extensions.adoc | 22 ++++++++++++++----- .../example/extensions/RandomNumberDemo.java | 2 +- .../extensions/RandomNumberExtension.java | 7 +++--- 4 files changed, 25 insertions(+), 10 deletions(-) diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.10.1.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.10.1.adoc index f3186db2b66a..f79ffa8d72d0 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-5.10.1.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.10.1.adoc @@ -31,7 +31,9 @@ JUnit repository on GitHub. ==== Bug Fixes -* ❓ +* The `RandomNumberExtension` example in the + <<../user-guide/index.adoc#extensions-RandomNumberExtension, User Guide>> has been + updated to properly support `Integer` types as well as non-static field injection. ==== Deprecations and Breaking Changes diff --git a/documentation/src/docs/asciidoc/user-guide/extensions.adoc b/documentation/src/docs/asciidoc/user-guide/extensions.adoc index 09ca2ec5e88b..e8a1512a5e40 100644 --- a/documentation/src/docs/asciidoc/user-guide/extensions.adoc +++ b/documentation/src/docs/asciidoc/user-guide/extensions.adoc @@ -99,7 +99,7 @@ public @interface DatabaseAndWebServerExtension { The above examples demonstrate how `@ExtendWith` can be applied at the class level or at the method level; however, for certain use cases it makes sense for an extension to be registered declaratively at the field or parameter level. Consider a -`RandomNumberExtension` that generates random numbers that can be injected into a field or +`RandomNumberExtension` which generates random numbers that can be injected into a field or via a parameter in a constructor, test method, or lifecycle method. If the extension provides a `@Random` annotation that is meta-annotated with `@ExtendWith(RandomNumberExtension.class)` (see listing below), the extension can be used @@ -118,17 +118,29 @@ 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 +`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 +- `BeforeEachCallback`: to support non-static field injection - `ParameterResolver`: to support constructor and method injection +[NOTE] +==== +Ideally, the `RandomNumberExtension` would implement `TestInstancePostProcessor` instead +of `BeforeEachCallback` in order to support non-static field injection immediately after +the test class has been instantiated. + +However, JUnit Jupiter currently does not allow a `TestInstancePostProcessor` to be +registered via `@ExtendWith` on a non-static field (see +link:{junit5-repo}/issues/3437[issue 3437]). In light of that, the `RandomNumberExtension` +implements `BeforeEachCallback` as an alternative approach. +==== + [source,java,indent=0] ---- include::{testDir}/example/extensions/RandomNumberExtension.java[tags=user_guide] diff --git a/documentation/src/test/java/example/extensions/RandomNumberDemo.java b/documentation/src/test/java/example/extensions/RandomNumberDemo.java index 8dd274bda73c..0589e1e19b29 100644 --- a/documentation/src/test/java/example/extensions/RandomNumberDemo.java +++ b/documentation/src/test/java/example/extensions/RandomNumberDemo.java @@ -27,7 +27,7 @@ class RandomNumberDemo { private int randomNumber1; RandomNumberDemo(@Random int randomNumber2) { - // Use randomNumber2 in constructor + // Use randomNumber2 in constructor. } @BeforeEach diff --git a/documentation/src/test/java/example/extensions/RandomNumberExtension.java b/documentation/src/test/java/example/extensions/RandomNumberExtension.java index b2a900dc5ea5..f550a57cd3d0 100644 --- a/documentation/src/test/java/example/extensions/RandomNumberExtension.java +++ b/documentation/src/test/java/example/extensions/RandomNumberExtension.java @@ -18,17 +18,17 @@ 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; // end::user_guide[] // @formatter:off // tag::user_guide[] class RandomNumberExtension - implements BeforeAllCallback, TestInstancePostProcessor, ParameterResolver { + implements BeforeAllCallback, BeforeEachCallback, ParameterResolver { private final java.util.Random random = new java.util.Random(System.nanoTime()); @@ -47,8 +47,9 @@ public void beforeAll(ExtensionContext context) { * {@code @Random} and can be assigned an integer value. */ @Override - public void postProcessTestInstance(Object testInstance, ExtensionContext context) { + public void beforeEach(ExtensionContext context) { Class testClass = context.getRequiredTestClass(); + Object testInstance = context.getRequiredTestInstance(); injectFields(testClass, testInstance, ModifierSupport::isNotStatic); } From 3084862f725a162e592f0bf68d25b858d2147f29 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Sat, 16 Sep 2023 14:57:53 +0200 Subject: [PATCH 11/45] Polishing --- .../src/docs/asciidoc/release-notes/release-notes-5.10.1.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.10.1.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.10.1.adoc index f79ffa8d72d0..1d98efa72b9c 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-5.10.1.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.10.1.adoc @@ -49,7 +49,7 @@ JUnit repository on GitHub. ==== Bug Fixes -* Fix reporting of JUnit 3 test classes with `@Ignored` annotation +* Fixed reporting for JUnit 3 test classes that use JUnit 4's `@Ignored` annotation. ==== Deprecations and Breaking Changes From 32e6b9ed19e30bbc3d0c6d7d303c92f85147845b Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Sat, 16 Sep 2023 15:03:39 +0200 Subject: [PATCH 12/45] Document additional enhancements in 5.10.1 --- .../src/docs/asciidoc/release-notes/release-notes-5.10.1.adoc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.10.1.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.10.1.adoc index 1d98efa72b9c..10d42587bbb9 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-5.10.1.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.10.1.adoc @@ -41,7 +41,9 @@ JUnit repository on GitHub. ==== New Features and Improvements -* ❓ +* Improved Javadoc for `Assertions.assertTimeoutPreemptively` regarding thread interrupt. +* Documentation for `@Disabled` and conditional annotations now explicitly explains that + such annotations are not inherited by subclasses. [[release-notes-5.10.1-junit-vintage]] From 0abdd9b6d73aeb256adb509f23723d5d0a584ca9 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Fri, 22 Sep 2023 12:54:55 +0200 Subject: [PATCH 13/45] Test 5.9.x behavior for null/"null" conversion support in parameterized tests 2 tests in this commit currently fail due to changes in 5.10 with the failure message: Failed to convert String "null" to type java.lang.Boolean This will be addressed in a subsequent commit to align with the expected behavior in 5.10.x. See #3472 --- .../ParameterizedTestIntegrationTests.java | 34 ++++++++++++ .../DefaultArgumentConverterTests.java | 53 +++++++++++++++++++ .../provider/CsvArgumentsProviderTests.java | 5 +- 3 files changed, 90 insertions(+), 2 deletions(-) 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..78a5ed438582 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, null + """) + 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(); // "null" --> false + 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..d33645068d05 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,6 +111,54 @@ 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); + } + + @Test + void convertsTheWordNullToBooleanFalse() { + assertConverts("null", boolean.class, false); + assertConverts("NULL", boolean.class, false); + assertConverts("null", Boolean.class, false); + assertConverts("NULL", Boolean.class, false); + } + + @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}") + // NOTE: everything except Boolean.class and Character.class. + @ValueSource(classes = { 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) // 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 From 1a1293597a08154e99c1fceaf20464c4606e0357 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Fri, 22 Sep 2023 13:13:38 +0200 Subject: [PATCH 14/45] Update null/"null" conversion tests to reflect status quo in 5.10.x This commit updates the tests introduced in the last commit in order to align with the expected behavior in 5.10.x. See #3177 See #3472 --- .../ParameterizedTestIntegrationTests.java | 4 ++-- .../DefaultArgumentConverterTests.java | 24 +++++++++++-------- 2 files changed, 16 insertions(+), 12 deletions(-) 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 78a5ed438582..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 @@ -116,14 +116,14 @@ class ParameterizedTestIntegrationTests { apple, True banana, true lemon, false - kumquat, null + 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(); // "null" --> false + case "kumquat" -> assertThat(sweet).isFalse(); default -> fail("Unexpected fruit : " + fruit); } } 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 d33645068d05..f801058f4e59 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 @@ -130,14 +130,6 @@ void convertsStringsToPrimitiveWrapperTypes() { assertConverts("42.2_3", Double.class, 42.23); } - @Test - void convertsTheWordNullToBooleanFalse() { - assertConverts("null", boolean.class, false); - assertConverts("NULL", boolean.class, false); - assertConverts("null", Boolean.class, false); - assertConverts("NULL", Boolean.class, false); - } - @ParameterizedTest(name = "[{index}] {0}") @ValueSource(classes = { char.class, boolean.class, short.class, byte.class, int.class, long.class, float.class, double.class }) @@ -148,8 +140,8 @@ void throwsExceptionForNullToPrimitiveTypeConversion(Class type) { } @ParameterizedTest(name = "[{index}] {0}") - // NOTE: everything except Boolean.class and Character.class. - @ValueSource(classes = { Short.class, Byte.class, Integer.class, Long.class, Float.class, Double.class }) + @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)) // @@ -172,6 +164,18 @@ void throwsExceptionOnInvalidStringForPrimitiveTypes() { .withMessage("Failed to convert String \"tru\" to type boolean") // .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() // + .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() // + .withMessage("String must be 'true' or 'false' (ignoring case): NULL"); } @Test From 20ebb3be96bd021b2d46997169b79df4b0a7cb6f Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Mon, 30 Oct 2023 18:00:01 +0100 Subject: [PATCH 15/45] Prepare release notes for 5.10.1 --- .../release-notes/release-notes-5.10.1.adoc | 22 +------------------ 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.10.1.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.10.1.adoc index 10d42587bbb9..cb575e6f4dc3 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-5.10.1.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.10.1.adoc @@ -3,7 +3,7 @@ *Date of Release:* ❓ -*Scope:* ❓ +*Scope:* minor bug fixes and improvements since 5.10.0. For a complete list of all _closed_ issues and pull requests for this release, consult the link:{junit5-repo}+/milestone/72?closed=1+[5.10.1] milestone page in the @@ -17,14 +17,6 @@ JUnit repository on GitHub. * ❓ -==== Deprecations and Breaking Changes - -* ❓ - -==== New Features and Improvements - -* ❓ - [[release-notes-5.10.1-junit-jupiter]] === JUnit Jupiter @@ -35,10 +27,6 @@ JUnit repository on GitHub. <<../user-guide/index.adoc#extensions-RandomNumberExtension, User Guide>> has been updated to properly support `Integer` types as well as non-static field injection. -==== Deprecations and Breaking Changes - -* ❓ - ==== New Features and Improvements * Improved Javadoc for `Assertions.assertTimeoutPreemptively` regarding thread interrupt. @@ -52,11 +40,3 @@ JUnit repository on GitHub. ==== Bug Fixes * Fixed reporting for JUnit 3 test classes that use JUnit 4's `@Ignored` annotation. - -==== Deprecations and Breaking Changes - -* ❓ - -==== New Features and Improvements - -* ❓ From 17523891b02d4c6b6886e36aab11fe9d7189cd17 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Mon, 9 Oct 2023 15:49:51 +0200 Subject: [PATCH 16/45] Apply method predicate before searching type hierarchy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Prior to this commit, findMethods() and streamMethods() in ReflectionSupport as well as findAnnotatedMethods() in AnnotationSupport first searched for all methods in the type hierarchy and then applied the user-supplied predicate (or "is annotated?" predicate) afterwards. This resulted in methods in subclasses incorrectly "shadowing" package-private methods in superclasses (in a different package) even if the predicate would otherwise exclude the method in such a subclass. For example, given a superclass that declares a package-private static @⁠BeforeAll "before()" method and a subclass (in a different package) that declares a @⁠BeforeEach "before()" method, when JUnit Jupiter looked up @⁠BeforeAll methods for the subclass, the @⁠BeforeAll "before()" method in the superclass was not found because the @⁠BeforeEach "before()" method shadowed it based solely on the method signature, ignoring the type of annotation sought. To address that, this commit modifies the internal search algorithms in ReflectionUtils so that method predicates are applied while searching the hierarchy for methods. Closes #3498 Closes #3500 --- .../release-notes/release-notes-5.10.1.adoc | 10 +++++- .../commons/util/ReflectionUtils.java | 32 ++++++++++--------- .../commons/util/AnnotationUtilsTests.java | 27 ++++++++++++++++ .../commons/util/ReflectionUtilsTests.java | 24 ++++++++++++++ ...sWithStaticPackagePrivateBeforeMethod.java | 25 +++++++++++++++ ...thNonStaticPackagePrivateBeforeMethod.java | 26 +++++++++++++++ 6 files changed, 128 insertions(+), 16 deletions(-) create mode 100644 platform-tests/src/test/java/org/junit/platform/commons/util/pkg1/SuperclassWithStaticPackagePrivateBeforeMethod.java create mode 100644 platform-tests/src/test/java/org/junit/platform/commons/util/pkg1/subpkg/SubclassWithNonStaticPackagePrivateBeforeMethod.java diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.10.1.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.10.1.adoc index cb575e6f4dc3..abd61752cc23 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-5.10.1.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.10.1.adoc @@ -15,7 +15,10 @@ JUnit repository on GitHub. ==== Bug Fixes -* ❓ +* Method predicates are now applied while searching the type hierarchy. This fixes bugs + in `findMethods(...)` and `streamMethods(...)` in `ReflectionSupport` as well as + `findAnnotatedMethods(...)` in `AnnotationSupport`. + - See link:https://github.com/junit-team/junit5/issues/3498[issue 3498] for details. [[release-notes-5.10.1-junit-jupiter]] @@ -23,6 +26,11 @@ JUnit repository on GitHub. ==== Bug Fixes +* A package-private class-level lifecycle method annotated with `@BeforeAll` or + `@AfterAll` is no longer _shadowed_ by a method-level lifecycle method annotated with + `@BeforeEach` or `@AfterEach` when the method-level lifecycle method resides in a + different package and has the same name as the class-level lifecycle method. + - See link:https://github.com/junit-team/junit5/issues/3498[issue 3498] for details. * The `RandomNumberExtension` example in the <<../user-guide/index.adoc#extensions-RandomNumberExtension, User Guide>> has been updated to properly support `Integer` types as well as non-static field injection. 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..927b97451e0a 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 @@ -1489,29 +1489,27 @@ public static Stream streamMethods(Class clazz, Predicate pre Preconditions.notNull(predicate, "Predicate must not be null"); Preconditions.notNull(traversalMode, "HierarchyTraversalMode must not be null"); - // @formatter:off - return findAllMethodsInHierarchy(clazz, traversalMode).stream() - .filter(predicate) - .distinct(); - // @formatter:on + return findAllMethodsInHierarchy(clazz, predicate, traversalMode).stream().distinct(); } /** * Find all non-synthetic methods in the superclass and interface hierarchy, - * excluding Object. + * excluding Object, that match the specified {@code predicate}. */ - private static List findAllMethodsInHierarchy(Class clazz, HierarchyTraversalMode traversalMode) { + private static List findAllMethodsInHierarchy(Class clazz, Predicate predicate, + HierarchyTraversalMode traversalMode) { + Preconditions.notNull(clazz, "Class must not be null"); Preconditions.notNull(traversalMode, "HierarchyTraversalMode must not be null"); // @formatter:off List localMethods = getDeclaredMethods(clazz, traversalMode).stream() - .filter(method -> !method.isSynthetic()) + .filter(predicate.and(method -> !method.isSynthetic())) .collect(toList()); - List superclassMethods = getSuperclassMethods(clazz, traversalMode).stream() + List superclassMethods = getSuperclassMethods(clazz, predicate, traversalMode).stream() .filter(method -> !isMethodShadowedByLocalMethods(method, localMethods)) .collect(toList()); - List interfaceMethods = getInterfaceMethods(clazz, traversalMode).stream() + List interfaceMethods = getInterfaceMethods(clazz, predicate, traversalMode).stream() .filter(method -> !isMethodShadowedByLocalMethods(method, localMethods)) .collect(toList()); // @formatter:on @@ -1647,16 +1645,18 @@ private static int defaultMethodSorter(Method method1, Method method2) { return comparison; } - private static List getInterfaceMethods(Class clazz, HierarchyTraversalMode traversalMode) { + private static List getInterfaceMethods(Class clazz, Predicate predicate, + HierarchyTraversalMode traversalMode) { + List allInterfaceMethods = new ArrayList<>(); for (Class ifc : clazz.getInterfaces()) { // @formatter:off List localInterfaceMethods = getMethods(ifc).stream() - .filter(m -> !isAbstract(m)) + .filter(predicate.and(method -> !isAbstract(method))) .collect(toList()); - List superinterfaceMethods = getInterfaceMethods(ifc, traversalMode).stream() + List superinterfaceMethods = getInterfaceMethods(ifc, predicate, traversalMode).stream() .filter(method -> !isMethodShadowedByLocalMethods(method, localInterfaceMethods)) .collect(toList()); // @formatter:on @@ -1706,12 +1706,14 @@ private static boolean isFieldShadowedByLocalFields(Field field, List loc return localFields.stream().anyMatch(local -> local.getName().equals(field.getName())); } - private static List getSuperclassMethods(Class clazz, HierarchyTraversalMode traversalMode) { + private static List getSuperclassMethods(Class clazz, Predicate predicate, + HierarchyTraversalMode traversalMode) { + Class superclass = clazz.getSuperclass(); if (!isSearchable(superclass)) { return Collections.emptyList(); } - return findAllMethodsInHierarchy(superclass, traversalMode); + return findAllMethodsInHierarchy(superclass, predicate, traversalMode); } private static boolean isMethodShadowedByLocalMethods(Method method, List localMethods) { diff --git a/platform-tests/src/test/java/org/junit/platform/commons/util/AnnotationUtilsTests.java b/platform-tests/src/test/java/org/junit/platform/commons/util/AnnotationUtilsTests.java index 478491014be1..f26f5ab755d3 100644 --- a/platform-tests/src/test/java/org/junit/platform/commons/util/AnnotationUtilsTests.java +++ b/platform-tests/src/test/java/org/junit/platform/commons/util/AnnotationUtilsTests.java @@ -36,13 +36,18 @@ import java.lang.annotation.Target; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Field; +import java.lang.reflect.Method; import java.math.BigDecimal; import java.util.List; import java.util.Optional; import java.util.function.Predicate; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.commons.util.pkg1.SuperclassWithStaticPackagePrivateBeforeMethod; +import org.junit.platform.commons.util.pkg1.subpkg.SubclassWithNonStaticPackagePrivateBeforeMethod; /** * Unit tests for {@link AnnotationUtils}. @@ -380,6 +385,28 @@ void findAnnotatedMethodsForAnnotationUsedInClassAndSuperclassHierarchyDown() th assertThat(methods.subList(1, 3)).containsOnly(method1, method3); } + /** + * @see https://github.com/junit-team/junit5/issues/3498 + */ + @Test + void findAnnotatedMethodsAppliesPredicateBeforeSearchingTypeHierarchy() throws Exception { + final String BEFORE = "before"; + Class superclass = SuperclassWithStaticPackagePrivateBeforeMethod.class; + Method beforeAllMethod = superclass.getDeclaredMethod(BEFORE); + Class subclass = SubclassWithNonStaticPackagePrivateBeforeMethod.class; + Method beforeEachMethod = subclass.getDeclaredMethod(BEFORE); + + // Prerequisite + var methods = findAnnotatedMethods(superclass, BeforeAll.class, TOP_DOWN); + assertThat(methods).containsExactly(beforeAllMethod); + + // Actual use cases for this test + methods = findAnnotatedMethods(subclass, BeforeAll.class, TOP_DOWN); + assertThat(methods).containsExactly(beforeAllMethod); + methods = findAnnotatedMethods(subclass, BeforeEach.class, TOP_DOWN); + assertThat(methods).containsExactly(beforeEachMethod); + } + @Test void findAnnotatedMethodsForAnnotationUsedInInterface() throws Exception { var interfaceMethod = InterfaceWithAnnotatedDefaultMethod.class.getDeclaredMethod("interfaceMethod"); diff --git a/platform-tests/src/test/java/org/junit/platform/commons/util/ReflectionUtilsTests.java b/platform-tests/src/test/java/org/junit/platform/commons/util/ReflectionUtilsTests.java index d74b74ce7592..714ecab17e05 100644 --- a/platform-tests/src/test/java/org/junit/platform/commons/util/ReflectionUtilsTests.java +++ b/platform-tests/src/test/java/org/junit/platform/commons/util/ReflectionUtilsTests.java @@ -72,6 +72,8 @@ import org.junit.platform.commons.util.ReflectionUtilsTests.OuterClass.StaticNestedSiblingClass; import org.junit.platform.commons.util.ReflectionUtilsTests.OuterClassImplementingInterface.InnerClassImplementingInterface; import org.junit.platform.commons.util.classes.CustomType; +import org.junit.platform.commons.util.pkg1.SuperclassWithStaticPackagePrivateBeforeMethod; +import org.junit.platform.commons.util.pkg1.subpkg.SubclassWithNonStaticPackagePrivateBeforeMethod; /** * Unit tests for {@link ReflectionUtils}. @@ -1345,6 +1347,28 @@ void findMethodsIgnoresBridgeMethods() throws Exception { assertEquals(0, methods.stream().filter(Method::isBridge).count()); } + /** + * @see https://github.com/junit-team/junit5/issues/3498 + */ + @Test + void findMethodsAppliesPredicateBeforeSearchingTypeHierarchy() throws Exception { + final String BEFORE = "before"; + Class superclass = SuperclassWithStaticPackagePrivateBeforeMethod.class; + Method staticMethod = superclass.getDeclaredMethod(BEFORE); + Class subclass = SubclassWithNonStaticPackagePrivateBeforeMethod.class; + Method nonStaticMethod = subclass.getDeclaredMethod(BEFORE); + + // Prerequisite + var methods = findMethods(superclass, ReflectionUtils::isStatic); + assertThat(methods).containsExactly(staticMethod); + + // Actual use cases for this test + methods = findMethods(subclass, ReflectionUtils::isStatic); + assertThat(methods).containsExactly(staticMethod); + methods = findMethods(subclass, ReflectionUtils::isNotStatic); + assertThat(methods).containsExactly(nonStaticMethod); + } + @Test void isGeneric() { for (var method : Generic.class.getMethods()) { diff --git a/platform-tests/src/test/java/org/junit/platform/commons/util/pkg1/SuperclassWithStaticPackagePrivateBeforeMethod.java b/platform-tests/src/test/java/org/junit/platform/commons/util/pkg1/SuperclassWithStaticPackagePrivateBeforeMethod.java new file mode 100644 index 000000000000..2895f2b4980d --- /dev/null +++ b/platform-tests/src/test/java/org/junit/platform/commons/util/pkg1/SuperclassWithStaticPackagePrivateBeforeMethod.java @@ -0,0 +1,25 @@ +/* + * 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.util.pkg1; + +import org.junit.jupiter.api.BeforeAll; + +/** + * @see https://github.com/junit-team/junit5/issues/3498 + */ +public class SuperclassWithStaticPackagePrivateBeforeMethod { + + @BeforeAll + static void before() { + // no-op + } + +} diff --git a/platform-tests/src/test/java/org/junit/platform/commons/util/pkg1/subpkg/SubclassWithNonStaticPackagePrivateBeforeMethod.java b/platform-tests/src/test/java/org/junit/platform/commons/util/pkg1/subpkg/SubclassWithNonStaticPackagePrivateBeforeMethod.java new file mode 100644 index 000000000000..c7c6d1e0ac22 --- /dev/null +++ b/platform-tests/src/test/java/org/junit/platform/commons/util/pkg1/subpkg/SubclassWithNonStaticPackagePrivateBeforeMethod.java @@ -0,0 +1,26 @@ +/* + * 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.util.pkg1.subpkg; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.platform.commons.util.pkg1.SuperclassWithStaticPackagePrivateBeforeMethod; + +/** + * @see https://github.com/junit-team/junit5/issues/3498 + */ +public class SubclassWithNonStaticPackagePrivateBeforeMethod extends SuperclassWithStaticPackagePrivateBeforeMethod { + + @BeforeEach + void before() { + // no-op + } + +} From f1a0f5f11db1cb61b838f940e39cf8c64a640e88 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Tue, 31 Oct 2023 13:52:15 +0100 Subject: [PATCH 17/45] Take into account failures of children for ON_SUCCESS cleanup mode Prior to this commit, the `ON_SUCCESS` cleanup mode of `@TempDir` would only take into account failures of the level the `@TempDir` was declared on but not of its children. Fixes #3510. (cherry picked from commit e9102be2a266d3d4c2962617542e534c7a521247) --- .../release-notes/release-notes-5.10.1.adoc | 2 + .../engine/extension/TempDirectory.java | 17 +++++- .../extension/TempDirectoryCleanupTests.java | 59 +++++++++++++++++++ 3 files changed, 76 insertions(+), 2 deletions(-) diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.10.1.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.10.1.adoc index abd61752cc23..65c3aa6055b4 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-5.10.1.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.10.1.adoc @@ -34,6 +34,8 @@ JUnit repository on GitHub. * The `RandomNumberExtension` example in the <<../user-guide/index.adoc#extensions-RandomNumberExtension, User Guide>> has been updated to properly support `Integer` types as well as non-static field injection. +* The `ON_SUCCESS` cleanup mode of `@TempDir` now takes into account failures of test + methods and nested tests when it's declared on the class level, e.g. as a static field. ==== New Features and Improvements diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TempDirectory.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TempDirectory.java index fe76498bd4ae..de2398b94f3e 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TempDirectory.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TempDirectory.java @@ -79,6 +79,8 @@ class TempDirectory implements BeforeAllCallback, BeforeEachCallback, ParameterR static final Namespace NAMESPACE = Namespace.create(TempDirectory.class); private static final String KEY = "temp.dir"; + private static final String FAILURE_TRACKER = "failure.tracker"; + private static final String CHILD_FAILED = "child.failed"; // for testing purposes static final String FILE_OPERATIONS_KEY = "file.operations"; @@ -96,6 +98,7 @@ public TempDirectory(JupiterConfiguration configuration) { */ @Override public void beforeAll(ExtensionContext context) { + installFailureTracker(context); injectStaticFields(context, context.getRequiredTestClass()); } @@ -106,10 +109,16 @@ public void beforeAll(ExtensionContext context) { */ @Override public void beforeEach(ExtensionContext context) { + installFailureTracker(context); context.getRequiredTestInstances().getAllInstances() // .forEach(instance -> injectInstanceFields(context, instance)); } + private static void installFailureTracker(ExtensionContext context) { + context.getStore(NAMESPACE).put(FAILURE_TRACKER, (CloseableResource) () -> context.getParent() // + .ifPresent(it -> it.getStore(NAMESPACE).put(CHILD_FAILED, selfOrChildFailed(context)))); + } + private void injectStaticFields(ExtensionContext context, Class testClass) { injectFields(context, null, testClass, ReflectionUtils::isStatic); } @@ -257,6 +266,11 @@ static CloseablePath createTempDir(TempDirFactory factory, CleanupMode cleanupMo } } + private static boolean selfOrChildFailed(ExtensionContext context) { + return context.getExecutionException().isPresent() // + || Boolean.TRUE.equals(context.getStore(NAMESPACE).get(CHILD_FAILED)); + } + static class CloseablePath implements CloseableResource { private static final Logger logger = LoggerFactory.getLogger(CloseablePath.class); @@ -281,8 +295,7 @@ Path get() { @Override public void close() throws IOException { try { - if (cleanupMode == NEVER - || (cleanupMode == ON_SUCCESS && extensionContext.getExecutionException().isPresent())) { + if (cleanupMode == NEVER || (cleanupMode == ON_SUCCESS && selfOrChildFailed(extensionContext))) { logger.info(() -> "Skipping cleanup of temp dir " + dir + " due to cleanup mode configuration."); return; } diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryCleanupTests.java b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryCleanupTests.java index ddaaa214f786..c3dfb781f8c8 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryCleanupTests.java +++ b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryCleanupTests.java @@ -16,6 +16,7 @@ import static org.junit.jupiter.api.io.CleanupMode.ALWAYS; import static org.junit.jupiter.api.io.CleanupMode.NEVER; import static org.junit.jupiter.api.io.CleanupMode.ON_SUCCESS; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; @@ -141,6 +142,36 @@ void cleanupModeOnSuccessFailingField() { assertThat(onSuccessFailingFieldDir).exists(); } + /** + * Ensure that ON_SUCCESS cleanup modes are obeyed for static fields when tests are failing. + *

+ * Expect the TempDir not to be cleaned up. + */ + @Test + void cleanupModeOnSuccessFailingStaticField() { + LauncherDiscoveryRequest request = request()// + .selectors(selectClass(OnSuccessFailingStaticFieldCase.class))// + .build(); + executeTests(request); + + assertThat(onSuccessFailingFieldDir).exists(); + } + + /** + * Ensure that ON_SUCCESS cleanup modes are obeyed for static fields when nested tests are failing. + *

+ * Expect the TempDir not to be cleaned up. + */ + @Test + void cleanupModeOnSuccessFailingStaticFieldWithNesting() { + LauncherDiscoveryRequest request = request()// + .selectors(selectClass(OnSuccessFailingStaticFieldWithNestingCase.class))// + .build(); + executeTests(request); + + assertThat(onSuccessFailingFieldDir).exists(); + } + @AfterAll static void afterAll() throws IOException { deleteIfExists(defaultFieldDir); @@ -208,6 +239,34 @@ void testOnSuccessFailingField() { } } + static class OnSuccessFailingStaticFieldCase { + + @TempDir(cleanup = ON_SUCCESS) + static Path onSuccessFailingFieldDir; + + @Test + void test() { + TempDirFieldTests.onSuccessFailingFieldDir = onSuccessFailingFieldDir; + fail(); + } + } + + static class OnSuccessFailingStaticFieldWithNestingCase { + + @TempDir(cleanup = ON_SUCCESS) + static Path onSuccessFailingFieldDir; + + @Nested + class NestedTestCase { + + @Test + void test() { + TempDirFieldTests.onSuccessFailingFieldDir = onSuccessFailingFieldDir; + fail(); + } + } + } + } @Nested From 277a253f9f96293b81c40543daffe37c11704fe3 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Tue, 31 Oct 2023 14:07:41 +0100 Subject: [PATCH 18/45] Avoid class-level errors in tests when running individual test methods (cherry picked from commit 3f6c32b76540ce9c7816c8b77cdcf10df5427eff) --- .../extension/TempDirectoryCleanupTests.java | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryCleanupTests.java b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryCleanupTests.java index c3dfb781f8c8..e4564fe55882 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryCleanupTests.java +++ b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryCleanupTests.java @@ -174,11 +174,17 @@ void cleanupModeOnSuccessFailingStaticFieldWithNesting() { @AfterAll static void afterAll() throws IOException { - deleteIfExists(defaultFieldDir); - deleteIfExists(neverFieldDir); - deleteIfExists(alwaysFieldDir); - deleteIfExists(onSuccessFailingFieldDir); - deleteIfExists(onSuccessPassingFieldDir); + deleteIfNotNullAndExists(defaultFieldDir); + deleteIfNotNullAndExists(neverFieldDir); + deleteIfNotNullAndExists(alwaysFieldDir); + deleteIfNotNullAndExists(onSuccessFailingFieldDir); + deleteIfNotNullAndExists(onSuccessPassingFieldDir); + } + + static void deleteIfNotNullAndExists(Path dir) throws IOException { + if (dir != null) { + deleteIfExists(dir); + } } // ------------------------------------------------------------------- @@ -373,11 +379,11 @@ void cleanupModeOnSuccessFailingParameter() { @AfterAll static void afterAll() throws IOException { - deleteIfExists(defaultParameterDir); - deleteIfExists(neverParameterDir); - deleteIfExists(alwaysParameterDir); - deleteIfExists(onSuccessFailingParameterDir); - deleteIfExists(onSuccessPassingParameterDir); + TempDirFieldTests.deleteIfNotNullAndExists(defaultParameterDir); + TempDirFieldTests.deleteIfNotNullAndExists(neverParameterDir); + TempDirFieldTests.deleteIfNotNullAndExists(alwaysParameterDir); + TempDirFieldTests.deleteIfNotNullAndExists(onSuccessFailingParameterDir); + TempDirFieldTests.deleteIfNotNullAndExists(onSuccessPassingParameterDir); } // ------------------------------------------------------------------- From 8449797277d0ff392ad08a21bb064281e8f66f19 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Thu, 2 Nov 2023 09:57:15 +0100 Subject: [PATCH 19/45] Reduce number of store operations (cherry picked from commit 6dd79b83922e2eda5cce3529b31b59119704f027) --- .../jupiter/engine/extension/TempDirectory.java | 8 ++++++-- .../engine/extension/TempDirectoryCleanupTests.java | 12 +++++++++++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TempDirectory.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TempDirectory.java index de2398b94f3e..7ac385561839 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TempDirectory.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TempDirectory.java @@ -116,7 +116,11 @@ public void beforeEach(ExtensionContext context) { private static void installFailureTracker(ExtensionContext context) { context.getStore(NAMESPACE).put(FAILURE_TRACKER, (CloseableResource) () -> context.getParent() // - .ifPresent(it -> it.getStore(NAMESPACE).put(CHILD_FAILED, selfOrChildFailed(context)))); + .ifPresent(it -> { + if (selfOrChildFailed(context)) { + it.getStore(NAMESPACE).put(CHILD_FAILED, true); + } + })); } private void injectStaticFields(ExtensionContext context, Class testClass) { @@ -268,7 +272,7 @@ static CloseablePath createTempDir(TempDirFactory factory, CleanupMode cleanupMo private static boolean selfOrChildFailed(ExtensionContext context) { return context.getExecutionException().isPresent() // - || Boolean.TRUE.equals(context.getStore(NAMESPACE).get(CHILD_FAILED)); + || context.getStore(NAMESPACE).getOrDefault(CHILD_FAILED, Boolean.class, false); } static class CloseablePath implements CloseableResource { diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryCleanupTests.java b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryCleanupTests.java index e4564fe55882..4e47ec390bb4 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryCleanupTests.java +++ b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryCleanupTests.java @@ -24,8 +24,11 @@ import java.nio.file.Path; import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; import org.junit.jupiter.api.io.CleanupMode; import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; @@ -245,16 +248,23 @@ void testOnSuccessFailingField() { } } + @TestMethodOrder(MethodOrderer.OrderAnnotation.class) static class OnSuccessFailingStaticFieldCase { @TempDir(cleanup = ON_SUCCESS) static Path onSuccessFailingFieldDir; @Test - void test() { + @Order(1) + void failing() { TempDirFieldTests.onSuccessFailingFieldDir = onSuccessFailingFieldDir; fail(); } + + @Test + @Order(2) + void passing() { + } } static class OnSuccessFailingStaticFieldWithNestingCase { From 907122ea10eeac1f74ac9a70d7b86d27c3f018c7 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Mon, 25 Sep 2023 11:07:15 +0200 Subject: [PATCH 20/45] Remove JDK 20 from cross-version testing matrix (cherry picked from commit 0876ceab2276511c96b35b6f52ddbd074c672304) --- .github/workflows/cross-version.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cross-version.yml b/.github/workflows/cross-version.yml index 8480a68a49c7..03db008121f5 100644 --- a/.github/workflows/cross-version.yml +++ b/.github/workflows/cross-version.yml @@ -20,7 +20,7 @@ jobs: strategy: fail-fast: false matrix: - jdk: [20, 21, 22] + jdk: [21, 22] name: "OpenJDK ${{ matrix.jdk }}" runs-on: ubuntu-latest steps: From b124d405b8e8d3a89f35ff28ae3ad05e8cf230ee Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Mon, 25 Sep 2023 17:09:10 +0200 Subject: [PATCH 21/45] Disable `kotlinSourcesJar` Gradle task Fixes #3474. (cherry picked from commit 2b3ddb21011b6518afece3f45f01b394f4f51ef7) --- .../kotlin/junitbuild.kotlin-library-conventions.gradle.kts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild.kotlin-library-conventions.gradle.kts b/gradle/plugins/common/src/main/kotlin/junitbuild.kotlin-library-conventions.gradle.kts index bfb33224a709..477400c2f037 100644 --- a/gradle/plugins/common/src/main/kotlin/junitbuild.kotlin-library-conventions.gradle.kts +++ b/gradle/plugins/common/src/main/kotlin/junitbuild.kotlin-library-conventions.gradle.kts @@ -5,6 +5,10 @@ plugins { kotlin("jvm") } +tasks.named("kotlinSourcesJar") { + enabled = false +} + tasks.withType().configureEach { kotlinOptions { apiVersion = "1.3" From a0412de869bc8f6ec6944b500a971693faa6966c Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Tue, 3 Oct 2023 11:26:56 +0200 Subject: [PATCH 22/45] Disable documentation checksum check for now To diagnose issues with the component diagram (cherry picked from commit 3c8f437f36735ba0ed042daee95a4bc9f50044cd) --- .github/workflows/main.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 676ec1a2f87a..4357e858991d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -105,6 +105,6 @@ jobs: with: arguments: --quiet - name: Upload Documentation - env: - GRGIT_USER: ${{ secrets.GH_TOKEN }} - run: ./gradle/scripts/publishDocumentationSnapshotOnlyIfNecessary.sh + uses: ./.github/actions/run-gradle + with: + arguments: gitPublishPush -Dscan.tag.Documentation From 7b1c0cdaf1212500c9cdca1b87eeee559a91196d Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Tue, 3 Oct 2023 11:35:27 +0200 Subject: [PATCH 23/45] Gradle Enterprise -> Develocity (cherry picked from commit 8cfe93b6fa5fc72b1c6e654b7c2857d15274ec40) --- README.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 43f900ade772..78fb026ea940 100644 --- a/README.md +++ b/README.md @@ -46,11 +46,14 @@ A code coverage report can also be generated locally via the [Gradle Wrapper] by executing `./gradlew -Ptesting.enableJaCoCo clean jacocoRootReport`. The results will be available in `build/reports/jacoco/jacocoRootReport/html/index.html`. -## Gradle Enterprise +## Develocity -[![Revved up by Gradle Enterprise](https://img.shields.io/badge/Revved%20up%20by-Gradle%20Enterprise-06A0CE?logo=Gradle&labelColor=02303A)](https://ge.junit.org/scans) +[![Revved up by Develocity](https://img.shields.io/badge/Revved%20up%20by-Develocity-06A0CE?logo=Gradle&labelColor=02303A)](https://ge.junit.org/scans) -JUnit 5 utilizes [Gradle Enterprise](https://gradle.com/) for _Build Scans_, _Build Cache_, and _Test Distribution_. +JUnit 5 utilizes [Develocity](https://gradle.com/) for [Build Scans](https://scans.gradle.com/), +[Build Cache](https://docs.gradle.org/current/userguide/build_cache.html), +[Predictive Test Selection](https://docs.gradle.com/enterprise/predictive-test-selection/), and +[Test Distribution](https://docs.gradle.com/enterprise/test-distribution/). The latest Build Scans are available on [ge.junit.org](https://ge.junit.org/). Currently, only core team members can publish Build Scans and use Test Distribution on that server. From 46d86335becdd2573abf369d3f021fade2f54c91 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Tue, 3 Oct 2023 11:42:05 +0200 Subject: [PATCH 24/45] Remove unnecessary build step (cherry picked from commit 54b07d52e09316fb9837c5bacd40f381f74ffd43) --- .github/workflows/main.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4357e858991d..17f0801dd61f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -100,10 +100,6 @@ jobs: run: | sudo apt-get update sudo apt-get install graphviz - - name: Restore Gradle cache and display toolchains - uses: ./.github/actions/run-gradle - with: - arguments: --quiet - name: Upload Documentation uses: ./.github/actions/run-gradle with: From 1a094697a74cdfd5bc6b4aff18ef029ca1335fd6 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Tue, 3 Oct 2023 11:42:25 +0200 Subject: [PATCH 25/45] Inject GitHub token again (cherry picked from commit ca5f02bb134abf71367964bec4fb204d9572c5d0) --- .github/workflows/main.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 17f0801dd61f..b8c1d24b0e54 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -104,3 +104,5 @@ jobs: uses: ./.github/actions/run-gradle with: arguments: gitPublishPush -Dscan.tag.Documentation + env: + GRGIT_USER: ${{ secrets.GH_TOKEN }} From 77419303d6354d82444c1027596abe431d81bda2 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Thu, 5 Oct 2023 17:35:37 +0200 Subject: [PATCH 26/45] Fix component diagram (#3487) * Add check for component diagram * Use latest version of asciidoctorj-diagram and asciidoctorj-pdf * Declare asccidoctorj artifacts in version catalog for Dependabot updates (cherry picked from commit 2619aee25745c8ff789c62aa7c871b7ca42a785c) --- documentation/documentation.gradle.kts | 10 ++++++++-- gradle/libs.versions.toml | 7 ++++++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/documentation/documentation.gradle.kts b/documentation/documentation.gradle.kts index fd4877cdd6fc..da73ae62b0e7 100644 --- a/documentation/documentation.gradle.kts +++ b/documentation/documentation.gradle.kts @@ -63,8 +63,8 @@ dependencies { asciidoctorj { modules { - diagram.use() - pdf.version(libs.versions.asciidoctor.pdf) + diagram.version(libs.versions.asciidoctorj.diagram) + pdf.version(libs.versions.asciidoctorj.pdf) } requires(file("src/docs/asciidoc/resources/themes/rouge_junit.rb")) } @@ -297,6 +297,12 @@ tasks { "userGuidePdfFileName" to userGuidePdfFileName, "releaseNotesUrl" to "../release-notes/index.html#release-notes" )) + doLast { + val componentDiagramSvg = outputDirProperty.file("user-guide/images/component-diagram.svg").get().asFile + require(componentDiagramSvg.exists()) { + "Component diagram was not generated at $componentDiagramSvg" + } + } } asciidoctorPdf { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d69b3f911c10..b10b06ce8bc1 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,8 @@ [versions] ant = "1.10.13" apiguardian = "1.1.2" -asciidoctor-pdf = "1.5.3" +asciidoctorj-diagram = "2.2.9" +asciidoctorj-pdf = "2.3.9" 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" @@ -65,6 +66,10 @@ xmlunit-assertj = { module = "org.xmlunit:xmlunit-assertj3", version.ref = "xmlu xmlunit-placeholders = { module = "org.xmlunit:xmlunit-placeholders", version.ref = "xmlunit" } testingAnnotations = { module = "com.gradle:gradle-enterprise-testing-annotations", version = "1.1" } +# These are only declared here so Dependabot knows when to update the referenced versions +asciidoctorj-diagram = { module = "org.asciidoctor:asciidoctorj-diagram", version.ref = "asciidoctorj-diagram" } +asciidoctorj-pdf = { module = "org.asciidoctor:asciidoctorj-pdf", version.ref = "asciidoctorj-pdf" } + [bundles] ant = ["ant", "ant-junit", "ant-junitlauncher"] log4j = ["log4j-core", "log4j-jul"] From 1d7b73ad23bcdf795321c2cf5a274954048054f0 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Thu, 5 Oct 2023 17:49:04 +0200 Subject: [PATCH 27/45] Disable Asciidoctor last update label to avoid unnecessary deployments (cherry picked from commit 3a3c20f5ba35ef0cc4055f7f335e817197e4e1df) --- documentation/src/docs/asciidoc/release-notes/index.adoc | 1 + documentation/src/docs/asciidoc/user-guide/index.adoc | 1 + 2 files changed, 2 insertions(+) diff --git a/documentation/src/docs/asciidoc/release-notes/index.adoc b/documentation/src/docs/asciidoc/release-notes/index.adoc index f6c89ae8dc6a..bec921094623 100644 --- a/documentation/src/docs/asciidoc/release-notes/index.adoc +++ b/documentation/src/docs/asciidoc/release-notes/index.adoc @@ -6,6 +6,7 @@ Stefan Bechtold; Sam Brannen; Johannes Link; Matthias Merdes; Marc Philipp; Juli :docinfodir: {includedir}/docinfos :docinfo2: :numbered!: +:last-update-label!: // This document contains the _change log_ for all JUnit 5 releases since 5.9 GA. diff --git a/documentation/src/docs/asciidoc/user-guide/index.adoc b/documentation/src/docs/asciidoc/user-guide/index.adoc index 57e0f30948e0..9c34f8d33739 100644 --- a/documentation/src/docs/asciidoc/user-guide/index.adoc +++ b/documentation/src/docs/asciidoc/user-guide/index.adoc @@ -16,6 +16,7 @@ ifdef::backend-pdf[:imagesdir: {imagesoutdir}] // :sectnums: :toclevels: 4 +:last-update-label!: // include::{includedir}/link-attributes.adoc[] From 507f0b99d120ffedd611488eccf486ddb056e5c1 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Thu, 5 Oct 2023 17:49:35 +0200 Subject: [PATCH 28/45] Delete unused script (cherry picked from commit 3f8f07575ca5468b5fab8701637a66a7a1a470d5) --- ...ishDocumentationSnapshotOnlyIfNecessary.sh | 62 ------------------- 1 file changed, 62 deletions(-) delete mode 100755 gradle/scripts/publishDocumentationSnapshotOnlyIfNecessary.sh diff --git a/gradle/scripts/publishDocumentationSnapshotOnlyIfNecessary.sh b/gradle/scripts/publishDocumentationSnapshotOnlyIfNecessary.sh deleted file mode 100755 index ecd0482a2368..000000000000 --- a/gradle/scripts/publishDocumentationSnapshotOnlyIfNecessary.sh +++ /dev/null @@ -1,62 +0,0 @@ -#!/usr/bin/env bash - -readonly checksum_directory='documentation/build/checksum' -readonly current="${checksum_directory}/current-checksum.txt" -readonly published="${checksum_directory}/published-checksum.txt" -readonly github_pages_url='https://raw.githubusercontent.com/junit-team/junit5/gh-pages/docs/snapshot/published-checksum.txt' - -# -# always generate current sums -# -echo "Generating checksum file ${current}..." -mkdir --parents "${checksum_directory}" -md5sum documentation/documentation.gradle.kts > "${current}" -md5sum $(find documentation/src -type f) >> "${current}" -# skip module junit-bom because it doesn't contain relevant documentation -md5sum $(find junit-jupiter -wholename '**/src/main/*.java') >> "${current}" -md5sum $(find junit-jupiter-api -wholename '**/src/main/*.java') >> "${current}" -md5sum $(find junit-jupiter-engine -wholename '**/src/main/*.java') >> "${current}" -md5sum $(find junit-jupiter-migrationsupport -wholename '**/src/main/*.java') >> "${current}" -md5sum $(find junit-jupiter-params -wholename '**/src/main/*.java') >> "${current}" -md5sum $(find junit-platform-commons -wholename '**/src/main/*.java') >> "${current}" -md5sum $(find junit-platform-console -wholename '**/src/main/*.java') >> "${current}" -# skip module junit-platform-console-standalone because it doesn't contain relevant documentation -md5sum $(find junit-platform-engine -wholename '**/src/main/*.java') >> "${current}" -md5sum $(find junit-platform-jfr -wholename '**/src/main/*.java') >> "${current}" -md5sum $(find junit-platform-launcher -wholename '**/src/main/*.java') >> "${current}" -md5sum $(find junit-platform-reporting -wholename '**/src/main/*.java') >> "${current}" -md5sum $(find junit-platform-runner -wholename '**/src/main/*.java') >> "${current}" -md5sum $(find junit-platform-suite -wholename '**/src/main/*.java') >> "${current}" -md5sum $(find junit-platform-suite-api -wholename '**/src/main/*.java') >> "${current}" -md5sum $(find junit-platform-suite-commons -wholename '**/src/main/*.java') >> "${current}" -md5sum $(find junit-platform-suite-engine -wholename '**/src/main/*.java') >> "${current}" -md5sum $(find junit-platform-testkit -wholename '**/src/main/*.java') >> "${current}" -md5sum $(find junit-vintage-engine -wholename '**/src/main/*.java') >> "${current}" -# skip module platform-tests because it doesn't contain relevant documentation -# skip module platform-tooling-support-tests because it doesn't contain relevant documentation -sort --output "${current}" "${current}" -echo -md5sum "${current}" - -# -# compare current with published sums -# -curl --silent --output "${published}" "${github_pages_url}" -md5sum "${published}" -if cmp --silent "${current}" "${published}" ; then - # - # no changes detected: we're done - # - echo - echo "Already published documentation with same source checksum." - echo -else - # - # update checksum file and trigger new documentation build and upload - # - echo - echo "Creating and publishing documentation..." - echo - cp --force "${current}" "${published}" - ./gradlew gitPublishPush -Porg.gradle.java.installations.auto-download=false -Dscan.tag.Documentation -fi From 9b5a072acb77fe62c4772c2faedc6d7d6184b813 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Fri, 27 Oct 2023 14:25:33 +0200 Subject: [PATCH 29/45] Generate component diagram with plantuml separately Since the file is missing intermittently from the output of Asciidoctor it's now generated separately in a standalone `plantuml` task. (cherry picked from commit 429815fec80f0e6e1887119e4a5eb6bd476e9279) --- documentation/documentation.gradle.kts | 18 ++-- .../docs/asciidoc/user-guide/appendix.adoc | 98 +------------------ .../src/plantuml/component-diagram.puml | 98 +++++++++++++++++++ gradle/libs.versions.toml | 1 + 4 files changed, 111 insertions(+), 104 deletions(-) create mode 100644 documentation/src/plantuml/component-diagram.puml diff --git a/documentation/documentation.gradle.kts b/documentation/documentation.gradle.kts index da73ae62b0e7..fbe0413e3b54 100644 --- a/documentation/documentation.gradle.kts +++ b/documentation/documentation.gradle.kts @@ -11,6 +11,7 @@ plugins { alias(libs.plugins.asciidoctorConvert) alias(libs.plugins.asciidoctorPdf) alias(libs.plugins.gitPublish) + alias(libs.plugins.plantuml) id("junitbuild.build-parameters") id("junitbuild.kotlin-library-conventions") id("junitbuild.testing-conventions") @@ -207,6 +208,13 @@ tasks { outputFile = standaloneConsoleLauncherShadowedArtifactsFile } + plantUml { + fileFormat = "SVG" + outputs.cacheIf { true } + } + + val componentDiagram = plantUml.flatMap { it.outputDirectory.file("component-diagram.svg") } + withType().configureEach { inputs.files( generateConsoleLauncherOptions, @@ -215,7 +223,8 @@ tasks { generateConsoleLauncherEnginesOptions, generateExperimentalApisTable, generateDeprecatedApisTable, - generateStandaloneConsoleLauncherShadowedArtifactsFile + generateStandaloneConsoleLauncherShadowedArtifactsFile, + componentDiagram ) resources { @@ -248,6 +257,7 @@ tasks { "experimentalApisTableFile" to experimentalApisTableFile.get(), "deprecatedApisTableFile" to deprecatedApisTableFile.get(), "standaloneConsoleLauncherShadowedArtifactsFile" to standaloneConsoleLauncherShadowedArtifactsFile.get(), + "componentDiagramFile" to componentDiagram.get(), "outdir" to outputDir.absolutePath, "source-highlighter" to "rouge", "rouge-style" to "junit", @@ -297,12 +307,6 @@ tasks { "userGuidePdfFileName" to userGuidePdfFileName, "releaseNotesUrl" to "../release-notes/index.html#release-notes" )) - doLast { - val componentDiagramSvg = outputDirProperty.file("user-guide/images/component-diagram.svg").get().asFile - require(componentDiagramSvg.exists()) { - "Component diagram was not generated at $componentDiagramSvg" - } - } } asciidoctorPdf { diff --git a/documentation/src/docs/asciidoc/user-guide/appendix.adoc b/documentation/src/docs/asciidoc/user-guide/appendix.adoc index a5d8290b5d75..06a72fb4880b 100644 --- a/documentation/src/docs/asciidoc/user-guide/appendix.adoc +++ b/documentation/src/docs/asciidoc/user-guide/appendix.adoc @@ -133,100 +133,4 @@ following _OpenTest4J_ JAR. [[dependency-diagram]] === Dependency Diagram -[plantuml, component-diagram, svg] ----- -skinparam { - defaultFontName Open Sans -} - -package org.junit.jupiter { - [junit-jupiter] as jupiter - [junit-jupiter-api] as jupiter_api - [junit-jupiter-engine] as jupiter_engine - [junit-jupiter-params] as jupiter_params - [junit-jupiter-migrationsupport] as jupiter_migration_support -} - -package org.junit.vintage { - [junit-vintage-engine] as vintage_engine -} - -package org.junit.platform { - [junit-platform-commons] as commons - [junit-platform-console] as console - [junit-platform-engine] as engine - [junit-platform-jfr] as jfr - [junit-platform-launcher] as launcher - [junit-platform-reporting] as reporting - [junit-platform-runner] as runner - [junit-platform-suite] as suite - [junit-platform-suite-api] as suite_api - [junit-platform-suite-commons] as suite_commons - [junit-platform-suite-engine] as suite_engine - [junit-platform-testkit] as testkit -} - -package "JUnit 4" { - [junit:junit] as junit4 -} - -package org.opentest4j { - [opentest4j] -} - -package org.apiguardian { - [apiguardian-api] as apiguardian - note bottom of apiguardian #white - All artifacts except - opentest4j and junit:junit - have a dependency on this - artifact. The edges have - been omitted from this - diagram for the sake of - readability. - endnote -} - -jupiter ..> jupiter_api -jupiter ..> jupiter_params -jupiter ..> jupiter_engine - -jupiter_api ....> opentest4j -jupiter_api ...> commons - -jupiter_engine ...> engine -jupiter_engine ..> jupiter_api - -jupiter_params ..> jupiter_api -jupiter_migration_support ..> jupiter_api -jupiter_migration_support ...> junit4 - -console ..> launcher -console ..> reporting - -launcher ..> engine - -jfr ..> launcher - -engine ....> opentest4j -engine ..> commons - -reporting ..> launcher - -runner ..> suite_commons -runner ...> junit4 - -suite ..> suite_api -suite ..> suite_engine - -suite_engine ..> suite_commons - -suite_commons ..> launcher -suite_commons ..> suite_api - -testkit ....> opentest4j -testkit ..> launcher - -vintage_engine ...> engine -vintage_engine ..> junit4 ----- +image::{componentDiagramFile}[] diff --git a/documentation/src/plantuml/component-diagram.puml b/documentation/src/plantuml/component-diagram.puml new file mode 100644 index 000000000000..4874f5e1abb8 --- /dev/null +++ b/documentation/src/plantuml/component-diagram.puml @@ -0,0 +1,98 @@ +@startuml + +skinparam { + defaultFontName sans-serif +} + +package org.junit.jupiter { + [junit-jupiter] as jupiter + [junit-jupiter-api] as jupiter_api + [junit-jupiter-engine] as jupiter_engine + [junit-jupiter-params] as jupiter_params + [junit-jupiter-migrationsupport] as jupiter_migration_support +} + +package org.junit.vintage { + [junit-vintage-engine] as vintage_engine +} + +package org.junit.platform { + [junit-platform-commons] as commons + [junit-platform-console] as console + [junit-platform-engine] as engine + [junit-platform-jfr] as jfr + [junit-platform-launcher] as launcher + [junit-platform-reporting] as reporting + [junit-platform-runner] as runner + [junit-platform-suite] as suite + [junit-platform-suite-api] as suite_api + [junit-platform-suite-commons] as suite_commons + [junit-platform-suite-engine] as suite_engine + [junit-platform-testkit] as testkit +} + +package "JUnit 4" { + [junit:junit] as junit4 +} + +package org.opentest4j { + [opentest4j] +} + +package org.apiguardian { + [apiguardian-api] as apiguardian + note bottom of apiguardian #white + All artifacts except + opentest4j and junit:junit + have a dependency on this + artifact. The edges have + been omitted from this + diagram for the sake of + readability. + endnote +} + +jupiter ..> jupiter_api +jupiter ..> jupiter_params +jupiter ..> jupiter_engine + +jupiter_api ....> opentest4j +jupiter_api ...> commons + +jupiter_engine ...> engine +jupiter_engine ..> jupiter_api + +jupiter_params ..> jupiter_api +jupiter_migration_support ..> jupiter_api +jupiter_migration_support ...> junit4 + +console ..> launcher +console ..> reporting + +launcher ..> engine + +jfr ..> launcher + +engine ....> opentest4j +engine ..> commons + +reporting ..> launcher + +runner ..> suite_commons +runner ...> junit4 + +suite ..> suite_api +suite ..> suite_engine + +suite_engine ..> suite_commons + +suite_commons ..> launcher +suite_commons ..> suite_api + +testkit ....> opentest4j +testkit ..> launcher + +vintage_engine ...> engine +vintage_engine ..> junit4 + +@enduml diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b10b06ce8bc1..cd66d0e289bc 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -83,4 +83,5 @@ gitPublish = { id = "org.ajoberstar.git-publish", version = "4.2.0" } jmh = { id = "me.champeau.jmh", version = "0.7.1" } nohttp = { id = "io.spring.nohttp", version = "0.0.11" } nexusPublish = { id = "io.github.gradle-nexus.publish-plugin", version = "2.0.0-rc-1" } +plantuml = { id = "io.freefair.plantuml", version = "8.4" } versions = { id = "com.github.ben-manes.versions", version.ref = "gradleVersionsPlugin" } From 839440c65e8075ba7011b2c6863f4c4958b9412c Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Fri, 27 Oct 2023 14:32:47 +0200 Subject: [PATCH 30/45] Remove dependency on asciidoctorj-diagram (cherry picked from commit 544bc85d83e50349009a3d8a1424d32753926c9a) --- documentation/documentation.gradle.kts | 1 - gradle/libs.versions.toml | 4 +--- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/documentation/documentation.gradle.kts b/documentation/documentation.gradle.kts index fbe0413e3b54..ea2755cba333 100644 --- a/documentation/documentation.gradle.kts +++ b/documentation/documentation.gradle.kts @@ -64,7 +64,6 @@ dependencies { asciidoctorj { modules { - diagram.version(libs.versions.asciidoctorj.diagram) pdf.version(libs.versions.asciidoctorj.pdf) } requires(file("src/docs/asciidoc/resources/themes/rouge_junit.rb")) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index cd66d0e289bc..ff769d422941 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,6 @@ [versions] ant = "1.10.13" apiguardian = "1.1.2" -asciidoctorj-diagram = "2.2.9" asciidoctorj-pdf = "2.3.9" asciidoctor-plugins = "4.0.0-alpha.1" # Check if workaround in documentation.gradle.kts can be removed when upgrading assertj = "3.24.2" @@ -66,8 +65,7 @@ xmlunit-assertj = { module = "org.xmlunit:xmlunit-assertj3", version.ref = "xmlu xmlunit-placeholders = { module = "org.xmlunit:xmlunit-placeholders", version.ref = "xmlunit" } testingAnnotations = { module = "com.gradle:gradle-enterprise-testing-annotations", version = "1.1" } -# These are only declared here so Dependabot knows when to update the referenced versions -asciidoctorj-diagram = { module = "org.asciidoctor:asciidoctorj-diagram", version.ref = "asciidoctorj-diagram" } +# Only declared here so Dependabot knows when to update the referenced versions asciidoctorj-pdf = { module = "org.asciidoctor:asciidoctorj-pdf", version.ref = "asciidoctorj-pdf" } [bundles] From 747bbdcd81af2f86b40c316fd402b4492d7b1d1a Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Thu, 17 Aug 2023 16:15:20 +0200 Subject: [PATCH 31/45] Polishing (cherry picked from commit 4eca30b4fb8e79cb134d01c22556827e1563ef75) --- .../org/junit/platform/console/ConsoleLauncherTests.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherTests.java b/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherTests.java index b56c5dd1be42..80ec67b578a1 100644 --- a/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherTests.java +++ b/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherTests.java @@ -12,6 +12,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.params.provider.Arguments.arguments; import java.io.PrintWriter; import java.io.StringWriter; @@ -81,9 +82,9 @@ void executeWithoutCommandLineOptions(String command, int expectedExitCode) { static Stream commandsWithEmptyOptionExitCodes() { return Stream.of( // - Arguments.of("execute", -1), // - Arguments.of("discover", -1), // - Arguments.of("engines", 0) // + arguments("execute", -1), // + arguments("discover", -1), // + arguments("engines", 0) // ); } From 14196fbaf8191316668267f062168d90ec1a45a1 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Thu, 17 Aug 2023 16:17:10 +0200 Subject: [PATCH 32/45] Avoid NullPointerExceptions in console output in tests Prior to this commit, the following types of NullPointerExceptions were overlooked in the console output. java.lang.NullPointerException: Cannot invoke "org.junit.platform.console.tasks.ConsoleTestExecutor.execute(java.io.PrintWriter, java.util.Optional)" because the return value of "org.junit.platform.console.tasks.ConsoleTestExecutor$Factory.create(org.junit.platform.console.options.TestDiscoveryOptions, org.junit.platform.console.options.TestConsoleOutputOptions)" is null Although the NullPointerExceptions did not cause the tests to fail, this commit updates the tests to avoid them anyway. (cherry picked from commit bc862e01288a8392992c32c03dd8f25cb1b6023f) --- .../org/junit/platform/console/ConsoleLauncherTests.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherTests.java b/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherTests.java index 80ec67b578a1..00fed18c5351 100644 --- a/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherTests.java +++ b/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherTests.java @@ -34,17 +34,17 @@ class ConsoleLauncherTests { @ParameterizedTest(name = "{0}") @MethodSource("commandsWithEmptyOptionExitCodes") void displayHelp(String command) { - var consoleLauncher = new ConsoleLauncher((__, ___) -> null, printSink, printSink); + var consoleLauncher = new ConsoleLauncher(ConsoleTestExecutor::new, printSink, printSink); var exitCode = consoleLauncher.run(command, "--help").getExitCode(); assertEquals(0, exitCode); - assertThat(stringWriter.toString()).contains("--help"); + assertThat(stringWriter.toString()).contains("--help", "--disable-banner", "--scan-classpath" /* ... */); } @ParameterizedTest(name = "{0}") @MethodSource("commandsWithEmptyOptionExitCodes") void displayBanner(String command) { - var consoleLauncher = new ConsoleLauncher((__, ___) -> null, printSink, printSink); + var consoleLauncher = new ConsoleLauncher(ConsoleTestExecutor::new, printSink, printSink); consoleLauncher.run(command); assertThat(stringWriter.toString()).contains( @@ -54,7 +54,7 @@ void displayBanner(String command) { @ParameterizedTest(name = "{0}") @MethodSource("commandsWithEmptyOptionExitCodes") void disableBanner(String command, int expectedExitCode) { - var consoleLauncher = new ConsoleLauncher((__, ___) -> null, printSink, printSink); + var consoleLauncher = new ConsoleLauncher(ConsoleTestExecutor::new, printSink, printSink); var exitCode = consoleLauncher.run(command, "--disable-banner").getExitCode(); assertEquals(expectedExitCode, exitCode); From 342b5ea7432f16bccbfaeb2029b211f038d6986d Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Thu, 17 Aug 2023 16:41:27 +0200 Subject: [PATCH 33/45] Fix failing test in ConsoleLauncherTests (cherry picked from commit 9a9063d428151e35f95e661b442e057f468d9e89) --- .../java/org/junit/platform/console/ConsoleLauncherTests.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherTests.java b/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherTests.java index 00fed18c5351..37d9c9349a73 100644 --- a/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherTests.java +++ b/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherTests.java @@ -38,7 +38,7 @@ void displayHelp(String command) { var exitCode = consoleLauncher.run(command, "--help").getExitCode(); assertEquals(0, exitCode); - assertThat(stringWriter.toString()).contains("--help", "--disable-banner", "--scan-classpath" /* ... */); + assertThat(stringWriter.toString()).contains("--help", "--disable-banner" /* ... */); } @ParameterizedTest(name = "{0}") From ca814941264e4644e82d1754d903796b7961fed5 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Sun, 10 Sep 2023 12:48:11 +0200 Subject: [PATCH 34/45] Add support for JDK22 env var for Gradle toolchains (cherry picked from commit 0d6dca6c5e08b2d1ac0e3c594b571810d9a52b95) --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index c1f5d184ea40..cceb980ef64a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -21,7 +21,7 @@ org.gradle.jvmargs=-Xmx1g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryEr --add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED org.gradle.caching=true org.gradle.parallel=true -org.gradle.java.installations.fromEnv=JDK8,JDK18,JDK19,JDK20,JDK21 +org.gradle.java.installations.fromEnv=JDK8,JDK18,JDK19,JDK20,JDK21,JDK22 org.gradle.kotlin.dsl.allWarningsAsErrors=true # Test Distribution From 2efacd35cd3c3c0d5bd0e2ebf33767f3997f0f6f Mon Sep 17 00:00:00 2001 From: Andrei Rybak Date: Sun, 6 Aug 2023 19:27:17 +0200 Subject: [PATCH 35/45] Drop misleading messages in StandaloneTests Test `execute()` in StandaloneTests asserts matching of lines between expected standard output and actual output lines. It also passes the same actual lines (but in a `String` form) as a message to method assertLinesMatch, which takes three arguments. Same situation for standard error. Drop this confusing argument from the two calls to assertLinesMatch and let class `AssertLinesMatch` construct the error message for us. (cherry picked from commit 7da21dd0052a0a34afabaf01394adebbc4db489b) --- .../java/platform/tooling/support/tests/StandaloneTests.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/StandaloneTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/StandaloneTests.java index eb8eb20254f2..bea82965ae4e 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/StandaloneTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/StandaloneTests.java @@ -354,8 +354,8 @@ void execute() throws IOException { var workspace = Request.WORKSPACE.resolve("standalone"); var expectedOutLines = Files.readAllLines(workspace.resolve("expected-out.txt")); var expectedErrLines = Files.readAllLines(workspace.resolve("expected-err.txt")); - assertLinesMatch(expectedOutLines, result.getOutputLines("out"), result.getOutput("out")); - assertLinesMatch(expectedErrLines, result.getOutputLines("err"), result.getOutput("err")); + assertLinesMatch(expectedOutLines, result.getOutputLines("out")); + assertLinesMatch(expectedErrLines, result.getOutputLines("err")); var jupiterVersion = Helper.version("junit-jupiter-engine"); var vintageVersion = Helper.version("junit-vintage-engine"); From 9ec57661c78c3889db004ab6a89416e56a2fb2e0 Mon Sep 17 00:00:00 2001 From: Andrei Rybak Date: Sun, 6 Aug 2023 19:27:17 +0200 Subject: [PATCH 36/45] Unify messages about exit codes in StandaloneTests Some tests in StandaloneTests provide concatenation of standard output and standard error as a message for the assertion about exit code, while other tests provide only standard output. Some use method de.sormuras.bartholdy.Result#getOutput while others use method de.sormuras.bartholdy.Result#getOutputLines. Unify these approaches by replacing the messages with a supplier that gives a) a human-readable message about exit codes, and b) both standard output and standard error. (cherry picked from commit 3184a6e350590aa23142cb31d919bd68c257cad4) --- .../support/tests/StandaloneTests.java | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/StandaloneTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/StandaloneTests.java index bea82965ae4e..3747e6d6dfeb 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/StandaloneTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/StandaloneTests.java @@ -69,7 +69,7 @@ void listAllObservableEngines() { .addArguments("engines", "--disable-banner").build() // .run(false); - assertEquals(0, result.getExitCode(), String.join("\n", result.getOutputLines("out"))); + assertEquals(0, result.getExitCode(), () -> getExitCodeMessage(result)); var jupiterVersion = Helper.version("junit-jupiter-engine"); var suiteVersion = Helper.version("junit-platform-suite-engine"); @@ -98,7 +98,7 @@ void compile() throws Exception { .addArguments(workspace.resolve("src/standalone/VintageIntegration.java")).build() // .run(); - assertEquals(0, result.getExitCode(), result.getOutput("out") + result.getOutput("err")); + assertEquals(0, result.getExitCode(), () -> getExitCodeMessage(result)); assertTrue(result.getOutput("out").isEmpty()); assertTrue(result.getOutput("err").isEmpty()); @@ -327,7 +327,7 @@ private static Result discover(String... args) { .build() // .run(false); - assertEquals(0, result.getExitCode(), String.join("\n", result.getOutputLines("out"))); + assertEquals(0, result.getExitCode(), () -> getExitCodeMessage(result)); return result; } @@ -349,7 +349,7 @@ void execute() throws IOException { .addArguments("--classpath", "bin").build() // .run(false); - assertEquals(1, result.getExitCode(), String.join("\n", result.getOutputLines("out"))); + assertEquals(1, result.getExitCode(), () -> getExitCodeMessage(result)); var workspace = Request.WORKSPACE.resolve("standalone"); var expectedOutLines = Files.readAllLines(workspace.resolve("expected-out.txt")); @@ -384,7 +384,7 @@ void executeOnJava8() throws IOException { .addArguments("--classpath", "bin").build() // .run(false); - assertEquals(1, result.getExitCode(), String.join("\n", result.getOutputLines("out"))); + assertEquals(1, result.getExitCode(), () -> getExitCodeMessage(result)); var workspace = Request.WORKSPACE.resolve("standalone"); var expectedOutLines = Files.readAllLines(workspace.resolve("expected-out.txt")); @@ -420,7 +420,7 @@ void executeOnJava8SelectPackage() throws IOException { .addArguments("--classpath", "bin").build() // .run(false); - assertEquals(1, result.getExitCode(), String.join("\n", result.getOutputLines("out"))); + assertEquals(1, result.getExitCode(), () -> getExitCodeMessage(result)); var workspace = Request.WORKSPACE.resolve("standalone"); var expectedOutLines = Files.readAllLines(workspace.resolve("expected-out.txt")); @@ -461,6 +461,11 @@ void executeWithJarredTestClasses() { .build() // .run(false); - assertEquals(1, result.getExitCode(), String.join("\n", result.getOutputLines("out"))); + assertEquals(1, result.getExitCode(), () -> getExitCodeMessage(result)); + } + + private static String getExitCodeMessage(Result result) { + return "Exit codes don't match. Stdout:\n" + result.getOutput("out") + // + "\n\nStderr:\n" + result.getOutput("err") + "\n"; } } From 808493ab09b30970b506a48fda3d616ac1ba4fff Mon Sep 17 00:00:00 2001 From: Andrei Rybak Date: Sun, 6 Aug 2023 23:47:02 +0200 Subject: [PATCH 37/45] Run StandaloneTests for Java 8 under Java 8 Problem ------- Tests that claim by their name to run on Java 8 don't actually run on Java 8. This can be clear from the output for tests that add option `--show-version` to the arguments and _don't_ fail -- they all print the version for the current JDK. The tool `java` of JDK 8 does _not_ have the option `--show-version`. The actual option that exists in JDK 8 has fewer hyphens, as per documentation of Java 8 [1]: -showversion Displays version information and continues execution of the application. This option is equivalent to the `-version` option except that the latter instructs the JVM to exit after displaying version information. And when I actually run Java 8 binary with the incorrect option `--show-version` used by the affected tests, I get: $ /usr/lib/jvm/java-8-openjdk-amd64/bin/java --show-version Unrecognized option: --show-version Error: Could not create the Java Virtual Machine. Error: A fatal exception has occurred. Program will exit. The option `--show-version` was only added in Java 9 [2]. Explanation ----------- In actuality, the tests are being run on whatever the current JDK is. These tests create an instance of class `de.sormuras.bartholdy.tool.Java`, which importantly has the following method: @Override public Path getHome() { return Bartholdy.currentJdkHome(); } When the `bartholdy` library creates the process, class `AbstractTool` does the following: protected Path createPathToProgram() { return getHome().resolve("bin").resolve(getProgram()); } The string `"java"` returned from method `getProgram` of class `Java` gets resolved against `Bartholdy.currentJdkHome()`. As far as I can tell, the library doesn't promise to look up the `java` binary in the `JAVA_HOME` supplied in the environment. In fact, just before consuming library user's environment, method `run()` of class `de.sormuras.bartholdy.tool.AbstractTool` puts the current JDK as `JAVA_HOME` into the environment to correspond to the behavior of class `de.sormuras.bartholdy.tool.Java` described above: builder.environment().put("JAVA_HOME", Bartholdy.currentJdkHome().toString()); The issue has been present since commit [3] where these tests were introduced. Fix --- Fix affected tests to run them under actual Java 8 by overriding method `de.sormuras.bartholdy.tool.Java#getHome`. Replace erroneous option `--show-version` with `-showversion`. To make tests executeOnJava8() and executeOnJava8SelectPackage() see the class files, update test compile() to use option `--release 8`. Because compiling to release 8 is deprecated, add a linter option to disable the warning to make compile() pass. Because option `-showversion` of Java 8 behaves slightly differently to option `--show-version` of later versions of Java, prepare two new files for expected stdout and stderr: expected-out-java8.txt and expected-err-java8.txt, which are similar to existing files expected-out.txt and expected-err.txt, but have different layout of fastforward lines "JAVA VERSION" and "TREE". Footnotes --------- [1] "Java Platform, Standard Edition Tools Reference", "java" https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html https://docs.oracle.com/javase/8/docs/technotes/tools/windows/java.html [2] https://docs.oracle.com/javase/9/tools/java.htm > `--show-version` or `-showversion` > > Displays version information and continues execution of the application. > This option is equivalent to the `-version` option except that the latter > instructs the JVM to exit after displaying version information. [3] c62cd6ab11 (Fix package path computation in `ClasspathScanner`, 2021-05-12) from https://github.com/junit-team/junit5/pull/2613 (cherry picked from commit 977c85fc31ad6825b4c68f6c6c972a93356ffe74) --- .../standalone/expected-err-java8.txt | 21 ++++++++++ .../standalone/expected-out-java8.txt | 18 ++++++++ .../support/tests/StandaloneTests.java | 42 ++++++++++++++----- 3 files changed, 71 insertions(+), 10 deletions(-) create mode 100644 platform-tooling-support-tests/projects/standalone/expected-err-java8.txt create mode 100644 platform-tooling-support-tests/projects/standalone/expected-out-java8.txt diff --git a/platform-tooling-support-tests/projects/standalone/expected-err-java8.txt b/platform-tooling-support-tests/projects/standalone/expected-err-java8.txt new file mode 100644 index 000000000000..70a3db689cc9 --- /dev/null +++ b/platform-tooling-support-tests/projects/standalone/expected-err-java8.txt @@ -0,0 +1,21 @@ +>> JAVA VERSION >> +.+ org.junit.platform.launcher.core.ServiceLoaderRegistry load +.+ Loaded LauncherInterceptor instances: .. +.+ org.junit.platform.launcher.core.ServiceLoaderRegistry load +.+ Loaded LauncherSessionListener instances: .. +.+ org.junit.platform.launcher.core.ServiceLoaderTestEngineRegistry loadTestEngines +.+ Discovered TestEngines: +- junit-jupiter .+ +- junit-vintage .+ +- junit-platform-suite .+ +.+ org.junit.platform.launcher.core.ServiceLoaderRegistry load +.+ Loaded PostDiscoveryFilter instances: .. +.+ org.junit.platform.launcher.core.ServiceLoaderRegistry load +.+ Loaded LauncherDiscoveryListener instances: .. +.+ org.junit.platform.launcher.core.ServiceLoaderRegistry load +.+ Loaded TestExecutionListener instances: .+ +.+ org.junit.platform.launcher.core.ServiceLoaderTestEngineRegistry loadTestEngines +.+ Discovered TestEngines: +- junit-jupiter .+ +- junit-vintage .+ +- junit-platform-suite .+ diff --git a/platform-tooling-support-tests/projects/standalone/expected-out-java8.txt b/platform-tooling-support-tests/projects/standalone/expected-out-java8.txt new file mode 100644 index 000000000000..0a1cc1ecfba8 --- /dev/null +++ b/platform-tooling-support-tests/projects/standalone/expected-out-java8.txt @@ -0,0 +1,18 @@ +>> TREE >> +Failures (2): +>> STACKTRACE >> + +Test run finished after \d+ ms +[ 11 containers found ] +[ 0 containers skipped ] +[ 11 containers started ] +[ 0 containers aborted ] +[ 11 containers successful ] +[ 0 containers failed ] +[ 10 tests found ] +[ 2 tests skipped ] +[ 8 tests started ] +[ 1 tests aborted ] +[ 5 tests successful ] +[ 2 tests failed ] + diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/StandaloneTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/StandaloneTests.java index 3747e6d6dfeb..33c5051de2c0 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/StandaloneTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/StandaloneTests.java @@ -89,6 +89,8 @@ void compile() throws Exception { var result = Request.builder() // .setTool(new Javac()) // .setProject("standalone") // + .addArguments("-Xlint:-options") // + .addArguments("--release", "8") // .addArguments("-proc:none") // .addArguments("-d", workspace.resolve("bin")) // .addArguments("--class-path", MavenRepo.jar("junit-platform-console-standalone")) // @@ -368,11 +370,12 @@ void execute() throws IOException { @Test @Order(4) void executeOnJava8() throws IOException { + Java java8 = getJava8(); var result = Request.builder() // - .setTool(new Java()) // - .setJavaHome(Helper.getJavaHome("8").orElseThrow(TestAbortedException::new)) // + .setTool(java8) // + .setJavaHome(java8.getHome()) // .setProject("standalone") // - .addArguments("--show-version") // + .addArguments("-showversion") // .addArguments("-enableassertions") // .addArguments("-Djava.util.logging.config.file=logging.properties") // .addArguments("-Djunit.platform.launcher.interceptors.enabled=true") // @@ -387,8 +390,8 @@ void executeOnJava8() throws IOException { assertEquals(1, result.getExitCode(), () -> getExitCodeMessage(result)); var workspace = Request.WORKSPACE.resolve("standalone"); - var expectedOutLines = Files.readAllLines(workspace.resolve("expected-out.txt")); - var expectedErrLines = Files.readAllLines(workspace.resolve("expected-err.txt")); + var expectedOutLines = Files.readAllLines(workspace.resolve("expected-out-java8.txt")); + var expectedErrLines = Files.readAllLines(workspace.resolve("expected-err-java8.txt")); assertLinesMatch(expectedOutLines, result.getOutputLines("out")); assertLinesMatch(expectedErrLines, result.getOutputLines("err")); @@ -404,11 +407,12 @@ void executeOnJava8() throws IOException { @Order(5) // https://github.com/junit-team/junit5/issues/2600 void executeOnJava8SelectPackage() throws IOException { + Java java8 = getJava8(); var result = Request.builder() // - .setTool(new Java()) // - .setJavaHome(Helper.getJavaHome("8").orElseThrow(TestAbortedException::new)) // + .setTool(java8) // + .setJavaHome(java8.getHome()) // .setProject("standalone") // - .addArguments("--show-version") // + .addArguments("-showversion") // .addArguments("-enableassertions") // .addArguments("-Djava.util.logging.config.file=logging.properties") // .addArguments("-Djunit.platform.launcher.interceptors.enabled=true") // @@ -423,8 +427,8 @@ void executeOnJava8SelectPackage() throws IOException { assertEquals(1, result.getExitCode(), () -> getExitCodeMessage(result)); var workspace = Request.WORKSPACE.resolve("standalone"); - var expectedOutLines = Files.readAllLines(workspace.resolve("expected-out.txt")); - var expectedErrLines = Files.readAllLines(workspace.resolve("expected-err.txt")); + var expectedOutLines = Files.readAllLines(workspace.resolve("expected-out-java8.txt")); + var expectedErrLines = Files.readAllLines(workspace.resolve("expected-err-java8.txt")); assertLinesMatch(expectedOutLines, result.getOutputLines("out")); assertLinesMatch(expectedErrLines, result.getOutputLines("err")); @@ -468,4 +472,22 @@ private static String getExitCodeMessage(Result result) { return "Exit codes don't match. Stdout:\n" + result.getOutput("out") + // "\n\nStderr:\n" + result.getOutput("err") + "\n"; } + + /** + * Special override of class {@link Java} to resolve against a different {@code JAVA_HOME}. + */ + private static Java getJava8() { + Path java8Home = Helper.getJavaHome("8").orElseThrow(TestAbortedException::new); + return new Java() { + @Override + public Path getHome() { + return java8Home; + } + + @Override + public String getVersion() { + return "8"; + } + }; + } } From c5567354c2556e772f8a0035ef7647161011d1c0 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Sun, 10 Sep 2023 12:37:53 +0200 Subject: [PATCH 38/45] Use same expected files for all JDK versions (cherry picked from commit e802db4643085cd1c609ac84aa3e635f98719d3f) --- .../standalone/expected-err-java8.txt | 21 ------------------- .../standalone/expected-out-java8.txt | 18 ---------------- .../support/tests/StandaloneTests.java | 16 ++++++++++---- 3 files changed, 12 insertions(+), 43 deletions(-) delete mode 100644 platform-tooling-support-tests/projects/standalone/expected-err-java8.txt delete mode 100644 platform-tooling-support-tests/projects/standalone/expected-out-java8.txt diff --git a/platform-tooling-support-tests/projects/standalone/expected-err-java8.txt b/platform-tooling-support-tests/projects/standalone/expected-err-java8.txt deleted file mode 100644 index 70a3db689cc9..000000000000 --- a/platform-tooling-support-tests/projects/standalone/expected-err-java8.txt +++ /dev/null @@ -1,21 +0,0 @@ ->> JAVA VERSION >> -.+ org.junit.platform.launcher.core.ServiceLoaderRegistry load -.+ Loaded LauncherInterceptor instances: .. -.+ org.junit.platform.launcher.core.ServiceLoaderRegistry load -.+ Loaded LauncherSessionListener instances: .. -.+ org.junit.platform.launcher.core.ServiceLoaderTestEngineRegistry loadTestEngines -.+ Discovered TestEngines: -- junit-jupiter .+ -- junit-vintage .+ -- junit-platform-suite .+ -.+ org.junit.platform.launcher.core.ServiceLoaderRegistry load -.+ Loaded PostDiscoveryFilter instances: .. -.+ org.junit.platform.launcher.core.ServiceLoaderRegistry load -.+ Loaded LauncherDiscoveryListener instances: .. -.+ org.junit.platform.launcher.core.ServiceLoaderRegistry load -.+ Loaded TestExecutionListener instances: .+ -.+ org.junit.platform.launcher.core.ServiceLoaderTestEngineRegistry loadTestEngines -.+ Discovered TestEngines: -- junit-jupiter .+ -- junit-vintage .+ -- junit-platform-suite .+ diff --git a/platform-tooling-support-tests/projects/standalone/expected-out-java8.txt b/platform-tooling-support-tests/projects/standalone/expected-out-java8.txt deleted file mode 100644 index 0a1cc1ecfba8..000000000000 --- a/platform-tooling-support-tests/projects/standalone/expected-out-java8.txt +++ /dev/null @@ -1,18 +0,0 @@ ->> TREE >> -Failures (2): ->> STACKTRACE >> - -Test run finished after \d+ ms -[ 11 containers found ] -[ 0 containers skipped ] -[ 11 containers started ] -[ 0 containers aborted ] -[ 11 containers successful ] -[ 0 containers failed ] -[ 10 tests found ] -[ 2 tests skipped ] -[ 8 tests started ] -[ 1 tests aborted ] -[ 5 tests successful ] -[ 2 tests failed ] - diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/StandaloneTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/StandaloneTests.java index 33c5051de2c0..d4775c42f64c 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/StandaloneTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/StandaloneTests.java @@ -21,6 +21,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; +import java.util.List; import de.sormuras.bartholdy.Result; import de.sormuras.bartholdy.jdk.Jar; @@ -390,8 +391,8 @@ void executeOnJava8() throws IOException { assertEquals(1, result.getExitCode(), () -> getExitCodeMessage(result)); var workspace = Request.WORKSPACE.resolve("standalone"); - var expectedOutLines = Files.readAllLines(workspace.resolve("expected-out-java8.txt")); - var expectedErrLines = Files.readAllLines(workspace.resolve("expected-err-java8.txt")); + var expectedOutLines = Files.readAllLines(workspace.resolve("expected-out.txt")); + var expectedErrLines = getExpectedErrLinesOnJava8(workspace); assertLinesMatch(expectedOutLines, result.getOutputLines("out")); assertLinesMatch(expectedErrLines, result.getOutputLines("err")); @@ -427,8 +428,8 @@ void executeOnJava8SelectPackage() throws IOException { assertEquals(1, result.getExitCode(), () -> getExitCodeMessage(result)); var workspace = Request.WORKSPACE.resolve("standalone"); - var expectedOutLines = Files.readAllLines(workspace.resolve("expected-out-java8.txt")); - var expectedErrLines = Files.readAllLines(workspace.resolve("expected-err-java8.txt")); + var expectedOutLines = Files.readAllLines(workspace.resolve("expected-out.txt")); + var expectedErrLines = getExpectedErrLinesOnJava8(workspace); assertLinesMatch(expectedOutLines, result.getOutputLines("out")); assertLinesMatch(expectedErrLines, result.getOutputLines("err")); @@ -440,6 +441,13 @@ void executeOnJava8SelectPackage() throws IOException { + " (group ID: org.junit.vintage, artifact ID: junit-vintage-engine, version: " + vintageVersion)); } + private static List getExpectedErrLinesOnJava8(Path workspace) throws IOException { + var expectedErrLines = new ArrayList(); + expectedErrLines.add(">> JAVA VERSION >>"); + expectedErrLines.addAll(Files.readAllLines(workspace.resolve("expected-err.txt"))); + return expectedErrLines; + } + @Test @Order(6) @Disabled("https://github.com/junit-team/junit5/issues/1724") From fea05c3aa80de76686f326b5ce26ddf7f153ff5a Mon Sep 17 00:00:00 2001 From: Christian Stein Date: Mon, 28 Aug 2023 11:17:00 +0200 Subject: [PATCH 39/45] Fix ConsoleLauncherTests and StandaloneTests (cherry picked from commit 548fb693ad00a924a71e04d07c5f90b4f5d84014) --- .../java/org/junit/platform/console/ConsoleLauncherTests.java | 3 +-- .../java/platform/tooling/support/tests/StandaloneTests.java | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherTests.java b/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherTests.java index 37d9c9349a73..70e6bd21d536 100644 --- a/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherTests.java +++ b/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherTests.java @@ -47,8 +47,7 @@ void displayBanner(String command) { var consoleLauncher = new ConsoleLauncher(ConsoleTestExecutor::new, printSink, printSink); consoleLauncher.run(command); - assertThat(stringWriter.toString()).contains( - "Thanks for using JUnit! Support its development at https://junit.org/sponsoring"); + assertThat(stringWriter.toString()).contains("Thanks for using JUnit!"); } @ParameterizedTest(name = "{0}") diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/StandaloneTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/StandaloneTests.java index d4775c42f64c..a80071de8d41 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/StandaloneTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/StandaloneTests.java @@ -67,7 +67,7 @@ void listAllObservableEngines() { .setTool(new Java()) // .setProject("standalone") // .addArguments("-jar", MavenRepo.jar("junit-platform-console-standalone")) // - .addArguments("engines", "--disable-banner").build() // + .addArguments("engines", "--disable-ansi-colors", "--disable-banner").build() // .run(false); assertEquals(0, result.getExitCode(), () -> getExitCodeMessage(result)); From 5ce280eff69b43759a3cb0c176207abe0a41b579 Mon Sep 17 00:00:00 2001 From: Christian Stein Date: Tue, 29 Aug 2023 07:15:27 +0200 Subject: [PATCH 40/45] Update picocli to 4.7.5 and enable help width computation (cherry picked from commit 50d1d4d86037e6381a12517ddaf540862e23e97e) --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ff769d422941..8adadb5a3262 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -57,7 +57,7 @@ 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" } -picocli = { module = "info.picocli:picocli", version = "4.7.4" } +picocli = { module = "info.picocli:picocli", version = "4.7.5" } slf4j-julBinding = { module = "org.slf4j:slf4j-jdk14", version = "2.0.7" } spock1 = { module = "org.spockframework:spock-core", version = "1.3-groovy-2.5" } univocity-parsers = { module = "com.univocity:univocity-parsers", version = "2.9.1" } From 1d1eb8571552bbf28e578241965010de6c8ee779 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Sat, 4 Nov 2023 12:41:35 +0100 Subject: [PATCH 41/45] Polishing (cherry picked from commit c295788bb031cdf8101ce18e2c15e564721d70b3) --- .../commons/util/ReflectionUtilsTests.java | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/platform-tests/src/test/java/org/junit/platform/commons/util/ReflectionUtilsTests.java b/platform-tests/src/test/java/org/junit/platform/commons/util/ReflectionUtilsTests.java index 714ecab17e05..3e11222be2e3 100644 --- a/platform-tests/src/test/java/org/junit/platform/commons/util/ReflectionUtilsTests.java +++ b/platform-tests/src/test/java/org/junit/platform/commons/util/ReflectionUtilsTests.java @@ -25,6 +25,7 @@ import static org.junit.platform.commons.function.Try.success; import static org.junit.platform.commons.util.ReflectionUtils.HierarchyTraversalMode.BOTTOM_UP; import static org.junit.platform.commons.util.ReflectionUtils.HierarchyTraversalMode.TOP_DOWN; +import static org.junit.platform.commons.util.ReflectionUtils.findFields; import static org.junit.platform.commons.util.ReflectionUtils.findMethod; import static org.junit.platform.commons.util.ReflectionUtils.findMethods; import static org.junit.platform.commons.util.ReflectionUtils.invokeMethod; @@ -1388,7 +1389,7 @@ void readFieldValuesPreconditions() { @Test void readFieldValuesFromInstance() { - var fields = ReflectionUtils.findFields(ClassWithFields.class, f -> true, TOP_DOWN); + var fields = findFields(ClassWithFields.class, f -> true, TOP_DOWN); var values = ReflectionUtils.readFieldValues(fields, new ClassWithFields()); @@ -1397,7 +1398,7 @@ void readFieldValuesFromInstance() { @Test void readFieldValuesFromClass() { - var fields = ReflectionUtils.findFields(ClassWithFields.class, ReflectionUtils::isStatic, TOP_DOWN); + var fields = findFields(ClassWithFields.class, ReflectionUtils::isStatic, TOP_DOWN); var values = ReflectionUtils.readFieldValues(fields, null); @@ -1406,7 +1407,7 @@ void readFieldValuesFromClass() { @Test void readFieldValuesFromInstanceWithTypeFilterForString() { - var fields = ReflectionUtils.findFields(ClassWithFields.class, isA(String.class), TOP_DOWN); + var fields = findFields(ClassWithFields.class, isA(String.class), TOP_DOWN); var values = ReflectionUtils.readFieldValues(fields, new ClassWithFields(), isA(String.class)); @@ -1415,8 +1416,7 @@ void readFieldValuesFromInstanceWithTypeFilterForString() { @Test void readFieldValuesFromClassWithTypeFilterForString() { - var fields = ReflectionUtils.findFields(ClassWithFields.class, isA(String.class).and(ReflectionUtils::isStatic), - TOP_DOWN); + var fields = findFields(ClassWithFields.class, isA(String.class).and(ReflectionUtils::isStatic), TOP_DOWN); var values = ReflectionUtils.readFieldValues(fields, null, isA(String.class)); @@ -1425,7 +1425,7 @@ void readFieldValuesFromClassWithTypeFilterForString() { @Test void readFieldValuesFromInstanceWithTypeFilterForInteger() { - var fields = ReflectionUtils.findFields(ClassWithFields.class, isA(int.class), TOP_DOWN); + var fields = findFields(ClassWithFields.class, isA(int.class), TOP_DOWN); var values = ReflectionUtils.readFieldValues(fields, new ClassWithFields(), isA(int.class)); @@ -1434,8 +1434,7 @@ void readFieldValuesFromInstanceWithTypeFilterForInteger() { @Test void readFieldValuesFromClassWithTypeFilterForInteger() { - var fields = ReflectionUtils.findFields(ClassWithFields.class, - isA(Integer.class).and(ReflectionUtils::isStatic), TOP_DOWN); + var fields = findFields(ClassWithFields.class, isA(Integer.class).and(ReflectionUtils::isStatic), TOP_DOWN); var values = ReflectionUtils.readFieldValues(fields, null, isA(Integer.class)); @@ -1444,7 +1443,7 @@ void readFieldValuesFromClassWithTypeFilterForInteger() { @Test void readFieldValuesFromInstanceWithTypeFilterForDouble() { - var fields = ReflectionUtils.findFields(ClassWithFields.class, isA(double.class), TOP_DOWN); + var fields = findFields(ClassWithFields.class, isA(double.class), TOP_DOWN); var values = ReflectionUtils.readFieldValues(fields, new ClassWithFields(), isA(double.class)); @@ -1453,8 +1452,7 @@ void readFieldValuesFromInstanceWithTypeFilterForDouble() { @Test void readFieldValuesFromClassWithTypeFilterForDouble() { - var fields = ReflectionUtils.findFields(ClassWithFields.class, isA(Double.class).and(ReflectionUtils::isStatic), - TOP_DOWN); + var fields = findFields(ClassWithFields.class, isA(Double.class).and(ReflectionUtils::isStatic), TOP_DOWN); var values = ReflectionUtils.readFieldValues(fields, null, isA(Double.class)); From f82dd1e716f8717e012152b1d1d5cc0da10d33cd Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Fri, 3 Nov 2023 18:25:52 +0100 Subject: [PATCH 42/45] Apply field predicate before searching type hierarchy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Prior to this commit, findFields() and streamFields() in ReflectionSupport as well as findAnnotatedFields() and findAnnotatedFieldValues() in AnnotationSupport first searched for all fields in the type hierarchy and then applied the user-supplied predicate (or "is annotated?" predicate) afterwards. This resulted in fields in subclasses incorrectly "shadowing" package-private fields in superclasses (in a different package) even if the predicate would otherwise exclude the field in such a subclass. For example, given a superclass that declares a package-private static @⁠TempDir "tempDir" field and a subclass (in a different package) that declares a @⁠TempDir "tempDir" field, when JUnit Jupiter looked up @⁠TempDir fields for the subclass, the @⁠TempDir "tempDir" field in the superclass was not found because the @⁠TempDir "tempDir" field shadowed it based solely on the field signature, ignoring the type of annotation sought. To address that, this commit modifies the internal search algorithms in ReflectionUtils so that field predicates are applied while searching the hierarchy for fields. See #3498 Closes #3532 Closes #3533 (cherry picked from commit f30a8d551795653843e3c44c1af01001bddfd846) --- .../release-notes/release-notes-5.10.1.adoc | 12 ++++- .../commons/util/ReflectionUtils.java | 44 ++++++++++++------- .../commons/util/AnnotationUtilsTests.java | 26 +++++++++++ .../commons/util/ReflectionUtilsTests.java | 24 ++++++++++ .../commons/util/pkg1/ClassLevelDir.java | 24 ++++++++++ .../commons/util/pkg1/InstanceLevelDir.java | 24 ++++++++++ ...sWithStaticPackagePrivateTempDirField.java | 23 ++++++++++ ...thNonStaticPackagePrivateTempDirField.java | 26 +++++++++++ 8 files changed, 184 insertions(+), 19 deletions(-) create mode 100644 platform-tests/src/test/java/org/junit/platform/commons/util/pkg1/ClassLevelDir.java create mode 100644 platform-tests/src/test/java/org/junit/platform/commons/util/pkg1/InstanceLevelDir.java create mode 100644 platform-tests/src/test/java/org/junit/platform/commons/util/pkg1/SuperclassWithStaticPackagePrivateTempDirField.java create mode 100644 platform-tests/src/test/java/org/junit/platform/commons/util/pkg1/subpkg/SubclassWithNonStaticPackagePrivateTempDirField.java diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.10.1.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.10.1.adoc index 65c3aa6055b4..515aa64f41b1 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-5.10.1.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.10.1.adoc @@ -15,6 +15,10 @@ JUnit repository on GitHub. ==== Bug Fixes +* Field predicates are now applied while searching the type hierarchy. This fixes bugs in + `findFields(...)` and `streamFields(...)` in `ReflectionSupport` as well as + `findAnnotatedFields(...)` and `findAnnotatedFieldValues(...)` in `AnnotationSupport`. + - See link:https://github.com/junit-team/junit5/issues/3532[issue 3532] for details. * Method predicates are now applied while searching the type hierarchy. This fixes bugs in `findMethods(...)` and `streamMethods(...)` in `ReflectionSupport` as well as `findAnnotatedMethods(...)` in `AnnotationSupport`. @@ -26,16 +30,20 @@ JUnit repository on GitHub. ==== Bug Fixes +* A package-private static field annotated with `@TempDir` is no longer _shadowed_ by a + non-static field annotated with `@TempDir` when the non-static field resides in a + different package and has the same name as the static field. + - See link:https://github.com/junit-team/junit5/issues/3532[issue 3532] for details. * A package-private class-level lifecycle method annotated with `@BeforeAll` or `@AfterAll` is no longer _shadowed_ by a method-level lifecycle method annotated with `@BeforeEach` or `@AfterEach` when the method-level lifecycle method resides in a different package and has the same name as the class-level lifecycle method. - See link:https://github.com/junit-team/junit5/issues/3498[issue 3498] for details. +* The `ON_SUCCESS` cleanup mode of `@TempDir` now takes into account failures of test + methods and nested tests when it's declared on the class level, e.g. as a static field. * The `RandomNumberExtension` example in the <<../user-guide/index.adoc#extensions-RandomNumberExtension, User Guide>> has been updated to properly support `Integer` types as well as non-static field injection. -* The `ON_SUCCESS` cleanup mode of `@TempDir` now takes into account failures of test - methods and nested tests when it's declared on the class level, e.g. as a static field. ==== New Features and Improvements 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 927b97451e0a..ea824ebd23a9 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 @@ -1238,6 +1238,7 @@ public static List> findConstructors(Class clazz, Predicate findFields(Class clazz, Predicate predicate, HierarchyTraversalMode traversalMode) { + return streamFields(clazz, predicate, traversalMode).collect(toUnmodifiableList()); } @@ -1252,21 +1253,23 @@ public static Stream streamFields(Class clazz, Predicate predic Preconditions.notNull(predicate, "Predicate must not be null"); Preconditions.notNull(traversalMode, "HierarchyTraversalMode must not be null"); - return findAllFieldsInHierarchy(clazz, traversalMode).stream().filter(predicate); + return findAllFieldsInHierarchy(clazz, predicate, traversalMode).stream(); } - private static List findAllFieldsInHierarchy(Class clazz, HierarchyTraversalMode traversalMode) { + private static List findAllFieldsInHierarchy(Class clazz, Predicate predicate, + HierarchyTraversalMode traversalMode) { + Preconditions.notNull(clazz, "Class must not be null"); Preconditions.notNull(traversalMode, "HierarchyTraversalMode must not be null"); // @formatter:off - List localFields = getDeclaredFields(clazz).stream() + List localFields = getDeclaredFields(clazz, predicate).stream() .filter(field -> !field.isSynthetic()) .collect(toList()); - List superclassFields = getSuperclassFields(clazz, traversalMode).stream() + List superclassFields = getSuperclassFields(clazz, predicate, traversalMode).stream() .filter(field -> !isFieldShadowedByLocalFields(field, localFields)) .collect(toList()); - List interfaceFields = getInterfaceFields(clazz, traversalMode).stream() + List interfaceFields = getInterfaceFields(clazz, predicate, traversalMode).stream() .filter(field -> !isFieldShadowedByLocalFields(field, localFields)) .collect(toList()); // @formatter:on @@ -1529,18 +1532,20 @@ private static List findAllMethodsInHierarchy(Class clazz, Predicate< /** * Custom alternative to {@link Class#getFields()} that sorts the fields - * and converts them to a mutable list. + * which match the supplied predicate and converts them to a mutable list. + * @param predicate the field filter; never {@code null} */ - private static List getFields(Class clazz) { - return toSortedMutableList(clazz.getFields()); + private static List getFields(Class clazz, Predicate predicate) { + return toSortedMutableList(clazz.getFields(), predicate); } /** * Custom alternative to {@link Class#getDeclaredFields()} that sorts the - * fields and converts them to a mutable list. + * fields which match the supplied predicate and converts them to a mutable list. + * @param predicate the field filter; never {@code null} */ - private static List getDeclaredFields(Class clazz) { - return toSortedMutableList(clazz.getDeclaredFields()); + private static List getDeclaredFields(Class clazz, Predicate predicate) { + return toSortedMutableList(clazz.getDeclaredFields(), predicate); } /** @@ -1602,9 +1607,10 @@ private static List getDefaultMethods(Class clazz) { // @formatter:on } - private static List toSortedMutableList(Field[] fields) { + private static List toSortedMutableList(Field[] fields, Predicate predicate) { // @formatter:off return Arrays.stream(fields) + .filter(predicate) .sorted(ReflectionUtils::defaultFieldSorter) // Use toCollection() instead of toList() to ensure list is mutable. .collect(toCollection(ArrayList::new)); @@ -1672,13 +1678,15 @@ private static List getInterfaceMethods(Class clazz, Predicate getInterfaceFields(Class clazz, HierarchyTraversalMode traversalMode) { + private static List getInterfaceFields(Class clazz, Predicate predicate, + HierarchyTraversalMode traversalMode) { + List allInterfaceFields = new ArrayList<>(); for (Class ifc : clazz.getInterfaces()) { - List localInterfaceFields = getFields(ifc); + List localInterfaceFields = getFields(ifc, predicate); // @formatter:off - List superinterfaceFields = getInterfaceFields(ifc, traversalMode).stream() + List superinterfaceFields = getInterfaceFields(ifc, predicate, traversalMode).stream() .filter(field -> !isFieldShadowedByLocalFields(field, localInterfaceFields)) .collect(toList()); // @formatter:on @@ -1694,12 +1702,14 @@ private static List getInterfaceFields(Class clazz, HierarchyTraversal return allInterfaceFields; } - private static List getSuperclassFields(Class clazz, HierarchyTraversalMode traversalMode) { + private static List getSuperclassFields(Class clazz, Predicate predicate, + HierarchyTraversalMode traversalMode) { + Class superclass = clazz.getSuperclass(); if (!isSearchable(superclass)) { return Collections.emptyList(); } - return findAllFieldsInHierarchy(superclass, traversalMode); + return findAllFieldsInHierarchy(superclass, predicate, traversalMode); } private static boolean isFieldShadowedByLocalFields(Field field, List localFields) { diff --git a/platform-tests/src/test/java/org/junit/platform/commons/util/AnnotationUtilsTests.java b/platform-tests/src/test/java/org/junit/platform/commons/util/AnnotationUtilsTests.java index f26f5ab755d3..5dde2c34cbd9 100644 --- a/platform-tests/src/test/java/org/junit/platform/commons/util/AnnotationUtilsTests.java +++ b/platform-tests/src/test/java/org/junit/platform/commons/util/AnnotationUtilsTests.java @@ -46,8 +46,12 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.commons.util.pkg1.ClassLevelDir; +import org.junit.platform.commons.util.pkg1.InstanceLevelDir; import org.junit.platform.commons.util.pkg1.SuperclassWithStaticPackagePrivateBeforeMethod; +import org.junit.platform.commons.util.pkg1.SuperclassWithStaticPackagePrivateTempDirField; import org.junit.platform.commons.util.pkg1.subpkg.SubclassWithNonStaticPackagePrivateBeforeMethod; +import org.junit.platform.commons.util.pkg1.subpkg.SubclassWithNonStaticPackagePrivateTempDirField; /** * Unit tests for {@link AnnotationUtils}. @@ -504,6 +508,28 @@ private List findShadowingAnnotatedFields(Class ann return findAnnotatedFields(ClassWithShadowedAnnotatedFields.class, annotationType, isStringField); } + /** + * @see https://github.com/junit-team/junit5/issues/3532 + */ + @Test + void findAnnotatedFieldsAppliesPredicateBeforeSearchingTypeHierarchy() throws Exception { + final String TEMP_DIR = "tempDir"; + Class superclass = SuperclassWithStaticPackagePrivateTempDirField.class; + Field staticField = superclass.getDeclaredField(TEMP_DIR); + Class subclass = SubclassWithNonStaticPackagePrivateTempDirField.class; + Field nonStaticField = subclass.getDeclaredField(TEMP_DIR); + + // Prerequisite + var fields = findAnnotatedFields(superclass, ClassLevelDir.class, field -> true); + assertThat(fields).containsExactly(staticField); + + // Actual use cases for this test + fields = findAnnotatedFields(subclass, ClassLevelDir.class, field -> true); + assertThat(fields).containsExactly(staticField); + fields = findAnnotatedFields(subclass, InstanceLevelDir.class, field -> true); + assertThat(fields).containsExactly(nonStaticField); + } + // === findPublicAnnotatedFields() ========================================= @Test diff --git a/platform-tests/src/test/java/org/junit/platform/commons/util/ReflectionUtilsTests.java b/platform-tests/src/test/java/org/junit/platform/commons/util/ReflectionUtilsTests.java index 3e11222be2e3..7cccbe13a70c 100644 --- a/platform-tests/src/test/java/org/junit/platform/commons/util/ReflectionUtilsTests.java +++ b/platform-tests/src/test/java/org/junit/platform/commons/util/ReflectionUtilsTests.java @@ -74,7 +74,9 @@ import org.junit.platform.commons.util.ReflectionUtilsTests.OuterClassImplementingInterface.InnerClassImplementingInterface; import org.junit.platform.commons.util.classes.CustomType; import org.junit.platform.commons.util.pkg1.SuperclassWithStaticPackagePrivateBeforeMethod; +import org.junit.platform.commons.util.pkg1.SuperclassWithStaticPackagePrivateTempDirField; import org.junit.platform.commons.util.pkg1.subpkg.SubclassWithNonStaticPackagePrivateBeforeMethod; +import org.junit.platform.commons.util.pkg1.subpkg.SubclassWithNonStaticPackagePrivateTempDirField; /** * Unit tests for {@link ReflectionUtils}. @@ -1380,6 +1382,28 @@ void isGeneric() { } } + /** + * @see https://github.com/junit-team/junit5/issues/3532 + */ + @Test + void findFieldsAppliesPredicateBeforeSearchingTypeHierarchy() throws Exception { + final String TEMP_DIR = "tempDir"; + Class superclass = SuperclassWithStaticPackagePrivateTempDirField.class; + Field staticField = superclass.getDeclaredField(TEMP_DIR); + Class subclass = SubclassWithNonStaticPackagePrivateTempDirField.class; + Field nonStaticField = subclass.getDeclaredField(TEMP_DIR); + + // Prerequisite + var fields = findFields(superclass, ReflectionUtils::isStatic, TOP_DOWN); + assertThat(fields).containsExactly(staticField); + + // Actual use cases for this test + fields = findFields(subclass, ReflectionUtils::isStatic, TOP_DOWN); + assertThat(fields).containsExactly(staticField); + fields = findFields(subclass, ReflectionUtils::isNotStatic, TOP_DOWN); + assertThat(fields).containsExactly(nonStaticField); + } + @Test void readFieldValuesPreconditions() { assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.readFieldValues(null, new Object())); diff --git a/platform-tests/src/test/java/org/junit/platform/commons/util/pkg1/ClassLevelDir.java b/platform-tests/src/test/java/org/junit/platform/commons/util/pkg1/ClassLevelDir.java new file mode 100644 index 000000000000..d6b94d9d2076 --- /dev/null +++ b/platform-tests/src/test/java/org/junit/platform/commons/util/pkg1/ClassLevelDir.java @@ -0,0 +1,24 @@ +/* + * 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.util.pkg1; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Mimics {@code @TempDir}. + */ +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +public @interface ClassLevelDir { +} diff --git a/platform-tests/src/test/java/org/junit/platform/commons/util/pkg1/InstanceLevelDir.java b/platform-tests/src/test/java/org/junit/platform/commons/util/pkg1/InstanceLevelDir.java new file mode 100644 index 000000000000..bfa4e4ad8b95 --- /dev/null +++ b/platform-tests/src/test/java/org/junit/platform/commons/util/pkg1/InstanceLevelDir.java @@ -0,0 +1,24 @@ +/* + * 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.util.pkg1; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Mimics {@code @TempDir}. + */ +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +public @interface InstanceLevelDir { +} diff --git a/platform-tests/src/test/java/org/junit/platform/commons/util/pkg1/SuperclassWithStaticPackagePrivateTempDirField.java b/platform-tests/src/test/java/org/junit/platform/commons/util/pkg1/SuperclassWithStaticPackagePrivateTempDirField.java new file mode 100644 index 000000000000..4e2bbe7ec696 --- /dev/null +++ b/platform-tests/src/test/java/org/junit/platform/commons/util/pkg1/SuperclassWithStaticPackagePrivateTempDirField.java @@ -0,0 +1,23 @@ +/* + * 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.util.pkg1; + +import java.nio.file.Path; + +/** + * @see https://github.com/junit-team/junit5/issues/3532 + */ +public class SuperclassWithStaticPackagePrivateTempDirField { + + @ClassLevelDir + static Path tempDir; + +} diff --git a/platform-tests/src/test/java/org/junit/platform/commons/util/pkg1/subpkg/SubclassWithNonStaticPackagePrivateTempDirField.java b/platform-tests/src/test/java/org/junit/platform/commons/util/pkg1/subpkg/SubclassWithNonStaticPackagePrivateTempDirField.java new file mode 100644 index 000000000000..d7eb33f6a326 --- /dev/null +++ b/platform-tests/src/test/java/org/junit/platform/commons/util/pkg1/subpkg/SubclassWithNonStaticPackagePrivateTempDirField.java @@ -0,0 +1,26 @@ +/* + * 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.util.pkg1.subpkg; + +import java.nio.file.Path; + +import org.junit.platform.commons.util.pkg1.InstanceLevelDir; +import org.junit.platform.commons.util.pkg1.SuperclassWithStaticPackagePrivateTempDirField; + +/** + * @see https://github.com/junit-team/junit5/issues/3532 + */ +public class SubclassWithNonStaticPackagePrivateTempDirField extends SuperclassWithStaticPackagePrivateTempDirField { + + @InstanceLevelDir + Path tempDir; + +} From 388c5beaf42944961ab5b455c900d958a6e15588 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Sat, 4 Nov 2023 14:15:55 +0100 Subject: [PATCH 43/45] Harmonize application of method and field filters in search algorithms This commit consistently applies method predicates in search algorithms analogous to the application of field predicates. See #3498 See #3532 Closes #3534 (cherry picked from commit a670d107cde5818f9cb5adfae94300f740d13fe8) --- .../commons/util/ReflectionUtils.java | 47 ++++++++++--------- 1 file changed, 24 insertions(+), 23 deletions(-) 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 ea824ebd23a9..478b3372add0 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 @@ -1407,11 +1407,11 @@ private static Optional findMethod(Class clazz, Predicate pre for (Class current = clazz; isSearchable(current); current = current.getSuperclass()) { // Search for match in current type - List methods = current.isInterface() ? getMethods(current) : getDeclaredMethods(current, BOTTOM_UP); - for (Method method : methods) { - if (predicate.test(method)) { - return Optional.of(method); - } + List methods = current.isInterface() ? getMethods(current, predicate) + : getDeclaredMethods(current, predicate, BOTTOM_UP); + if (!methods.isEmpty()) { + // Since the predicate has already been applied, return the first match. + return Optional.of(methods.get(0)); } // Search for match in interfaces implemented by current type @@ -1506,8 +1506,8 @@ private static List findAllMethodsInHierarchy(Class clazz, Predicate< Preconditions.notNull(traversalMode, "HierarchyTraversalMode must not be null"); // @formatter:off - List localMethods = getDeclaredMethods(clazz, traversalMode).stream() - .filter(predicate.and(method -> !method.isSynthetic())) + List localMethods = getDeclaredMethods(clazz, predicate, traversalMode).stream() + .filter(method -> !method.isSynthetic()) .collect(toList()); List superclassMethods = getSuperclassMethods(clazz, predicate, traversalMode).stream() .filter(method -> !isMethodShadowedByLocalMethods(method, localMethods)) @@ -1533,7 +1533,6 @@ private static List findAllMethodsInHierarchy(Class clazz, Predicate< /** * Custom alternative to {@link Class#getFields()} that sorts the fields * which match the supplied predicate and converts them to a mutable list. - * @param predicate the field filter; never {@code null} */ private static List getFields(Class clazz, Predicate predicate) { return toSortedMutableList(clazz.getFields(), predicate); @@ -1542,7 +1541,6 @@ private static List getFields(Class clazz, Predicate predicate) /** * Custom alternative to {@link Class#getDeclaredFields()} that sorts the * fields which match the supplied predicate and converts them to a mutable list. - * @param predicate the field filter; never {@code null} */ private static List getDeclaredFields(Class clazz, Predicate predicate) { return toSortedMutableList(clazz.getDeclaredFields(), predicate); @@ -1550,24 +1548,26 @@ private static List getDeclaredFields(Class clazz, Predicate pr /** * Custom alternative to {@link Class#getMethods()} that sorts the methods - * and converts them to a mutable list. + * which match the supplied predicate and converts them to a mutable list. */ - private static List getMethods(Class clazz) { - return toSortedMutableList(clazz.getMethods()); + private static List getMethods(Class clazz, Predicate predicate) { + return toSortedMutableList(clazz.getMethods(), predicate); } /** * Custom alternative to {@link Class#getDeclaredMethods()} that sorts the - * methods and converts them to a mutable list. + * methods which match the supplied predicate and converts them to a mutable list. * *

In addition, the list returned by this method includes interface * default methods which are either prepended or appended to the list of * declared methods depending on the supplied traversal mode. */ - private static List getDeclaredMethods(Class clazz, HierarchyTraversalMode traversalMode) { + private static List getDeclaredMethods(Class clazz, Predicate predicate, + HierarchyTraversalMode traversalMode) { + // Note: getDefaultMethods() already sorts the methods, - List defaultMethods = getDefaultMethods(clazz); - List declaredMethods = toSortedMutableList(clazz.getDeclaredMethods()); + List defaultMethods = getDefaultMethods(clazz, predicate); + List declaredMethods = toSortedMutableList(clazz.getDeclaredMethods(), predicate); // Take the traversal mode into account in order to retain the inherited // nature of interface default methods. @@ -1584,23 +1584,23 @@ private static List getDeclaredMethods(Class clazz, HierarchyTraversa /** * Get a sorted, mutable list of all default methods present in interfaces * implemented by the supplied class which are also visible within - * the supplied class. + * the supplied class and match the supplied predicate. * * @see Method Visibility * in the Java Language Specification */ - private static List getDefaultMethods(Class clazz) { + private static List getDefaultMethods(Class clazz, Predicate predicate) { // @formatter:off // Visible default methods are interface default methods that have not // been overridden. List visibleDefaultMethods = Arrays.stream(clazz.getMethods()) - .filter(Method::isDefault) + .filter(predicate.and(Method::isDefault)) .collect(toCollection(ArrayList::new)); if (visibleDefaultMethods.isEmpty()) { return visibleDefaultMethods; } return Arrays.stream(clazz.getInterfaces()) - .map(ReflectionUtils::getMethods) + .map(ifc -> getMethods(ifc, predicate)) .flatMap(List::stream) .filter(visibleDefaultMethods::contains) .collect(toCollection(ArrayList::new)); @@ -1617,9 +1617,10 @@ private static List toSortedMutableList(Field[] fields, Predicate // @formatter:on } - private static List toSortedMutableList(Method[] methods) { + private static List toSortedMutableList(Method[] methods, Predicate predicate) { // @formatter:off return Arrays.stream(methods) + .filter(predicate) .sorted(ReflectionUtils::defaultMethodSorter) // Use toCollection() instead of toList() to ensure list is mutable. .collect(toCollection(ArrayList::new)); @@ -1658,8 +1659,8 @@ private static List getInterfaceMethods(Class clazz, Predicate ifc : clazz.getInterfaces()) { // @formatter:off - List localInterfaceMethods = getMethods(ifc).stream() - .filter(predicate.and(method -> !isAbstract(method))) + List localInterfaceMethods = getMethods(ifc, predicate).stream() + .filter(method -> !isAbstract(method)) .collect(toList()); List superinterfaceMethods = getInterfaceMethods(ifc, predicate, traversalMode).stream() From ac86d18e9b15dbebe046e82743ac7f9534a17582 Mon Sep 17 00:00:00 2001 From: Marcono1234 Date: Sun, 5 Nov 2023 17:17:22 +0100 Subject: [PATCH 44/45] Fix typo in AfterAll documentation (cherry picked from commit 4a1046de42c9322c376b2dc2d82c321e8862c34b) --- .../src/main/java/org/junit/jupiter/api/AfterAll.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AfterAll.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AfterAll.java index 52743ebef416..40f2e949a3d9 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AfterAll.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AfterAll.java @@ -40,7 +40,7 @@ * methods may optionally declare parameters to be resolved by * {@link org.junit.jupiter.api.extension.ParameterResolver ParameterResolvers}. * - *

Using {@code private} visibility for {@code @BeforeAll} methods is + *

Using {@code private} visibility for {@code @AfterAll} methods is * strongly discouraged and will be disallowed in a future release. * *

Inheritance and Execution Order

From e5f50d8720741623915979ac154b65bcbcd6a577 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Sun, 5 Nov 2023 17:48:47 +0100 Subject: [PATCH 45/45] Release 5.10.1 --- README.md | 2 +- .../docs/asciidoc/release-notes/release-notes-5.10.1.adoc | 2 +- gradle.properties | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 78fb026ea940..2f3c0be2b3ad 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ This repository is the home of _JUnit 5_. ## Latest Releases -- General Availability (GA): [JUnit 5.10.0](https://github.com/junit-team/junit5/releases/tag/r5.10.0) (July 23, 2023) +- General Availability (GA): [JUnit 5.10.1](https://github.com/junit-team/junit5/releases/tag/r5.10.1) (November 5, 2023) - Preview (Milestone/Release Candidate): N/A ## Documentation diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.10.1.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.10.1.adoc index 515aa64f41b1..af0ac43916b4 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-5.10.1.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.10.1.adoc @@ -1,7 +1,7 @@ [[release-notes-5.10.1]] == 5.10.1 -*Date of Release:* ❓ +*Date of Release:* November 5, 2023 *Scope:* minor bug fixes and improvements since 5.10.0. diff --git a/gradle.properties b/gradle.properties index cceb980ef64a..1521b53da109 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,13 +1,13 @@ group = org.junit -version = 5.10.1-SNAPSHOT +version = 5.10.1 jupiterGroup = org.junit.jupiter platformGroup = org.junit.platform -platformVersion = 1.10.1-SNAPSHOT +platformVersion = 1.10.1 vintageGroup = org.junit.vintage -vintageVersion = 5.10.1-SNAPSHOT +vintageVersion = 5.10.1 defaultBuiltBy = JUnit Team pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy