From dcdc5b8596a3ea7a8ff5c132cae28beb7396db91 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Tue, 26 Jul 2022 21:32:21 +0200 Subject: [PATCH 01/59] 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 05e31a8013af..9677b589bc3f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,13 +1,13 @@ group = org.junit -version = 5.9.0 +version = 5.10.0-SNAPSHOT jupiterGroup = org.junit.jupiter platformGroup = org.junit.platform -platformVersion = 1.9.0 +platformVersion = 1.10.0-SNAPSHOT vintageGroup = org.junit.vintage -vintageVersion = 5.9.0 +vintageVersion = 5.10.0-SNAPSHOT defaultBuiltBy = JUnit Team From f503bc6ab01951f98c0044061750b4e50d1f7c6f Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Thu, 4 Aug 2022 11:31:27 +0300 Subject: [PATCH 02/59] Switch to 5.9.1 snapshots --- gradle.properties | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gradle.properties b/gradle.properties index 9677b589bc3f..e79f19f1ca6e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,13 +1,13 @@ group = org.junit -version = 5.10.0-SNAPSHOT +version = 5.9.1-SNAPSHOT jupiterGroup = org.junit.jupiter platformGroup = org.junit.platform -platformVersion = 1.10.0-SNAPSHOT +platformVersion = 1.9.1-SNAPSHOT vintageGroup = org.junit.vintage -vintageVersion = 5.10.0-SNAPSHOT +vintageVersion = 5.9.1-SNAPSHOT defaultBuiltBy = JUnit Team From 2b3e32ec53ef6cc95964ff716acb388b95dc3111 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Tue, 26 Jul 2022 22:29:41 +0200 Subject: [PATCH 03/59] Remove obsolete include directives --- documentation/src/docs/asciidoc/release-notes/index.adoc | 4 ---- 1 file changed, 4 deletions(-) diff --git a/documentation/src/docs/asciidoc/release-notes/index.adoc b/documentation/src/docs/asciidoc/release-notes/index.adoc index 6cca6088e477..4923e46ba7fa 100644 --- a/documentation/src/docs/asciidoc/release-notes/index.adoc +++ b/documentation/src/docs/asciidoc/release-notes/index.adoc @@ -18,10 +18,6 @@ include::{includedir}/link-attributes.adoc[] include::{basedir}/release-notes-5.9.0.adoc[] -include::{basedir}/release-notes-5.9.0-RC1.adoc[] - -include::{basedir}/release-notes-5.9.0-M1.adoc[] - include::{basedir}/release-notes-5.8.2.adoc[] include::{basedir}/release-notes-5.8.1.adoc[] From 2da57418e2480d06b80568229bbb2d54a00ff845 Mon Sep 17 00:00:00 2001 From: Stefano Cordio Date: Wed, 27 Jul 2022 07:45:58 +0200 Subject: [PATCH 04/59] Fix documentation typo (#2986) --- documentation/src/docs/asciidoc/user-guide/running-tests.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/src/docs/asciidoc/user-guide/running-tests.adoc b/documentation/src/docs/asciidoc/user-guide/running-tests.adoc index 3fcb484f7193..4a5d1bd57e6b 100644 --- a/documentation/src/docs/asciidoc/user-guide/running-tests.adoc +++ b/documentation/src/docs/asciidoc/user-guide/running-tests.adoc @@ -527,7 +527,7 @@ link:https://ant.apache.org/manual/Tasks/junitlauncher.html[`junitlauncher` task link:https://spring.io/projects/spring-boot[Spring Boot] provides automatic support for managing the version of JUnit used in your project. In addition, the `spring-boot-starter-test` artifact automatically includes testing libraries such as JUnit -Jupiter, AspectJ, Mockito, etc. +Jupiter, AssertJ, Mockito, etc. If your build relies on dependency management support from Spring Boot, you should not import the <> in your build script since that From a51eb20084771d48a5d0484fdedf0be72e8020a6 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Fri, 29 Jul 2022 09:21:28 +0200 Subject: [PATCH 05/59] Remove GH Actions specific env vars (#2992) This avoids capturing nested Gradle builds in the jobs summary. --- buildSrc/src/main/kotlin/testing-conventions.gradle.kts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/buildSrc/src/main/kotlin/testing-conventions.gradle.kts b/buildSrc/src/main/kotlin/testing-conventions.gradle.kts index cb70596eb53d..a1ee801a05b7 100644 --- a/buildSrc/src/main/kotlin/testing-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/testing-conventions.gradle.kts @@ -52,6 +52,12 @@ tasks.withType().configureEach { // Track OS as input so that tests are executed on all configured operating systems on CI trackOperationSystemAsInput() + // Avoid passing unnecessary environment variables to the JVM (from GitHub Actions) + if (isCiServer) { + environment.remove("RUNNER_TEMP") + environment.remove("GITHUB_ACTION") + } + jvmArgumentProviders += CommandLineArgumentProvider { listOf( "-Djunit.platform.reporting.open.xml.enabled=true", From 67bc38731ab934db8d5b0455c39f23fd439870bd Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Thu, 4 Aug 2022 11:12:59 +0300 Subject: [PATCH 06/59] Ensure ReflectionUtils.findMethods() returns distinct methods Closes #2993 --- .../docs/asciidoc/release-notes/index.adoc | 2 + .../release-notes/release-notes-5.9.1.adoc | 60 +++++++++++++++++++ .../commons/support/ReflectionSupport.java | 4 +- .../commons/util/ReflectionUtils.java | 1 + .../commons/util/ReflectionUtilsTests.java | 25 ++++++++ 5 files changed, 90 insertions(+), 2 deletions(-) create mode 100644 documentation/src/docs/asciidoc/release-notes/release-notes-5.9.1.adoc diff --git a/documentation/src/docs/asciidoc/release-notes/index.adoc b/documentation/src/docs/asciidoc/release-notes/index.adoc index 4923e46ba7fa..e4be51b9779a 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.9.1.adoc[] + include::{basedir}/release-notes-5.9.0.adoc[] include::{basedir}/release-notes-5.8.2.adoc[] diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.1.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.1.adoc new file mode 100644 index 000000000000..4236da2f18d1 --- /dev/null +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.1.adoc @@ -0,0 +1,60 @@ +[[release-notes-5.9.1]] +== 5.9.1 + +*Date of Release:* ❓ + +*Scope:* Minor bug fixes since 5.9.0 + +For a complete list of all _closed_ issues and pull requests for this release, consult the +link:{junit5-repo}+/milestone/63?closed=1+[5.9.1] milestone page in the JUnit repository +on GitHub. + + +[[release-notes-5.9.1-junit-platform]] +=== JUnit Platform + +==== Bug Fixes + +* `ReflectionSupport.findMethods(...)` now returns a distinct set of methods. + +==== Deprecations and Breaking Changes + +* ❓ + +==== New Features and Improvements + +* ❓ + + +[[release-notes-5.9.1-junit-jupiter]] +=== JUnit Jupiter + +==== Bug Fixes + +* A `@ParameterizedTest` method configured with a `@MethodSource` annotation that + references a factory method inherited from multiple interfaces no longer fails with an + exception stating that multiple factory methods with the same name were found. + +==== Deprecations and Breaking Changes + +* ❓ + +==== New Features and Improvements + +* ❓ + + +[[release-notes-5.9.1-junit-vintage]] +=== JUnit Vintage + +==== Bug Fixes + +* ❓ + +==== Deprecations and Breaking Changes + +* ❓ + +==== New Features and Improvements + +* ❓ diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/ReflectionSupport.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/ReflectionSupport.java index f0741d0b5c9e..ae11ea789ed0 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/ReflectionSupport.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/ReflectionSupport.java @@ -282,8 +282,8 @@ public static Optional findMethod(Class clazz, String methodName, Cla } /** - * Find all {@linkplain Method methods} of the supplied class or interface - * that match the specified {@code predicate}. + * Find all distinct {@linkplain Method methods} of the supplied class or + * interface that match the specified {@code predicate}. * *

The results will not contain instance methods that are overridden * or {@code static} methods that are hidden. 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 654bef9a68e0..31dd5cdc8572 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 @@ -1418,6 +1418,7 @@ public static List findMethods(Class clazz, Predicate predica // @formatter:off return findAllMethodsInHierarchy(clazz, traversalMode).stream() .filter(predicate) + .distinct() // unmodifiable since returned by public, non-internal method(s) .collect(toUnmodifiableList()); // @formatter:on 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 041a459c61ad..9744850a9da6 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 @@ -1074,6 +1074,16 @@ private static void assertOneFooMethodIn(Class clazz) { assertThat(findMethods(clazz, isFooMethod)).hasSize(1); } + /** + * @since 1.9.1 + * @see https://github.com/junit-team/junit5/issues/2993 + */ + @Test + void findMethodsFindsDistinctMethodsDeclaredInMultipleInterfaces() { + Predicate isStringsMethod = method -> method.getName().equals("strings"); + assertThat(findMethods(DoubleInheritedInterfaceMethodTestCase.class, isStringsMethod)).hasSize(1); + } + @Test void findMethodsInObject() { var methods = findMethods(Object.class, method -> true); @@ -1427,6 +1437,21 @@ void methodWithMultidimensionalObjectArray(Double[][][][][] data) { void methodWithParameterizedMap(Map map) { } + interface StringsInterface1 { + static Stream strings() { + return Stream.of("abc", "def"); + } + } + + interface StringsInterface2 extends StringsInterface1 { + } + + /** + * Inherits strings() from interfaces StringsInterface1 and StringsInterface2. + */ + static class DoubleInheritedInterfaceMethodTestCase implements StringsInterface1, StringsInterface2 { + } + interface Generic { X foo(); From 783435eb498fb6e6b4f9cd394badc8d01e130107 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Sat, 13 Aug 2022 17:52:53 +0200 Subject: [PATCH 07/59] Fix regression in @MethodSource factory method resolution JUnit Jupiter 5.9.0 introduced a regression regarding @MethodSource factory method resolution for a @ParameterizedTest. Specifically, if @Test, @TestFactory, or @TestTemplate methods existed with the same name as the designated @MethodSource factory method, an exception was thrown incorrectly stating that multiple factory methods with the same name were found. This commit addresses this by filtering out @Test, @TestFactory, and @TestTemplate methods when searching for an appropriate @MethodSource factory method. Closes #3001 --- .../release-notes/release-notes-5.9.1.adoc | 4 ++ .../provider/MethodArgumentsProvider.java | 21 +++++- .../ParameterizedTestIntegrationTests.java | 67 +++++++++++++++++-- 3 files changed, 84 insertions(+), 8 deletions(-) diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.1.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.1.adoc index 4236da2f18d1..d4e13d291128 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.1.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.1.adoc @@ -34,6 +34,10 @@ on GitHub. * A `@ParameterizedTest` method configured with a `@MethodSource` annotation that references a factory method inherited from multiple interfaces no longer fails with an exception stating that multiple factory methods with the same name were found. +* A `@ParameterizedTest` method configured with a `@MethodSource` annotation that + references a factory method whose name is the same as other test methods in the same + class no longer fails with an exception stating that multiple factory methods with the + same name were found. ==== Deprecations and Breaking Changes diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodArgumentsProvider.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodArgumentsProvider.java index 4be203387d09..f8df21bacdf1 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodArgumentsProvider.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodArgumentsProvider.java @@ -12,12 +12,17 @@ import static java.lang.String.format; import static org.junit.jupiter.params.provider.Arguments.arguments; +import static org.junit.platform.commons.util.AnnotationUtils.isAnnotated; import java.lang.reflect.Method; import java.util.Arrays; import java.util.List; +import java.util.function.Predicate; import java.util.stream.Stream; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestFactory; +import org.junit.jupiter.api.TestTemplate; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.support.AnnotationConsumer; import org.junit.platform.commons.JUnitException; @@ -72,10 +77,15 @@ private Method getFactoryMethodByFullyQualifiedName(String fullyQualifiedMethodN methodParameters, className))); } + /** + * Find all methods in the given {@code testClass} with the desired {@code factoryMethodName}, + * ignoring the {@code testMethod} itself as well as any {@code @Test}, {@code @TestTemplate}, + * or {@code @TestFactory} methods with the same name. + */ private Method getFactoryMethodBySimpleName(Class testClass, Method testMethod, String factoryMethodName) { - // Find all methods with the desired factory method name, but ignore the test method itself. - List methods = ReflectionUtils.findMethods(testClass, - factoryMethod -> factoryMethodName.equals(factoryMethod.getName()) && !testMethod.equals(factoryMethod)); + Predicate isFactoryMethod = candidate -> factoryMethodName.equals(candidate.getName()) + && !(testMethod.equals(candidate) || isTestMethod(candidate)); + List methods = ReflectionUtils.findMethods(testClass, isFactoryMethod); Preconditions.condition(methods.size() > 0, () -> format("Could not find factory method [%s] in class [%s]", factoryMethodName, testClass.getName())); Preconditions.condition(methods.size() == 1, @@ -84,6 +94,11 @@ private Method getFactoryMethodBySimpleName(Class testClass, Method testMetho return methods.get(0); } + private boolean isTestMethod(Method candidate) { + return isAnnotated(candidate, Test.class) || isAnnotated(candidate, TestTemplate.class) + || isAnnotated(candidate, TestFactory.class); + } + private Class loadRequiredClass(String className) { return ReflectionUtils.tryToLoadClass(className).getOrThrow( cause -> new JUnitException(format("Could not load class [%s]", className), cause)); 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 50aa14fc2732..4682db4193d5 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 @@ -12,9 +12,12 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.within; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.DynamicTest.dynamicTest; import static org.junit.jupiter.api.Named.named; import static org.junit.jupiter.engine.discovery.JupiterUniqueIdBuilder.appendTestTemplateInvocationSegment; import static org.junit.jupiter.engine.discovery.JupiterUniqueIdBuilder.uniqueIdForTestTemplateMethod; @@ -53,13 +56,17 @@ import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; import org.junit.jupiter.api.Named; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.RepeatedTest; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestFactory; import org.junit.jupiter.api.TestInfo; import org.junit.jupiter.api.TestMethodOrder; +import org.junit.jupiter.api.TestReporter; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.api.extension.ParameterResolutionException; @@ -722,11 +729,6 @@ void reportsContainerWithAssumptionFailureInMethodSourceAsAborted() { message("Assumption failed: nothing to test")))); } - private EngineExecutionResults execute(String methodName, Class... methodParameterTypes) { - return ParameterizedTestIntegrationTests.this.execute(MethodSourceTestCase.class, methodName, - methodParameterTypes); - } - @Test void namedParameters() { execute("namedParameters", String.class).allEvents().assertThatEvents() // @@ -745,6 +747,27 @@ void nameParametersAlias() { event(test(), displayName("default name"), finishedWithFailure(message("default name")))); } + /** + * @since 5.9.1 + * @see https://github.com/junit-team/junit5/issues/3001 + */ + @Test + void duplicateMethodNames() { + // It is sufficient to assert that 8 tests started and finished, because + // without the fix for #3001 the 4 parameterized tests would fail. In + // other words, we're not really testing the support for @RepeatedTest + // and @TestFactory, but their presence also contributes to the bug + // reported in #3001. + ParameterizedTestIntegrationTests.this.execute(selectClass(DuplicateMethodNamesMethodSourceTestCase.class))// + .testEvents()// + .assertStatistics(stats -> stats.started(8).failed(0).finished(8)); + } + + private EngineExecutionResults execute(String methodName, Class... methodParameterTypes) { + return ParameterizedTestIntegrationTests.this.execute(MethodSourceTestCase.class, methodName, + methodParameterTypes); + } + } @Nested @@ -1251,6 +1274,40 @@ static List assumptionFailureInMethodSourceFactoryMethod() { } + /** + * @since 5.9.1 + * @see https://github.com/junit-team/junit5/issues/3001 + */ + static class DuplicateMethodNamesMethodSourceTestCase { + + @ParameterizedTest + @MethodSource + void test(String value) { + assertEquals(1, value.length()); + } + + @ParameterizedTest + @MethodSource("test") + void anotherTest(String value) { + assertEquals(1, value.length()); + } + + @RepeatedTest(2) + void test(TestReporter testReporter) { + assertNotNull(testReporter); + } + + @TestFactory + Stream test(TestInfo testInfo) { + return test().map(value -> dynamicTest(value, () -> assertEquals(1, value.length()))); + } + + private static Stream test() { + return Stream.of("a", "b"); + } + + } + static class UnusedArgumentsTestCase { @ParameterizedTest From 5369a8e5b68c34e7bfb95e99e43d2bbc9ff48e82 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Sun, 14 Aug 2022 14:27:23 +0200 Subject: [PATCH 08/59] Improve diagnostics when multiple @MethodSource factory methods are detected --- .../jupiter/params/provider/MethodArgumentsProvider.java | 4 ++-- .../params/provider/MethodArgumentsProviderTests.java | 7 +++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodArgumentsProvider.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodArgumentsProvider.java index f8df21bacdf1..885ad510a811 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodArgumentsProvider.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodArgumentsProvider.java @@ -89,8 +89,8 @@ private Method getFactoryMethodBySimpleName(Class testClass, Method testMetho Preconditions.condition(methods.size() > 0, () -> format("Could not find factory method [%s] in class [%s]", factoryMethodName, testClass.getName())); Preconditions.condition(methods.size() == 1, - () -> format("Several factory methods named [%s] were found in class [%s]", factoryMethodName, - testClass.getName())); + () -> format("%d factory methods named [%s] were found in class [%s]: %s", methods.size(), + factoryMethodName, testClass.getName(), methods)); return methods.get(0); } diff --git a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/MethodArgumentsProviderTests.java b/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/MethodArgumentsProviderTests.java index 1a32fa1f35a7..27450a474a1f 100644 --- a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/MethodArgumentsProviderTests.java +++ b/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/MethodArgumentsProviderTests.java @@ -395,8 +395,11 @@ void throwsExceptionWhenSeveralFactoryMethodsWithSameNameAreAvailable() { var exception = assertThrows(PreconditionViolationException.class, () -> provideArguments("stringStreamProviderWithOrWithoutParameter").toArray()); - assertThat(exception.getMessage()).isEqualTo( - "Several factory methods named [stringStreamProviderWithOrWithoutParameter] were found in class [org.junit.jupiter.params.provider.MethodArgumentsProviderTests$TestCase]"); + assertThat(exception.getMessage())// + .startsWith("2 factory methods named [stringStreamProviderWithOrWithoutParameter] were found in " + + "class [org.junit.jupiter.params.provider.MethodArgumentsProviderTests$TestCase]: ")// + .contains("stringStreamProviderWithOrWithoutParameter()", + "stringStreamProviderWithOrWithoutParameter(java.lang.String)"); } @Test From bb718750207ce6c7de78328b5f01341cad9057db Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Sun, 14 Aug 2022 15:23:05 +0200 Subject: [PATCH 09/59] Introduce CollectionUtils.isConvertibleToStream() --- .../commons/util/CollectionUtils.java | 29 +++++++++ .../commons/util/CollectionUtilsTests.java | 61 +++++++++++++++++++ 2 files changed, 90 insertions(+) diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/CollectionUtils.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/CollectionUtils.java index c8f677b1b325..32c89dcc26a8 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/CollectionUtils.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/CollectionUtils.java @@ -112,6 +112,34 @@ public static Set toSet(T[] values) { return collectingAndThen(toList(), Collections::unmodifiableList); } + /** + * Determine if an instance of the supplied type can be converted into a + * {@code Stream}. + * + *

If this method returns {@code true}, {@link #toStream(Object)} can + * successfully convert an object of the specified type into a stream. See + * {@link #toStream(Object)} for supported types. + * + * @param type the type to check; may be {@code null} + * @return {@code true} if an instance of the type can be converted into a stream + * @since 1.9.1 + * @see #toStream(Object) + */ + @API(status = INTERNAL, since = "1.9.1") + public static boolean isConvertibleToStream(Class type) { + if (type == null || type == void.class) { + return false; + } + return (Stream.class.isAssignableFrom(type)// + || DoubleStream.class.isAssignableFrom(type)// + || IntStream.class.isAssignableFrom(type)// + || LongStream.class.isAssignableFrom(type)// + || Iterable.class.isAssignableFrom(type)// + || Iterator.class.isAssignableFrom(type)// + || Object[].class.isAssignableFrom(type)// + || (type.isArray() && type.getComponentType().isPrimitive())); + } + /** * Convert an object of one of the following supported types into a {@code Stream}. * @@ -131,6 +159,7 @@ public static Set toSet(T[] values) { * @return the resulting stream * @throws PreconditionViolationException if the supplied object is {@code null} * or not one of the supported types + * @see #isConvertibleToStream(Object) */ public static Stream toStream(Object object) { Preconditions.notNull(object, "Object must not be null"); diff --git a/platform-tests/src/test/java/org/junit/platform/commons/util/CollectionUtilsTests.java b/platform-tests/src/test/java/org/junit/platform/commons/util/CollectionUtilsTests.java index e8093659ee2b..db8bdad2433f 100644 --- a/platform-tests/src/test/java/org/junit/platform/commons/util/CollectionUtilsTests.java +++ b/platform-tests/src/test/java/org/junit/platform/commons/util/CollectionUtilsTests.java @@ -22,6 +22,7 @@ import java.lang.reflect.Array; import java.util.ArrayList; import java.util.Collection; +import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; @@ -33,6 +34,10 @@ import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestFactory; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; import org.junit.platform.commons.PreconditionViolationException; /** @@ -75,6 +80,62 @@ void toUnmodifiableListThrowsOnMutation() { assertThrows(UnsupportedOperationException.class, numbers::clear); } + @ParameterizedTest + @ValueSource(classes = { // + Stream.class, // + DoubleStream.class, // + IntStream.class, // + LongStream.class, // + Collection.class, // + Iterable.class, // + Iterator.class, // + Object[].class, // + String[].class, // + int[].class, // + double[].class, // + char[].class // + }) + void isConvertibleToStreamForSupportedTypes(Class type) { + assertThat(CollectionUtils.isConvertibleToStream(type)).isTrue(); + } + + @ParameterizedTest + @MethodSource("objectsConvertibleToStreams") + void isConvertibleToStreamForSupportedTypesFromObjects(Object object) { + assertThat(CollectionUtils.isConvertibleToStream(object.getClass())).isTrue(); + } + + static Stream objectsConvertibleToStreams() { + return Stream.of(// + Stream.of("cat", "dog"), // + DoubleStream.of(42.3), // + IntStream.of(99), // + LongStream.of(100000000), // + Set.of(1, 2, 3), // + Arguments.of((Object) new Object[] { 9, 8, 7 }), // + new int[] { 5, 10, 15 }// + ); + } + + @ParameterizedTest + @ValueSource(classes = { // + void.class, // + Void.class, // + Object.class, // + Integer.class, // + String.class, // + int.class, // + boolean.class // + }) + void isConvertibleToStreamForUnsupportedTypes(Class type) { + assertThat(CollectionUtils.isConvertibleToStream(type)).isFalse(); + } + + @Test + void isConvertibleToStreamForNull() { + assertThat(CollectionUtils.isConvertibleToStream(null)).isFalse(); + } + @Test void toStreamWithNull() { Exception exception = assertThrows(PreconditionViolationException.class, () -> CollectionUtils.toStream(null)); From 5508b8d506740e0e54535bb86b3e2951df96205e Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Sun, 14 Aug 2022 16:31:39 +0200 Subject: [PATCH 10/59] Revise fix for @MethodSource factory method resolution This commit revises the fix for the @MethodSource factory method resolution regression by also ignoring candidate factory methods (i.e., those with the same name as the designated factory method) with an invalid return type. Such methods may in fact be local utility methods that are not intended to be used as a factory method. Closes #3001 --- .../release-notes/release-notes-5.9.1.adoc | 6 +-- .../provider/MethodArgumentsProvider.java | 38 +++++++++++++------ .../ParameterizedTestIntegrationTests.java | 19 ++++++++-- .../MethodArgumentsProviderTests.java | 22 ++++++----- 4 files changed, 59 insertions(+), 26 deletions(-) diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.1.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.1.adoc index d4e13d291128..f0fb4b21c945 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.1.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.1.adoc @@ -35,9 +35,9 @@ on GitHub. references a factory method inherited from multiple interfaces no longer fails with an exception stating that multiple factory methods with the same name were found. * A `@ParameterizedTest` method configured with a `@MethodSource` annotation that - references a factory method whose name is the same as other test methods in the same - class no longer fails with an exception stating that multiple factory methods with the - same name were found. + references a factory method whose name is the same as other non-factory methods in the + same class no longer fails with an exception stating that multiple factory methods with + the same name were found. ==== Deprecations and Breaking Changes diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodArgumentsProvider.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodArgumentsProvider.java index 885ad510a811..a4696a2739ec 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodArgumentsProvider.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodArgumentsProvider.java @@ -11,8 +11,10 @@ package org.junit.jupiter.params.provider; import static java.lang.String.format; +import static java.util.stream.Collectors.toList; import static org.junit.jupiter.params.provider.Arguments.arguments; import static org.junit.platform.commons.util.AnnotationUtils.isAnnotated; +import static org.junit.platform.commons.util.CollectionUtils.isConvertibleToStream; import java.lang.reflect.Method; import java.util.Arrays; @@ -78,20 +80,34 @@ private Method getFactoryMethodByFullyQualifiedName(String fullyQualifiedMethodN } /** - * Find all methods in the given {@code testClass} with the desired {@code factoryMethodName}, - * ignoring the {@code testMethod} itself as well as any {@code @Test}, {@code @TestTemplate}, + * Find all methods in the given {@code testClass} with the desired {@code factoryMethodName} + * which have return types that can be converted to a {@link Stream}, ignoring the + * {@code testMethod} itself as well as any {@code @Test}, {@code @TestTemplate}, * or {@code @TestFactory} methods with the same name. */ private Method getFactoryMethodBySimpleName(Class testClass, Method testMethod, String factoryMethodName) { - Predicate isFactoryMethod = candidate -> factoryMethodName.equals(candidate.getName()) - && !(testMethod.equals(candidate) || isTestMethod(candidate)); - List methods = ReflectionUtils.findMethods(testClass, isFactoryMethod); - Preconditions.condition(methods.size() > 0, - () -> format("Could not find factory method [%s] in class [%s]", factoryMethodName, testClass.getName())); - Preconditions.condition(methods.size() == 1, - () -> format("%d factory methods named [%s] were found in class [%s]: %s", methods.size(), - factoryMethodName, testClass.getName(), methods)); - return methods.get(0); + Predicate isCandidate = candidate -> factoryMethodName.equals(candidate.getName()) + && !testMethod.equals(candidate); + List candidates = ReflectionUtils.findMethods(testClass, isCandidate); + Predicate isFactoryMethod = method -> isConvertibleToStream(method.getReturnType()) + && !isTestMethod(method); + List factoryMethods = candidates.stream().filter(isFactoryMethod).collect(toList()); + Preconditions.condition(factoryMethods.size() > 0, () -> { + // If we didn't find the factory method using the isFactoryMethod Predicate, perhaps + // the specified factory method has an invalid return type or is a test method. + // In that case, we report the invalid candidates that were found. + if (candidates.size() > 0) { + return format( + "Could not find valid factory method [%s] in class [%s] but found the following invalid candidates: %s", + factoryMethodName, testClass.getName(), candidates); + } + // Otherwise, report that we didn't find anything. + return format("Could not find factory method [%s] in class [%s]", factoryMethodName, testClass.getName()); + }); + Preconditions.condition(factoryMethods.size() == 1, + () -> format("%d factory methods named [%s] were found in class [%s]: %s", factoryMethods.size(), + factoryMethodName, testClass.getName(), factoryMethods)); + return factoryMethods.get(0); } private boolean isTestMethod(Method candidate) { 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 4682db4193d5..56f35329262d 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 @@ -1283,13 +1283,13 @@ static class DuplicateMethodNamesMethodSourceTestCase { @ParameterizedTest @MethodSource void test(String value) { - assertEquals(1, value.length()); + test(1, value); } @ParameterizedTest @MethodSource("test") void anotherTest(String value) { - assertEquals(1, value.length()); + assertTrue(test(value, 1)); } @RepeatedTest(2) @@ -1299,9 +1299,22 @@ void test(TestReporter testReporter) { @TestFactory Stream test(TestInfo testInfo) { - return test().map(value -> dynamicTest(value, () -> assertEquals(1, value.length()))); + return test().map(value -> dynamicTest(value, () -> test(1, value))); } + // neither a test method nor a factory method. + // intentionally void. + private void test(int expectedLength, String value) { + assertEquals(expectedLength, value.length()); + } + + // neither a test method nor a factory method. + // intentionally non-void and also not convertible to a Stream. + private boolean test(String value, int expectedLength) { + return (value.length() == expectedLength); + } + + // legitimate factory method. private static Stream test() { return Stream.of("a", "b"); } diff --git a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/MethodArgumentsProviderTests.java b/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/MethodArgumentsProviderTests.java index 27450a474a1f..21f14662ecc9 100644 --- a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/MethodArgumentsProviderTests.java +++ b/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/MethodArgumentsProviderTests.java @@ -48,12 +48,24 @@ class MethodArgumentsProviderTests { private MutableExtensionRegistry extensionRegistry; + @Test + void throwsExceptionWhenFactoryMethodDoesNotExist() { + var exception = assertThrows(JUnitException.class, () -> provideArguments("unknownMethod").toArray()); + + assertThat(exception.getMessage()).isEqualTo( + "Could not find factory method [unknownMethod] in class [" + TestCase.class.getName() + "]"); + } + @Test void throwsExceptionForIllegalReturnType() { var exception = assertThrows(PreconditionViolationException.class, () -> provideArguments("providerWithIllegalReturnType").toArray()); - assertThat(exception).hasMessageContaining("Cannot convert instance of java.lang.Integer into a Stream"); + assertThat(exception.getMessage())// + .containsSubsequence("Could not find valid factory method [providerWithIllegalReturnType] in class [", + TestCase.class.getName() + "]", // + "but found the following invalid candidates: ", // + "[static java.lang.Object org.junit.jupiter.params.provider.MethodArgumentsProviderTests$TestCase.providerWithIllegalReturnType()]"); } @Test @@ -194,14 +206,6 @@ void providesArgumentsFromNonStaticFactoryMethodWhenStaticIsNotRequired() { assertThat(arguments).containsExactly(array("foo"), array("bar")); } - @Test - void throwsExceptionWhenFactoryMethodDoesNotExist() { - var exception = assertThrows(JUnitException.class, () -> provideArguments("unknownMethod").toArray()); - - assertThat(exception.getMessage()).contains("Could not find factory method [unknownMethod] in class [", - TestCase.class.getName()); - } - @Test void providesArgumentsUsingDefaultFactoryMethodName() throws Exception { Class testClass = DefaultFactoryMethodNameTestCase.class; From 4bc4e12f71e921bca6e012a19cd807f680d92346 Mon Sep 17 00:00:00 2001 From: Kir Merzlikin Date: Wed, 24 Aug 2022 00:26:23 +0300 Subject: [PATCH 11/59] Fix header support in @CsvSource for records supplied via value attribute Previously, when parsing the `value` attribute of the `@CsvSource` annotation with `useHeadersInDisplayName` set to true, `CsvArgumentsProvider` didn't handle headers correctly. After parsing the first line with headers, instead of moving to the next line, it tried to validate a not-yet-parsed CSV record which lead to an exception being thrown. This was due to the missing `continue` statement in the `for` loop. Now, with `continue` added, as soon as headers are parsed, the `for` loop is advanced to the next iteration and to the next line of CSV input, containing the expected CSV record. Fixes #3010 Closes #3011 --- .../params/provider/CsvArgumentsProvider.java | 1 + .../provider/CsvArgumentsProviderTests.java | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java index 7f475a23081f..7a2999eaaf56 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java @@ -95,6 +95,7 @@ private Stream parseValueArray() { // Lazily retrieve headers if necessary. if (useHeadersInDisplayName && headers == null) { headers = getHeaders(this.csvParser); + continue; } Preconditions.notNull(csvRecord, () -> "Record at index " + index + " contains invalid CSV: \"" + input + "\""); 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 872ff28397e8..a62a3c713f87 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 @@ -353,6 +353,24 @@ void supportsCsvHeadersWhenUsingTextBlockAttribute() { array("FRUIT = banana", "RANK = 2")); } + @Test + void supportsCsvHeadersWhenUsingValueAttribute() { + var annotation = csvSource().useHeadersInDisplayName(true).lines("FRUIT, RANK", "apple, 1", + "banana, 2").build(); + + var arguments = provideArguments(annotation); + Stream argumentsAsStrings = arguments.map(array -> { + String[] strings = new String[array.length]; + for (int i = 0; i < array.length; i++) { + strings[i] = String.valueOf(array[i]); + } + return strings; + }); + + assertThat(argumentsAsStrings).containsExactly(array("FRUIT = apple", "RANK = 1"), + array("FRUIT = banana", "RANK = 2")); + } + @Test void throwsExceptionIfColumnCountExceedsHeaderCount() { var annotation = csvSource().useHeadersInDisplayName(true).textBlock(""" From 6aa62a7cbb7d56525488f0a8f7d4c2f409071d2b Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Wed, 24 Aug 2022 10:18:33 +0200 Subject: [PATCH 12/59] Polishing See #3011 --- .../provider/CsvArgumentsProviderTests.java | 24 ++++++------------- 1 file changed, 7 insertions(+), 17 deletions(-) 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 a62a3c713f87..2fb55af504e6 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 @@ -334,31 +334,21 @@ void honorsCommentCharacterWhenUsingTextBlockAttribute() { @Test void supportsCsvHeadersWhenUsingTextBlockAttribute() { - var annotation = csvSource().useHeadersInDisplayName(true).textBlock(""" + supportsCsvHeaders(csvSource().useHeadersInDisplayName(true).textBlock(""" FRUIT, RANK apple, 1 banana, 2 - """).build(); - - var arguments = provideArguments(annotation); - Stream argumentsAsStrings = arguments.map(array -> { - String[] strings = new String[array.length]; - for (int i = 0; i < array.length; i++) { - strings[i] = String.valueOf(array[i]); - } - return strings; - }); - - assertThat(argumentsAsStrings).containsExactly(array("FRUIT = apple", "RANK = 1"), - array("FRUIT = banana", "RANK = 2")); + """).build()); } @Test void supportsCsvHeadersWhenUsingValueAttribute() { - var annotation = csvSource().useHeadersInDisplayName(true).lines("FRUIT, RANK", "apple, 1", - "banana, 2").build(); + supportsCsvHeaders(csvSource().useHeadersInDisplayName(true)// + .lines("FRUIT, RANK", "apple, 1", "banana, 2").build()); + } - var arguments = provideArguments(annotation); + private void supportsCsvHeaders(CsvSource csvSource) { + var arguments = provideArguments(csvSource); Stream argumentsAsStrings = arguments.map(array -> { String[] strings = new String[array.length]; for (int i = 0; i < array.length; i++) { From 2b596c5946f191e23c533fbd5ae24aa64d96daa8 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Wed, 24 Aug 2022 10:23:30 +0200 Subject: [PATCH 13/59] Document fix for #3010 in the release notes --- .../src/docs/asciidoc/release-notes/release-notes-5.9.1.adoc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.1.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.1.adoc index f0fb4b21c945..8827a8633af6 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.1.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.1.adoc @@ -31,6 +31,8 @@ on GitHub. ==== Bug Fixes +* Headers provided via the `value` attribute in `@CsvSource` for a `@ParameterizedTest` + are now properly parsed when the `useHeadersInDisplayName` attribute is set to `true`. * A `@ParameterizedTest` method configured with a `@MethodSource` annotation that references a factory method inherited from multiple interfaces no longer fails with an exception stating that multiple factory methods with the same name were found. From b628d183b070f1d3aab1c1b4129dd40f6bc263e2 Mon Sep 17 00:00:00 2001 From: Kir Merzlikin Date: Wed, 24 Aug 2022 13:41:24 +0300 Subject: [PATCH 14/59] Fix method reference in CollectionUtils javadoc --- .../java/org/junit/platform/commons/util/CollectionUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/CollectionUtils.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/CollectionUtils.java index 32c89dcc26a8..a3f83b2f6656 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/CollectionUtils.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/CollectionUtils.java @@ -159,7 +159,7 @@ public static boolean isConvertibleToStream(Class type) { * @return the resulting stream * @throws PreconditionViolationException if the supplied object is {@code null} * or not one of the supported types - * @see #isConvertibleToStream(Object) + * @see #isConvertibleToStream(Class) */ public static Stream toStream(Object object) { Preconditions.notNull(object, "Object must not be null"); From e9c15bffaa231a5979c31b998e7cf93025788b29 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Fri, 16 Sep 2022 16:43:32 +0200 Subject: [PATCH 15/59] Introduce @EnabledInNativeImage & @DisabledInNativeImage conditions for GraalVM Closes: #2830 --- .../src/docs/asciidoc/link-attributes.adoc | 6 +- .../asciidoc/user-guide/writing-tests.adoc | 15 ++++ .../example/ConditionalTestExecutionDemo.java | 16 ++++ .../java/org/junit/jupiter/api/Disabled.java | 2 + .../api/condition/DisabledForJreRange.java | 16 ++-- .../jupiter/api/condition/DisabledIf.java | 6 +- .../DisabledIfEnvironmentVariable.java | 16 ++-- .../condition/DisabledIfSystemProperty.java | 16 ++-- .../api/condition/DisabledInNativeImage.java | 76 +++++++++++++++++++ .../jupiter/api/condition/DisabledOnJre.java | 14 ++-- .../jupiter/api/condition/DisabledOnOs.java | 10 ++- .../api/condition/EnabledForJreRange.java | 16 ++-- .../jupiter/api/condition/EnabledIf.java | 6 +- .../EnabledIfEnvironmentVariable.java | 16 ++-- .../condition/EnabledIfSystemProperty.java | 16 ++-- .../api/condition/EnabledInNativeImage.java | 76 +++++++++++++++++++ .../jupiter/api/condition/EnabledOnJre.java | 14 ++-- .../jupiter/api/condition/EnabledOnOs.java | 10 ++- .../api/extension/ExecutionCondition.java | 16 ++-- 19 files changed, 289 insertions(+), 74 deletions(-) create mode 100644 junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledInNativeImage.java create mode 100644 junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledInNativeImage.java diff --git a/documentation/src/docs/asciidoc/link-attributes.adoc b/documentation/src/docs/asciidoc/link-attributes.adoc index 820c808abb47..025ab6fc86d4 100644 --- a/documentation/src/docs/asciidoc/link-attributes.adoc +++ b/documentation/src/docs/asciidoc/link-attributes.adoc @@ -119,17 +119,19 @@ endif::[] :TestWatcher: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/TestWatcher.html[TestWatcher] // Jupiter Conditions :DisabledForJreRange: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/DisabledForJreRange.html[@DisabledForJreRange] +:DisabledIf: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/DisabledIf.html[@DisabledIf] :DisabledIfEnvironmentVariable: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariable.html[@DisabledIfEnvironmentVariable] :DisabledIfSystemProperty: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/DisabledIfSystemProperty.html[@DisabledIfSystemProperty] +:DisabledInNativeImage: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/DisabledInNativeImage.html[@DisabledInNativeImage] :DisabledOnJre: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/DisabledOnJre.html[@DisabledOnJre] :DisabledOnOs: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/DisabledOnOs.html[@DisabledOnOs] -:DisabledIf: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/DisabledIf.html[@DisabledIf] :EnabledForJreRange: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/EnabledForJreRange.html[@EnabledForJreRange] +:EnabledIf: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/EnabledIf.html[@EnabledIf] :EnabledIfEnvironmentVariable: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariable.html[@EnabledIfEnvironmentVariable] :EnabledIfSystemProperty: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/EnabledIfSystemProperty.html[@EnabledIfSystemProperty] +:EnabledInNativeImage: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/EnabledInNativeImage.html[@EnabledInNativeImage] :EnabledOnJre: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/EnabledOnJre.html[@EnabledOnJre] :EnabledOnOs: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/EnabledOnOs.html[@EnabledOnOs] -:EnabledIf: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/EnabledIf.html[@EnabledIf] :JRE: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/JRE.html[JRE] // Jupiter I/O :TempDir: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/io/TempDir.html[@TempDir] diff --git a/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc b/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc index 92432cc8a935..32705596e269 100644 --- a/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc +++ b/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc @@ -453,6 +453,21 @@ half open ranges. include::{testDir}/example/ConditionalTestExecutionDemo.java[tags=user_guide_jre] ---- +[[writing-tests-conditional-execution-native]] +==== Native Image Conditions + +A container or test may be enabled or disabled within a +https://www.graalvm.org/reference-manual/native-image/[GraalVM native image] via the +`{EnabledInNativeImage}` and `{DisabledInNativeImage}` annotations. These annotations are +typically used when running tests within a native image using the Gradle and Maven +plug-ins from the GraalVM https://graalvm.github.io/native-build-tools/latest/[Native +Build Tools] project. + +[source,java,indent=0] +---- +include::{testDir}/example/ConditionalTestExecutionDemo.java[tags=user_guide_native] +---- + [[writing-tests-conditional-execution-system-properties]] ==== System Property Conditions diff --git a/documentation/src/test/java/example/ConditionalTestExecutionDemo.java b/documentation/src/test/java/example/ConditionalTestExecutionDemo.java index d4dea7b4f31f..a85031a1117c 100644 --- a/documentation/src/test/java/example/ConditionalTestExecutionDemo.java +++ b/documentation/src/test/java/example/ConditionalTestExecutionDemo.java @@ -28,12 +28,14 @@ import org.junit.jupiter.api.condition.DisabledIf; import org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable; import org.junit.jupiter.api.condition.DisabledIfSystemProperty; +import org.junit.jupiter.api.condition.DisabledInNativeImage; import org.junit.jupiter.api.condition.DisabledOnJre; import org.junit.jupiter.api.condition.DisabledOnOs; import org.junit.jupiter.api.condition.EnabledForJreRange; import org.junit.jupiter.api.condition.EnabledIf; import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; import org.junit.jupiter.api.condition.EnabledIfSystemProperty; +import org.junit.jupiter.api.condition.EnabledInNativeImage; import org.junit.jupiter.api.condition.EnabledOnJre; import org.junit.jupiter.api.condition.EnabledOnOs; @@ -153,6 +155,20 @@ void notFromJava8to11() { } // end::user_guide_jre[] + // tag::user_guide_native[] + @Test + @EnabledInNativeImage + void onlyWithinNativeImage() { + // ... + } + + @Test + @DisabledInNativeImage + void neverWithinNativeImage() { + // ... + } + // end::user_guide_native[] + // tag::user_guide_system_property[] @Test @EnabledIfSystemProperty(named = "os.arch", matches = ".*64.*") 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 8961e0bdea7a..88b6f16d4b54 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 @@ -48,6 +48,8 @@ * @see org.junit.jupiter.api.condition.DisabledForJreRange * @see org.junit.jupiter.api.condition.EnabledOnOs * @see org.junit.jupiter.api.condition.DisabledOnOs + * @see org.junit.jupiter.api.condition.EnabledInNativeImage + * @see org.junit.jupiter.api.condition.DisabledInNativeImage * @see org.junit.jupiter.api.extension.ExecutionCondition */ @Target({ ElementType.TYPE, ElementType.METHOD }) 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 5c8cf34c34a6..be2edd9642e6 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 @@ -51,17 +51,19 @@ * * @since 5.6 * @see JRE - * @see org.junit.jupiter.api.condition.EnabledForJreRange - * @see org.junit.jupiter.api.condition.EnabledOnJre - * @see org.junit.jupiter.api.condition.DisabledOnJre + * @see org.junit.jupiter.api.condition.EnabledIf + * @see org.junit.jupiter.api.condition.DisabledIf * @see org.junit.jupiter.api.condition.EnabledOnOs * @see org.junit.jupiter.api.condition.DisabledOnOs - * @see org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable - * @see org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable + * @see org.junit.jupiter.api.condition.EnabledOnJre + * @see org.junit.jupiter.api.condition.DisabledOnJre + * @see org.junit.jupiter.api.condition.EnabledForJreRange + * @see org.junit.jupiter.api.condition.EnabledInNativeImage + * @see org.junit.jupiter.api.condition.DisabledInNativeImage * @see org.junit.jupiter.api.condition.EnabledIfSystemProperty * @see org.junit.jupiter.api.condition.DisabledIfSystemProperty - * @see org.junit.jupiter.api.condition.EnabledIf - * @see org.junit.jupiter.api.condition.DisabledIf + * @see org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable + * @see org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable * @see org.junit.jupiter.api.Disabled */ @Target({ ElementType.TYPE, ElementType.METHOD }) 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 56ef7a7fb194..0eb3bc627911 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 @@ -57,10 +57,12 @@ * @see org.junit.jupiter.api.condition.DisabledOnJre * @see org.junit.jupiter.api.condition.EnabledForJreRange * @see org.junit.jupiter.api.condition.DisabledForJreRange - * @see org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable - * @see org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable + * @see org.junit.jupiter.api.condition.EnabledInNativeImage + * @see org.junit.jupiter.api.condition.DisabledInNativeImage * @see org.junit.jupiter.api.condition.EnabledIfSystemProperty * @see org.junit.jupiter.api.condition.DisabledIfSystemProperty + * @see org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable + * @see org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable * @see org.junit.jupiter.api.Disabled */ @Target({ ElementType.TYPE, ElementType.METHOD }) 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 e4583598c057..c32b693c5b03 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 @@ -52,17 +52,19 @@ * given element. * * @since 5.1 - * @see org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable - * @see org.junit.jupiter.api.condition.EnabledIfSystemProperty - * @see org.junit.jupiter.api.condition.DisabledIfSystemProperty + * @see org.junit.jupiter.api.condition.EnabledIf + * @see org.junit.jupiter.api.condition.DisabledIf + * @see org.junit.jupiter.api.condition.EnabledOnOs + * @see org.junit.jupiter.api.condition.DisabledOnOs * @see org.junit.jupiter.api.condition.EnabledOnJre * @see org.junit.jupiter.api.condition.DisabledOnJre * @see org.junit.jupiter.api.condition.EnabledForJreRange * @see org.junit.jupiter.api.condition.DisabledForJreRange - * @see org.junit.jupiter.api.condition.EnabledOnOs - * @see org.junit.jupiter.api.condition.DisabledOnOs - * @see org.junit.jupiter.api.condition.EnabledIf - * @see org.junit.jupiter.api.condition.DisabledIf + * @see org.junit.jupiter.api.condition.EnabledInNativeImage + * @see org.junit.jupiter.api.condition.DisabledInNativeImage + * @see org.junit.jupiter.api.condition.EnabledIfSystemProperty + * @see org.junit.jupiter.api.condition.DisabledIfSystemProperty + * @see org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable * @see org.junit.jupiter.api.Disabled */ @Target({ ElementType.TYPE, ElementType.METHOD }) 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 cda6efd2ce08..84f7c717f886 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 @@ -52,17 +52,19 @@ * given element. * * @since 5.1 - * @see org.junit.jupiter.api.condition.EnabledIfSystemProperty - * @see org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable - * @see org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable + * @see org.junit.jupiter.api.condition.EnabledIf + * @see org.junit.jupiter.api.condition.DisabledIf + * @see org.junit.jupiter.api.condition.EnabledOnOs + * @see org.junit.jupiter.api.condition.DisabledOnOs * @see org.junit.jupiter.api.condition.EnabledOnJre * @see org.junit.jupiter.api.condition.DisabledOnJre * @see org.junit.jupiter.api.condition.EnabledForJreRange * @see org.junit.jupiter.api.condition.DisabledForJreRange - * @see org.junit.jupiter.api.condition.EnabledOnOs - * @see org.junit.jupiter.api.condition.DisabledOnOs - * @see org.junit.jupiter.api.condition.EnabledIf - * @see org.junit.jupiter.api.condition.DisabledIf + * @see org.junit.jupiter.api.condition.EnabledInNativeImage + * @see org.junit.jupiter.api.condition.DisabledInNativeImage + * @see org.junit.jupiter.api.condition.EnabledIfSystemProperty + * @see org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable + * @see org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable * @see org.junit.jupiter.api.Disabled */ @Target({ ElementType.TYPE, ElementType.METHOD }) 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 new file mode 100644 index 000000000000..a51fa19a81ec --- /dev/null +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledInNativeImage.java @@ -0,0 +1,76 @@ +/* + * Copyright 2015-2022 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.jupiter.api.condition; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; + +/** + * {@code @DisabledInNativeImage} is used to signal that the annotated test class + * or test method is only disabled when executing within a GraalVM native + * image. + * + *

When applied at the class level, all test methods within that class will + * be disabled within a native image. + * + *

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} + * methods, {@code @AfterEach} methods, and corresponding extension APIs. + * + *

This annotation may be used as a meta-annotation in order to create a + * custom composed annotation that inherits the semantics of this + * annotation. + * + *

Technical Details

+ * + *

JUnit detects whether tests are executing within a GraalVM native image by + * checking for the presence of the {@code org.graalvm.nativeimage.imagecode} + * system property (see + * org.graalvm.nativeimage.ImageInfo + * for details). The GraalVM compiler sets the property to {@code buildtime} while + * compiling a native image; the property is set to {@code runtime} while a native + * image is executing; and the Gradle and Maven plug-ins in the GraalVM + * Native Build Tools + * project set the property to {@code agent} while executing tests with the GraalVM + * tracing agent. + * + * @since 5.9.1 + * @see org.junit.jupiter.api.condition.EnabledIf + * @see org.junit.jupiter.api.condition.DisabledIf + * @see org.junit.jupiter.api.condition.EnabledOnOs + * @see org.junit.jupiter.api.condition.DisabledOnOs + * @see org.junit.jupiter.api.condition.EnabledOnJre + * @see org.junit.jupiter.api.condition.DisabledOnJre + * @see org.junit.jupiter.api.condition.EnabledForJreRange + * @see org.junit.jupiter.api.condition.DisabledForJreRange + * @see org.junit.jupiter.api.condition.EnabledInNativeImage + * @see org.junit.jupiter.api.condition.EnabledIfSystemProperty + * @see org.junit.jupiter.api.condition.DisabledIfSystemProperty + * @see org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable + * @see org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable + * @see org.junit.jupiter.api.Disabled + */ +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@DisabledIfSystemProperty(named = "org.graalvm.nativeimage.imagecode", matches = ".+", // + disabledReason = "Currently executing within a GraalVM native image") +@API(status = STABLE, since = "5.9.1") +public @interface DisabledInNativeImage { +} 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 3fdf227cfa02..dac3c3a46c4a 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 @@ -51,17 +51,19 @@ * * @since 5.1 * @see JRE + * @see org.junit.jupiter.api.condition.EnabledIf + * @see org.junit.jupiter.api.condition.DisabledIf + * @see org.junit.jupiter.api.condition.EnabledOnOs + * @see org.junit.jupiter.api.condition.DisabledOnOs * @see org.junit.jupiter.api.condition.EnabledOnJre * @see org.junit.jupiter.api.condition.EnabledForJreRange * @see org.junit.jupiter.api.condition.DisabledForJreRange - * @see org.junit.jupiter.api.condition.EnabledOnOs - * @see org.junit.jupiter.api.condition.DisabledOnOs - * @see org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable - * @see org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable + * @see org.junit.jupiter.api.condition.EnabledInNativeImage + * @see org.junit.jupiter.api.condition.DisabledInNativeImage * @see org.junit.jupiter.api.condition.EnabledIfSystemProperty * @see org.junit.jupiter.api.condition.DisabledIfSystemProperty - * @see org.junit.jupiter.api.condition.EnabledIf - * @see org.junit.jupiter.api.condition.DisabledIf + * @see org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable + * @see org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable * @see org.junit.jupiter.api.Disabled */ @Target({ ElementType.TYPE, ElementType.METHOD }) 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 529aee6b1fc5..52b60f486cff 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 @@ -56,17 +56,19 @@ * * @since 5.1 * @see OS + * @see org.junit.jupiter.api.condition.EnabledIf + * @see org.junit.jupiter.api.condition.DisabledIf * @see org.junit.jupiter.api.condition.EnabledOnOs * @see org.junit.jupiter.api.condition.EnabledOnJre * @see org.junit.jupiter.api.condition.DisabledOnJre * @see org.junit.jupiter.api.condition.EnabledForJreRange * @see org.junit.jupiter.api.condition.DisabledForJreRange - * @see org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable - * @see org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable + * @see org.junit.jupiter.api.condition.EnabledInNativeImage + * @see org.junit.jupiter.api.condition.DisabledInNativeImage * @see org.junit.jupiter.api.condition.EnabledIfSystemProperty * @see org.junit.jupiter.api.condition.DisabledIfSystemProperty - * @see org.junit.jupiter.api.condition.EnabledIf - * @see org.junit.jupiter.api.condition.DisabledIf + * @see org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable + * @see org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable * @see org.junit.jupiter.api.Disabled */ @Target({ ElementType.TYPE, ElementType.METHOD }) 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 89f92c7ca758..e18d02c67bc9 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 @@ -51,17 +51,19 @@ * * @since 5.6 * @see JRE - * @see org.junit.jupiter.api.condition.DisabledForJreRange - * @see org.junit.jupiter.api.condition.EnabledOnJre - * @see org.junit.jupiter.api.condition.DisabledOnJre + * @see org.junit.jupiter.api.condition.EnabledIf + * @see org.junit.jupiter.api.condition.DisabledIf * @see org.junit.jupiter.api.condition.EnabledOnOs * @see org.junit.jupiter.api.condition.DisabledOnOs - * @see org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable - * @see org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable + * @see org.junit.jupiter.api.condition.EnabledOnJre + * @see org.junit.jupiter.api.condition.DisabledOnJre + * @see org.junit.jupiter.api.condition.DisabledForJreRange + * @see org.junit.jupiter.api.condition.EnabledInNativeImage + * @see org.junit.jupiter.api.condition.DisabledInNativeImage * @see org.junit.jupiter.api.condition.EnabledIfSystemProperty * @see org.junit.jupiter.api.condition.DisabledIfSystemProperty - * @see org.junit.jupiter.api.condition.EnabledIf - * @see org.junit.jupiter.api.condition.DisabledIf + * @see org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable + * @see org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable * @see org.junit.jupiter.api.Disabled */ @Target({ ElementType.TYPE, ElementType.METHOD }) 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 f4b9fa99249c..3005a8f0a460 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 @@ -57,10 +57,12 @@ * @see org.junit.jupiter.api.condition.DisabledOnJre * @see org.junit.jupiter.api.condition.EnabledForJreRange * @see org.junit.jupiter.api.condition.DisabledForJreRange - * @see org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable - * @see org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable + * @see org.junit.jupiter.api.condition.EnabledInNativeImage + * @see org.junit.jupiter.api.condition.DisabledInNativeImage * @see org.junit.jupiter.api.condition.EnabledIfSystemProperty * @see org.junit.jupiter.api.condition.DisabledIfSystemProperty + * @see org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable + * @see org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable * @see org.junit.jupiter.api.Disabled */ @Target({ ElementType.TYPE, ElementType.METHOD }) 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 2898881c08ee..7eca6dffc9bd 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 @@ -51,17 +51,19 @@ * given element. * * @since 5.1 - * @see org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable - * @see org.junit.jupiter.api.condition.EnabledIfSystemProperty - * @see org.junit.jupiter.api.condition.DisabledIfSystemProperty + * @see org.junit.jupiter.api.condition.EnabledIf + * @see org.junit.jupiter.api.condition.DisabledIf + * @see org.junit.jupiter.api.condition.EnabledOnOs + * @see org.junit.jupiter.api.condition.DisabledOnOs * @see org.junit.jupiter.api.condition.EnabledOnJre * @see org.junit.jupiter.api.condition.DisabledOnJre * @see org.junit.jupiter.api.condition.EnabledForJreRange * @see org.junit.jupiter.api.condition.DisabledForJreRange - * @see org.junit.jupiter.api.condition.EnabledOnOs - * @see org.junit.jupiter.api.condition.DisabledOnOs - * @see org.junit.jupiter.api.condition.EnabledIf - * @see org.junit.jupiter.api.condition.DisabledIf + * @see org.junit.jupiter.api.condition.EnabledInNativeImage + * @see org.junit.jupiter.api.condition.DisabledInNativeImage + * @see org.junit.jupiter.api.condition.EnabledIfSystemProperty + * @see org.junit.jupiter.api.condition.DisabledIfSystemProperty + * @see org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable * @see org.junit.jupiter.api.Disabled */ @Target({ ElementType.TYPE, ElementType.METHOD }) 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 428aa55676da..f26b972ce6e7 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 @@ -51,17 +51,19 @@ * given element. * * @since 5.1 - * @see org.junit.jupiter.api.condition.DisabledIfSystemProperty - * @see org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable - * @see org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable + * @see org.junit.jupiter.api.condition.EnabledIf + * @see org.junit.jupiter.api.condition.DisabledIf + * @see org.junit.jupiter.api.condition.EnabledOnOs + * @see org.junit.jupiter.api.condition.DisabledOnOs * @see org.junit.jupiter.api.condition.EnabledOnJre * @see org.junit.jupiter.api.condition.DisabledOnJre * @see org.junit.jupiter.api.condition.EnabledForJreRange * @see org.junit.jupiter.api.condition.DisabledForJreRange - * @see org.junit.jupiter.api.condition.EnabledOnOs - * @see org.junit.jupiter.api.condition.DisabledOnOs - * @see org.junit.jupiter.api.condition.EnabledIf - * @see org.junit.jupiter.api.condition.DisabledIf + * @see org.junit.jupiter.api.condition.EnabledInNativeImage + * @see org.junit.jupiter.api.condition.DisabledInNativeImage + * @see org.junit.jupiter.api.condition.DisabledIfSystemProperty + * @see org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable + * @see org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable * @see org.junit.jupiter.api.Disabled */ @Target({ ElementType.TYPE, ElementType.METHOD }) 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 new file mode 100644 index 000000000000..81fd19cbf338 --- /dev/null +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledInNativeImage.java @@ -0,0 +1,76 @@ +/* + * Copyright 2015-2022 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.jupiter.api.condition; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; + +/** + * {@code @EnabledInNativeImage} is used to signal that the annotated test class + * or test method is only enabled when executing within a GraalVM native + * image. + * + *

When applied at the class level, all test methods within that class will + * be enabled within a native image. + * + *

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} + * methods, {@code @AfterEach} methods, and corresponding extension APIs. + * + *

This annotation may be used as a meta-annotation in order to create a + * custom composed annotation that inherits the semantics of this + * annotation. + * + *

Technical Details

+ * + *

JUnit detects whether tests are executing within a GraalVM native image by + * checking for the presence of the {@code org.graalvm.nativeimage.imagecode} + * system property (see + * org.graalvm.nativeimage.ImageInfo + * for details). The GraalVM compiler sets the property to {@code buildtime} while + * compiling a native image; the property is set to {@code runtime} while a native + * image is executing; and the Gradle and Maven plug-ins in the GraalVM + * Native Build Tools + * project set the property to {@code agent} while executing tests with the GraalVM + * tracing agent. + * + * @since 5.9.1 + * @see org.junit.jupiter.api.condition.EnabledIf + * @see org.junit.jupiter.api.condition.DisabledIf + * @see org.junit.jupiter.api.condition.EnabledOnOs + * @see org.junit.jupiter.api.condition.DisabledOnOs + * @see org.junit.jupiter.api.condition.EnabledOnJre + * @see org.junit.jupiter.api.condition.DisabledOnJre + * @see org.junit.jupiter.api.condition.EnabledForJreRange + * @see org.junit.jupiter.api.condition.DisabledForJreRange + * @see org.junit.jupiter.api.condition.DisabledInNativeImage + * @see org.junit.jupiter.api.condition.EnabledIfSystemProperty + * @see org.junit.jupiter.api.condition.DisabledIfSystemProperty + * @see org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable + * @see org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable + * @see org.junit.jupiter.api.Disabled + */ +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@EnabledIfSystemProperty(named = "org.graalvm.nativeimage.imagecode", matches = ".+", // + disabledReason = "Not currently executing within a GraalVM native image") +@API(status = STABLE, since = "5.9.1") +public @interface EnabledInNativeImage { +} 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 adca5ff552ac..d8210bf71f8b 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 @@ -51,17 +51,19 @@ * * @since 5.1 * @see JRE + * @see org.junit.jupiter.api.condition.EnabledIf + * @see org.junit.jupiter.api.condition.DisabledIf + * @see org.junit.jupiter.api.condition.EnabledOnOs + * @see org.junit.jupiter.api.condition.DisabledOnOs * @see org.junit.jupiter.api.condition.DisabledOnJre * @see org.junit.jupiter.api.condition.EnabledForJreRange * @see org.junit.jupiter.api.condition.DisabledForJreRange - * @see org.junit.jupiter.api.condition.EnabledOnOs - * @see org.junit.jupiter.api.condition.DisabledOnOs - * @see org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable - * @see org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable + * @see org.junit.jupiter.api.condition.EnabledInNativeImage + * @see org.junit.jupiter.api.condition.DisabledInNativeImage * @see org.junit.jupiter.api.condition.EnabledIfSystemProperty * @see org.junit.jupiter.api.condition.DisabledIfSystemProperty - * @see org.junit.jupiter.api.condition.EnabledIf - * @see org.junit.jupiter.api.condition.DisabledIf + * @see org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable + * @see org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable * @see org.junit.jupiter.api.Disabled */ @Target({ ElementType.TYPE, ElementType.METHOD }) 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 7db330c896ca..e0c17deda556 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 @@ -56,17 +56,19 @@ * * @since 5.1 * @see OS + * @see org.junit.jupiter.api.condition.EnabledIf + * @see org.junit.jupiter.api.condition.DisabledIf * @see org.junit.jupiter.api.condition.DisabledOnOs * @see org.junit.jupiter.api.condition.EnabledOnJre * @see org.junit.jupiter.api.condition.DisabledOnJre * @see org.junit.jupiter.api.condition.EnabledForJreRange * @see org.junit.jupiter.api.condition.DisabledForJreRange - * @see org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable - * @see org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable + * @see org.junit.jupiter.api.condition.EnabledInNativeImage + * @see org.junit.jupiter.api.condition.DisabledInNativeImage * @see org.junit.jupiter.api.condition.EnabledIfSystemProperty * @see org.junit.jupiter.api.condition.DisabledIfSystemProperty - * @see org.junit.jupiter.api.condition.EnabledIf - * @see org.junit.jupiter.api.condition.DisabledIf + * @see org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable + * @see org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable * @see org.junit.jupiter.api.Disabled */ @Target({ ElementType.TYPE, ElementType.METHOD }) diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExecutionCondition.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExecutionCondition.java index ca7d725c8068..4edc1dafb849 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExecutionCondition.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExecutionCondition.java @@ -36,16 +36,20 @@ * * @since 5.0 * @see org.junit.jupiter.api.Disabled - * @see org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable - * @see org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable - * @see org.junit.jupiter.api.condition.EnabledIfSystemProperty - * @see org.junit.jupiter.api.condition.DisabledIfSystemProperty + * @see org.junit.jupiter.api.condition.EnabledIf + * @see org.junit.jupiter.api.condition.DisabledIf + * @see org.junit.jupiter.api.condition.EnabledOnOs + * @see org.junit.jupiter.api.condition.DisabledOnOs * @see org.junit.jupiter.api.condition.EnabledOnJre * @see org.junit.jupiter.api.condition.DisabledOnJre * @see org.junit.jupiter.api.condition.EnabledForJreRange * @see org.junit.jupiter.api.condition.DisabledForJreRange - * @see org.junit.jupiter.api.condition.EnabledOnOs - * @see org.junit.jupiter.api.condition.DisabledOnOs + * @see org.junit.jupiter.api.condition.EnabledInNativeImage + * @see org.junit.jupiter.api.condition.DisabledInNativeImage + * @see org.junit.jupiter.api.condition.EnabledIfSystemProperty + * @see org.junit.jupiter.api.condition.DisabledIfSystemProperty + * @see org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable + * @see org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable */ @FunctionalInterface @API(status = STABLE, since = "5.0") From 4ebec4cd44bbc94b5892fe1d46a5af3fb77fd14d Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Fri, 16 Sep 2022 16:57:15 +0200 Subject: [PATCH 16/59] Document #2830 in the release notes for 5.9.1 --- .../src/docs/asciidoc/release-notes/release-notes-5.9.1.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.1.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.1.adoc index 8827a8633af6..517307ffe191 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.1.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.1.adoc @@ -47,8 +47,8 @@ on GitHub. ==== New Features and Improvements -* ❓ - +* New `@EnabledInNativeImage` and `@DisabledInNativeImage` annotations for enabling and + disabling tests within a GraalVM native image. [[release-notes-5.9.1-junit-vintage]] === JUnit Vintage From 29d51a52bdec3eef170d270583cdb2d0d342bf14 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Mon, 8 Aug 2022 13:11:50 +0200 Subject: [PATCH 17/59] Make build cache URL configurable --- settings.gradle.kts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/settings.gradle.kts b/settings.gradle.kts index 1b3ced7b5dcf..63ef445d5280 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -38,6 +38,7 @@ dependencyResolutionManagement { val gradleEnterpriseServer = "https://ge.junit.org" val isCiServer = System.getenv("CI") != null +val junitBuildCacheUrl: String? by extra val junitBuildCacheUsername: String? by extra val junitBuildCachePassword: String? by extra @@ -79,7 +80,7 @@ buildCache { isEnabled = !isCiServer } remote { - url = uri("$gradleEnterpriseServer/cache/") + url = uri(junitBuildCacheUrl ?: "$gradleEnterpriseServer/cache/") isPush = isCiServer && !junitBuildCacheUsername.isNullOrEmpty() && !junitBuildCachePassword.isNullOrEmpty() credentials { username = junitBuildCacheUsername?.ifEmpty { null } From 052f9d4be5807bcf601a6b3942a8e720fcd97104 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Mon, 8 Aug 2022 13:31:22 +0200 Subject: [PATCH 18/59] Upgradle to 7.5.1 --- gradle/wrapper/gradle-wrapper.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 012d6d90445b..b916c04dbb24 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionSha256Sum=cb87f222c5585bd46838ad4db78463a5c5f3d336e5e2b98dc7c0c586527351c2 -distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip +distributionSha256Sum=f6b8596b10cce501591e92f229816aa4046424f3b24d771751b06779d58c8ec4 +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From 75f586cae87339e3d49b16557160470030fe3663 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Sat, 3 Sep 2022 12:35:47 +0200 Subject: [PATCH 19/59] Update test-retry plugin to 1.4.1 --- buildSrc/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index ef4f4f161e5e..5ab34e817b7f 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -15,7 +15,7 @@ dependencies { implementation("com.diffplug.spotless:spotless-plugin-gradle:6.0.0") implementation("com.github.ben-manes:gradle-versions-plugin:0.39.0") implementation("gradle.plugin.com.github.johnrengelman:shadow:7.1.2") - implementation("org.gradle:test-retry-gradle-plugin:1.3.1") + implementation("org.gradle:test-retry-gradle-plugin:1.4.1") compileOnly("com.gradle.enterprise:test-distribution-gradle-plugin:2.3.5") // keep in sync with root settings.gradle.kts } From 6ed6241614e116f0722bf70d70f0323d45d2a804 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Sat, 3 Sep 2022 15:48:58 +0200 Subject: [PATCH 20/59] Remove obsolete Jenkins-specific config --- .../projects/gradle-kotlin-extensions/build.gradle.kts | 3 --- .../projects/gradle-missing-engine/build.gradle.kts | 3 --- .../projects/gradle-starter/build.gradle.kts | 3 --- 3 files changed, 9 deletions(-) diff --git a/platform-tooling-support-tests/projects/gradle-kotlin-extensions/build.gradle.kts b/platform-tooling-support-tests/projects/gradle-kotlin-extensions/build.gradle.kts index c5245e8a531e..c79b5ff77427 100644 --- a/platform-tooling-support-tests/projects/gradle-kotlin-extensions/build.gradle.kts +++ b/platform-tooling-support-tests/projects/gradle-kotlin-extensions/build.gradle.kts @@ -9,9 +9,6 @@ repositories { mavenCentral() } -// don't use `build` as target to prevent Jenkins picking up -project.buildDir = file("bin") - // grab jupiter version from system environment val jupiterVersion = System.getenv("JUNIT_JUPITER_VERSION") diff --git a/platform-tooling-support-tests/projects/gradle-missing-engine/build.gradle.kts b/platform-tooling-support-tests/projects/gradle-missing-engine/build.gradle.kts index 29906a0122be..022b1d3a71eb 100644 --- a/platform-tooling-support-tests/projects/gradle-missing-engine/build.gradle.kts +++ b/platform-tooling-support-tests/projects/gradle-missing-engine/build.gradle.kts @@ -2,9 +2,6 @@ plugins { java } -// don't use `build` as target to prevent Jenkins picking up -buildDir = file("bin") - // grab jupiter version from system environment val jupiterVersion: String = System.getenv("JUNIT_JUPITER_VERSION") val vintageVersion: String = System.getenv("JUNIT_VINTAGE_VERSION") diff --git a/platform-tooling-support-tests/projects/gradle-starter/build.gradle.kts b/platform-tooling-support-tests/projects/gradle-starter/build.gradle.kts index fc89adfbd7eb..b1e87656b938 100644 --- a/platform-tooling-support-tests/projects/gradle-starter/build.gradle.kts +++ b/platform-tooling-support-tests/projects/gradle-starter/build.gradle.kts @@ -2,9 +2,6 @@ plugins { java } -// don't use `build` as target to prevent Jenkins picking up -buildDir = file("bin") - // grab jupiter version from system environment val jupiterVersion: String = System.getenv("JUNIT_JUPITER_VERSION") val vintageVersion: String = System.getenv("JUNIT_VINTAGE_VERSION") From 3267213c8a7bd800081cb66821c438c7a5421d65 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Sat, 3 Sep 2022 15:49:58 +0200 Subject: [PATCH 21/59] Add build tool integration tests for open-test-reporting on JDK 8 --- .../platform-tooling-support-tests.gradle.kts | 2 + .../projects/ant-starter/build.xml | 3 + .../projects/gradle-starter/build.gradle.kts | 9 + .../projects/maven-starter/pom.xml | 15 ++ .../support/tests/AntStarterTests.java | 11 +- .../support/tests/GradleStarterTests.java | 11 +- .../support/tests/MavenStarterTests.java | 11 +- .../tooling/support/tests/XmlAssertions.java | 158 ++++++++++++++++++ 8 files changed, 211 insertions(+), 9 deletions(-) create mode 100644 platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/XmlAssertions.java diff --git a/platform-tooling-support-tests/platform-tooling-support-tests.gradle.kts b/platform-tooling-support-tests/platform-tooling-support-tests.gradle.kts index 56dd772c6069..cb2a80ecaba8 100644 --- a/platform-tooling-support-tests/platform-tooling-support-tests.gradle.kts +++ b/platform-tooling-support-tests/platform-tooling-support-tests.gradle.kts @@ -40,6 +40,7 @@ dependencies { testImplementation(libs.ant) { because("we reference Ant's main class") } + testImplementation(libs.bundles.xmlunit) thirdPartyJars(libs.junit4) thirdPartyJars(libs.assertj) @@ -51,6 +52,7 @@ dependencies { antJars(libs.bundles.ant) antJars(projects.junitPlatformConsoleStandalone) antJars(projects.junitPlatformLauncher) + antJars(projects.junitPlatformReporting) mavenDistribution(libs.maven) { artifact { diff --git a/platform-tooling-support-tests/projects/ant-starter/build.xml b/platform-tooling-support-tests/projects/ant-starter/build.xml index 953cd72f852f..6c805ceb1ad7 100644 --- a/platform-tooling-support-tests/projects/ant-starter/build.xml +++ b/platform-tooling-support-tests/projects/ant-starter/build.xml @@ -52,8 +52,11 @@ + + + diff --git a/platform-tooling-support-tests/projects/gradle-starter/build.gradle.kts b/platform-tooling-support-tests/projects/gradle-starter/build.gradle.kts index b1e87656b938..33068798beb2 100644 --- a/platform-tooling-support-tests/projects/gradle-starter/build.gradle.kts +++ b/platform-tooling-support-tests/projects/gradle-starter/build.gradle.kts @@ -22,6 +22,7 @@ repositories { dependencies { testImplementation("org.junit.jupiter:junit-jupiter:$jupiterVersion") + testRuntimeOnly("org.junit.platform:junit-platform-reporting:$platformVersion") } tasks.test { @@ -35,6 +36,14 @@ tasks.test { html.isEnabled = true } + val outputDir = reports.junitXml.outputLocation + jvmArgumentProviders += CommandLineArgumentProvider { + listOf( + "-Djunit.platform.reporting.open.xml.enabled=true", + "-Djunit.platform.reporting.output.dir=${outputDir.get().asFile.absolutePath}" + ) + } + doFirst { println("Using Java version: ${JavaVersion.current()}") } diff --git a/platform-tooling-support-tests/projects/maven-starter/pom.xml b/platform-tooling-support-tests/projects/maven-starter/pom.xml index 21259e2fb99f..ad598a0fdec8 100644 --- a/platform-tooling-support-tests/projects/maven-starter/pom.xml +++ b/platform-tooling-support-tests/projects/maven-starter/pom.xml @@ -12,6 +12,7 @@ 1.8 ${maven.compiler.source} ${env.JUNIT_JUPITER_VERSION} + ${env.JUNIT_PLATFORM_VERSION} @@ -21,6 +22,12 @@ ${junit.jupiter.version} test + + org.junit.platform + junit-platform-reporting + ${junit.platform.version} + test + @@ -32,6 +39,14 @@ maven-surefire-plugin 2.22.2 + + + + junit.platform.reporting.open.xml.enabled = true + junit.platform.reporting.output.dir = ${project.build.directory}/surefire-reports + + + org.codehaus.gmaven diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/AntStarterTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/AntStarterTests.java index 900e3e8c8736..371dfe77d598 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/AntStarterTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/AntStarterTests.java @@ -13,6 +13,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertLinesMatch; +import static platform.tooling.support.tests.XmlAssertions.verifyContainsExpectedStartedOpenTestReport; import java.util.List; @@ -29,13 +30,14 @@ class AntStarterTests { @Test void ant_starter() { - var result = Request.builder() // + var request = Request.builder() // .setTool(new Java()) // .setProject("ant-starter") // .addArguments("-cp", System.getProperty("antJars"), Main.class.getName()) // .addArguments("-verbose") // - .build() // - .run(); + .build(); + + var result = request.run(); assertFalse(result.isTimedOut(), () -> "tool timed out: " + result); @@ -54,5 +56,8 @@ void ant_starter() { " \\[java\\] \\[ 0 tests failed \\]", // ">> TAIL >>"), // result.getOutputLines("out")); + + var testResultsDir = Request.WORKSPACE.resolve(request.getWorkspace()).resolve("build/test-report"); + verifyContainsExpectedStartedOpenTestReport(testResultsDir); } } diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleStarterTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleStarterTests.java index 975a6f3d0986..076c998ca40b 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleStarterTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleStarterTests.java @@ -15,6 +15,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static platform.tooling.support.Helper.TOOL_TIMEOUT; +import static platform.tooling.support.tests.XmlAssertions.verifyContainsExpectedStartedOpenTestReport; import java.nio.file.Paths; @@ -34,20 +35,24 @@ class GradleStarterTests { @Test void gradle_wrapper() { - var result = Request.builder() // + var request = Request.builder() // .setTool(new GradleWrapper(Paths.get(".."))) // .setProject("gradle-starter") // .addArguments("-Dmaven.repo=" + MavenRepo.dir()) // .addArguments("build", "--no-daemon", "--stacktrace") // .setTimeout(TOOL_TIMEOUT) // .setJavaHome(Helper.getJavaHome("8").orElseThrow(TestAbortedException::new)) // - .build() // - .run(); + .build(); + + var result = request.run(); assertFalse(result.isTimedOut(), () -> "tool timed out: " + result); assertEquals(0, result.getExitCode()); assertTrue(result.getOutputLines("out").stream().anyMatch(line -> line.contains("BUILD SUCCESSFUL"))); assertThat(result.getOutput("out")).contains("Using Java version: 1.8"); + + var testResultsDir = Request.WORKSPACE.resolve(request.getWorkspace()).resolve("build/test-results/test"); + verifyContainsExpectedStartedOpenTestReport(testResultsDir); } } diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenStarterTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenStarterTests.java index e7c4b9b520cd..f4caa516af2e 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenStarterTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenStarterTests.java @@ -15,6 +15,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static platform.tooling.support.Helper.TOOL_TIMEOUT; +import static platform.tooling.support.tests.XmlAssertions.verifyContainsExpectedStartedOpenTestReport; import org.junit.jupiter.api.Test; import org.opentest4j.TestAbortedException; @@ -30,15 +31,16 @@ class MavenStarterTests { @Test void verifyMavenStarterProject() { - var result = Request.builder() // + var request = Request.builder() // .setTool(Request.maven()) // .setProject("maven-starter") // .addArguments("-Dmaven.repo=" + MavenRepo.dir()) // .addArguments("--update-snapshots", "--batch-mode", "verify") // .setTimeout(TOOL_TIMEOUT) // .setJavaHome(Helper.getJavaHome("8").orElseThrow(TestAbortedException::new)) // - .build() // - .run(); + .build(); + + var result = request.run(); assertFalse(result.isTimedOut(), () -> "tool timed out: " + result); @@ -47,5 +49,8 @@ void verifyMavenStarterProject() { assertTrue(result.getOutputLines("out").contains("[INFO] BUILD SUCCESS")); assertTrue(result.getOutputLines("out").contains("[INFO] Tests run: 5, Failures: 0, Errors: 0, Skipped: 0")); assertThat(result.getOutput("out")).contains("Using Java version: 1.8"); + + var testResultsDir = Request.WORKSPACE.resolve(request.getWorkspace()).resolve("target/surefire-reports"); + verifyContainsExpectedStartedOpenTestReport(testResultsDir); } } diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/XmlAssertions.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/XmlAssertions.java new file mode 100644 index 000000000000..976920ae03bd --- /dev/null +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/XmlAssertions.java @@ -0,0 +1,158 @@ +/* + * Copyright 2015-2022 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 platform.tooling.support.tests; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; + +import org.xmlunit.assertj3.XmlAssert; +import org.xmlunit.placeholder.PlaceholderDifferenceEvaluator; + +class XmlAssertions { + + static void verifyContainsExpectedStartedOpenTestReport(Path testResultsDir) { + try (var files = Files.list(testResultsDir)) { + Path xmlFile = files.filter(it -> it.getFileName().toString().startsWith("junit-platform-events-")) // + .findAny() // + .orElseThrow(() -> new AssertionError("Missing open-test-reporting XML file in " + testResultsDir)); + verifyContent(xmlFile); + } + catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + private static void verifyContent(Path xmlFile) { + var expected = """ + + + ${xmlunit.ignore} + ${xmlunit.ignore} + ${xmlunit.ignore} + ${xmlunit.ignore} + ${xmlunit.ignore} + ${xmlunit.ignore} + + + + + [engine:junit-jupiter] + JUnit Jupiter + CONTAINER + + + + + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests] + com.example.project.CalculatorTests + CONTAINER + + + + + + + + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[method:addsTwoNumbers()] + addsTwoNumbers() + TEST + + + + + + + + + + + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)] + add(int, int, int) + CONTAINER + + + + + + + + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#1] + add(int, int, int)[1] + TEST + + + + + + + + + + + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#2] + add(int, int, int)[2] + TEST + + + + + + + + + + + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#3] + add(int, int, int)[3] + TEST + + + + + + + + + + + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#4] + add(int, int, int)[4] + TEST + + + + + + + + + + + + + + + + + + + """; + + XmlAssert.assertThat(xmlFile).and(expected) // + .withDifferenceEvaluator(new PlaceholderDifferenceEvaluator()) // + .ignoreWhitespace() // + .areIdentical(); + } +} From 7c64906cbc3e4c6559b6ad4857cee9d4135f9b8c Mon Sep 17 00:00:00 2001 From: Stefano Cordio Date: Wed, 7 Sep 2022 12:31:29 +0200 Subject: [PATCH 22/59] Fix formatting of `assertTimeoutPreemptively()` in user guide (#3023) --- documentation/src/docs/asciidoc/user-guide/writing-tests.adoc | 2 +- 1 file changed, 1 insertion(+), 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 32705596e269..6fb1838f63ec 100644 --- a/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc +++ b/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc @@ -270,7 +270,7 @@ include::{testDir}/example/AssertionsDemo.java[tags=user_guide] [WARNING] .Preemptive Timeouts with `assertTimeoutPreemptively()` ==== -The various`assertTimeoutPreemptively()` methods in the `Assertions` class execute +The various `assertTimeoutPreemptively()` methods in the `Assertions` class execute the provided `executable` or `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 `executable` or `supplier` relies on `java.lang.ThreadLocal` storage. From 62d7aa34366b515adb4078daf0f2ff9a67d77e1d Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Wed, 7 Sep 2022 12:46:35 +0200 Subject: [PATCH 23/59] Fix custom reporting output dir on Windows (#3024) --- .../user-guide/advanced-topics/junit-platform-reporting.adoc | 2 +- platform-tooling-support-tests/projects/maven-starter/pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/documentation/src/docs/asciidoc/user-guide/advanced-topics/junit-platform-reporting.adoc b/documentation/src/docs/asciidoc/user-guide/advanced-topics/junit-platform-reporting.adoc index c6d89a0edba8..655d6bc7903b 100644 --- a/documentation/src/docs/asciidoc/user-guide/advanced-topics/junit-platform-reporting.adoc +++ b/documentation/src/docs/asciidoc/user-guide/advanced-topics/junit-platform-reporting.adoc @@ -114,7 +114,7 @@ XML reports as follows: junit.platform.reporting.open.xml.enabled = true - junit.platform.reporting.output.dir = ${project.build.directory}/surefire-reports + junit.platform.reporting.output.dir = target/surefire-reports diff --git a/platform-tooling-support-tests/projects/maven-starter/pom.xml b/platform-tooling-support-tests/projects/maven-starter/pom.xml index ad598a0fdec8..9ec72ae39829 100644 --- a/platform-tooling-support-tests/projects/maven-starter/pom.xml +++ b/platform-tooling-support-tests/projects/maven-starter/pom.xml @@ -43,7 +43,7 @@ junit.platform.reporting.open.xml.enabled = true - junit.platform.reporting.output.dir = ${project.build.directory}/surefire-reports + junit.platform.reporting.output.dir = target/surefire-reports From 289053f20f712323a69483f7fd6ab7049b4ef372 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Sun, 11 Sep 2022 10:29:28 +0200 Subject: [PATCH 24/59] Update to Gradle Enterprise plugin 3.11.1 Closes #3002. --- buildSrc/build.gradle.kts | 2 +- settings.gradle.kts | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 5ab34e817b7f..9fd96fb5833b 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -16,7 +16,7 @@ dependencies { implementation("com.github.ben-manes:gradle-versions-plugin:0.39.0") implementation("gradle.plugin.com.github.johnrengelman:shadow:7.1.2") implementation("org.gradle:test-retry-gradle-plugin:1.4.1") - compileOnly("com.gradle.enterprise:test-distribution-gradle-plugin:2.3.5") // keep in sync with root settings.gradle.kts + compileOnly("com.gradle:gradle-enterprise-gradle-plugin:3.11.1") // keep in sync with root settings.gradle.kts } tasks { diff --git a/settings.gradle.kts b/settings.gradle.kts index 63ef445d5280..74bb84d09465 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -5,8 +5,7 @@ pluginManagement { gradlePluginPortal() } plugins { - id("com.gradle.enterprise") version "3.10.3" - id("com.gradle.enterprise.test-distribution") version "2.3.5" // keep in sync with buildSrc/build.gradle.kts + id("com.gradle.enterprise") version "3.11.1" // keep in sync with buildSrc/build.gradle.kts id("com.gradle.common-custom-user-data-gradle-plugin") version "1.7.2" id("org.ajoberstar.git-publish") version "3.0.0" kotlin("jvm") version "1.5.31" @@ -21,7 +20,6 @@ pluginManagement { plugins { id("com.gradle.enterprise") - id("com.gradle.enterprise.test-distribution") id("com.gradle.common-custom-user-data-gradle-plugin") } From 4ca59cb2bdcb78ad5a9c426d4d694762e3800ce6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 11 Sep 2022 10:30:53 +0200 Subject: [PATCH 25/59] Bump CCUD Gradle plugin from 1.7.2 to 1.8.1 (#3031) --- settings.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings.gradle.kts b/settings.gradle.kts index 74bb84d09465..6cc4aa4ef877 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -6,7 +6,7 @@ pluginManagement { } plugins { id("com.gradle.enterprise") version "3.11.1" // keep in sync with buildSrc/build.gradle.kts - id("com.gradle.common-custom-user-data-gradle-plugin") version "1.7.2" + id("com.gradle.common-custom-user-data-gradle-plugin") version "1.8.1" id("org.ajoberstar.git-publish") version "3.0.0" kotlin("jvm") version "1.5.31" // Check if workaround in documentation.gradle.kts can be removed when upgrading From 438d50a6d302dfebc7d79373ee7d6dd7fdfbc09b Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Sun, 11 Sep 2022 11:55:37 +0200 Subject: [PATCH 26/59] Fix build with for JDK 20-ea+14 toolchain Issue: #3029 --- .../src/main/kotlin/java-toolchain-conventions.gradle.kts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/buildSrc/src/main/kotlin/java-toolchain-conventions.gradle.kts b/buildSrc/src/main/kotlin/java-toolchain-conventions.gradle.kts index e72c2244497b..daab6ae2c567 100644 --- a/buildSrc/src/main/kotlin/java-toolchain-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/java-toolchain-conventions.gradle.kts @@ -18,4 +18,11 @@ project.pluginManager.withPlugin("java") { languageVersion.set(defaultLanguageVersion) }) } + if (javaLanguageVersion.asInt() >= 20) { + tasks.compileJava { + options.compilerArgs.add( + "-Xlint:-options" // see https://github.com/junit-team/junit5/issues/3029 + ) + } + } } From cc7cdecb398706f9968a8cd95cb4254aa499194d Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Sun, 11 Sep 2022 12:00:58 +0200 Subject: [PATCH 27/59] Fix build --- buildSrc/src/main/kotlin/java-toolchain-conventions.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/kotlin/java-toolchain-conventions.gradle.kts b/buildSrc/src/main/kotlin/java-toolchain-conventions.gradle.kts index daab6ae2c567..0c05bc1c94d1 100644 --- a/buildSrc/src/main/kotlin/java-toolchain-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/java-toolchain-conventions.gradle.kts @@ -19,7 +19,7 @@ project.pluginManager.withPlugin("java") { }) } if (javaLanguageVersion.asInt() >= 20) { - tasks.compileJava { + tasks.named("compileJava") { options.compilerArgs.add( "-Xlint:-options" // see https://github.com/junit-team/junit5/issues/3029 ) From bd90d72b43ed98325d2c12c7d51f2ac103ad2421 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Sun, 11 Sep 2022 13:53:49 +0200 Subject: [PATCH 28/59] Adjust all compile tasks with --release=8 --- .../kotlin/java-toolchain-conventions.gradle.kts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/buildSrc/src/main/kotlin/java-toolchain-conventions.gradle.kts b/buildSrc/src/main/kotlin/java-toolchain-conventions.gradle.kts index 0c05bc1c94d1..c85e77e260f2 100644 --- a/buildSrc/src/main/kotlin/java-toolchain-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/java-toolchain-conventions.gradle.kts @@ -11,6 +11,13 @@ project.pluginManager.withPlugin("java") { } tasks.withType().configureEach { outputs.cacheIf { javaLanguageVersion == defaultLanguageVersion } + doFirst { + if (options.release.orNull == 8 && javaLanguageVersion.asInt() >= 20) { + options.compilerArgs.add( + "-Xlint:-options" // see https://github.com/junit-team/junit5/issues/3029 + ) + } + } } tasks.withType().configureEach { javaLauncher.set(javaToolchainService.launcherFor { @@ -18,11 +25,4 @@ project.pluginManager.withPlugin("java") { languageVersion.set(defaultLanguageVersion) }) } - if (javaLanguageVersion.asInt() >= 20) { - tasks.named("compileJava") { - options.compilerArgs.add( - "-Xlint:-options" // see https://github.com/junit-team/junit5/issues/3029 - ) - } - } } From b05b2dc7da5c6872ee0527d8fd083935908e8984 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Sun, 11 Sep 2022 14:40:51 +0200 Subject: [PATCH 29/59] Stabilize timeout test --- .../junit/jupiter/engine/extension/TimeoutExtensionTests.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutExtensionTests.java b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutExtensionTests.java index 38249d9b03ec..449ee09a085f 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutExtensionTests.java +++ b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutExtensionTests.java @@ -376,7 +376,7 @@ void separateThreadHandlesInvocationExceptions() { Execution execution = findExecution(results.testEvents(), "test()"); assertThat(execution.getDuration()) // - .isLessThanOrEqualTo(Duration.ofMillis(10)); + .isLessThan(Duration.ofSeconds(5)); assertThat(execution.getTerminationInfo().getExecutionResult().getThrowable().orElseThrow()) // .isInstanceOf(RuntimeException.class) // .hasMessage("Oppps!"); @@ -723,7 +723,7 @@ void test() { static class ExceptionInSeparateThreadTestCase { @Test - @Timeout(value = 100, unit = MILLISECONDS, threadMode = SEPARATE_THREAD) + @Timeout(value = 5, unit = SECONDS, threadMode = SEPARATE_THREAD) void test() { throw new RuntimeException("Oppps!"); } From 39d8cfcc29a8c8455934c6bdbc2882c2828f8ecb Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Sun, 11 Sep 2022 17:18:58 +0200 Subject: [PATCH 30/59] Retry tests in new JVM on Windows --- platform-tests/platform-tests.gradle.kts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/platform-tests/platform-tests.gradle.kts b/platform-tests/platform-tests.gradle.kts index 8f67df13a8bf..cca78db6b588 100644 --- a/platform-tests/platform-tests.gradle.kts +++ b/platform-tests/platform-tests.gradle.kts @@ -1,5 +1,6 @@ import org.gradle.api.tasks.PathSensitivity.NONE import org.gradle.api.tasks.PathSensitivity.RELATIVE +import org.gradle.internal.os.OperatingSystem plugins { `java-library-conventions` @@ -61,6 +62,11 @@ tasks { excludeTags("exclude") } jvmArgs("-Xmx1g") + distribution { + // Retry in a new JVM on Windows to improve chances of successful retries when + // cached resources are used (e.g. in ClasspathScannerTests) + retryInSameJvm.set(!OperatingSystem.current().isWindows) + } } test { // Additional inputs for remote execution with Test Distribution From 1971445712dfd148fb0912f40c0b1cfdb3176b89 Mon Sep 17 00:00:00 2001 From: Gilberto J Requena Date: Mon, 15 Aug 2022 19:39:27 +0200 Subject: [PATCH 31/59] Fix incorrect timeout failure when assertion fails in separate thread Fixes #3000. --- .../org/junit/jupiter/api/AssertTimeout.java | 144 +++++++++++++----- .../org/junit/jupiter/api/Assertions.java | 32 ++++ .../SeparateThreadTimeoutInvocation.java | 19 ++- .../api/AssertTimeoutAssertionsTests.java | 35 +++++ .../SeparateThreadTimeoutInvocationTest.java | 3 +- .../extension/TimeoutExtensionTests.java | 35 ++++- 6 files changed, 223 insertions(+), 45 deletions(-) diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java index 81268f7eafec..c0c8f018a02e 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java @@ -10,7 +10,10 @@ package org.junit.jupiter.api; +import static org.junit.jupiter.api.AssertTimeout.PreemptiveTimeoutAssertionExecutor.Throwing.ASSERTION_EXCEPTION; +import static org.junit.jupiter.api.AssertTimeout.PreemptiveTimeoutAssertionExecutor.Throwing.MASKED_TIMEOUT_EXCEPTION; import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; +import static org.junit.platform.commons.util.ExceptionUtils.throwAsUncheckedException; import java.time.Duration; import java.util.concurrent.ExecutionException; @@ -112,27 +115,55 @@ static void assertTimeoutPreemptively(Duration timeout, Executable executable, S } static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier supplier) { - return assertTimeoutPreemptively(timeout, supplier, (Object) null); + return new PreemptiveTimeoutAssertionExecutor(timeout, supplier, null).executeThrowing(ASSERTION_EXCEPTION); } static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier supplier, String message) { - return assertTimeoutPreemptively(timeout, supplier, (Object) message); + return new PreemptiveTimeoutAssertionExecutor(timeout, supplier, message).executeThrowing( + ASSERTION_EXCEPTION); } static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier supplier, Supplier messageSupplier) { + return new PreemptiveTimeoutAssertionExecutor(timeout, supplier, messageSupplier).executeThrowing( + ASSERTION_EXCEPTION); + } - return assertTimeoutPreemptively(timeout, supplier, (Object) messageSupplier); + static T assertTimeoutPreemptivelyThrowingTimeoutException(Duration timeout, ThrowingSupplier supplier, + Supplier messageSupplier) { + return new PreemptiveTimeoutAssertionExecutor(timeout, supplier, messageSupplier).executeThrowing( + MASKED_TIMEOUT_EXCEPTION); } - private static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier supplier, - Object messageOrSupplier) { + static class PreemptiveTimeoutAssertionExecutor { + private final Duration timeout; + private final ThrowingSupplier supplier; + private final Object messageOrSupplier; + + PreemptiveTimeoutAssertionExecutor(Duration timeout, ThrowingSupplier supplier, Object messageOrSupplier) { + this.timeout = timeout; + this.supplier = supplier; + this.messageOrSupplier = messageOrSupplier; + } - AtomicReference threadReference = new AtomicReference<>(); - ExecutorService executorService = Executors.newSingleThreadExecutor(new TimeoutThreadFactory()); + T executeThrowing(Throwing throwing) { + AtomicReference threadReference = new AtomicReference<>(); + ExecutorService executorService = Executors.newSingleThreadExecutor(new TimeoutThreadFactory()); - try { - Future future = executorService.submit(() -> { + try { + Future future = submitTask(supplier, threadReference, executorService); + FutureResolverWithExceptionHandling resolver = createFutureResolver(messageOrSupplier, + threadReference, throwing); + return resolver.resolveFutureAndHandleException(future, timeout.toMillis()); + } + finally { + executorService.shutdownNow(); + } + } + + private Future submitTask(ThrowingSupplier supplier, AtomicReference threadReference, + ExecutorService executorService) { + return executorService.submit(() -> { try { threadReference.set(Thread.currentThread()); return supplier.get(); @@ -141,34 +172,26 @@ private static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplie throw ExceptionUtils.throwAsUncheckedException(throwable); } }); + } - long timeoutInMillis = timeout.toMillis(); - try { - return future.get(timeoutInMillis, TimeUnit.MILLISECONDS); - } - catch (TimeoutException ex) { - AssertionFailureBuilder failure = assertionFailure() // - .message(messageOrSupplier) // - .reason("execution timed out after " + timeoutInMillis + " ms"); - - Thread thread = threadReference.get(); - if (thread != null) { - ExecutionTimeoutException exception = new ExecutionTimeoutException( - "Execution timed out in thread " + thread.getName()); - exception.setStackTrace(thread.getStackTrace()); - failure.cause(exception); - } - throw failure.build(); - } - catch (ExecutionException ex) { - throw ExceptionUtils.throwAsUncheckedException(ex.getCause()); - } - catch (Throwable ex) { - throw ExceptionUtils.throwAsUncheckedException(ex); + private FutureResolverWithExceptionHandling createFutureResolver(Object messageOrSupplier, + AtomicReference threadReference, Throwing throwing) { + FutureResolverWithExceptionHandling resolver; + switch (throwing) { + case MASKED_TIMEOUT_EXCEPTION: + resolver = new TimeoutPropagatingFutureResolver<>(); + break; + case ASSERTION_EXCEPTION: + resolver = new AssertiveFutureResolver<>(threadReference, messageOrSupplier); + break; + default: + throw new IllegalStateException("Unexpected value: " + throwing); } + return resolver; } - finally { - executorService.shutdownNow(); + + enum Throwing { + ASSERTION_EXCEPTION, MASKED_TIMEOUT_EXCEPTION } } @@ -194,4 +217,57 @@ public Thread newThread(Runnable r) { } } + private abstract static class FutureResolverWithExceptionHandling { + T resolveFutureAndHandleException(Future future, long timeoutInMillis) { + try { + return future.get(timeoutInMillis, TimeUnit.MILLISECONDS); + } + catch (TimeoutException ex) { + handleTimeoutAndThrow(ex, timeoutInMillis); + return null; + } + catch (ExecutionException ex) { + throw throwAsUncheckedException(ex.getCause()); + } + catch (Throwable ex) { + throw throwAsUncheckedException(ex); + } + } + + protected abstract void handleTimeoutAndThrow(TimeoutException ex, long timeoutInMillis); + } + + private static class AssertiveFutureResolver extends FutureResolverWithExceptionHandling { + + private final AtomicReference threadReference; + private final Object messageOrSupplier; + + private AssertiveFutureResolver(AtomicReference threadReference, Object messageOrSupplier) { + this.threadReference = threadReference; + this.messageOrSupplier = messageOrSupplier; + } + + @Override + protected void handleTimeoutAndThrow(TimeoutException ex, long timeoutInMillis) { + AssertionFailureBuilder failure = assertionFailure() // + .message(messageOrSupplier) // + .reason("execution timed out after " + timeoutInMillis + " ms"); + + Thread thread = threadReference.get(); + if (thread != null) { + ExecutionTimeoutException exception = new ExecutionTimeoutException( + "Execution timed out in thread " + thread.getName()); + exception.setStackTrace(thread.getStackTrace()); + failure.cause(exception); + } + throw failure.build(); + } + } + + private static class TimeoutPropagatingFutureResolver extends FutureResolverWithExceptionHandling { + @Override + protected void handleTimeoutAndThrow(TimeoutException ex, long timeoutInMillis) { + throw throwAsUncheckedException(ex); + } + } } 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 fa6585771612..ec46e79cd1c0 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 @@ -11,6 +11,7 @@ package org.junit.jupiter.api; import static org.apiguardian.api.API.Status.EXPERIMENTAL; +import static org.apiguardian.api.API.Status.INTERNAL; import static org.apiguardian.api.API.Status.STABLE; import java.time.Duration; @@ -3523,6 +3524,37 @@ public static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier return AssertTimeout.assertTimeoutPreemptively(timeout, supplier, messageSupplier); } + /** + * 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. + * + *

In the case the assertion does not pass, a {@code TimeoutException} will be + * 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}. + * + * @see #assertTimeoutPreemptively(Duration, Executable) + * @see #assertTimeoutPreemptively(Duration, Executable, String) + * @see #assertTimeoutPreemptively(Duration, Executable, Supplier) + * @see #assertTimeoutPreemptively(Duration, ThrowingSupplier) + * @see #assertTimeoutPreemptively(Duration, ThrowingSupplier, String) + * @see #assertTimeout(Duration, Executable, Supplier) + */ + @API(status = INTERNAL) + public static T assertTimeoutPreemptivelyThrowingTimeoutException(Duration timeout, + ThrowingSupplier supplier, Supplier messageSupplier) { + return AssertTimeout.assertTimeoutPreemptivelyThrowingTimeoutException(timeout, supplier, messageSupplier); + } + // --- assertInstanceOf ---------------------------------------------------- /** diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/SeparateThreadTimeoutInvocation.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/SeparateThreadTimeoutInvocation.java index 1860f413455d..bda2d685aaa5 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/SeparateThreadTimeoutInvocation.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/SeparateThreadTimeoutInvocation.java @@ -10,13 +10,12 @@ package org.junit.jupiter.engine.extension; -import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively; +import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptivelyThrowingTimeoutException; import java.util.concurrent.TimeoutException; import java.util.function.Supplier; import org.junit.jupiter.api.extension.InvocationInterceptor.Invocation; -import org.opentest4j.AssertionFailedError; /** * @since 5.9 @@ -37,12 +36,18 @@ class SeparateThreadTimeoutInvocation implements Invocation { @Override public T proceed() throws Throwable { try { - return assertTimeoutPreemptively(timeout.toDuration(), delegate::proceed, descriptionSupplier); + return assertTimeoutPreemptivelyThrowingTimeoutException(timeout.toDuration(), delegate::proceed, + descriptionSupplier); } - catch (AssertionFailedError failure) { - TimeoutException exception = TimeoutExceptionFactory.create(descriptionSupplier.get(), timeout, null); - exception.initCause(failure.getCause()); - throw exception; + catch (Throwable failure) { + if (failure instanceof TimeoutException) { + TimeoutException exception = TimeoutExceptionFactory.create(descriptionSupplier.get(), timeout, null); + exception.initCause(failure.getCause()); + throw exception; + } + else { + throw failure; + } } } } diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertTimeoutAssertionsTests.java b/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertTimeoutAssertionsTests.java index 424317ed6b3b..512d1013eeda 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertTimeoutAssertionsTests.java +++ b/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertTimeoutAssertionsTests.java @@ -19,12 +19,14 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTimeout; import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively; +import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptivelyThrowingTimeoutException; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.condition.OS.WINDOWS; import java.time.Duration; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; @@ -314,6 +316,39 @@ void assertTimeoutPreemptivelyUsesThreadsWithSpecificNamePrefix() { "Thread name does not match the expected prefix"); } + @Test + void assertTimeoutPreemptivelyThrowingTimeoutExceptionWithMessageForSupplierThatCompletesAfterTheTimeout() { + assertThrows(TimeoutException.class, + () -> assertTimeoutPreemptivelyThrowingTimeoutException(PREEMPTIVE_TIMEOUT, () -> { + waitForInterrupt(); + return "Tempus Fugit"; + }, () -> "Tempus Fugit")); + } + + @Test + void assertTimeoutPreemptivelyThrowingTimeoutExceptionWithMessageForSupplierThatThrowsAnAssertionFailedError() { + AssertionFailedError exception = assertThrows(AssertionFailedError.class, + () -> assertTimeoutPreemptivelyThrowingTimeoutException(ofMillis(500), () -> fail("enigma"), + () -> "Tempus Fugit")); + assertMessageEquals(exception, "enigma"); + } + + @Test + void assertTimeoutPreemptivelyThrowingTimeoutExceptionWithMessageForSupplierThatThrowsAnException() { + RuntimeException exception = assertThrows(RuntimeException.class, + () -> assertTimeoutPreemptivelyThrowingTimeoutException(ofMillis(500), + () -> ExceptionUtils.throwAsUncheckedException(new RuntimeException(":(")), () -> "Tempus Fugit")); + assertMessageEquals(exception, ":("); + } + + @Test + void assertTimeoutPreemptivelyThrowingTimeoutExceptionWithMessageForSupplierThatCompletesBeforeTimeout() { + var result = assertTimeoutPreemptivelyThrowingTimeoutException(PREEMPTIVE_TIMEOUT, () -> "Tempus Fugit", + () -> "Tempus Fugit"); + + assertThat(result).isEqualTo("Tempus Fugit"); + } + /** * Take a nap for 100 milliseconds. */ diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/SeparateThreadTimeoutInvocationTest.java b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/SeparateThreadTimeoutInvocationTest.java index a5355fa63682..5f8e867b3c86 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/SeparateThreadTimeoutInvocationTest.java +++ b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/SeparateThreadTimeoutInvocationTest.java @@ -47,8 +47,7 @@ void throwsTimeoutException() { assertThatThrownBy(invocation::proceed) // .hasMessage("method() timed out after " + PREEMPTIVE_TIMEOUT_MILLIS + " milliseconds") // - .isInstanceOf(TimeoutException.class) // - .hasRootCauseMessage("Execution timed out in thread " + threadName.get()); + .isInstanceOf(TimeoutException.class); } @Test diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutExtensionTests.java b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutExtensionTests.java index 449ee09a085f..183543711789 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutExtensionTests.java +++ b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutExtensionTests.java @@ -60,6 +60,7 @@ import org.junit.platform.testkit.engine.EngineExecutionResults; import org.junit.platform.testkit.engine.Events; import org.junit.platform.testkit.engine.Execution; +import org.opentest4j.AssertionFailedError; /** * @since 5.5 @@ -382,13 +383,29 @@ void separateThreadHandlesInvocationExceptions() { .hasMessage("Oppps!"); } + @Test + @DisplayName("propagates assertion exceptions") + void separateThreadHandlesOpenTestFailedAssertion() { + EngineExecutionResults results = executeTestsForClass(FailedAssertionInSeparateThreadTestCase.class); + + Execution openTestFailure = findExecution(results.testEvents(), "testOpenTestAssertion()"); + assertThat(openTestFailure.getDuration()) // + .isLessThan(Duration.ofSeconds(5)); + assertThat(openTestFailure.getTerminationInfo().getExecutionResult().getThrowable().orElseThrow()) // + .isInstanceOf(AssertionFailedError.class); + + Execution javaLangFailure = findExecution(results.testEvents(), "testJavaLangAssertion()"); + assertThat(javaLangFailure.getDuration()) // + .isLessThan(Duration.ofSeconds(5)); + assertThat(javaLangFailure.getTerminationInfo().getExecutionResult().getThrowable().orElseThrow()) // + .isInstanceOf(AssertionError.class); + } + @Test @DisplayName("when one test is stuck \"forever\" the next tests should not get stuck") void oneThreadStuckForever() { EngineExecutionResults results = executeTestsForClass(OneTestStuckForeverAndTheOthersNotTestCase.class); - results.allEvents().debug(); - Execution stuckExecution = findExecution(results.testEvents(), "stuck()"); assertThat(stuckExecution.getTerminationInfo().getExecutionResult().getThrowable().orElseThrow()) // .isInstanceOf(TimeoutException.class) // @@ -729,6 +746,20 @@ void test() { } } + static class FailedAssertionInSeparateThreadTestCase { + @Test + @Timeout(value = 5, unit = SECONDS, threadMode = SEPARATE_THREAD) + void testOpenTestAssertion() { + throw new AssertionFailedError(); + } + + @Test + @Timeout(value = 5, unit = SECONDS, threadMode = SEPARATE_THREAD) + void testJavaLangAssertion() { + throw new AssertionError(); + } + } + @Timeout(value = 100, unit = MILLISECONDS, threadMode = SEPARATE_THREAD) static class TimeoutExceededOnClassLevelTestCase { @Test From 024a1e059380a85b06612aff4bf6dbc2ad6ae980 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Fri, 9 Sep 2022 14:37:50 +0200 Subject: [PATCH 32/59] Use constants for PreemptiveTimeoutAssertionExecutor instances --- .../org/junit/jupiter/api/AssertTimeout.java | 40 +++++++++---------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java index c0c8f018a02e..afb1d2a70d41 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java @@ -10,7 +10,7 @@ package org.junit.jupiter.api; -import static org.junit.jupiter.api.AssertTimeout.PreemptiveTimeoutAssertionExecutor.Throwing.ASSERTION_EXCEPTION; +import static org.junit.jupiter.api.AssertTimeout.PreemptiveTimeoutAssertionExecutor.Throwing.ASSERTION_ERROR; import static org.junit.jupiter.api.AssertTimeout.PreemptiveTimeoutAssertionExecutor.Throwing.MASKED_TIMEOUT_EXCEPTION; import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; import static org.junit.platform.commons.util.ExceptionUtils.throwAsUncheckedException; @@ -40,6 +40,11 @@ */ class AssertTimeout { + private static final PreemptiveTimeoutAssertionExecutor ASSERTION_ERROR_TIMEOUT_EXECUTOR = new PreemptiveTimeoutAssertionExecutor( + ASSERTION_ERROR); + private static final PreemptiveTimeoutAssertionExecutor TIMEOUT_EXCEPTION_TIMEOUT_EXECUTOR = new PreemptiveTimeoutAssertionExecutor( + MASKED_TIMEOUT_EXCEPTION); + private AssertTimeout() { /* no-op */ } @@ -115,38 +120,31 @@ static void assertTimeoutPreemptively(Duration timeout, Executable executable, S } static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier supplier) { - return new PreemptiveTimeoutAssertionExecutor(timeout, supplier, null).executeThrowing(ASSERTION_EXCEPTION); + return ASSERTION_ERROR_TIMEOUT_EXECUTOR.executeThrowing(timeout, supplier, null); } static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier supplier, String message) { - return new PreemptiveTimeoutAssertionExecutor(timeout, supplier, message).executeThrowing( - ASSERTION_EXCEPTION); + return ASSERTION_ERROR_TIMEOUT_EXECUTOR.executeThrowing(timeout, supplier, message); } static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier supplier, Supplier messageSupplier) { - return new PreemptiveTimeoutAssertionExecutor(timeout, supplier, messageSupplier).executeThrowing( - ASSERTION_EXCEPTION); + return ASSERTION_ERROR_TIMEOUT_EXECUTOR.executeThrowing(timeout, supplier, messageSupplier); } static T assertTimeoutPreemptivelyThrowingTimeoutException(Duration timeout, ThrowingSupplier supplier, Supplier messageSupplier) { - return new PreemptiveTimeoutAssertionExecutor(timeout, supplier, messageSupplier).executeThrowing( - MASKED_TIMEOUT_EXCEPTION); + return TIMEOUT_EXCEPTION_TIMEOUT_EXECUTOR.executeThrowing(timeout, supplier, messageSupplier); } - static class PreemptiveTimeoutAssertionExecutor { - private final Duration timeout; - private final ThrowingSupplier supplier; - private final Object messageOrSupplier; + static class PreemptiveTimeoutAssertionExecutor { + private final Throwing throwing; - PreemptiveTimeoutAssertionExecutor(Duration timeout, ThrowingSupplier supplier, Object messageOrSupplier) { - this.timeout = timeout; - this.supplier = supplier; - this.messageOrSupplier = messageOrSupplier; + PreemptiveTimeoutAssertionExecutor(Throwing throwing) { + this.throwing = throwing; } - T executeThrowing(Throwing throwing) { + T executeThrowing(Duration timeout, ThrowingSupplier supplier, Object messageOrSupplier) { AtomicReference threadReference = new AtomicReference<>(); ExecutorService executorService = Executors.newSingleThreadExecutor(new TimeoutThreadFactory()); @@ -161,7 +159,7 @@ T executeThrowing(Throwing throwing) { } } - private Future submitTask(ThrowingSupplier supplier, AtomicReference threadReference, + private Future submitTask(ThrowingSupplier supplier, AtomicReference threadReference, ExecutorService executorService) { return executorService.submit(() -> { try { @@ -174,14 +172,14 @@ private Future submitTask(ThrowingSupplier supplier, AtomicReference createFutureResolver(Object messageOrSupplier, + private FutureResolverWithExceptionHandling createFutureResolver(Object messageOrSupplier, AtomicReference threadReference, Throwing throwing) { FutureResolverWithExceptionHandling resolver; switch (throwing) { case MASKED_TIMEOUT_EXCEPTION: resolver = new TimeoutPropagatingFutureResolver<>(); break; - case ASSERTION_EXCEPTION: + case ASSERTION_ERROR: resolver = new AssertiveFutureResolver<>(threadReference, messageOrSupplier); break; default: @@ -191,7 +189,7 @@ private FutureResolverWithExceptionHandling createFutureResolver(Object messa } enum Throwing { - ASSERTION_EXCEPTION, MASKED_TIMEOUT_EXCEPTION + ASSERTION_ERROR, MASKED_TIMEOUT_EXCEPTION } } From 636fa78f1092636eb497a78adf47e11dccc3cab6 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Fri, 9 Sep 2022 14:40:18 +0200 Subject: [PATCH 33/59] Move generics to method --- .../org/junit/jupiter/api/AssertTimeout.java | 22 ++++++++----------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java index afb1d2a70d41..2ba3338a5023 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java @@ -150,8 +150,8 @@ T executeThrowing(Duration timeout, ThrowingSupplier supplier, Object mes try { Future future = submitTask(supplier, threadReference, executorService); - FutureResolverWithExceptionHandling resolver = createFutureResolver(messageOrSupplier, - threadReference, throwing); + FutureResolverWithExceptionHandling resolver = createFutureResolver(messageOrSupplier, threadReference, + throwing); return resolver.resolveFutureAndHandleException(future, timeout.toMillis()); } finally { @@ -172,20 +172,16 @@ private Future submitTask(ThrowingSupplier supplier, AtomicReference FutureResolverWithExceptionHandling createFutureResolver(Object messageOrSupplier, + private FutureResolverWithExceptionHandling createFutureResolver(Object messageOrSupplier, AtomicReference threadReference, Throwing throwing) { - FutureResolverWithExceptionHandling resolver; switch (throwing) { case MASKED_TIMEOUT_EXCEPTION: - resolver = new TimeoutPropagatingFutureResolver<>(); - break; + return new TimeoutPropagatingFutureResolver<>(); case ASSERTION_ERROR: - resolver = new AssertiveFutureResolver<>(threadReference, messageOrSupplier); - break; + return new AssertiveFutureResolver<>(threadReference, messageOrSupplier); default: throw new IllegalStateException("Unexpected value: " + throwing); } - return resolver; } enum Throwing { @@ -215,8 +211,8 @@ public Thread newThread(Runnable r) { } } - private abstract static class FutureResolverWithExceptionHandling { - T resolveFutureAndHandleException(Future future, long timeoutInMillis) { + private abstract static class FutureResolverWithExceptionHandling { + T resolveFutureAndHandleException(Future future, long timeoutInMillis) { try { return future.get(timeoutInMillis, TimeUnit.MILLISECONDS); } @@ -235,7 +231,7 @@ T resolveFutureAndHandleException(Future future, long timeoutInMillis) { protected abstract void handleTimeoutAndThrow(TimeoutException ex, long timeoutInMillis); } - private static class AssertiveFutureResolver extends FutureResolverWithExceptionHandling { + private static class AssertiveFutureResolver extends FutureResolverWithExceptionHandling { private final AtomicReference threadReference; private final Object messageOrSupplier; @@ -262,7 +258,7 @@ protected void handleTimeoutAndThrow(TimeoutException ex, long timeoutInMillis) } } - private static class TimeoutPropagatingFutureResolver extends FutureResolverWithExceptionHandling { + private static class TimeoutPropagatingFutureResolver extends FutureResolverWithExceptionHandling { @Override protected void handleTimeoutAndThrow(TimeoutException ex, long timeoutInMillis) { throw throwAsUncheckedException(ex); From 64f029a0b5a999da0a18c5266fb7c3f00b0ae1bd Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Fri, 9 Sep 2022 14:45:08 +0200 Subject: [PATCH 34/59] Simplify interface --- .../org/junit/jupiter/api/AssertTimeout.java | 51 ++++++++++--------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java index 2ba3338a5023..3162d9d0ce6b 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java @@ -152,7 +152,7 @@ T executeThrowing(Duration timeout, ThrowingSupplier supplier, Object mes Future future = submitTask(supplier, threadReference, executorService); FutureResolverWithExceptionHandling resolver = createFutureResolver(messageOrSupplier, threadReference, throwing); - return resolver.resolveFutureAndHandleException(future, timeout.toMillis()); + return resolveFutureAndHandleException(future, timeout.toMillis(), resolver); } finally { executorService.shutdownNow(); @@ -172,13 +172,30 @@ private Future submitTask(ThrowingSupplier supplier, AtomicReference T resolveFutureAndHandleException(Future future, long timeoutInMillis, + FutureResolverWithExceptionHandling resolver) { + try { + return future.get(timeoutInMillis, TimeUnit.MILLISECONDS); + } + catch (TimeoutException ex) { + resolver.handleTimeoutAndThrow(ex, timeoutInMillis); + return null; + } + catch (ExecutionException ex) { + throw throwAsUncheckedException(ex.getCause()); + } + catch (Throwable ex) { + throw throwAsUncheckedException(ex); + } + } + private FutureResolverWithExceptionHandling createFutureResolver(Object messageOrSupplier, AtomicReference threadReference, Throwing throwing) { switch (throwing) { case MASKED_TIMEOUT_EXCEPTION: - return new TimeoutPropagatingFutureResolver<>(); + return new TimeoutPropagatingFutureResolver(); case ASSERTION_ERROR: - return new AssertiveFutureResolver<>(threadReference, messageOrSupplier); + return new AssertiveFutureResolver(threadReference, messageOrSupplier); default: throw new IllegalStateException("Unexpected value: " + throwing); } @@ -211,27 +228,11 @@ public Thread newThread(Runnable r) { } } - private abstract static class FutureResolverWithExceptionHandling { - T resolveFutureAndHandleException(Future future, long timeoutInMillis) { - try { - return future.get(timeoutInMillis, TimeUnit.MILLISECONDS); - } - catch (TimeoutException ex) { - handleTimeoutAndThrow(ex, timeoutInMillis); - return null; - } - catch (ExecutionException ex) { - throw throwAsUncheckedException(ex.getCause()); - } - catch (Throwable ex) { - throw throwAsUncheckedException(ex); - } - } - - protected abstract void handleTimeoutAndThrow(TimeoutException ex, long timeoutInMillis); + private interface FutureResolverWithExceptionHandling { + void handleTimeoutAndThrow(TimeoutException ex, long timeoutInMillis); } - private static class AssertiveFutureResolver extends FutureResolverWithExceptionHandling { + private static class AssertiveFutureResolver implements FutureResolverWithExceptionHandling { private final AtomicReference threadReference; private final Object messageOrSupplier; @@ -242,7 +243,7 @@ private AssertiveFutureResolver(AtomicReference threadReference, Object } @Override - protected void handleTimeoutAndThrow(TimeoutException ex, long timeoutInMillis) { + public void handleTimeoutAndThrow(TimeoutException ex, long timeoutInMillis) { AssertionFailureBuilder failure = assertionFailure() // .message(messageOrSupplier) // .reason("execution timed out after " + timeoutInMillis + " ms"); @@ -258,9 +259,9 @@ protected void handleTimeoutAndThrow(TimeoutException ex, long timeoutInMillis) } } - private static class TimeoutPropagatingFutureResolver extends FutureResolverWithExceptionHandling { + private static class TimeoutPropagatingFutureResolver implements FutureResolverWithExceptionHandling { @Override - protected void handleTimeoutAndThrow(TimeoutException ex, long timeoutInMillis) { + public void handleTimeoutAndThrow(TimeoutException ex, long timeoutInMillis) { throw throwAsUncheckedException(ex); } } From abc153010bd578f9d418b93e12f895b5211150eb Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Fri, 9 Sep 2022 14:49:47 +0200 Subject: [PATCH 35/59] Use Supplier --- .../org/junit/jupiter/api/AssertTimeout.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java index 3162d9d0ce6b..b87fb1e26a7b 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java @@ -150,8 +150,8 @@ T executeThrowing(Duration timeout, ThrowingSupplier supplier, Object mes try { Future future = submitTask(supplier, threadReference, executorService); - FutureResolverWithExceptionHandling resolver = createFutureResolver(messageOrSupplier, threadReference, - throwing); + FutureResolverWithExceptionHandling resolver = createFutureResolver(messageOrSupplier, + threadReference::get, throwing); return resolveFutureAndHandleException(future, timeout.toMillis(), resolver); } finally { @@ -190,12 +190,12 @@ private T resolveFutureAndHandleException(Future future, long timeoutInMi } private FutureResolverWithExceptionHandling createFutureResolver(Object messageOrSupplier, - AtomicReference threadReference, Throwing throwing) { + Supplier thread, Throwing throwing) { switch (throwing) { case MASKED_TIMEOUT_EXCEPTION: return new TimeoutPropagatingFutureResolver(); case ASSERTION_ERROR: - return new AssertiveFutureResolver(threadReference, messageOrSupplier); + return new AssertiveFutureResolver(thread, messageOrSupplier); default: throw new IllegalStateException("Unexpected value: " + throwing); } @@ -234,11 +234,11 @@ private interface FutureResolverWithExceptionHandling { private static class AssertiveFutureResolver implements FutureResolverWithExceptionHandling { - private final AtomicReference threadReference; + private final Supplier threadSupplier; private final Object messageOrSupplier; - private AssertiveFutureResolver(AtomicReference threadReference, Object messageOrSupplier) { - this.threadReference = threadReference; + private AssertiveFutureResolver(Supplier threadSupplier, Object messageOrSupplier) { + this.threadSupplier = threadSupplier; this.messageOrSupplier = messageOrSupplier; } @@ -248,7 +248,7 @@ public void handleTimeoutAndThrow(TimeoutException ex, long timeoutInMillis) { .message(messageOrSupplier) // .reason("execution timed out after " + timeoutInMillis + " ms"); - Thread thread = threadReference.get(); + Thread thread = threadSupplier.get(); if (thread != null) { ExecutionTimeoutException exception = new ExecutionTimeoutException( "Execution timed out in thread " + thread.getName()); From f92705029e2c897ce9616aa6005dbe473e599d7d Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Fri, 9 Sep 2022 14:58:46 +0200 Subject: [PATCH 36/59] Simplify to TimeoutFailureFactory --- .../org/junit/jupiter/api/AssertTimeout.java | 48 ++++++++----------- 1 file changed, 20 insertions(+), 28 deletions(-) diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java index b87fb1e26a7b..5e39f2cb9894 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java @@ -11,7 +11,7 @@ package org.junit.jupiter.api; import static org.junit.jupiter.api.AssertTimeout.PreemptiveTimeoutAssertionExecutor.Throwing.ASSERTION_ERROR; -import static org.junit.jupiter.api.AssertTimeout.PreemptiveTimeoutAssertionExecutor.Throwing.MASKED_TIMEOUT_EXCEPTION; +import static org.junit.jupiter.api.AssertTimeout.PreemptiveTimeoutAssertionExecutor.Throwing.TIMEOUT_EXCEPTION; import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; import static org.junit.platform.commons.util.ExceptionUtils.throwAsUncheckedException; @@ -30,7 +30,7 @@ import org.junit.jupiter.api.function.Executable; import org.junit.jupiter.api.function.ThrowingSupplier; import org.junit.platform.commons.JUnitException; -import org.junit.platform.commons.util.ExceptionUtils; +import org.opentest4j.AssertionFailedError; /** * {@code AssertTimeout} is a collection of utility methods that support asserting @@ -43,7 +43,7 @@ class AssertTimeout { private static final PreemptiveTimeoutAssertionExecutor ASSERTION_ERROR_TIMEOUT_EXECUTOR = new PreemptiveTimeoutAssertionExecutor( ASSERTION_ERROR); private static final PreemptiveTimeoutAssertionExecutor TIMEOUT_EXCEPTION_TIMEOUT_EXECUTOR = new PreemptiveTimeoutAssertionExecutor( - MASKED_TIMEOUT_EXCEPTION); + TIMEOUT_EXCEPTION); private AssertTimeout() { /* no-op */ @@ -87,7 +87,7 @@ private static T assertTimeout(Duration timeout, ThrowingSupplier supplie result = supplier.get(); } catch (Throwable ex) { - ExceptionUtils.throwAsUncheckedException(ex); + throwAsUncheckedException(ex); } long timeElapsed = System.currentTimeMillis() - start; @@ -150,9 +150,9 @@ T executeThrowing(Duration timeout, ThrowingSupplier supplier, Object mes try { Future future = submitTask(supplier, threadReference, executorService); - FutureResolverWithExceptionHandling resolver = createFutureResolver(messageOrSupplier, - threadReference::get, throwing); - return resolveFutureAndHandleException(future, timeout.toMillis(), resolver); + TimeoutFailureFactory factory = createTimeoutFailureFactory(messageOrSupplier, threadReference::get, + throwing); + return resolveFutureAndHandleException(future, timeout.toMillis(), factory); } finally { executorService.shutdownNow(); @@ -167,19 +167,18 @@ private Future submitTask(ThrowingSupplier supplier, AtomicReference T resolveFutureAndHandleException(Future future, long timeoutInMillis, - FutureResolverWithExceptionHandling resolver) { + TimeoutFailureFactory factory) { try { return future.get(timeoutInMillis, TimeUnit.MILLISECONDS); } catch (TimeoutException ex) { - resolver.handleTimeoutAndThrow(ex, timeoutInMillis); - return null; + throw throwAsUncheckedException(factory.handleTimeout(ex, timeoutInMillis)); } catch (ExecutionException ex) { throw throwAsUncheckedException(ex.getCause()); @@ -189,11 +188,11 @@ private T resolveFutureAndHandleException(Future future, long timeoutInMi } } - private FutureResolverWithExceptionHandling createFutureResolver(Object messageOrSupplier, - Supplier thread, Throwing throwing) { + private TimeoutFailureFactory createTimeoutFailureFactory(Object messageOrSupplier, Supplier thread, + Throwing throwing) { switch (throwing) { - case MASKED_TIMEOUT_EXCEPTION: - return new TimeoutPropagatingFutureResolver(); + case TIMEOUT_EXCEPTION: + return (e, __) -> e; case ASSERTION_ERROR: return new AssertiveFutureResolver(thread, messageOrSupplier); default: @@ -202,7 +201,7 @@ private FutureResolverWithExceptionHandling createFutureResolver(Object messageO } enum Throwing { - ASSERTION_ERROR, MASKED_TIMEOUT_EXCEPTION + ASSERTION_ERROR, TIMEOUT_EXCEPTION } } @@ -228,11 +227,11 @@ public Thread newThread(Runnable r) { } } - private interface FutureResolverWithExceptionHandling { - void handleTimeoutAndThrow(TimeoutException ex, long timeoutInMillis); + private interface TimeoutFailureFactory { + T handleTimeout(TimeoutException ex, long timeoutInMillis); } - private static class AssertiveFutureResolver implements FutureResolverWithExceptionHandling { + private static class AssertiveFutureResolver implements TimeoutFailureFactory { private final Supplier threadSupplier; private final Object messageOrSupplier; @@ -243,7 +242,7 @@ private AssertiveFutureResolver(Supplier threadSupplier, Object messageO } @Override - public void handleTimeoutAndThrow(TimeoutException ex, long timeoutInMillis) { + public AssertionFailedError handleTimeout(TimeoutException ex, long timeoutInMillis) { AssertionFailureBuilder failure = assertionFailure() // .message(messageOrSupplier) // .reason("execution timed out after " + timeoutInMillis + " ms"); @@ -255,14 +254,7 @@ public void handleTimeoutAndThrow(TimeoutException ex, long timeoutInMillis) { exception.setStackTrace(thread.getStackTrace()); failure.cause(exception); } - throw failure.build(); - } - } - - private static class TimeoutPropagatingFutureResolver implements FutureResolverWithExceptionHandling { - @Override - public void handleTimeoutAndThrow(TimeoutException ex, long timeoutInMillis) { - throw throwAsUncheckedException(ex); + return failure.build(); } } } From 96407b6ce5537ac7451ccd6c30d2bddee95d9ac6 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Fri, 9 Sep 2022 15:04:06 +0200 Subject: [PATCH 37/59] Make TimeoutFailureFactory implementations stateless --- .../org/junit/jupiter/api/AssertTimeout.java | 36 ++++++++----------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java index 5e39f2cb9894..1c32567a43bf 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java @@ -150,9 +150,9 @@ T executeThrowing(Duration timeout, ThrowingSupplier supplier, Object mes try { Future future = submitTask(supplier, threadReference, executorService); - TimeoutFailureFactory factory = createTimeoutFailureFactory(messageOrSupplier, threadReference::get, - throwing); - return resolveFutureAndHandleException(future, timeout.toMillis(), factory); + TimeoutFailureFactory factory = createTimeoutFailureFactory(throwing); + return resolveFutureAndHandleException(future, timeout.toMillis(), messageOrSupplier, + threadReference::get, factory); } finally { executorService.shutdownNow(); @@ -172,13 +172,14 @@ private Future submitTask(ThrowingSupplier supplier, AtomicReference T resolveFutureAndHandleException(Future future, long timeoutInMillis, - TimeoutFailureFactory factory) { + private T resolveFutureAndHandleException(Future future, long timeoutInMillis, Object messageOrSupplier, + Supplier threadSupplier, TimeoutFailureFactory factory) { try { return future.get(timeoutInMillis, TimeUnit.MILLISECONDS); } catch (TimeoutException ex) { - throw throwAsUncheckedException(factory.handleTimeout(ex, timeoutInMillis)); + throw throwAsUncheckedException( + factory.handleTimeout(ex, timeoutInMillis, messageOrSupplier, threadSupplier)); } catch (ExecutionException ex) { throw throwAsUncheckedException(ex.getCause()); @@ -188,13 +189,12 @@ private T resolveFutureAndHandleException(Future future, long timeoutInMi } } - private TimeoutFailureFactory createTimeoutFailureFactory(Object messageOrSupplier, Supplier thread, - Throwing throwing) { + private TimeoutFailureFactory createTimeoutFailureFactory(Throwing throwing) { switch (throwing) { case TIMEOUT_EXCEPTION: - return (e, __) -> e; + return (e, __, ___, ____) -> e; case ASSERTION_ERROR: - return new AssertiveFutureResolver(thread, messageOrSupplier); + return new AssertiveFutureResolver(); default: throw new IllegalStateException("Unexpected value: " + throwing); } @@ -216,7 +216,7 @@ private static class ExecutionTimeoutException extends JUnitException { /** * The thread factory used for preemptive timeout. - * + *

* The factory creates threads with meaningful names, helpful for debugging purposes. */ private static class TimeoutThreadFactory implements ThreadFactory { @@ -228,21 +228,15 @@ public Thread newThread(Runnable r) { } private interface TimeoutFailureFactory { - T handleTimeout(TimeoutException ex, long timeoutInMillis); + T handleTimeout(TimeoutException ex, long timeoutInMillis, Object messageOrSupplier, + Supplier threadSupplier); } private static class AssertiveFutureResolver implements TimeoutFailureFactory { - private final Supplier threadSupplier; - private final Object messageOrSupplier; - - private AssertiveFutureResolver(Supplier threadSupplier, Object messageOrSupplier) { - this.threadSupplier = threadSupplier; - this.messageOrSupplier = messageOrSupplier; - } - @Override - public AssertionFailedError handleTimeout(TimeoutException ex, long timeoutInMillis) { + public AssertionFailedError handleTimeout(TimeoutException ex, long timeoutInMillis, Object messageOrSupplier, + Supplier threadSupplier) { AssertionFailureBuilder failure = assertionFailure() // .message(messageOrSupplier) // .reason("execution timed out after " + timeoutInMillis + " ms"); From 132be8d80cd2b9c8c5b00175c03c081a55028773 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Fri, 9 Sep 2022 15:07:46 +0200 Subject: [PATCH 38/59] Delete Throwing enum --- .../org/junit/jupiter/api/AssertTimeout.java | 34 +++++-------------- 1 file changed, 8 insertions(+), 26 deletions(-) diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java index 1c32567a43bf..d6cdb6d70a78 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java @@ -10,8 +10,6 @@ package org.junit.jupiter.api; -import static org.junit.jupiter.api.AssertTimeout.PreemptiveTimeoutAssertionExecutor.Throwing.ASSERTION_ERROR; -import static org.junit.jupiter.api.AssertTimeout.PreemptiveTimeoutAssertionExecutor.Throwing.TIMEOUT_EXCEPTION; import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; import static org.junit.platform.commons.util.ExceptionUtils.throwAsUncheckedException; @@ -41,9 +39,9 @@ class AssertTimeout { private static final PreemptiveTimeoutAssertionExecutor ASSERTION_ERROR_TIMEOUT_EXECUTOR = new PreemptiveTimeoutAssertionExecutor( - ASSERTION_ERROR); + new AssertiveFutureResolver()); private static final PreemptiveTimeoutAssertionExecutor TIMEOUT_EXCEPTION_TIMEOUT_EXECUTOR = new PreemptiveTimeoutAssertionExecutor( - TIMEOUT_EXCEPTION); + (e, __, ___, ____) -> e); private AssertTimeout() { /* no-op */ @@ -138,10 +136,10 @@ static T assertTimeoutPreemptivelyThrowingTimeoutException(Duration timeout, } static class PreemptiveTimeoutAssertionExecutor { - private final Throwing throwing; + private final TimeoutFailureFactory failureFactory; - PreemptiveTimeoutAssertionExecutor(Throwing throwing) { - this.throwing = throwing; + PreemptiveTimeoutAssertionExecutor(TimeoutFailureFactory failureFactory) { + this.failureFactory = failureFactory; } T executeThrowing(Duration timeout, ThrowingSupplier supplier, Object messageOrSupplier) { @@ -150,9 +148,8 @@ T executeThrowing(Duration timeout, ThrowingSupplier supplier, Object mes try { Future future = submitTask(supplier, threadReference, executorService); - TimeoutFailureFactory factory = createTimeoutFailureFactory(throwing); return resolveFutureAndHandleException(future, timeout.toMillis(), messageOrSupplier, - threadReference::get, factory); + threadReference::get); } finally { executorService.shutdownNow(); @@ -173,13 +170,13 @@ private Future submitTask(ThrowingSupplier supplier, AtomicReference T resolveFutureAndHandleException(Future future, long timeoutInMillis, Object messageOrSupplier, - Supplier threadSupplier, TimeoutFailureFactory factory) { + Supplier threadSupplier) { try { return future.get(timeoutInMillis, TimeUnit.MILLISECONDS); } catch (TimeoutException ex) { throw throwAsUncheckedException( - factory.handleTimeout(ex, timeoutInMillis, messageOrSupplier, threadSupplier)); + failureFactory.handleTimeout(ex, timeoutInMillis, messageOrSupplier, threadSupplier)); } catch (ExecutionException ex) { throw throwAsUncheckedException(ex.getCause()); @@ -188,21 +185,6 @@ private T resolveFutureAndHandleException(Future future, long timeoutInMi throw throwAsUncheckedException(ex); } } - - private TimeoutFailureFactory createTimeoutFailureFactory(Throwing throwing) { - switch (throwing) { - case TIMEOUT_EXCEPTION: - return (e, __, ___, ____) -> e; - case ASSERTION_ERROR: - return new AssertiveFutureResolver(); - default: - throw new IllegalStateException("Unexpected value: " + throwing); - } - } - - enum Throwing { - ASSERTION_ERROR, TIMEOUT_EXCEPTION - } } private static class ExecutionTimeoutException extends JUnitException { From cc96506d2e4c19abe8379a69e2173c1aa4afb3c1 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Fri, 9 Sep 2022 15:15:53 +0200 Subject: [PATCH 39/59] Declare and handle TimeoutException directly --- .../org/junit/jupiter/api/AssertTimeout.java | 29 +++++++++---------- .../org/junit/jupiter/api/Assertions.java | 3 +- .../SeparateThreadTimeoutInvocation.java | 13 +++------ .../api/AssertTimeoutAssertionsTests.java | 3 +- 4 files changed, 22 insertions(+), 26 deletions(-) diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java index d6cdb6d70a78..65d8923be60a 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java @@ -38,9 +38,9 @@ */ class AssertTimeout { - private static final PreemptiveTimeoutAssertionExecutor ASSERTION_ERROR_TIMEOUT_EXECUTOR = new PreemptiveTimeoutAssertionExecutor( - new AssertiveFutureResolver()); - private static final PreemptiveTimeoutAssertionExecutor TIMEOUT_EXCEPTION_TIMEOUT_EXECUTOR = new PreemptiveTimeoutAssertionExecutor( + private static final PreemptiveTimeoutAssertionExecutor ASSERTION_ERROR_TIMEOUT_EXECUTOR = new PreemptiveTimeoutAssertionExecutor<>( + new AssertionTimeoutFailureFactory()); + private static final PreemptiveTimeoutAssertionExecutor TIMEOUT_EXCEPTION_TIMEOUT_EXECUTOR = new PreemptiveTimeoutAssertionExecutor<>( (e, __, ___, ____) -> e); private AssertTimeout() { @@ -131,23 +131,23 @@ static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier sup } static T assertTimeoutPreemptivelyThrowingTimeoutException(Duration timeout, ThrowingSupplier supplier, - Supplier messageSupplier) { + Supplier messageSupplier) throws TimeoutException { return TIMEOUT_EXCEPTION_TIMEOUT_EXECUTOR.executeThrowing(timeout, supplier, messageSupplier); } - static class PreemptiveTimeoutAssertionExecutor { - private final TimeoutFailureFactory failureFactory; + static class PreemptiveTimeoutAssertionExecutor { + private final TimeoutFailureFactory failureFactory; - PreemptiveTimeoutAssertionExecutor(TimeoutFailureFactory failureFactory) { + PreemptiveTimeoutAssertionExecutor(TimeoutFailureFactory failureFactory) { this.failureFactory = failureFactory; } - T executeThrowing(Duration timeout, ThrowingSupplier supplier, Object messageOrSupplier) { + V executeThrowing(Duration timeout, ThrowingSupplier supplier, Object messageOrSupplier) throws T { AtomicReference threadReference = new AtomicReference<>(); ExecutorService executorService = Executors.newSingleThreadExecutor(new TimeoutThreadFactory()); try { - Future future = submitTask(supplier, threadReference, executorService); + Future future = submitTask(supplier, threadReference, executorService); return resolveFutureAndHandleException(future, timeout.toMillis(), messageOrSupplier, threadReference::get); } @@ -156,7 +156,7 @@ T executeThrowing(Duration timeout, ThrowingSupplier supplier, Object mes } } - private Future submitTask(ThrowingSupplier supplier, AtomicReference threadReference, + private Future submitTask(ThrowingSupplier supplier, AtomicReference threadReference, ExecutorService executorService) { return executorService.submit(() -> { try { @@ -169,14 +169,13 @@ private Future submitTask(ThrowingSupplier supplier, AtomicReference T resolveFutureAndHandleException(Future future, long timeoutInMillis, Object messageOrSupplier, - Supplier threadSupplier) { + private V resolveFutureAndHandleException(Future future, long timeoutInMillis, Object messageOrSupplier, + Supplier threadSupplier) throws T { try { return future.get(timeoutInMillis, TimeUnit.MILLISECONDS); } catch (TimeoutException ex) { - throw throwAsUncheckedException( - failureFactory.handleTimeout(ex, timeoutInMillis, messageOrSupplier, threadSupplier)); + throw failureFactory.handleTimeout(ex, timeoutInMillis, messageOrSupplier, threadSupplier); } catch (ExecutionException ex) { throw throwAsUncheckedException(ex.getCause()); @@ -214,7 +213,7 @@ T handleTimeout(TimeoutException ex, long timeoutInMillis, Object messageOrSuppl Supplier threadSupplier); } - private static class AssertiveFutureResolver implements TimeoutFailureFactory { + private static class AssertionTimeoutFailureFactory implements TimeoutFailureFactory { @Override public AssertionFailedError handleTimeout(TimeoutException ex, long timeoutInMillis, Object messageOrSupplier, 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 ec46e79cd1c0..aab2f3dd3447 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 @@ -19,6 +19,7 @@ import java.util.Collection; import java.util.List; import java.util.Objects; +import java.util.concurrent.TimeoutException; import java.util.function.BooleanSupplier; import java.util.function.Supplier; import java.util.stream.Stream; @@ -3551,7 +3552,7 @@ public static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier */ @API(status = INTERNAL) public static T assertTimeoutPreemptivelyThrowingTimeoutException(Duration timeout, - ThrowingSupplier supplier, Supplier messageSupplier) { + ThrowingSupplier supplier, Supplier messageSupplier) throws TimeoutException { return AssertTimeout.assertTimeoutPreemptivelyThrowingTimeoutException(timeout, supplier, messageSupplier); } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/SeparateThreadTimeoutInvocation.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/SeparateThreadTimeoutInvocation.java index bda2d685aaa5..71033b3df887 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/SeparateThreadTimeoutInvocation.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/SeparateThreadTimeoutInvocation.java @@ -39,15 +39,10 @@ public T proceed() throws Throwable { return assertTimeoutPreemptivelyThrowingTimeoutException(timeout.toDuration(), delegate::proceed, descriptionSupplier); } - catch (Throwable failure) { - if (failure instanceof TimeoutException) { - TimeoutException exception = TimeoutExceptionFactory.create(descriptionSupplier.get(), timeout, null); - exception.initCause(failure.getCause()); - throw exception; - } - else { - throw failure; - } + catch (TimeoutException failure) { + TimeoutException exception = TimeoutExceptionFactory.create(descriptionSupplier.get(), timeout, null); + exception.initCause(failure.getCause()); + throw exception; } } } diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertTimeoutAssertionsTests.java b/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertTimeoutAssertionsTests.java index 512d1013eeda..f6409b3229cd 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertTimeoutAssertionsTests.java +++ b/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertTimeoutAssertionsTests.java @@ -342,7 +342,8 @@ void assertTimeoutPreemptivelyThrowingTimeoutExceptionWithMessageForSupplierThat } @Test - void assertTimeoutPreemptivelyThrowingTimeoutExceptionWithMessageForSupplierThatCompletesBeforeTimeout() { + void assertTimeoutPreemptivelyThrowingTimeoutExceptionWithMessageForSupplierThatCompletesBeforeTimeout() + throws Exception { var result = assertTimeoutPreemptivelyThrowingTimeoutException(PREEMPTIVE_TIMEOUT, () -> "Tempus Fugit", () -> "Tempus Fugit"); From cd9ec535f726daa0e8bec89788a0a32fdd0f9ebb Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Fri, 9 Sep 2022 15:32:06 +0200 Subject: [PATCH 40/59] Pass cause to TimeoutFailureFactory --- .../org/junit/jupiter/api/AssertTimeout.java | 30 +++++++++---------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java index 65d8923be60a..4dd754524ee0 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java @@ -175,7 +175,13 @@ private V resolveFutureAndHandleException(Future future, long timeoutInMi return future.get(timeoutInMillis, TimeUnit.MILLISECONDS); } catch (TimeoutException ex) { - throw failureFactory.handleTimeout(ex, timeoutInMillis, messageOrSupplier, threadSupplier); + Thread thread = threadSupplier.get(); + ExecutionTimeoutException cause = null; + if (thread != null) { + cause = new ExecutionTimeoutException("Execution timed out in thread " + thread.getName()); + cause.setStackTrace(thread.getStackTrace()); + } + throw failureFactory.handleTimeout(ex, timeoutInMillis, messageOrSupplier, cause); } catch (ExecutionException ex) { throw throwAsUncheckedException(ex.getCause()); @@ -209,27 +215,19 @@ public Thread newThread(Runnable r) { } private interface TimeoutFailureFactory { - T handleTimeout(TimeoutException ex, long timeoutInMillis, Object messageOrSupplier, - Supplier threadSupplier); + T handleTimeout(TimeoutException exception, long timeoutInMillis, Object messageOrSupplier, Throwable cause); } private static class AssertionTimeoutFailureFactory implements TimeoutFailureFactory { @Override - public AssertionFailedError handleTimeout(TimeoutException ex, long timeoutInMillis, Object messageOrSupplier, - Supplier threadSupplier) { - AssertionFailureBuilder failure = assertionFailure() // + public AssertionFailedError handleTimeout(TimeoutException exception, long timeoutInMillis, + Object messageOrSupplier, Throwable cause) { + return assertionFailure() // .message(messageOrSupplier) // - .reason("execution timed out after " + timeoutInMillis + " ms"); - - Thread thread = threadSupplier.get(); - if (thread != null) { - ExecutionTimeoutException exception = new ExecutionTimeoutException( - "Execution timed out in thread " + thread.getName()); - exception.setStackTrace(thread.getStackTrace()); - failure.cause(exception); - } - return failure.build(); + .reason("execution timed out after " + timeoutInMillis + " ms") // + .cause(cause) // + .build(); } } } From 3096ff1882e70d154b422df00db11b09525ed754 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Fri, 9 Sep 2022 15:33:16 +0200 Subject: [PATCH 41/59] Pass duration to TimeoutFailureFactory --- .../java/org/junit/jupiter/api/AssertTimeout.java | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java index 4dd754524ee0..7c1314ac7113 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java @@ -148,8 +148,7 @@ V executeThrowing(Duration timeout, ThrowingSupplier supplier, Object mes try { Future future = submitTask(supplier, threadReference, executorService); - return resolveFutureAndHandleException(future, timeout.toMillis(), messageOrSupplier, - threadReference::get); + return resolveFutureAndHandleException(future, timeout, messageOrSupplier, threadReference::get); } finally { executorService.shutdownNow(); @@ -169,10 +168,10 @@ private Future submitTask(ThrowingSupplier supplier, AtomicReference V resolveFutureAndHandleException(Future future, long timeoutInMillis, Object messageOrSupplier, + private V resolveFutureAndHandleException(Future future, Duration timeout, Object messageOrSupplier, Supplier threadSupplier) throws T { try { - return future.get(timeoutInMillis, TimeUnit.MILLISECONDS); + return future.get(timeout.toMillis(), TimeUnit.MILLISECONDS); } catch (TimeoutException ex) { Thread thread = threadSupplier.get(); @@ -181,7 +180,7 @@ private V resolveFutureAndHandleException(Future future, long timeoutInMi cause = new ExecutionTimeoutException("Execution timed out in thread " + thread.getName()); cause.setStackTrace(thread.getStackTrace()); } - throw failureFactory.handleTimeout(ex, timeoutInMillis, messageOrSupplier, cause); + throw failureFactory.handleTimeout(ex, timeout, messageOrSupplier, cause); } catch (ExecutionException ex) { throw throwAsUncheckedException(ex.getCause()); @@ -215,17 +214,17 @@ public Thread newThread(Runnable r) { } private interface TimeoutFailureFactory { - T handleTimeout(TimeoutException exception, long timeoutInMillis, Object messageOrSupplier, Throwable cause); + T handleTimeout(TimeoutException exception, Duration timeout, Object messageOrSupplier, Throwable cause); } private static class AssertionTimeoutFailureFactory implements TimeoutFailureFactory { @Override - public AssertionFailedError handleTimeout(TimeoutException exception, long timeoutInMillis, + public AssertionFailedError handleTimeout(TimeoutException exception, Duration timeout, Object messageOrSupplier, Throwable cause) { return assertionFailure() // .message(messageOrSupplier) // - .reason("execution timed out after " + timeoutInMillis + " ms") // + .reason("execution timed out after " + timeout.toMillis() + " ms") // .cause(cause) // .build(); } From 33b792ee4274a311753a3b92b74ee3b4df85515f Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Fri, 9 Sep 2022 15:36:38 +0200 Subject: [PATCH 42/59] Pass supplier to TimeoutFailureFactory --- .../org/junit/jupiter/api/AssertTimeout.java | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java index 7c1314ac7113..d24a6ef55211 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java @@ -122,7 +122,7 @@ static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier sup } static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier supplier, String message) { - return ASSERTION_ERROR_TIMEOUT_EXECUTOR.executeThrowing(timeout, supplier, message); + return ASSERTION_ERROR_TIMEOUT_EXECUTOR.executeThrowing(timeout, supplier, () -> message); } static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier supplier, @@ -142,13 +142,14 @@ static class PreemptiveTimeoutAssertionExecutor { this.failureFactory = failureFactory; } - V executeThrowing(Duration timeout, ThrowingSupplier supplier, Object messageOrSupplier) throws T { + V executeThrowing(Duration timeout, ThrowingSupplier supplier, Supplier messageSupplier) + throws T { AtomicReference threadReference = new AtomicReference<>(); ExecutorService executorService = Executors.newSingleThreadExecutor(new TimeoutThreadFactory()); try { Future future = submitTask(supplier, threadReference, executorService); - return resolveFutureAndHandleException(future, timeout, messageOrSupplier, threadReference::get); + return resolveFutureAndHandleException(future, timeout, messageSupplier, threadReference::get); } finally { executorService.shutdownNow(); @@ -168,8 +169,8 @@ private Future submitTask(ThrowingSupplier supplier, AtomicReference V resolveFutureAndHandleException(Future future, Duration timeout, Object messageOrSupplier, - Supplier threadSupplier) throws T { + private V resolveFutureAndHandleException(Future future, Duration timeout, + Supplier messageSupplier, Supplier threadSupplier) throws T { try { return future.get(timeout.toMillis(), TimeUnit.MILLISECONDS); } @@ -180,7 +181,7 @@ private V resolveFutureAndHandleException(Future future, Duration timeout cause = new ExecutionTimeoutException("Execution timed out in thread " + thread.getName()); cause.setStackTrace(thread.getStackTrace()); } - throw failureFactory.handleTimeout(ex, timeout, messageOrSupplier, cause); + throw failureFactory.handleTimeout(ex, timeout, messageSupplier, cause); } catch (ExecutionException ex) { throw throwAsUncheckedException(ex.getCause()); @@ -214,16 +215,17 @@ public Thread newThread(Runnable r) { } private interface TimeoutFailureFactory { - T handleTimeout(TimeoutException exception, Duration timeout, Object messageOrSupplier, Throwable cause); + T handleTimeout(TimeoutException exception, Duration timeout, Supplier messageSupplier, + Throwable cause); } private static class AssertionTimeoutFailureFactory implements TimeoutFailureFactory { @Override public AssertionFailedError handleTimeout(TimeoutException exception, Duration timeout, - Object messageOrSupplier, Throwable cause) { + Supplier messageSupplier, Throwable cause) { return assertionFailure() // - .message(messageOrSupplier) // + .message(messageSupplier) // .reason("execution timed out after " + timeout.toMillis() + " ms") // .cause(cause) // .build(); From c927f0f205b071bccc9c582e62d6155bb21a803e Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Fri, 9 Sep 2022 15:58:22 +0200 Subject: [PATCH 43/59] Allow specifying a TimeoutFailureFactory directly --- .../org/junit/jupiter/api/AssertTimeout.java | 18 ++++++------- .../org/junit/jupiter/api/Assertions.java | 18 ++++++++----- .../SeparateThreadTimeoutInvocation.java | 17 ++++++------- .../api/AssertTimeoutAssertionsTests.java | 25 ++++++++++--------- .../extension/TimeoutExtensionTests.java | 6 ++++- 5 files changed, 44 insertions(+), 40 deletions(-) diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java index d24a6ef55211..dd77693ce4d8 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java @@ -25,6 +25,7 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.function.Supplier; +import org.junit.jupiter.api.Assertions.TimeoutFailureFactory; import org.junit.jupiter.api.function.Executable; import org.junit.jupiter.api.function.ThrowingSupplier; import org.junit.platform.commons.JUnitException; @@ -40,8 +41,6 @@ class AssertTimeout { private static final PreemptiveTimeoutAssertionExecutor ASSERTION_ERROR_TIMEOUT_EXECUTOR = new PreemptiveTimeoutAssertionExecutor<>( new AssertionTimeoutFailureFactory()); - private static final PreemptiveTimeoutAssertionExecutor TIMEOUT_EXCEPTION_TIMEOUT_EXECUTOR = new PreemptiveTimeoutAssertionExecutor<>( - (e, __, ___, ____) -> e); private AssertTimeout() { /* no-op */ @@ -122,7 +121,8 @@ static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier sup } static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier supplier, String message) { - return ASSERTION_ERROR_TIMEOUT_EXECUTOR.executeThrowing(timeout, supplier, () -> message); + return ASSERTION_ERROR_TIMEOUT_EXECUTOR.executeThrowing(timeout, supplier, + message == null ? null : () -> message); } static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier supplier, @@ -130,9 +130,10 @@ static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier sup return ASSERTION_ERROR_TIMEOUT_EXECUTOR.executeThrowing(timeout, supplier, messageSupplier); } - static T assertTimeoutPreemptivelyThrowingTimeoutException(Duration timeout, ThrowingSupplier supplier, - Supplier messageSupplier) throws TimeoutException { - return TIMEOUT_EXCEPTION_TIMEOUT_EXECUTOR.executeThrowing(timeout, supplier, messageSupplier); + static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier supplier, + Supplier messageSupplier, TimeoutFailureFactory failureFactory) throws E { + return new PreemptiveTimeoutAssertionExecutor<>(failureFactory).executeThrowing(timeout, supplier, + messageSupplier); } static class PreemptiveTimeoutAssertionExecutor { @@ -214,11 +215,6 @@ public Thread newThread(Runnable r) { } } - private interface TimeoutFailureFactory { - T handleTimeout(TimeoutException exception, Duration timeout, Supplier messageSupplier, - Throwable cause); - } - private static class AssertionTimeoutFailureFactory implements TimeoutFailureFactory { @Override 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 aab2f3dd3447..f7b225d31d86 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 @@ -3531,8 +3531,9 @@ public static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier * *

If the assertion passes then the {@code supplier}'s result is returned. * - *

In the case the assertion does not pass, a {@code TimeoutException} will be - * thrown. + *

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 @@ -3550,10 +3551,10 @@ public static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier * @see #assertTimeoutPreemptively(Duration, ThrowingSupplier, String) * @see #assertTimeout(Duration, Executable, Supplier) */ - @API(status = INTERNAL) - public static T assertTimeoutPreemptivelyThrowingTimeoutException(Duration timeout, - ThrowingSupplier supplier, Supplier messageSupplier) throws TimeoutException { - return AssertTimeout.assertTimeoutPreemptivelyThrowingTimeoutException(timeout, supplier, messageSupplier); + @API(status = INTERNAL, since = "5.9.1") + public static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier supplier, + Supplier messageSupplier, TimeoutFailureFactory failureFactory) throws E { + return AssertTimeout.assertTimeoutPreemptively(timeout, supplier, messageSupplier, failureFactory); } // --- assertInstanceOf ---------------------------------------------------- @@ -3605,4 +3606,9 @@ public static T assertInstanceOf(Class expectedType, Object actualValue, return AssertInstanceOf.assertInstanceOf(expectedType, actualValue, messageSupplier); } + @API(status = INTERNAL, since = "5.9.1") + public interface TimeoutFailureFactory { + T handleTimeout(TimeoutException exception, Duration timeout, Supplier messageSupplier, + Throwable cause); + } } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/SeparateThreadTimeoutInvocation.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/SeparateThreadTimeoutInvocation.java index 71033b3df887..59233fb2b8b2 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/SeparateThreadTimeoutInvocation.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/SeparateThreadTimeoutInvocation.java @@ -10,7 +10,7 @@ package org.junit.jupiter.engine.extension; -import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptivelyThrowingTimeoutException; +import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively; import java.util.concurrent.TimeoutException; import java.util.function.Supplier; @@ -35,14 +35,11 @@ class SeparateThreadTimeoutInvocation implements Invocation { @Override public T proceed() throws Throwable { - try { - return assertTimeoutPreemptivelyThrowingTimeoutException(timeout.toDuration(), delegate::proceed, - descriptionSupplier); - } - catch (TimeoutException failure) { - TimeoutException exception = TimeoutExceptionFactory.create(descriptionSupplier.get(), timeout, null); - exception.initCause(failure.getCause()); - throw exception; - } + return assertTimeoutPreemptively(timeout.toDuration(), delegate::proceed, descriptionSupplier, + (e, __, messageSupplier, cause) -> { + TimeoutException exception = TimeoutExceptionFactory.create(messageSupplier.get(), timeout, null); + exception.initCause(cause); + return exception; + }); } } diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertTimeoutAssertionsTests.java b/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertTimeoutAssertionsTests.java index f6409b3229cd..ba5ef967a077 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertTimeoutAssertionsTests.java +++ b/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertTimeoutAssertionsTests.java @@ -19,7 +19,6 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTimeout; import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively; -import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptivelyThrowingTimeoutException; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.condition.OS.WINDOWS; @@ -42,6 +41,8 @@ class AssertTimeoutAssertionsTests { private static final Duration PREEMPTIVE_TIMEOUT = ofMillis(WINDOWS.isCurrentOs() ? 1000 : 100); + private static final Assertions.TimeoutFailureFactory TIMEOUT_EXCEPTION_FACTORY = (__, ___, ____, + _____) -> new TimeoutException(); private static ThreadLocal changed = ThreadLocal.withInitial(() -> new AtomicBoolean(false)); @@ -318,34 +319,34 @@ void assertTimeoutPreemptivelyUsesThreadsWithSpecificNamePrefix() { @Test void assertTimeoutPreemptivelyThrowingTimeoutExceptionWithMessageForSupplierThatCompletesAfterTheTimeout() { - assertThrows(TimeoutException.class, - () -> assertTimeoutPreemptivelyThrowingTimeoutException(PREEMPTIVE_TIMEOUT, () -> { - waitForInterrupt(); - return "Tempus Fugit"; - }, () -> "Tempus Fugit")); + assertThrows(TimeoutException.class, () -> Assertions.assertTimeoutPreemptively(PREEMPTIVE_TIMEOUT, () -> { + waitForInterrupt(); + return "Tempus Fugit"; + }, () -> "Tempus Fugit", TIMEOUT_EXCEPTION_FACTORY)); } @Test void assertTimeoutPreemptivelyThrowingTimeoutExceptionWithMessageForSupplierThatThrowsAnAssertionFailedError() { AssertionFailedError exception = assertThrows(AssertionFailedError.class, - () -> assertTimeoutPreemptivelyThrowingTimeoutException(ofMillis(500), () -> fail("enigma"), - () -> "Tempus Fugit")); + () -> Assertions.assertTimeoutPreemptively(ofMillis(500), () -> fail("enigma"), () -> "Tempus Fugit", + TIMEOUT_EXCEPTION_FACTORY)); assertMessageEquals(exception, "enigma"); } @Test void assertTimeoutPreemptivelyThrowingTimeoutExceptionWithMessageForSupplierThatThrowsAnException() { RuntimeException exception = assertThrows(RuntimeException.class, - () -> assertTimeoutPreemptivelyThrowingTimeoutException(ofMillis(500), - () -> ExceptionUtils.throwAsUncheckedException(new RuntimeException(":(")), () -> "Tempus Fugit")); + () -> Assertions.assertTimeoutPreemptively(ofMillis(500), + () -> ExceptionUtils.throwAsUncheckedException(new RuntimeException(":(")), () -> "Tempus Fugit", + TIMEOUT_EXCEPTION_FACTORY)); assertMessageEquals(exception, ":("); } @Test void assertTimeoutPreemptivelyThrowingTimeoutExceptionWithMessageForSupplierThatCompletesBeforeTimeout() throws Exception { - var result = assertTimeoutPreemptivelyThrowingTimeoutException(PREEMPTIVE_TIMEOUT, () -> "Tempus Fugit", - () -> "Tempus Fugit"); + var result = Assertions.assertTimeoutPreemptively(PREEMPTIVE_TIMEOUT, () -> "Tempus Fugit", + () -> "Tempus Fugit", TIMEOUT_EXCEPTION_FACTORY); assertThat(result).isEqualTo("Tempus Fugit"); } diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutExtensionTests.java b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutExtensionTests.java index 183543711789..442d34c83e0a 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutExtensionTests.java +++ b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutExtensionTests.java @@ -349,9 +349,13 @@ void timeoutExceededInSeparateThread() { EngineExecutionResults results = executeTestsForClass(TimeoutExceedingSeparateThreadTestCase.class); Execution execution = findExecution(results.testEvents(), "testMethod()"); - assertThat(execution.getTerminationInfo().getExecutionResult().getThrowable().orElseThrow()) // + Throwable failure = execution.getTerminationInfo().getExecutionResult().getThrowable().orElseThrow(); + assertThat(failure) // .isInstanceOf(TimeoutException.class) // .hasMessage("testMethod() timed out after 10 milliseconds"); + assertThat(failure.getCause()) // + .hasMessageStartingWith("Execution timed out in ") // + .hasStackTraceContaining(TimeoutExceedingSeparateThreadTestCase.class.getName() + ".testMethod"); } @Test From f91d2795645f1dcb5deaa5084bea3c7a1a8eb7f9 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Fri, 9 Sep 2022 16:01:01 +0200 Subject: [PATCH 44/59] Remove unused parameter from TimeoutFailureFactory --- .../src/main/java/org/junit/jupiter/api/AssertTimeout.java | 6 +++--- .../src/main/java/org/junit/jupiter/api/Assertions.java | 4 +--- .../engine/extension/SeparateThreadTimeoutInvocation.java | 2 +- .../org/junit/jupiter/api/AssertTimeoutAssertionsTests.java | 4 ++-- 4 files changed, 7 insertions(+), 9 deletions(-) diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java index dd77693ce4d8..ff12f010f17e 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java @@ -182,7 +182,7 @@ private V resolveFutureAndHandleException(Future future, Duration timeout cause = new ExecutionTimeoutException("Execution timed out in thread " + thread.getName()); cause.setStackTrace(thread.getStackTrace()); } - throw failureFactory.handleTimeout(ex, timeout, messageSupplier, cause); + throw failureFactory.createTimeoutFailure(timeout, messageSupplier, cause); } catch (ExecutionException ex) { throw throwAsUncheckedException(ex.getCause()); @@ -218,8 +218,8 @@ public Thread newThread(Runnable r) { private static class AssertionTimeoutFailureFactory implements TimeoutFailureFactory { @Override - public AssertionFailedError handleTimeout(TimeoutException exception, Duration timeout, - Supplier messageSupplier, Throwable cause) { + public AssertionFailedError createTimeoutFailure(Duration timeout, Supplier messageSupplier, + Throwable cause) { return assertionFailure() // .message(messageSupplier) // .reason("execution timed out after " + timeout.toMillis() + " ms") // 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 f7b225d31d86..c8a26f9a9829 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 @@ -19,7 +19,6 @@ import java.util.Collection; import java.util.List; import java.util.Objects; -import java.util.concurrent.TimeoutException; import java.util.function.BooleanSupplier; import java.util.function.Supplier; import java.util.stream.Stream; @@ -3608,7 +3607,6 @@ public static T assertInstanceOf(Class expectedType, Object actualValue, @API(status = INTERNAL, since = "5.9.1") public interface TimeoutFailureFactory { - T handleTimeout(TimeoutException exception, Duration timeout, Supplier messageSupplier, - Throwable cause); + T createTimeoutFailure(Duration timeout, Supplier messageSupplier, Throwable cause); } } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/SeparateThreadTimeoutInvocation.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/SeparateThreadTimeoutInvocation.java index 59233fb2b8b2..16f22d1b09f6 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/SeparateThreadTimeoutInvocation.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/SeparateThreadTimeoutInvocation.java @@ -36,7 +36,7 @@ class SeparateThreadTimeoutInvocation implements Invocation { @Override public T proceed() throws Throwable { return assertTimeoutPreemptively(timeout.toDuration(), delegate::proceed, descriptionSupplier, - (e, __, messageSupplier, cause) -> { + (__, messageSupplier, cause) -> { TimeoutException exception = TimeoutExceptionFactory.create(messageSupplier.get(), timeout, null); exception.initCause(cause); return exception; diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertTimeoutAssertionsTests.java b/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertTimeoutAssertionsTests.java index ba5ef967a077..e0a17bfa96db 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertTimeoutAssertionsTests.java +++ b/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertTimeoutAssertionsTests.java @@ -41,8 +41,8 @@ class AssertTimeoutAssertionsTests { private static final Duration PREEMPTIVE_TIMEOUT = ofMillis(WINDOWS.isCurrentOs() ? 1000 : 100); - private static final Assertions.TimeoutFailureFactory TIMEOUT_EXCEPTION_FACTORY = (__, ___, ____, - _____) -> new TimeoutException(); + private static final Assertions.TimeoutFailureFactory TIMEOUT_EXCEPTION_FACTORY = (__, ___, + ____) -> new TimeoutException(); private static ThreadLocal changed = ThreadLocal.withInitial(() -> new AtomicBoolean(false)); From 7cce38f1ee3cdec6ff7f0e05b2522801a9d86848 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Fri, 9 Sep 2022 16:04:16 +0200 Subject: [PATCH 45/59] Add Javadoc --- .../main/java/org/junit/jupiter/api/Assertions.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) 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 c8a26f9a9829..e30cf6e043ed 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 @@ -3605,8 +3605,21 @@ public static T assertInstanceOf(Class expectedType, Object actualValue, return AssertInstanceOf.assertInstanceOf(expectedType, actualValue, messageSupplier); } + /** + * Factory for timeout failures. + * + * @param The type of error or exception created + * @since 5.9.1 + * @see Assertions#assertTimeoutPreemptively(Duration, ThrowingSupplier, Supplier, TimeoutFailureFactory) + */ @API(status = INTERNAL, since = "5.9.1") public interface TimeoutFailureFactory { + + /** + * Create a failure for the given timeout, message, and cause. + * + * @return timeout failure; never {@code null} + */ T createTimeoutFailure(Duration timeout, Supplier messageSupplier, Throwable cause); } } From 64cac6f069f135403167bae4aeb85ed1c4286662 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Fri, 9 Sep 2022 16:11:54 +0200 Subject: [PATCH 46/59] Avoid extra instance creation --- .../org/junit/jupiter/api/AssertTimeout.java | 119 ++++++++---------- 1 file changed, 50 insertions(+), 69 deletions(-) diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java index ff12f010f17e..f02648bf65d6 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java @@ -39,9 +39,6 @@ */ class AssertTimeout { - private static final PreemptiveTimeoutAssertionExecutor ASSERTION_ERROR_TIMEOUT_EXECUTOR = new PreemptiveTimeoutAssertionExecutor<>( - new AssertionTimeoutFailureFactory()); - private AssertTimeout() { /* no-op */ } @@ -117,82 +114,79 @@ static void assertTimeoutPreemptively(Duration timeout, Executable executable, S } static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier supplier) { - return ASSERTION_ERROR_TIMEOUT_EXECUTOR.executeThrowing(timeout, supplier, null); + return assertTimeoutPreemptively(timeout, supplier, null, AssertTimeout::createAssertionFailure); } static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier supplier, String message) { - return ASSERTION_ERROR_TIMEOUT_EXECUTOR.executeThrowing(timeout, supplier, - message == null ? null : () -> message); + return assertTimeoutPreemptively(timeout, supplier, message == null ? null : () -> message, + AssertTimeout::createAssertionFailure); } static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier supplier, Supplier messageSupplier) { - return ASSERTION_ERROR_TIMEOUT_EXECUTOR.executeThrowing(timeout, supplier, messageSupplier); + return assertTimeoutPreemptively(timeout, supplier, messageSupplier, AssertTimeout::createAssertionFailure); } static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier supplier, Supplier messageSupplier, TimeoutFailureFactory failureFactory) throws E { - return new PreemptiveTimeoutAssertionExecutor<>(failureFactory).executeThrowing(timeout, supplier, - messageSupplier); - } - - static class PreemptiveTimeoutAssertionExecutor { - private final TimeoutFailureFactory failureFactory; + AtomicReference threadReference = new AtomicReference<>(); + ExecutorService executorService = Executors.newSingleThreadExecutor(new TimeoutThreadFactory()); - PreemptiveTimeoutAssertionExecutor(TimeoutFailureFactory failureFactory) { - this.failureFactory = failureFactory; + try { + Future future = submitTask(supplier, threadReference, executorService); + return resolveFutureAndHandleException(future, timeout, messageSupplier, threadReference::get, + failureFactory); } + finally { + executorService.shutdownNow(); + } + } - V executeThrowing(Duration timeout, ThrowingSupplier supplier, Supplier messageSupplier) - throws T { - AtomicReference threadReference = new AtomicReference<>(); - ExecutorService executorService = Executors.newSingleThreadExecutor(new TimeoutThreadFactory()); - + private static Future submitTask(ThrowingSupplier supplier, AtomicReference threadReference, + ExecutorService executorService) { + return executorService.submit(() -> { try { - Future future = submitTask(supplier, threadReference, executorService); - return resolveFutureAndHandleException(future, timeout, messageSupplier, threadReference::get); + threadReference.set(Thread.currentThread()); + return supplier.get(); } - finally { - executorService.shutdownNow(); + catch (Throwable throwable) { + throw throwAsUncheckedException(throwable); } - } + }); + } - private Future submitTask(ThrowingSupplier supplier, AtomicReference threadReference, - ExecutorService executorService) { - return executorService.submit(() -> { - try { - threadReference.set(Thread.currentThread()); - return supplier.get(); - } - catch (Throwable throwable) { - throw throwAsUncheckedException(throwable); - } - }); + private static T resolveFutureAndHandleException(Future future, Duration timeout, + Supplier messageSupplier, Supplier threadSupplier, TimeoutFailureFactory failureFactory) + throws E { + try { + return future.get(timeout.toMillis(), TimeUnit.MILLISECONDS); } - - private V resolveFutureAndHandleException(Future future, Duration timeout, - Supplier messageSupplier, Supplier threadSupplier) throws T { - try { - return future.get(timeout.toMillis(), TimeUnit.MILLISECONDS); - } - catch (TimeoutException ex) { - Thread thread = threadSupplier.get(); - ExecutionTimeoutException cause = null; - if (thread != null) { - cause = new ExecutionTimeoutException("Execution timed out in thread " + thread.getName()); - cause.setStackTrace(thread.getStackTrace()); - } - throw failureFactory.createTimeoutFailure(timeout, messageSupplier, cause); - } - catch (ExecutionException ex) { - throw throwAsUncheckedException(ex.getCause()); - } - catch (Throwable ex) { - throw throwAsUncheckedException(ex); + catch (TimeoutException ex) { + Thread thread = threadSupplier.get(); + ExecutionTimeoutException cause = null; + if (thread != null) { + cause = new ExecutionTimeoutException("Execution timed out in thread " + thread.getName()); + cause.setStackTrace(thread.getStackTrace()); } + throw failureFactory.createTimeoutFailure(timeout, messageSupplier, cause); + } + catch (ExecutionException ex) { + throw throwAsUncheckedException(ex.getCause()); + } + catch (Throwable ex) { + throw throwAsUncheckedException(ex); } } + private static AssertionFailedError createAssertionFailure(Duration timeout, Supplier messageSupplier, + Throwable cause) { + return assertionFailure() // + .message(messageSupplier) // + .reason("execution timed out after " + timeout.toMillis() + " ms") // + .cause(cause) // + .build(); + } + private static class ExecutionTimeoutException extends JUnitException { private static final long serialVersionUID = 1L; @@ -214,17 +208,4 @@ public Thread newThread(Runnable r) { return new Thread(r, "junit-timeout-thread-" + threadNumber.getAndIncrement()); } } - - private static class AssertionTimeoutFailureFactory implements TimeoutFailureFactory { - - @Override - public AssertionFailedError createTimeoutFailure(Duration timeout, Supplier messageSupplier, - Throwable cause) { - return assertionFailure() // - .message(messageSupplier) // - .reason("execution timed out after " + timeout.toMillis() + " ms") // - .cause(cause) // - .build(); - } - } } From ef8f2c20053392055cd1b606eb0b5c6e61ef13ff Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Fri, 9 Sep 2022 16:22:22 +0200 Subject: [PATCH 47/59] Extract AssertTimeoutPreemptively from AssertTimeout --- .../org/junit/jupiter/api/AssertTimeout.java | 125 --------- .../api/AssertTimeoutPreemptively.java | 156 +++++++++++ .../org/junit/jupiter/api/Assertions.java | 14 +- .../api/AssertTimeoutAssertionsTests.java | 221 +-------------- ...ertTimeoutPreemptivelyAssertionsTests.java | 255 ++++++++++++++++++ 5 files changed, 420 insertions(+), 351 deletions(-) create mode 100644 junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeoutPreemptively.java create mode 100644 junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertTimeoutPreemptivelyAssertionsTests.java diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java index f02648bf65d6..93c7c11f8a71 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java @@ -14,22 +14,10 @@ import static org.junit.platform.commons.util.ExceptionUtils.throwAsUncheckedException; import java.time.Duration; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicReference; import java.util.function.Supplier; -import org.junit.jupiter.api.Assertions.TimeoutFailureFactory; import org.junit.jupiter.api.function.Executable; import org.junit.jupiter.api.function.ThrowingSupplier; -import org.junit.platform.commons.JUnitException; -import org.opentest4j.AssertionFailedError; /** * {@code AssertTimeout} is a collection of utility methods that support asserting @@ -95,117 +83,4 @@ private static T assertTimeout(Duration timeout, ThrowingSupplier supplie return result; } - static void assertTimeoutPreemptively(Duration timeout, Executable executable) { - assertTimeoutPreemptively(timeout, executable, (String) null); - } - - static void assertTimeoutPreemptively(Duration timeout, Executable executable, String message) { - assertTimeoutPreemptively(timeout, () -> { - executable.execute(); - return null; - }, message); - } - - static void assertTimeoutPreemptively(Duration timeout, Executable executable, Supplier messageSupplier) { - assertTimeoutPreemptively(timeout, () -> { - executable.execute(); - return null; - }, messageSupplier); - } - - static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier supplier) { - return assertTimeoutPreemptively(timeout, supplier, null, AssertTimeout::createAssertionFailure); - } - - static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier supplier, String message) { - return assertTimeoutPreemptively(timeout, supplier, message == null ? null : () -> message, - AssertTimeout::createAssertionFailure); - } - - static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier supplier, - Supplier messageSupplier) { - return assertTimeoutPreemptively(timeout, supplier, messageSupplier, AssertTimeout::createAssertionFailure); - } - - static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier supplier, - Supplier messageSupplier, TimeoutFailureFactory failureFactory) throws E { - AtomicReference threadReference = new AtomicReference<>(); - ExecutorService executorService = Executors.newSingleThreadExecutor(new TimeoutThreadFactory()); - - try { - Future future = submitTask(supplier, threadReference, executorService); - return resolveFutureAndHandleException(future, timeout, messageSupplier, threadReference::get, - failureFactory); - } - finally { - executorService.shutdownNow(); - } - } - - private static Future submitTask(ThrowingSupplier supplier, AtomicReference threadReference, - ExecutorService executorService) { - return executorService.submit(() -> { - try { - threadReference.set(Thread.currentThread()); - return supplier.get(); - } - catch (Throwable throwable) { - throw throwAsUncheckedException(throwable); - } - }); - } - - private static T resolveFutureAndHandleException(Future future, Duration timeout, - Supplier messageSupplier, Supplier threadSupplier, TimeoutFailureFactory failureFactory) - throws E { - try { - return future.get(timeout.toMillis(), TimeUnit.MILLISECONDS); - } - catch (TimeoutException ex) { - Thread thread = threadSupplier.get(); - ExecutionTimeoutException cause = null; - if (thread != null) { - cause = new ExecutionTimeoutException("Execution timed out in thread " + thread.getName()); - cause.setStackTrace(thread.getStackTrace()); - } - throw failureFactory.createTimeoutFailure(timeout, messageSupplier, cause); - } - catch (ExecutionException ex) { - throw throwAsUncheckedException(ex.getCause()); - } - catch (Throwable ex) { - throw throwAsUncheckedException(ex); - } - } - - private static AssertionFailedError createAssertionFailure(Duration timeout, Supplier messageSupplier, - Throwable cause) { - return assertionFailure() // - .message(messageSupplier) // - .reason("execution timed out after " + timeout.toMillis() + " ms") // - .cause(cause) // - .build(); - } - - private static class ExecutionTimeoutException extends JUnitException { - - private static final long serialVersionUID = 1L; - - ExecutionTimeoutException(String message) { - super(message); - } - } - - /** - * The thread factory used for preemptive timeout. - *

- * The factory creates threads with meaningful names, helpful for debugging purposes. - */ - private static class TimeoutThreadFactory implements ThreadFactory { - private static final AtomicInteger threadNumber = new AtomicInteger(1); - - public Thread newThread(Runnable r) { - return new Thread(r, "junit-timeout-thread-" + threadNumber.getAndIncrement()); - } - } } diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeoutPreemptively.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeoutPreemptively.java new file mode 100644 index 000000000000..94534171dd01 --- /dev/null +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeoutPreemptively.java @@ -0,0 +1,156 @@ +/* + * Copyright 2015-2022 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.jupiter.api; + +import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; +import static org.junit.platform.commons.util.ExceptionUtils.throwAsUncheckedException; + +import java.time.Duration; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Supplier; + +import org.junit.jupiter.api.function.Executable; +import org.junit.jupiter.api.function.ThrowingSupplier; +import org.junit.platform.commons.JUnitException; +import org.opentest4j.AssertionFailedError; + +/** + * {@code AssertTimeout} is a collection of utility methods that support asserting + * the execution of the code under test did not take longer than the timeout duration + * using a preemptive approach. + * + * @since 5.9.1 + */ +class AssertTimeoutPreemptively { + + static void assertTimeoutPreemptively(Duration timeout, Executable executable) { + assertTimeoutPreemptively(timeout, executable, (String) null); + } + + static void assertTimeoutPreemptively(Duration timeout, Executable executable, String message) { + assertTimeoutPreemptively(timeout, () -> { + executable.execute(); + return null; + }, message); + } + + static void assertTimeoutPreemptively(Duration timeout, Executable executable, Supplier messageSupplier) { + assertTimeoutPreemptively(timeout, () -> { + executable.execute(); + return null; + }, messageSupplier); + } + + static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier supplier) { + return assertTimeoutPreemptively(timeout, supplier, null, AssertTimeoutPreemptively::createAssertionFailure); + } + + static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier supplier, String message) { + return assertTimeoutPreemptively(timeout, supplier, message == null ? null : () -> message, + AssertTimeoutPreemptively::createAssertionFailure); + } + + static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier supplier, + Supplier messageSupplier) { + return assertTimeoutPreemptively(timeout, supplier, messageSupplier, + AssertTimeoutPreemptively::createAssertionFailure); + } + + static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier supplier, + Supplier messageSupplier, Assertions.TimeoutFailureFactory failureFactory) throws E { + AtomicReference threadReference = new AtomicReference<>(); + ExecutorService executorService = Executors.newSingleThreadExecutor(new TimeoutThreadFactory()); + + try { + Future future = submitTask(supplier, threadReference, executorService); + return resolveFutureAndHandleException(future, timeout, messageSupplier, threadReference::get, + failureFactory); + } + finally { + executorService.shutdownNow(); + } + } + + private static Future submitTask(ThrowingSupplier supplier, AtomicReference threadReference, + ExecutorService executorService) { + return executorService.submit(() -> { + try { + threadReference.set(Thread.currentThread()); + return supplier.get(); + } + catch (Throwable throwable) { + throw throwAsUncheckedException(throwable); + } + }); + } + + private static T resolveFutureAndHandleException(Future future, Duration timeout, + Supplier messageSupplier, Supplier threadSupplier, + Assertions.TimeoutFailureFactory failureFactory) throws E { + try { + return future.get(timeout.toMillis(), TimeUnit.MILLISECONDS); + } + catch (TimeoutException ex) { + Thread thread = threadSupplier.get(); + ExecutionTimeoutException cause = null; + if (thread != null) { + cause = new ExecutionTimeoutException("Execution timed out in thread " + thread.getName()); + cause.setStackTrace(thread.getStackTrace()); + } + throw failureFactory.createTimeoutFailure(timeout, messageSupplier, cause); + } + catch (ExecutionException ex) { + throw throwAsUncheckedException(ex.getCause()); + } + catch (Throwable ex) { + throw throwAsUncheckedException(ex); + } + } + + private static AssertionFailedError createAssertionFailure(Duration timeout, Supplier messageSupplier, + Throwable cause) { + return assertionFailure() // + .message(messageSupplier) // + .reason("execution timed out after " + timeout.toMillis() + " ms") // + .cause(cause) // + .build(); + } + + private static class ExecutionTimeoutException extends JUnitException { + + private static final long serialVersionUID = 1L; + + ExecutionTimeoutException(String message) { + super(message); + } + } + + /** + * The thread factory used for preemptive timeout. + *

+ * The factory creates threads with meaningful names, helpful for debugging purposes. + */ + private static class TimeoutThreadFactory implements ThreadFactory { + private static final AtomicInteger threadNumber = new AtomicInteger(1); + + public Thread newThread(Runnable r) { + return new Thread(r, "junit-timeout-thread-" + threadNumber.getAndIncrement()); + } + } +} 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 e30cf6e043ed..62fefbe630fb 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 @@ -3396,7 +3396,7 @@ public static T assertTimeout(Duration timeout, ThrowingSupplier supplier * @see #assertTimeout(Duration, Executable) */ public static void assertTimeoutPreemptively(Duration timeout, Executable executable) { - AssertTimeout.assertTimeoutPreemptively(timeout, executable); + AssertTimeoutPreemptively.assertTimeoutPreemptively(timeout, executable); } /** @@ -3419,7 +3419,7 @@ public static void assertTimeoutPreemptively(Duration timeout, Executable execut * @see #assertTimeout(Duration, Executable, String) */ public static void assertTimeoutPreemptively(Duration timeout, Executable executable, String message) { - AssertTimeout.assertTimeoutPreemptively(timeout, executable, message); + AssertTimeoutPreemptively.assertTimeoutPreemptively(timeout, executable, message); } /** @@ -3444,7 +3444,7 @@ public static void assertTimeoutPreemptively(Duration timeout, Executable execut */ public static void assertTimeoutPreemptively(Duration timeout, Executable executable, Supplier messageSupplier) { - AssertTimeout.assertTimeoutPreemptively(timeout, executable, messageSupplier); + AssertTimeoutPreemptively.assertTimeoutPreemptively(timeout, executable, messageSupplier); } // --- supplier - preemptively --- @@ -3469,7 +3469,7 @@ public static void assertTimeoutPreemptively(Duration timeout, Executable execut * @see #assertTimeout(Duration, Executable) */ public static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier supplier) { - return AssertTimeout.assertTimeoutPreemptively(timeout, supplier); + return AssertTimeoutPreemptively.assertTimeoutPreemptively(timeout, supplier); } /** @@ -3494,7 +3494,7 @@ public static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier * @see #assertTimeout(Duration, Executable, String) */ public static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier supplier, String message) { - return AssertTimeout.assertTimeoutPreemptively(timeout, supplier, message); + return AssertTimeoutPreemptively.assertTimeoutPreemptively(timeout, supplier, message); } /** @@ -3521,7 +3521,7 @@ public static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier */ public static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier supplier, Supplier messageSupplier) { - return AssertTimeout.assertTimeoutPreemptively(timeout, supplier, messageSupplier); + return AssertTimeoutPreemptively.assertTimeoutPreemptively(timeout, supplier, messageSupplier); } /** @@ -3553,7 +3553,7 @@ public static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier @API(status = INTERNAL, since = "5.9.1") public static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier supplier, Supplier messageSupplier, TimeoutFailureFactory failureFactory) throws E { - return AssertTimeout.assertTimeoutPreemptively(timeout, supplier, messageSupplier, failureFactory); + return AssertTimeoutPreemptively.assertTimeoutPreemptively(timeout, supplier, messageSupplier, failureFactory); } // --- assertInstanceOf ---------------------------------------------------- diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertTimeoutAssertionsTests.java b/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertTimeoutAssertionsTests.java index e0a17bfa96db..0dfd4e7d6e13 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertTimeoutAssertionsTests.java +++ b/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertTimeoutAssertionsTests.java @@ -11,40 +11,28 @@ package org.junit.jupiter.api; import static java.time.Duration.ofMillis; -import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.AssertionTestUtils.assertMessageEquals; import static org.junit.jupiter.api.AssertionTestUtils.assertMessageStartsWith; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTimeout; -import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; -import static org.junit.jupiter.api.condition.OS.WINDOWS; -import java.time.Duration; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicReference; import org.junit.jupiter.api.function.Executable; import org.junit.platform.commons.util.ExceptionUtils; import org.opentest4j.AssertionFailedError; /** - * Unit tests for JUnit Jupiter {@link Assertions}. + * Unit tests for {@link AssertTimeout}. * * @since 5.0 */ class AssertTimeoutAssertionsTests { - private static final Duration PREEMPTIVE_TIMEOUT = ofMillis(WINDOWS.isCurrentOs() ? 1000 : 100); - private static final Assertions.TimeoutFailureFactory TIMEOUT_EXCEPTION_FACTORY = (__, ___, - ____) -> new TimeoutException(); - - private static ThreadLocal changed = ThreadLocal.withInitial(() -> new AtomicBoolean(false)); + private static final ThreadLocal changed = ThreadLocal.withInitial(() -> new AtomicBoolean(false)); private final Executable nix = () -> { }; @@ -166,191 +154,6 @@ void assertTimeoutWithMessageSupplierForSupplierThatCompletesAfterTheTimeout() { assertMessageStartsWith(error, "Tempus Fugit ==> execution exceeded timeout of 10 ms by"); } - // -- executable - preemptively --- - - @Test - void assertTimeoutPreemptivelyForExecutableThatCompletesBeforeTheTimeout() { - changed.get().set(false); - assertTimeoutPreemptively(ofMillis(500), () -> changed.get().set(true)); - assertFalse(changed.get().get(), "should have executed in a different thread"); - assertTimeoutPreemptively(ofMillis(500), nix, "message"); - assertTimeoutPreemptively(ofMillis(500), nix, () -> "message"); - } - - @Test - void assertTimeoutPreemptivelyForExecutableThatThrowsAnException() { - RuntimeException exception = assertThrows(RuntimeException.class, - () -> assertTimeoutPreemptively(ofMillis(500), () -> { - throw new RuntimeException("not this time"); - })); - assertMessageEquals(exception, "not this time"); - } - - @Test - void assertTimeoutPreemptivelyForExecutableThatThrowsAnAssertionFailedError() { - AssertionFailedError exception = assertThrows(AssertionFailedError.class, - () -> assertTimeoutPreemptively(ofMillis(500), () -> fail("enigma"))); - assertMessageEquals(exception, "enigma"); - } - - @Test - void assertTimeoutPreemptivelyForExecutableThatCompletesAfterTheTimeout() { - AssertionFailedError error = assertThrows(AssertionFailedError.class, - () -> assertTimeoutPreemptively(PREEMPTIVE_TIMEOUT, this::waitForInterrupt)); - assertMessageEquals(error, "execution timed out after " + PREEMPTIVE_TIMEOUT.toMillis() + " ms"); - assertMessageStartsWith(error.getCause(), "Execution timed out in "); - assertStackTraceContains(error.getCause().getStackTrace(), "CountDownLatch", "await"); - } - - @Test - void assertTimeoutPreemptivelyWithMessageForExecutableThatCompletesAfterTheTimeout() { - AssertionFailedError error = assertThrows(AssertionFailedError.class, - () -> assertTimeoutPreemptively(PREEMPTIVE_TIMEOUT, this::waitForInterrupt, "Tempus Fugit")); - assertMessageEquals(error, - "Tempus Fugit ==> execution timed out after " + PREEMPTIVE_TIMEOUT.toMillis() + " ms"); - assertMessageStartsWith(error.getCause(), "Execution timed out in "); - assertStackTraceContains(error.getCause().getStackTrace(), "CountDownLatch", "await"); - } - - @Test - void assertTimeoutPreemptivelyWithMessageSupplierForExecutableThatCompletesAfterTheTimeout() { - AssertionFailedError error = assertThrows(AssertionFailedError.class, - () -> assertTimeoutPreemptively(PREEMPTIVE_TIMEOUT, this::waitForInterrupt, - () -> "Tempus" + " " + "Fugit")); - assertMessageEquals(error, - "Tempus Fugit ==> execution timed out after " + PREEMPTIVE_TIMEOUT.toMillis() + " ms"); - assertMessageStartsWith(error.getCause(), "Execution timed out in "); - assertStackTraceContains(error.getCause().getStackTrace(), "CountDownLatch", "await"); - } - - @Test - void assertTimeoutPreemptivelyWithMessageSupplierForExecutableThatCompletesBeforeTheTimeout() { - assertTimeoutPreemptively(ofMillis(500), nix, () -> "Tempus" + " " + "Fugit"); - } - - // -- supplier - preemptively --- - - @Test - void assertTimeoutPreemptivelyForSupplierThatCompletesBeforeTheTimeout() { - changed.get().set(false); - String result = assertTimeoutPreemptively(ofMillis(500), () -> { - changed.get().set(true); - return "Tempus Fugit"; - }); - assertFalse(changed.get().get(), "should have executed in a different thread"); - assertEquals("Tempus Fugit", result); - assertEquals("Tempus Fugit", assertTimeoutPreemptively(ofMillis(500), () -> "Tempus Fugit", "message")); - assertEquals("Tempus Fugit", assertTimeoutPreemptively(ofMillis(500), () -> "Tempus Fugit", () -> "message")); - } - - @Test - void assertTimeoutPreemptivelyForSupplierThatThrowsAnException() { - RuntimeException exception = assertThrows(RuntimeException.class, () -> { - assertTimeoutPreemptively(ofMillis(500), () -> { - ExceptionUtils.throwAsUncheckedException(new RuntimeException("not this time")); - return "Tempus Fugit"; - }); - }); - assertMessageEquals(exception, "not this time"); - } - - @Test - void assertTimeoutPreemptivelyForSupplierThatThrowsAnAssertionFailedError() { - AssertionFailedError exception = assertThrows(AssertionFailedError.class, () -> { - assertTimeoutPreemptively(ofMillis(500), () -> { - fail("enigma"); - return "Tempus Fugit"; - }); - }); - assertMessageEquals(exception, "enigma"); - } - - @Test - void assertTimeoutPreemptivelyForSupplierThatCompletesAfterTheTimeout() { - AssertionFailedError error = assertThrows(AssertionFailedError.class, () -> { - assertTimeoutPreemptively(PREEMPTIVE_TIMEOUT, () -> { - waitForInterrupt(); - return "Tempus Fugit"; - }); - }); - - assertMessageEquals(error, "execution timed out after " + PREEMPTIVE_TIMEOUT.toMillis() + " ms"); - assertMessageStartsWith(error.getCause(), "Execution timed out in "); - assertStackTraceContains(error.getCause().getStackTrace(), "CountDownLatch", "await"); - } - - @Test - void assertTimeoutPreemptivelyWithMessageForSupplierThatCompletesAfterTheTimeout() { - AssertionFailedError error = assertThrows(AssertionFailedError.class, () -> { - assertTimeoutPreemptively(PREEMPTIVE_TIMEOUT, () -> { - waitForInterrupt(); - return "Tempus Fugit"; - }, "Tempus Fugit"); - }); - - assertMessageEquals(error, - "Tempus Fugit ==> execution timed out after " + PREEMPTIVE_TIMEOUT.toMillis() + " ms"); - assertMessageStartsWith(error.getCause(), "Execution timed out in "); - assertStackTraceContains(error.getCause().getStackTrace(), "CountDownLatch", "await"); - } - - @Test - void assertTimeoutPreemptivelyWithMessageSupplierForSupplierThatCompletesAfterTheTimeout() { - AssertionFailedError error = assertThrows(AssertionFailedError.class, () -> { - assertTimeoutPreemptively(PREEMPTIVE_TIMEOUT, () -> { - waitForInterrupt(); - return "Tempus Fugit"; - }, () -> "Tempus" + " " + "Fugit"); - }); - - assertMessageEquals(error, - "Tempus Fugit ==> execution timed out after " + PREEMPTIVE_TIMEOUT.toMillis() + " ms"); - assertMessageStartsWith(error.getCause(), "Execution timed out in "); - assertStackTraceContains(error.getCause().getStackTrace(), "CountDownLatch", "await"); - } - - @Test - void assertTimeoutPreemptivelyUsesThreadsWithSpecificNamePrefix() { - AtomicReference threadName = new AtomicReference<>(""); - assertTimeoutPreemptively(ofMillis(1000), () -> threadName.set(Thread.currentThread().getName())); - assertTrue(threadName.get().startsWith("junit-timeout-thread-"), - "Thread name does not match the expected prefix"); - } - - @Test - void assertTimeoutPreemptivelyThrowingTimeoutExceptionWithMessageForSupplierThatCompletesAfterTheTimeout() { - assertThrows(TimeoutException.class, () -> Assertions.assertTimeoutPreemptively(PREEMPTIVE_TIMEOUT, () -> { - waitForInterrupt(); - return "Tempus Fugit"; - }, () -> "Tempus Fugit", TIMEOUT_EXCEPTION_FACTORY)); - } - - @Test - void assertTimeoutPreemptivelyThrowingTimeoutExceptionWithMessageForSupplierThatThrowsAnAssertionFailedError() { - AssertionFailedError exception = assertThrows(AssertionFailedError.class, - () -> Assertions.assertTimeoutPreemptively(ofMillis(500), () -> fail("enigma"), () -> "Tempus Fugit", - TIMEOUT_EXCEPTION_FACTORY)); - assertMessageEquals(exception, "enigma"); - } - - @Test - void assertTimeoutPreemptivelyThrowingTimeoutExceptionWithMessageForSupplierThatThrowsAnException() { - RuntimeException exception = assertThrows(RuntimeException.class, - () -> Assertions.assertTimeoutPreemptively(ofMillis(500), - () -> ExceptionUtils.throwAsUncheckedException(new RuntimeException(":(")), () -> "Tempus Fugit", - TIMEOUT_EXCEPTION_FACTORY)); - assertMessageEquals(exception, ":("); - } - - @Test - void assertTimeoutPreemptivelyThrowingTimeoutExceptionWithMessageForSupplierThatCompletesBeforeTimeout() - throws Exception { - var result = Assertions.assertTimeoutPreemptively(PREEMPTIVE_TIMEOUT, () -> "Tempus Fugit", - () -> "Tempus Fugit", TIMEOUT_EXCEPTION_FACTORY); - - assertThat(result).isEqualTo("Tempus Fugit"); - } - /** * Take a nap for 100 milliseconds. */ @@ -362,24 +165,4 @@ private void nap() throws InterruptedException { } while (System.currentTimeMillis() - start < 100); } - private void waitForInterrupt() { - try { - assertFalse(Thread.interrupted(), "Already interrupted"); - new CountDownLatch(1).await(); - } - catch (InterruptedException ignore) { - // ignore - } - } - - /** - * Assert the given stack trace elements contain an element with the given class name and method name. - */ - private static void assertStackTraceContains(StackTraceElement[] stackTrace, String className, String methodName) { - assertThat(stackTrace).anySatisfy(element -> { - assertThat(element.getClassName()).endsWith(className); - assertThat(element.getMethodName()).isEqualTo(methodName); - }); - } - } diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertTimeoutPreemptivelyAssertionsTests.java b/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertTimeoutPreemptivelyAssertionsTests.java new file mode 100644 index 000000000000..1f4d453cd698 --- /dev/null +++ b/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertTimeoutPreemptivelyAssertionsTests.java @@ -0,0 +1,255 @@ +/* + * Copyright 2015-2022 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.jupiter.api; + +import static java.time.Duration.ofMillis; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.AssertionTestUtils.assertMessageEquals; +import static org.junit.jupiter.api.AssertionTestUtils.assertMessageStartsWith; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.condition.OS.WINDOWS; + +import java.time.Duration; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +import org.junit.jupiter.api.function.Executable; +import org.junit.platform.commons.util.ExceptionUtils; +import org.opentest4j.AssertionFailedError; + +/** + * Unit tests for {@link AssertTimeoutPreemptively}. + * + * @since 5.0 + */ +class AssertTimeoutPreemptivelyAssertionsTests { + + private static final Duration PREEMPTIVE_TIMEOUT = ofMillis(WINDOWS.isCurrentOs() ? 1000 : 100); + private static final Assertions.TimeoutFailureFactory TIMEOUT_EXCEPTION_FACTORY = (__, ___, + ____) -> new TimeoutException(); + + private static final ThreadLocal changed = ThreadLocal.withInitial(() -> new AtomicBoolean(false)); + + private final Executable nix = () -> { + }; + + // --- executable ---------------------------------------------------------- + + @Test + void assertTimeoutPreemptivelyForExecutableThatCompletesBeforeTheTimeout() { + changed.get().set(false); + assertTimeoutPreemptively(ofMillis(500), () -> changed.get().set(true)); + assertFalse(changed.get().get(), "should have executed in a different thread"); + assertTimeoutPreemptively(ofMillis(500), nix, "message"); + assertTimeoutPreemptively(ofMillis(500), nix, () -> "message"); + } + + @Test + void assertTimeoutPreemptivelyForExecutableThatThrowsAnException() { + RuntimeException exception = assertThrows(RuntimeException.class, + () -> assertTimeoutPreemptively(ofMillis(500), () -> { + throw new RuntimeException("not this time"); + })); + assertMessageEquals(exception, "not this time"); + } + + @Test + void assertTimeoutPreemptivelyForExecutableThatThrowsAnAssertionFailedError() { + AssertionFailedError exception = assertThrows(AssertionFailedError.class, + () -> assertTimeoutPreemptively(ofMillis(500), () -> fail("enigma"))); + assertMessageEquals(exception, "enigma"); + } + + @Test + void assertTimeoutPreemptivelyForExecutableThatCompletesAfterTheTimeout() { + AssertionFailedError error = assertThrows(AssertionFailedError.class, + () -> assertTimeoutPreemptively(PREEMPTIVE_TIMEOUT, this::waitForInterrupt)); + assertMessageEquals(error, "execution timed out after " + PREEMPTIVE_TIMEOUT.toMillis() + " ms"); + assertMessageStartsWith(error.getCause(), "Execution timed out in "); + assertStackTraceContains(error.getCause().getStackTrace(), "CountDownLatch", "await"); + } + + @Test + void assertTimeoutPreemptivelyWithMessageForExecutableThatCompletesAfterTheTimeout() { + AssertionFailedError error = assertThrows(AssertionFailedError.class, + () -> assertTimeoutPreemptively(PREEMPTIVE_TIMEOUT, this::waitForInterrupt, "Tempus Fugit")); + assertMessageEquals(error, + "Tempus Fugit ==> execution timed out after " + PREEMPTIVE_TIMEOUT.toMillis() + " ms"); + assertMessageStartsWith(error.getCause(), "Execution timed out in "); + assertStackTraceContains(error.getCause().getStackTrace(), "CountDownLatch", "await"); + } + + @Test + void assertTimeoutPreemptivelyWithMessageSupplierForExecutableThatCompletesAfterTheTimeout() { + AssertionFailedError error = assertThrows(AssertionFailedError.class, + () -> assertTimeoutPreemptively(PREEMPTIVE_TIMEOUT, this::waitForInterrupt, + () -> "Tempus" + " " + "Fugit")); + assertMessageEquals(error, + "Tempus Fugit ==> execution timed out after " + PREEMPTIVE_TIMEOUT.toMillis() + " ms"); + assertMessageStartsWith(error.getCause(), "Execution timed out in "); + assertStackTraceContains(error.getCause().getStackTrace(), "CountDownLatch", "await"); + } + + @Test + void assertTimeoutPreemptivelyWithMessageSupplierForExecutableThatCompletesBeforeTheTimeout() { + assertTimeoutPreemptively(ofMillis(500), nix, () -> "Tempus" + " " + "Fugit"); + } + + // --- supplier ------------------------------------------------------------ + + @Test + void assertTimeoutPreemptivelyForSupplierThatCompletesBeforeTheTimeout() { + changed.get().set(false); + String result = assertTimeoutPreemptively(ofMillis(500), () -> { + changed.get().set(true); + return "Tempus Fugit"; + }); + assertFalse(changed.get().get(), "should have executed in a different thread"); + assertEquals("Tempus Fugit", result); + assertEquals("Tempus Fugit", assertTimeoutPreemptively(ofMillis(500), () -> "Tempus Fugit", "message")); + assertEquals("Tempus Fugit", assertTimeoutPreemptively(ofMillis(500), () -> "Tempus Fugit", () -> "message")); + } + + @Test + void assertTimeoutPreemptivelyForSupplierThatThrowsAnException() { + RuntimeException exception = assertThrows(RuntimeException.class, () -> { + assertTimeoutPreemptively(ofMillis(500), () -> { + ExceptionUtils.throwAsUncheckedException(new RuntimeException("not this time")); + return "Tempus Fugit"; + }); + }); + assertMessageEquals(exception, "not this time"); + } + + @Test + void assertTimeoutPreemptivelyForSupplierThatThrowsAnAssertionFailedError() { + AssertionFailedError exception = assertThrows(AssertionFailedError.class, () -> { + assertTimeoutPreemptively(ofMillis(500), () -> { + fail("enigma"); + return "Tempus Fugit"; + }); + }); + assertMessageEquals(exception, "enigma"); + } + + @Test + void assertTimeoutPreemptivelyForSupplierThatCompletesAfterTheTimeout() { + AssertionFailedError error = assertThrows(AssertionFailedError.class, () -> { + assertTimeoutPreemptively(PREEMPTIVE_TIMEOUT, () -> { + waitForInterrupt(); + return "Tempus Fugit"; + }); + }); + + assertMessageEquals(error, "execution timed out after " + PREEMPTIVE_TIMEOUT.toMillis() + " ms"); + assertMessageStartsWith(error.getCause(), "Execution timed out in "); + assertStackTraceContains(error.getCause().getStackTrace(), "CountDownLatch", "await"); + } + + @Test + void assertTimeoutPreemptivelyWithMessageForSupplierThatCompletesAfterTheTimeout() { + AssertionFailedError error = assertThrows(AssertionFailedError.class, () -> { + assertTimeoutPreemptively(PREEMPTIVE_TIMEOUT, () -> { + waitForInterrupt(); + return "Tempus Fugit"; + }, "Tempus Fugit"); + }); + + assertMessageEquals(error, + "Tempus Fugit ==> execution timed out after " + PREEMPTIVE_TIMEOUT.toMillis() + " ms"); + assertMessageStartsWith(error.getCause(), "Execution timed out in "); + assertStackTraceContains(error.getCause().getStackTrace(), "CountDownLatch", "await"); + } + + @Test + void assertTimeoutPreemptivelyWithMessageSupplierForSupplierThatCompletesAfterTheTimeout() { + AssertionFailedError error = assertThrows(AssertionFailedError.class, () -> { + assertTimeoutPreemptively(PREEMPTIVE_TIMEOUT, () -> { + waitForInterrupt(); + return "Tempus Fugit"; + }, () -> "Tempus" + " " + "Fugit"); + }); + + assertMessageEquals(error, + "Tempus Fugit ==> execution timed out after " + PREEMPTIVE_TIMEOUT.toMillis() + " ms"); + assertMessageStartsWith(error.getCause(), "Execution timed out in "); + assertStackTraceContains(error.getCause().getStackTrace(), "CountDownLatch", "await"); + } + + @Test + void assertTimeoutPreemptivelyUsesThreadsWithSpecificNamePrefix() { + AtomicReference threadName = new AtomicReference<>(""); + assertTimeoutPreemptively(ofMillis(1000), () -> threadName.set(Thread.currentThread().getName())); + assertTrue(threadName.get().startsWith("junit-timeout-thread-"), + "Thread name does not match the expected prefix"); + } + + @Test + void assertTimeoutPreemptivelyThrowingTimeoutExceptionWithMessageForSupplierThatCompletesAfterTheTimeout() { + assertThrows(TimeoutException.class, () -> Assertions.assertTimeoutPreemptively(PREEMPTIVE_TIMEOUT, () -> { + waitForInterrupt(); + return "Tempus Fugit"; + }, () -> "Tempus Fugit", TIMEOUT_EXCEPTION_FACTORY)); + } + + @Test + void assertTimeoutPreemptivelyThrowingTimeoutExceptionWithMessageForSupplierThatThrowsAnAssertionFailedError() { + AssertionFailedError exception = assertThrows(AssertionFailedError.class, + () -> Assertions.assertTimeoutPreemptively(ofMillis(500), () -> fail("enigma"), () -> "Tempus Fugit", + TIMEOUT_EXCEPTION_FACTORY)); + assertMessageEquals(exception, "enigma"); + } + + @Test + void assertTimeoutPreemptivelyThrowingTimeoutExceptionWithMessageForSupplierThatThrowsAnException() { + RuntimeException exception = assertThrows(RuntimeException.class, + () -> Assertions.assertTimeoutPreemptively(ofMillis(500), + () -> ExceptionUtils.throwAsUncheckedException(new RuntimeException(":(")), () -> "Tempus Fugit", + TIMEOUT_EXCEPTION_FACTORY)); + assertMessageEquals(exception, ":("); + } + + @Test + void assertTimeoutPreemptivelyThrowingTimeoutExceptionWithMessageForSupplierThatCompletesBeforeTimeout() + throws Exception { + var result = Assertions.assertTimeoutPreemptively(PREEMPTIVE_TIMEOUT, () -> "Tempus Fugit", + () -> "Tempus Fugit", TIMEOUT_EXCEPTION_FACTORY); + + assertThat(result).isEqualTo("Tempus Fugit"); + } + + private void waitForInterrupt() { + try { + assertFalse(Thread.interrupted(), "Already interrupted"); + new CountDownLatch(1).await(); + } + catch (InterruptedException ignore) { + // ignore + } + } + + /** + * Assert the given stack trace elements contain an element with the given class name and method name. + */ + private static void assertStackTraceContains(StackTraceElement[] stackTrace, String className, String methodName) { + assertThat(stackTrace).anySatisfy(element -> { + assertThat(element.getClassName()).endsWith(className); + assertThat(element.getMethodName()).isEqualTo(methodName); + }); + } +} From e3b7c539a621b71f2da449f2a7a912276c76ff1b Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Fri, 9 Sep 2022 16:24:58 +0200 Subject: [PATCH 48/59] Reinstate test --- .../engine/extension/SeparateThreadTimeoutInvocationTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/SeparateThreadTimeoutInvocationTest.java b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/SeparateThreadTimeoutInvocationTest.java index 5f8e867b3c86..a5355fa63682 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/SeparateThreadTimeoutInvocationTest.java +++ b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/SeparateThreadTimeoutInvocationTest.java @@ -47,7 +47,8 @@ void throwsTimeoutException() { assertThatThrownBy(invocation::proceed) // .hasMessage("method() timed out after " + PREEMPTIVE_TIMEOUT_MILLIS + " milliseconds") // - .isInstanceOf(TimeoutException.class); + .isInstanceOf(TimeoutException.class) // + .hasRootCauseMessage("Execution timed out in thread " + threadName.get()); } @Test From 8160378c5300b13cdd52096bef82041be2757291 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Fri, 9 Sep 2022 16:30:16 +0200 Subject: [PATCH 49/59] Add to release notes --- .../src/docs/asciidoc/release-notes/release-notes-5.9.1.adoc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.1.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.1.adoc index 517307ffe191..3c68595cc318 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.1.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.1.adoc @@ -40,6 +40,8 @@ on GitHub. references a factory method whose name is the same as other non-factory methods in the same class no longer fails with an exception stating that multiple factory methods with the same name were found. +* Assertion failures thrown from methods with applied timeouts using `ThreadMode.SEPARATE` + are now properly reported. ==== Deprecations and Breaking Changes From 352d06b3b27d5f1921dda1876c2dedb6f4f6b70f Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Sat, 10 Sep 2022 10:55:18 +0200 Subject: [PATCH 50/59] Attempt to stabilize test on Windows --- .../junit/jupiter/engine/extension/TimeoutExtensionTests.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutExtensionTests.java b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutExtensionTests.java index 442d34c83e0a..012074155dbe 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutExtensionTests.java +++ b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutExtensionTests.java @@ -352,7 +352,7 @@ void timeoutExceededInSeparateThread() { Throwable failure = execution.getTerminationInfo().getExecutionResult().getThrowable().orElseThrow(); assertThat(failure) // .isInstanceOf(TimeoutException.class) // - .hasMessage("testMethod() timed out after 10 milliseconds"); + .hasMessage("testMethod() timed out after 100 milliseconds"); assertThat(failure.getCause()) // .hasMessageStartingWith("Execution timed out in ") // .hasStackTraceContaining(TimeoutExceedingSeparateThreadTestCase.class.getName() + ".testMethod"); @@ -721,7 +721,7 @@ void testMethod() throws InterruptedException { static class TimeoutExceedingSeparateThreadTestCase { @Test - @Timeout(value = 10, unit = MILLISECONDS, threadMode = SEPARATE_THREAD) + @Timeout(value = 100, unit = MILLISECONDS, threadMode = SEPARATE_THREAD) void testMethod() throws InterruptedException { Thread.sleep(1000); } From 343170f314221ac8d91fea52617234058abfc39a Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Fri, 16 Sep 2022 13:39:12 +0200 Subject: [PATCH 51/59] Fix running tests in documentation from IntelliJ IDEA --- documentation/documentation.gradle.kts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/documentation/documentation.gradle.kts b/documentation/documentation.gradle.kts index a8a96bd98f1c..721a35e1b06d 100644 --- a/documentation/documentation.gradle.kts +++ b/documentation/documentation.gradle.kts @@ -48,6 +48,9 @@ dependencies { testRuntimeOnly(libs.apiguardian) { because("it's required to generate API tables") } + testRuntimeOnly(libs.openTestReporting.events) { + because("it's required to run tests via IntelliJ which does not consumed the shadowed jar of junit-platform-reporting") + } testImplementation(libs.classgraph) { because("ApiReportGenerator needs it") From 7229385d5edc7f2b78363f8bd0026a86c53bc44e Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Fri, 16 Sep 2022 18:40:57 +0200 Subject: [PATCH 52/59] Add failing integration test for execution on GraalVM native image --- .../platform-tooling-support-tests.gradle.kts | 3 + .../projects/graalvm-starter/build.gradle.kts | 29 ++++++++++ .../graalvm-starter/settings.gradle.kts | 11 ++++ .../java/com/example/project/Calculator.java | 19 ++++++ .../com/example/project/CalculatorTests.java | 41 +++++++++++++ .../projects/gradle-starter/build.gradle.kts | 8 --- .../support/tests/GraalVmStarterTests.java | 58 +++++++++++++++++++ 7 files changed, 161 insertions(+), 8 deletions(-) create mode 100644 platform-tooling-support-tests/projects/graalvm-starter/build.gradle.kts create mode 100644 platform-tooling-support-tests/projects/graalvm-starter/settings.gradle.kts create mode 100644 platform-tooling-support-tests/projects/graalvm-starter/src/main/java/com/example/project/Calculator.java create mode 100644 platform-tooling-support-tests/projects/graalvm-starter/src/test/java/com/example/project/CalculatorTests.java create mode 100644 platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GraalVmStarterTests.java diff --git a/platform-tooling-support-tests/platform-tooling-support-tests.gradle.kts b/platform-tooling-support-tests/platform-tooling-support-tests.gradle.kts index cb2a80ecaba8..8a27b7b936b0 100644 --- a/platform-tooling-support-tests/platform-tooling-support-tests.gradle.kts +++ b/platform-tooling-support-tests/platform-tooling-support-tests.gradle.kts @@ -109,6 +109,9 @@ tasks.test { distribution { requirements.add("jdk=8") + localOnly { + includeClasses.add("platform.tooling.support.tests.GraalVmStarterTests") // GraalVM is not installed on Test Distribution agents + } } jvmArgumentProviders += JavaHomeDir(project, 8) } diff --git a/platform-tooling-support-tests/projects/graalvm-starter/build.gradle.kts b/platform-tooling-support-tests/projects/graalvm-starter/build.gradle.kts new file mode 100644 index 000000000000..edd8bd7b7474 --- /dev/null +++ b/platform-tooling-support-tests/projects/graalvm-starter/build.gradle.kts @@ -0,0 +1,29 @@ +plugins { + java + id("org.graalvm.buildtools.native") +} + +val jupiterVersion: String = System.getenv("JUNIT_JUPITER_VERSION") +val platformVersion: String = System.getenv("JUNIT_PLATFORM_VERSION") + +repositories { + maven { url = uri(file(System.getProperty("maven.repo"))) } + mavenCentral() +} + +dependencies { + testImplementation("org.junit.jupiter:junit-jupiter:$jupiterVersion") + testRuntimeOnly("org.junit.platform:junit-platform-reporting:$platformVersion") +} + +tasks.test { + useJUnitPlatform() + + val outputDir = reports.junitXml.outputLocation + jvmArgumentProviders += CommandLineArgumentProvider { + listOf( + "-Djunit.platform.reporting.open.xml.enabled=true", + "-Djunit.platform.reporting.output.dir=${outputDir.get().asFile.absolutePath}" + ) + } +} diff --git a/platform-tooling-support-tests/projects/graalvm-starter/settings.gradle.kts b/platform-tooling-support-tests/projects/graalvm-starter/settings.gradle.kts new file mode 100644 index 000000000000..c928064e495a --- /dev/null +++ b/platform-tooling-support-tests/projects/graalvm-starter/settings.gradle.kts @@ -0,0 +1,11 @@ +pluginManagement { + plugins { + id("org.graalvm.buildtools.native") version "0.9.13" + } + repositories { + mavenCentral() + gradlePluginPortal() + } +} + +rootProject.name = "graalvm-starter" diff --git a/platform-tooling-support-tests/projects/graalvm-starter/src/main/java/com/example/project/Calculator.java b/platform-tooling-support-tests/projects/graalvm-starter/src/main/java/com/example/project/Calculator.java new file mode 100644 index 000000000000..b90710f2c68d --- /dev/null +++ b/platform-tooling-support-tests/projects/graalvm-starter/src/main/java/com/example/project/Calculator.java @@ -0,0 +1,19 @@ +/* + * Copyright 2015-2022 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 com.example.project; + +public class Calculator { + + public int add(int a, int b) { + return a + b; + } + +} diff --git a/platform-tooling-support-tests/projects/graalvm-starter/src/test/java/com/example/project/CalculatorTests.java b/platform-tooling-support-tests/projects/graalvm-starter/src/test/java/com/example/project/CalculatorTests.java new file mode 100644 index 000000000000..7f043f0dd3ea --- /dev/null +++ b/platform-tooling-support-tests/projects/graalvm-starter/src/test/java/com/example/project/CalculatorTests.java @@ -0,0 +1,41 @@ +/* + * Copyright 2015-2022 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 com.example.project; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +class CalculatorTests { + + @Test + @DisplayName("1 + 1 = 2") + void addsTwoNumbers() { + Calculator calculator = new Calculator(); + assertEquals(2, calculator.add(1, 1), "1 + 1 should equal 2"); + } + + @ParameterizedTest(name = "{0} + {1} = {2}") + @CsvSource({ + "0, 1, 1", + "1, 2, 3", + "49, 51, 100", + "1, 100, 101" + }) + void add(int first, int second, int expectedResult) { + Calculator calculator = new Calculator(); + assertEquals(expectedResult, calculator.add(first, second), + () -> first + " + " + second + " should equal " + expectedResult); + } +} diff --git a/platform-tooling-support-tests/projects/gradle-starter/build.gradle.kts b/platform-tooling-support-tests/projects/gradle-starter/build.gradle.kts index 33068798beb2..dfc4a31ad87e 100644 --- a/platform-tooling-support-tests/projects/gradle-starter/build.gradle.kts +++ b/platform-tooling-support-tests/projects/gradle-starter/build.gradle.kts @@ -7,14 +7,6 @@ val jupiterVersion: String = System.getenv("JUNIT_JUPITER_VERSION") val vintageVersion: String = System.getenv("JUNIT_VINTAGE_VERSION") val platformVersion: String = System.getenv("JUNIT_PLATFORM_VERSION") -// emit default file encoding to a file -file("file.encoding.txt").writeText(System.getProperty("file.encoding")) -file("junit.versions.txt").writeText(""" -jupiterVersion=$jupiterVersion -vintageVersion=$vintageVersion -platformVersion=$platformVersion -""") - repositories { maven { url = uri(file(System.getProperty("maven.repo"))) } mavenCentral() diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GraalVmStarterTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GraalVmStarterTests.java new file mode 100644 index 000000000000..0d3c8dd5bced --- /dev/null +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GraalVmStarterTests.java @@ -0,0 +1,58 @@ +/* + * Copyright 2015-2022 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 platform.tooling.support.tests; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assumptions.assumeFalse; +import static platform.tooling.support.Helper.TOOL_TIMEOUT; +import static platform.tooling.support.tests.XmlAssertions.verifyContainsExpectedStartedOpenTestReport; + +import java.nio.file.Paths; + +import de.sormuras.bartholdy.tool.GradleWrapper; + +import org.junit.jupiter.api.Test; + +import platform.tooling.support.MavenRepo; +import platform.tooling.support.Request; + +/** + * @since 1.9.1 + */ +class GraalVmStarterTests { + + @Test + void runsTestsInNativeImage() { + var request = Request.builder() // + .setTool(new GradleWrapper(Paths.get(".."))) // + .setProject("graalvm-starter") // + .addArguments("-Dmaven.repo=" + MavenRepo.dir()) // + .addArguments("javaToolchains", "nativeTest", "--no-daemon", "--stacktrace") // + .setTimeout(TOOL_TIMEOUT) // + .build(); + + var result = request.run(); + + assertFalse(result.isTimedOut(), () -> "tool timed out: " + result); + + assumeFalse( + result.getOutputLines("err").stream().anyMatch(line -> line.contains("No compatible toolchains found")), + "Abort test if GraalVM is not installed"); + + assertEquals(0, result.getExitCode()); + assertTrue(result.getOutputLines("out").stream().anyMatch(line -> line.contains("BUILD SUCCESSFUL"))); + + var testResultsDir = Request.WORKSPACE.resolve(request.getWorkspace()).resolve("build/test-results/test"); + verifyContainsExpectedStartedOpenTestReport(testResultsDir); + } +} From 79f47f51aa8880c78ceeb04e8c837b28d73a2b94 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Fri, 16 Sep 2022 18:41:16 +0200 Subject: [PATCH 53/59] Refactor OpenTestReportGeneratingListener to work in native images Fixes #3035. --- .../release-notes/release-notes-5.9.1.adoc | 2 ++ .../open/xml/OpenTestReportGeneratingListener.java | 14 +++++++------- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.1.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.1.adoc index 3c68595cc318..1fa43bc7dfcd 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.1.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.1.adoc @@ -16,6 +16,8 @@ on GitHub. ==== Bug Fixes * `ReflectionSupport.findMethods(...)` now returns a distinct set of methods. +* Execution in GraalVM native images no longer requires `--initialize-at-build-time` for + `OpenTestReportGeneratingListener`. ==== Deprecations and Breaking Changes diff --git a/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/OpenTestReportGeneratingListener.java b/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/OpenTestReportGeneratingListener.java index 73967cb55582..b826a63a5296 100644 --- a/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/OpenTestReportGeneratingListener.java +++ b/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/OpenTestReportGeneratingListener.java @@ -90,12 +90,6 @@ public class OpenTestReportGeneratingListener implements TestExecutionListener { static final String ENABLED_PROPERTY_NAME = "junit.platform.reporting.open.xml.enabled"; static final String OUTPUT_DIR_PROPERTY_NAME = "junit.platform.reporting.output.dir"; - private static final NamespaceRegistry NAMESPACE_REGISTRY = NamespaceRegistry.builder(Namespace.REPORTING_CORE) // - .add("e", Namespace.REPORTING_EVENTS) // - .add("java", Namespace.REPORTING_JAVA) // - .add("junit", JUnitFactory.NAMESPACE, "https://junit.org/junit5/schemas/open-test-reporting/junit-1.9.xsd") // - .build(); - private final AtomicInteger idCounter = new AtomicInteger(); private final Map inProgressIds = new ConcurrentHashMap<>(); private DocumentWriter eventsFileWriter = DocumentWriter.noop(); @@ -107,10 +101,16 @@ public OpenTestReportGeneratingListener() { public void testPlanExecutionStarted(TestPlan testPlan) { ConfigurationParameters config = testPlan.getConfigurationParameters(); if (isEnabled(config)) { + NamespaceRegistry namespaceRegistry = NamespaceRegistry.builder(Namespace.REPORTING_CORE) // + .add("e", Namespace.REPORTING_EVENTS) // + .add("java", Namespace.REPORTING_JAVA) // + .add("junit", JUnitFactory.NAMESPACE, + "https://junit.org/junit5/schemas/open-test-reporting/junit-1.9.xsd") // + .build(); Path eventsXml = OutputDir.create(config.get(OUTPUT_DIR_PROPERTY_NAME)) // .createFile("junit-platform-events", "xml"); try { - eventsFileWriter = Events.createDocumentWriter(NAMESPACE_REGISTRY, eventsXml); + eventsFileWriter = Events.createDocumentWriter(namespaceRegistry, eventsXml); reportInfrastructure(); } catch (Exception e) { From 2a809848e56c7d26b3dbd964a1d99e37ca61acc6 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Fri, 16 Sep 2022 18:43:43 +0200 Subject: [PATCH 54/59] Install GraalVM for main CI build on Linux --- .github/workflows/main.yml | 7 +++++++ .../projects/graalvm-starter/gradle.properties | 1 + 2 files changed, 8 insertions(+) create mode 100644 platform-tooling-support-tests/projects/graalvm-starter/gradle.properties diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 43754d118b7c..f7a7ef835937 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -27,6 +27,13 @@ jobs: run: | sudo apt-get update sudo apt-get install graphviz + - name: Install GraalVM + uses: graalvm/setup-graalvm@v1 + with: + version: 'latest' + java-version: '17' + components: 'native-image' + github-token: ${{ secrets.GITHUB_TOKEN }} - name: Build uses: ./.github/actions/main-build with: diff --git a/platform-tooling-support-tests/projects/graalvm-starter/gradle.properties b/platform-tooling-support-tests/projects/graalvm-starter/gradle.properties new file mode 100644 index 000000000000..83d0ce01114f --- /dev/null +++ b/platform-tooling-support-tests/projects/graalvm-starter/gradle.properties @@ -0,0 +1 @@ +org.gradle.java.installations.fromEnv=GRAALVM_HOME From 76719bb085c1e395824af8d941ed40b9ac359d1d Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Sat, 17 Sep 2022 13:27:02 +0200 Subject: [PATCH 55/59] Increase timeout for GraalVM test Timed out on CI --- .../platform/tooling/support/tests/GraalVmStarterTests.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GraalVmStarterTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GraalVmStarterTests.java index 0d3c8dd5bced..526393d13a47 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GraalVmStarterTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GraalVmStarterTests.java @@ -14,10 +14,10 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assumptions.assumeFalse; -import static platform.tooling.support.Helper.TOOL_TIMEOUT; import static platform.tooling.support.tests.XmlAssertions.verifyContainsExpectedStartedOpenTestReport; import java.nio.file.Paths; +import java.time.Duration; import de.sormuras.bartholdy.tool.GradleWrapper; @@ -38,7 +38,7 @@ void runsTestsInNativeImage() { .setProject("graalvm-starter") // .addArguments("-Dmaven.repo=" + MavenRepo.dir()) // .addArguments("javaToolchains", "nativeTest", "--no-daemon", "--stacktrace") // - .setTimeout(TOOL_TIMEOUT) // + .setTimeout(Duration.ofMinutes(5)) // .build(); var result = request.run(); From 9823f7329a97b4ca6d0922b1c62b6526d615f761 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Mon, 19 Sep 2022 18:15:28 +0200 Subject: [PATCH 56/59] Link to all 5.9 milestone pages ... because it otherwise looks like 5.9.0 only addressed 3 issues. --- .../docs/asciidoc/release-notes/release-notes-5.9.0.adoc | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.0.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.0.adoc index 06c77728fa2b..aa292b95a634 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.0.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.0.adoc @@ -17,8 +17,10 @@ format * Various improvements to `ConsoleLauncher` For a complete list of all _closed_ issues and pull requests for this release, consult the -link:{junit5-repo}+/milestone/62?closed=1+[5.9.0] milestone page in the JUnit repository on -GitHub. +link:{junit5-repo}+/milestone/58?closed=1+[5.9 M1], +link:{junit5-repo}+/milestone/61?closed=1+[5.9 RC1], and +link:{junit5-repo}+/milestone/62?closed=1+[5.9 GA] milestone pages in the JUnit repository +on GitHub. [[release-notes-5.9.0-junit-platform]] From d75e34d20f3b9c297b6c38a679888a676f0b92a3 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Mon, 19 Sep 2022 18:26:14 +0200 Subject: [PATCH 57/59] Update scope for 5.9.1 --- .../src/docs/asciidoc/release-notes/release-notes-5.9.1.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.1.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.1.adoc index 1fa43bc7dfcd..d5af9f07f5e9 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.1.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.1.adoc @@ -3,7 +3,7 @@ *Date of Release:* ❓ -*Scope:* Minor bug fixes since 5.9.0 +*Scope:* Minor enhancements and bug fixes since 5.9.0 For a complete list of all _closed_ issues and pull requests for this release, consult the link:{junit5-repo}+/milestone/63?closed=1+[5.9.1] milestone page in the JUnit repository From 88bf48d54534b90f74b64b7060f3d09205c9ff9a Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Tue, 20 Sep 2022 19:32:03 +0200 Subject: [PATCH 58/59] Prepare release notes for 5.9.1 --- .../release-notes/release-notes-5.9.1.adoc | 33 +++++-------------- 1 file changed, 8 insertions(+), 25 deletions(-) diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.1.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.1.adoc index d5af9f07f5e9..3c82815368e1 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.1.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.1.adoc @@ -1,9 +1,13 @@ [[release-notes-5.9.1]] == 5.9.1 -*Date of Release:* ❓ +*Date of Release:* September 20, 2022 -*Scope:* Minor enhancements and bug fixes since 5.9.0 +*Scope:* + +* New `@EnabledInNativeImage` and `@DisabledInNativeImage` annotations for testing in + GraalVM native images. +* Minor bug fixes and enhancements since 5.9.0 For a complete list of all _closed_ issues and pull requests for this release, consult the link:{junit5-repo}+/milestone/63?closed=1+[5.9.1] milestone page in the JUnit repository @@ -19,14 +23,6 @@ on GitHub. * Execution in GraalVM native images no longer requires `--initialize-at-build-time` for `OpenTestReportGeneratingListener`. -==== Deprecations and Breaking Changes - -* ❓ - -==== New Features and Improvements - -* ❓ - [[release-notes-5.9.1-junit-jupiter]] === JUnit Jupiter @@ -45,26 +41,13 @@ on GitHub. * Assertion failures thrown from methods with applied timeouts using `ThreadMode.SEPARATE` are now properly reported. -==== Deprecations and Breaking Changes - -* ❓ - ==== New Features and Improvements * New `@EnabledInNativeImage` and `@DisabledInNativeImage` annotations for enabling and disabling tests within a GraalVM native image. + [[release-notes-5.9.1-junit-vintage]] === JUnit Vintage -==== Bug Fixes - -* ❓ - -==== Deprecations and Breaking Changes - -* ❓ - -==== New Features and Improvements - -* ❓ +No changes. From 732a5400f80c8f446daa8b43eaa4b41b3da929be Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Tue, 20 Sep 2022 20:32:41 +0200 Subject: [PATCH 59/59] Release 5.9.1 --- README.md | 2 +- gradle.properties | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index aa04df17a909..ecf74ef5e4d5 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ This repository is the home of the next generation of JUnit, _JUnit 5_. ## Latest Releases -- General Availability (GA): [JUnit 5.9.0](https://github.com/junit-team/junit5/releases/tag/r5.9.0) (July 26, 2022) +- General Availability (GA): [JUnit 5.9.1](https://github.com/junit-team/junit5/releases/tag/r5.9.1) (September 20, 2022) - Preview (Milestone/Release Candidate): n/a ## Documentation diff --git a/gradle.properties b/gradle.properties index e79f19f1ca6e..6ada11024510 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,13 +1,13 @@ group = org.junit -version = 5.9.1-SNAPSHOT +version = 5.9.1 jupiterGroup = org.junit.jupiter platformGroup = org.junit.platform -platformVersion = 1.9.1-SNAPSHOT +platformVersion = 1.9.1 vintageGroup = org.junit.vintage -vintageVersion = 5.9.1-SNAPSHOT +vintageVersion = 5.9.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