diff --git a/independent-projects/junit5-virtual-threads/pom.xml b/independent-projects/junit5-virtual-threads/pom.xml index 875e37d03ee88f..c999a9bebfb93e 100644 --- a/independent-projects/junit5-virtual-threads/pom.xml +++ b/independent-projects/junit5-virtual-threads/pom.xml @@ -46,6 +46,7 @@ 1.11.0 5.10.3 + 1.10.3 3.26.3 @@ -57,6 +58,11 @@ compile ${junit.jupiter.version} + + org.junit.platform + junit-platform-testkit + ${junit.testkit.version} + org.assertj assertj-core @@ -135,6 +141,9 @@ -Djava.io.tmpdir="${project.build.directory}" MAVEN_OPTS + + io.quarkus.test.junit5.virtual.internal.ignore.**Test.java + diff --git a/independent-projects/junit5-virtual-threads/src/main/java/io/quarkus/test/junit5/virtual/ShouldNotPin.java b/independent-projects/junit5-virtual-threads/src/main/java/io/quarkus/test/junit5/virtual/ShouldNotPin.java index b6ec800d78d984..eb2f3a2596b80a 100644 --- a/independent-projects/junit5-virtual-threads/src/main/java/io/quarkus/test/junit5/virtual/ShouldNotPin.java +++ b/independent-projects/junit5-virtual-threads/src/main/java/io/quarkus/test/junit5/virtual/ShouldNotPin.java @@ -1,6 +1,7 @@ package io.quarkus.test.junit5.virtual; import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @@ -12,6 +13,7 @@ */ @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.METHOD, ElementType.TYPE }) +@Inherited public @interface ShouldNotPin { /** diff --git a/independent-projects/junit5-virtual-threads/src/main/java/io/quarkus/test/junit5/virtual/ShouldPin.java b/independent-projects/junit5-virtual-threads/src/main/java/io/quarkus/test/junit5/virtual/ShouldPin.java index d98f5166a8c3a6..84b2800fb95f33 100644 --- a/independent-projects/junit5-virtual-threads/src/main/java/io/quarkus/test/junit5/virtual/ShouldPin.java +++ b/independent-projects/junit5-virtual-threads/src/main/java/io/quarkus/test/junit5/virtual/ShouldPin.java @@ -1,6 +1,7 @@ package io.quarkus.test.junit5.virtual; import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @@ -12,6 +13,7 @@ */ @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.METHOD, ElementType.TYPE }) +@Inherited public @interface ShouldPin { int atMost() default Integer.MAX_VALUE; diff --git a/independent-projects/junit5-virtual-threads/src/test/java/io/quarkus/test/junit5/virtual/internal/JUnitEngine.java b/independent-projects/junit5-virtual-threads/src/test/java/io/quarkus/test/junit5/virtual/internal/JUnitEngine.java new file mode 100644 index 00000000000000..1da2015711a4bb --- /dev/null +++ b/independent-projects/junit5-virtual-threads/src/test/java/io/quarkus/test/junit5/virtual/internal/JUnitEngine.java @@ -0,0 +1,34 @@ +package io.quarkus.test.junit5.virtual.internal; + +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; +import static org.junit.platform.testkit.engine.EventConditions.*; + +import org.assertj.core.api.Condition; +import org.junit.platform.testkit.engine.EngineTestKit; +import org.junit.platform.testkit.engine.Events; + +public class JUnitEngine { + + public static void runTestAndAssertFailure(Class clazz, String methodName, String message) { + runTest(clazz, methodName).assertThatEvents() + .haveExactly(1, event(test(methodName), + finishedWithFailure(new Condition<>( + throwable -> throwable instanceof AssertionError && throwable.getMessage().contains(message), + "")))); + } + + public static void runTestAndAssertSuccess(Class clazz, String methodName) { + runTest(clazz, methodName).assertThatEvents() + .haveExactly(1, event(test(methodName), + finishedSuccessfully())); + } + + public static Events runTest(Class clazz, String methodName) { + return EngineTestKit + .engine("junit-jupiter") + .selectors(selectMethod(clazz, methodName)) + .execute() + .testEvents(); + } + +} diff --git a/independent-projects/junit5-virtual-threads/src/test/java/io/quarkus/test/junit5/virtual/internal/LoomUnitExampleOnClassTest.java b/independent-projects/junit5-virtual-threads/src/test/java/io/quarkus/test/junit5/virtual/internal/LoomUnitExampleOnClassTest.java deleted file mode 100644 index 470343dd01067f..00000000000000 --- a/independent-projects/junit5-virtual-threads/src/test/java/io/quarkus/test/junit5/virtual/internal/LoomUnitExampleOnClassTest.java +++ /dev/null @@ -1,33 +0,0 @@ -package io.quarkus.test.junit5.virtual.internal; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledForJreRange; -import org.junit.jupiter.api.condition.JRE; - -import io.quarkus.test.junit5.virtual.ShouldNotPin; -import io.quarkus.test.junit5.virtual.ShouldPin; -import io.quarkus.test.junit5.virtual.VirtualThreadUnit; - -@VirtualThreadUnit -@ShouldNotPin // You can use @ShouldNotPin or @ShouldPin on the class itself, it's applied to each method. -public class LoomUnitExampleOnClassTest { - - @Test - public void testThatShouldNotPin() { - // ... - } - - @Test - @ShouldPin(atMost = 1) // Method annotation overrides the class annotation - @EnabledForJreRange(min = JRE.JAVA_21) - public void testThatShouldPinAtMostOnce() { - TestPinJfrEvent.pin(); - } - - @Test - @ShouldNotPin(atMost = 1) // Method annotation overrides the class annotation - public void testThatShouldNotPinAtMostOnce() { - TestPinJfrEvent.pin(); - } - -} diff --git a/independent-projects/junit5-virtual-threads/src/test/java/io/quarkus/test/junit5/virtual/internal/ShouldNotPinTest.java b/independent-projects/junit5-virtual-threads/src/test/java/io/quarkus/test/junit5/virtual/internal/ShouldNotPinTest.java new file mode 100644 index 00000000000000..3105b2d3eefeb9 --- /dev/null +++ b/independent-projects/junit5-virtual-threads/src/test/java/io/quarkus/test/junit5/virtual/internal/ShouldNotPinTest.java @@ -0,0 +1,46 @@ +package io.quarkus.test.junit5.virtual.internal; + +import static io.quarkus.test.junit5.virtual.internal.JUnitEngine.runTestAndAssertFailure; +import static io.quarkus.test.junit5.virtual.internal.JUnitEngine.runTestAndAssertSuccess; +import static org.junit.jupiter.params.provider.Arguments.arguments; + +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledForJreRange; +import org.junit.jupiter.api.condition.JRE; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import io.quarkus.test.junit5.virtual.internal.ignore.LoomUnitExampleOnMethodTest; +import io.quarkus.test.junit5.virtual.internal.ignore.LoomUnitExampleShouldNotPinOnClassTest; +import io.quarkus.test.junit5.virtual.internal.ignore.LoomUnitExampleShouldNotPinOnSuperClassTest; +import io.quarkus.test.junit5.virtual.internal.ignore.LoomUnitExampleShouldPinOnSuperClassTest; + +public class ShouldNotPinTest { + + @ParameterizedTest + @MethodSource + @EnabledForJreRange(min = JRE.JAVA_21) + void testShouldNotPinButPinEventDetected(Class clazz, String methodName) { + runTestAndAssertFailure(clazz, methodName, "was expected to NOT pin the carrier thread"); + } + + public static Stream testShouldNotPinButPinEventDetected() { + return Stream.of( + arguments(LoomUnitExampleOnMethodTest.class, "failWhenShouldNotPinAndPinDetected"), + arguments(LoomUnitExampleOnMethodTest.class, "failWhenShouldNotPinAtMostAndTooManyPinDetected"), + arguments(LoomUnitExampleShouldNotPinOnClassTest.class, "failWhenShouldNotPinAndPinDetected"), + arguments(LoomUnitExampleShouldNotPinOnSuperClassTest.class, "failWhenShouldNotPinAndPinDetected"), + arguments(LoomUnitExampleShouldPinOnSuperClassTest.class, "failWhenShouldNotPinAndPinDetected")); + } + + @Test + @EnabledForJreRange(min = JRE.JAVA_21) + void shouldNotPinOnMethodOverridesClassAnnotation() { + runTestAndAssertSuccess(LoomUnitExampleShouldNotPinOnClassTest.class, "overrideClassAnnotation"); + runTestAndAssertSuccess(LoomUnitExampleShouldNotPinOnSuperClassTest.class, "overrideClassAnnotation"); + } + +} diff --git a/independent-projects/junit5-virtual-threads/src/test/java/io/quarkus/test/junit5/virtual/internal/ShouldPinTest.java b/independent-projects/junit5-virtual-threads/src/test/java/io/quarkus/test/junit5/virtual/internal/ShouldPinTest.java new file mode 100644 index 00000000000000..346c5ea5aedb47 --- /dev/null +++ b/independent-projects/junit5-virtual-threads/src/test/java/io/quarkus/test/junit5/virtual/internal/ShouldPinTest.java @@ -0,0 +1,36 @@ +package io.quarkus.test.junit5.virtual.internal; + +import static io.quarkus.test.junit5.virtual.internal.JUnitEngine.runTestAndAssertFailure; +import static org.junit.jupiter.params.provider.Arguments.arguments; + +import java.util.stream.Stream; + +import org.junit.jupiter.api.condition.EnabledForJreRange; +import org.junit.jupiter.api.condition.JRE; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import io.quarkus.test.junit5.virtual.internal.ignore.LoomUnitExampleOnMethodTest; +import io.quarkus.test.junit5.virtual.internal.ignore.LoomUnitExampleShouldNotPinOnClassTest; +import io.quarkus.test.junit5.virtual.internal.ignore.LoomUnitExampleShouldNotPinOnSuperClassTest; +import io.quarkus.test.junit5.virtual.internal.ignore.LoomUnitExampleShouldPinOnSuperClassTest; + +public class ShouldPinTest { + + @ParameterizedTest + @MethodSource + @EnabledForJreRange(min = JRE.JAVA_21) + void testShouldPinButNoPinEventDetected(Class clazz, String methodName) { + runTestAndAssertFailure(clazz, methodName, "was expected to pin the carrier thread, it didn't"); + } + + public static Stream testShouldPinButNoPinEventDetected() { + return Stream.of( + arguments(LoomUnitExampleOnMethodTest.class, "failWhenMethodShouldPinButNoPinDetected"), + arguments(LoomUnitExampleShouldNotPinOnClassTest.class, "failWhenMethodShouldPinButNoPinDetected"), + arguments(LoomUnitExampleShouldNotPinOnSuperClassTest.class, "failWhenShouldPinAndNoPinDetected"), + arguments(LoomUnitExampleShouldPinOnSuperClassTest.class, "failWhenShouldPinAndNoPinDetected")); + } + +} diff --git a/independent-projects/junit5-virtual-threads/src/test/java/io/quarkus/test/junit5/virtual/internal/TestPinJfrEvent.java b/independent-projects/junit5-virtual-threads/src/test/java/io/quarkus/test/junit5/virtual/internal/TestPinJfrEvent.java index 1c56fb259ae207..27ed1bc43e7578 100644 --- a/independent-projects/junit5-virtual-threads/src/test/java/io/quarkus/test/junit5/virtual/internal/TestPinJfrEvent.java +++ b/independent-projects/junit5-virtual-threads/src/test/java/io/quarkus/test/junit5/virtual/internal/TestPinJfrEvent.java @@ -17,7 +17,7 @@ public TestPinJfrEvent(String message) { this.message = message; } - static void pin() { + public static void pin() { TestPinJfrEvent event = new TestPinJfrEvent("Hello, JFR!"); event.commit(); } diff --git a/independent-projects/junit5-virtual-threads/src/test/java/io/quarkus/test/junit5/virtual/internal/ignore/LoomUnitExampleOnMethodTest.java b/independent-projects/junit5-virtual-threads/src/test/java/io/quarkus/test/junit5/virtual/internal/ignore/LoomUnitExampleOnMethodTest.java new file mode 100644 index 00000000000000..84623f4723aa86 --- /dev/null +++ b/independent-projects/junit5-virtual-threads/src/test/java/io/quarkus/test/junit5/virtual/internal/ignore/LoomUnitExampleOnMethodTest.java @@ -0,0 +1,31 @@ +package io.quarkus.test.junit5.virtual.internal.ignore; + +import org.junit.jupiter.api.Test; + +import io.quarkus.test.junit5.virtual.ShouldNotPin; +import io.quarkus.test.junit5.virtual.ShouldPin; +import io.quarkus.test.junit5.virtual.VirtualThreadUnit; +import io.quarkus.test.junit5.virtual.internal.TestPinJfrEvent; + +@VirtualThreadUnit +public class LoomUnitExampleOnMethodTest { + + @Test + @ShouldNotPin + void failWhenShouldNotPinAndPinDetected() { + TestPinJfrEvent.pin(); + } + + @Test + @ShouldNotPin(atMost = 1) + void failWhenShouldNotPinAtMostAndTooManyPinDetected() { + TestPinJfrEvent.pin(); + TestPinJfrEvent.pin(); + } + + @Test + @ShouldPin + void failWhenMethodShouldPinButNoPinDetected() { + } + +} diff --git a/independent-projects/junit5-virtual-threads/src/test/java/io/quarkus/test/junit5/virtual/internal/ignore/LoomUnitExampleShouldNotPinOnClassTest.java b/independent-projects/junit5-virtual-threads/src/test/java/io/quarkus/test/junit5/virtual/internal/ignore/LoomUnitExampleShouldNotPinOnClassTest.java new file mode 100644 index 00000000000000..5fef87cd94d099 --- /dev/null +++ b/independent-projects/junit5-virtual-threads/src/test/java/io/quarkus/test/junit5/virtual/internal/ignore/LoomUnitExampleShouldNotPinOnClassTest.java @@ -0,0 +1,31 @@ +package io.quarkus.test.junit5.virtual.internal.ignore; + +import org.junit.jupiter.api.Test; + +import io.quarkus.test.junit5.virtual.ShouldNotPin; +import io.quarkus.test.junit5.virtual.ShouldPin; +import io.quarkus.test.junit5.virtual.VirtualThreadUnit; +import io.quarkus.test.junit5.virtual.internal.TestPinJfrEvent; + +@VirtualThreadUnit +@ShouldNotPin // You can use @ShouldNotPin or @ShouldPin on the class itself, it's applied to each method. +public class LoomUnitExampleShouldNotPinOnClassTest { + + @Test + public void failWhenShouldNotPinAndPinDetected() { + TestPinJfrEvent.pin(); + } + + @Test + @ShouldPin(atMost = 1) + public void overrideClassAnnotation() { + TestPinJfrEvent.pin(); + } + + @Test + @ShouldPin + public void failWhenMethodShouldPinButNoPinDetected() { + + } + +} diff --git a/independent-projects/junit5-virtual-threads/src/test/java/io/quarkus/test/junit5/virtual/internal/ignore/LoomUnitExampleShouldNotPinOnSuperClass.java b/independent-projects/junit5-virtual-threads/src/test/java/io/quarkus/test/junit5/virtual/internal/ignore/LoomUnitExampleShouldNotPinOnSuperClass.java new file mode 100644 index 00000000000000..4741e9e31cd5b6 --- /dev/null +++ b/independent-projects/junit5-virtual-threads/src/test/java/io/quarkus/test/junit5/virtual/internal/ignore/LoomUnitExampleShouldNotPinOnSuperClass.java @@ -0,0 +1,9 @@ +package io.quarkus.test.junit5.virtual.internal.ignore; + +import io.quarkus.test.junit5.virtual.ShouldNotPin; +import io.quarkus.test.junit5.virtual.VirtualThreadUnit; + +@VirtualThreadUnit +@ShouldNotPin // You can use @ShouldNotPin or @ShouldPin on the super class itself, it's applied to each method. +public abstract class LoomUnitExampleShouldNotPinOnSuperClass { +} diff --git a/independent-projects/junit5-virtual-threads/src/test/java/io/quarkus/test/junit5/virtual/internal/ignore/LoomUnitExampleShouldNotPinOnSuperClassTest.java b/independent-projects/junit5-virtual-threads/src/test/java/io/quarkus/test/junit5/virtual/internal/ignore/LoomUnitExampleShouldNotPinOnSuperClassTest.java new file mode 100644 index 00000000000000..25b2cc82e521fa --- /dev/null +++ b/independent-projects/junit5-virtual-threads/src/test/java/io/quarkus/test/junit5/virtual/internal/ignore/LoomUnitExampleShouldNotPinOnSuperClassTest.java @@ -0,0 +1,27 @@ +package io.quarkus.test.junit5.virtual.internal.ignore; + +import org.junit.jupiter.api.Test; + +import io.quarkus.test.junit5.virtual.ShouldPin; +import io.quarkus.test.junit5.virtual.internal.TestPinJfrEvent; + +public class LoomUnitExampleShouldNotPinOnSuperClassTest extends LoomUnitExampleShouldNotPinOnSuperClass { + + @Test + public void failWhenShouldNotPinAndPinDetected() { + TestPinJfrEvent.pin(); + } + + @Test + @ShouldPin(atMost = 1) + public void overrideClassAnnotation() { + TestPinJfrEvent.pin(); + } + + @Test + @ShouldPin // Method annotation overrides the class annotation + public void failWhenShouldPinAndNoPinDetected() { + + } + +} diff --git a/independent-projects/junit5-virtual-threads/src/test/java/io/quarkus/test/junit5/virtual/internal/ignore/LoomUnitExampleShouldPinOnSuperClass.java b/independent-projects/junit5-virtual-threads/src/test/java/io/quarkus/test/junit5/virtual/internal/ignore/LoomUnitExampleShouldPinOnSuperClass.java new file mode 100644 index 00000000000000..4cc571619671a5 --- /dev/null +++ b/independent-projects/junit5-virtual-threads/src/test/java/io/quarkus/test/junit5/virtual/internal/ignore/LoomUnitExampleShouldPinOnSuperClass.java @@ -0,0 +1,9 @@ +package io.quarkus.test.junit5.virtual.internal.ignore; + +import io.quarkus.test.junit5.virtual.ShouldPin; +import io.quarkus.test.junit5.virtual.VirtualThreadUnit; + +@VirtualThreadUnit +@ShouldPin // You can use @ShouldNotPin or @ShouldPin on the super class itself, it's applied to each method. +public abstract class LoomUnitExampleShouldPinOnSuperClass { +} diff --git a/independent-projects/junit5-virtual-threads/src/test/java/io/quarkus/test/junit5/virtual/internal/ignore/LoomUnitExampleShouldPinOnSuperClassTest.java b/independent-projects/junit5-virtual-threads/src/test/java/io/quarkus/test/junit5/virtual/internal/ignore/LoomUnitExampleShouldPinOnSuperClassTest.java new file mode 100644 index 00000000000000..322b024b1a88d4 --- /dev/null +++ b/independent-projects/junit5-virtual-threads/src/test/java/io/quarkus/test/junit5/virtual/internal/ignore/LoomUnitExampleShouldPinOnSuperClassTest.java @@ -0,0 +1,21 @@ +package io.quarkus.test.junit5.virtual.internal.ignore; + +import org.junit.jupiter.api.Test; + +import io.quarkus.test.junit5.virtual.ShouldNotPin; +import io.quarkus.test.junit5.virtual.internal.TestPinJfrEvent; + +public class LoomUnitExampleShouldPinOnSuperClassTest extends LoomUnitExampleShouldPinOnSuperClass { + + @Test + @ShouldNotPin // Method annotation overrides the class annotation + public void failWhenShouldNotPinAndPinDetected() { + TestPinJfrEvent.pin(); + } + + @Test + public void failWhenShouldPinAndNoPinDetected() { + + } + +}