From 904b48edd74ded69fd46038325bb99e81fb3796a Mon Sep 17 00:00:00 2001 From: Falko Modler Date: Sun, 21 Nov 2021 23:00:31 +0100 Subject: [PATCH 1/3] Cover restricted `@QuarkusTestResource` and also `@QuarkusMainTest` in `QuarkusTestProfileAwareClassOrderer` Resolves #20420 --- .../QuarkusTestProfileAwareClassOrderer.java | 50 +++++++-- ...arkusTestProfileAwareClassOrdererTest.java | 102 ++++++++++++------ 2 files changed, 112 insertions(+), 40 deletions(-) diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/util/QuarkusTestProfileAwareClassOrderer.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/util/QuarkusTestProfileAwareClassOrderer.java index 0b98384513a01..2e1d1450ad78b 100644 --- a/test-framework/junit5/src/main/java/io/quarkus/test/junit/util/QuarkusTestProfileAwareClassOrderer.java +++ b/test-framework/junit5/src/main/java/io/quarkus/test/junit/util/QuarkusTestProfileAwareClassOrderer.java @@ -1,5 +1,6 @@ package io.quarkus.test.junit.util; +import java.util.Arrays; import java.util.Comparator; import java.util.Optional; @@ -7,16 +8,22 @@ import org.junit.jupiter.api.ClassOrderer; import org.junit.jupiter.api.ClassOrdererContext; +import io.quarkus.test.common.QuarkusTestResource; import io.quarkus.test.junit.QuarkusIntegrationTest; import io.quarkus.test.junit.QuarkusTest; import io.quarkus.test.junit.TestProfile; +import io.quarkus.test.junit.main.QuarkusMainTest; /** - * A {@link ClassOrderer} that orders {@link QuarkusTest} and {@link QuarkusIntegrationTest} classes for minimum Quarkus - * restarts by grouping them by their {@link TestProfile}. + * A {@link ClassOrderer} that orders {@link QuarkusTest}, {@link QuarkusIntegrationTest} and {@link QuarkusMainTest} classes + * for minimum Quarkus + * restarts by grouping them by their {@link TestProfile} and {@link QuarkusTestResource} annotation(s). *

* By default, Quarkus*Tests not using any profile come first, then classes using a profile (in groups) and then all other - * non-Quarkus tests (e.g. plain unit tests). + * non-Quarkus tests (e.g. plain unit tests).
+ * Quarkus*Tests with {@linkplain QuarkusTestResource#restrictToAnnotatedClass() restricted} {@code QuarkusTestResource} come + * after tests with profiles and Quarkus*Tests with only unrestricted resources are handled like tests without a profile (come + * first). *

* Internally, ordering is based on three prefixes that are prepended to the fully qualified name of the respective class, with * the fully qualified class name of the {@link io.quarkus.test.junit.QuarkusTestProfile QuarkusTestProfile} as an infix (if @@ -30,8 +37,6 @@ *

* Limitations: *

*/ @@ -39,10 +44,12 @@ public class QuarkusTestProfileAwareClassOrderer implements ClassOrderer { protected static final String DEFAULT_ORDER_PREFIX_QUARKUS_TEST = "20_"; protected static final String DEFAULT_ORDER_PREFIX_QUARKUS_TEST_WITH_PROFILE = "40_"; + protected static final String DEFAULT_ORDER_PREFIX_QUARKUS_TEST_WITH_RESTRICTED_RES = "45_"; protected static final String DEFAULT_ORDER_PREFIX_NON_QUARKUS_TEST = "60_"; static final String CFGKEY_ORDER_PREFIX_QUARKUS_TEST = "quarkus.test.orderer.prefix.quarkus-test"; static final String CFGKEY_ORDER_PREFIX_QUARKUS_TEST_WITH_PROFILE = "quarkus.test.orderer.prefix.quarkus-test-with-profile"; + static final String CFGKEY_ORDER_PREFIX_QUARKUS_TEST_WITH_RESTRICTED_RES = "quarkus.test.orderer.prefix.quarkus-test-with-restricted-resource"; static final String CFGKEY_ORDER_PREFIX_NON_QUARKUS_TEST = "quarkus.test.orderer.prefix.non-quarkus-test"; @Override @@ -50,11 +57,17 @@ public void orderClasses(ClassOrdererContext context) { if (context.getClassDescriptors().size() <= 1) { return; } - var prefixQuarkusTest = context.getConfigurationParameter(CFGKEY_ORDER_PREFIX_QUARKUS_TEST) + var prefixQuarkusTest = context + .getConfigurationParameter(CFGKEY_ORDER_PREFIX_QUARKUS_TEST) .orElse(DEFAULT_ORDER_PREFIX_QUARKUS_TEST); - var prefixQuarkusTestWithProfile = context.getConfigurationParameter(CFGKEY_ORDER_PREFIX_QUARKUS_TEST_WITH_PROFILE) + var prefixQuarkusTestWithProfile = context + .getConfigurationParameter(CFGKEY_ORDER_PREFIX_QUARKUS_TEST_WITH_PROFILE) .orElse(DEFAULT_ORDER_PREFIX_QUARKUS_TEST_WITH_PROFILE); - var prefixNonQuarkusTest = context.getConfigurationParameter(CFGKEY_ORDER_PREFIX_NON_QUARKUS_TEST) + var prefixQuarkusTestWithRestrictedResource = context + .getConfigurationParameter(CFGKEY_ORDER_PREFIX_QUARKUS_TEST_WITH_RESTRICTED_RES) + .orElse(DEFAULT_ORDER_PREFIX_QUARKUS_TEST_WITH_RESTRICTED_RES); + var prefixNonQuarkusTest = context + .getConfigurationParameter(CFGKEY_ORDER_PREFIX_NON_QUARKUS_TEST) .orElse(DEFAULT_ORDER_PREFIX_NON_QUARKUS_TEST); context.getClassDescriptors().sort(Comparator.comparing(classDescriptor -> { @@ -64,16 +77,33 @@ public void orderClasses(ClassOrdererContext context) { } var testClassName = classDescriptor.getTestClass().getName(); if (classDescriptor.isAnnotated(QuarkusTest.class) - || classDescriptor.isAnnotated(QuarkusIntegrationTest.class)) { + || classDescriptor.isAnnotated(QuarkusIntegrationTest.class) + || classDescriptor.isAnnotated(QuarkusMainTest.class)) { return classDescriptor.findAnnotation(TestProfile.class) .map(TestProfile::value) .map(profileClass -> prefixQuarkusTestWithProfile + profileClass.getName() + "@" + testClassName) - .orElseGet(() -> prefixQuarkusTest + testClassName); + .orElseGet(() -> { + var prefix = hasRestrictedResource(classDescriptor) + ? prefixQuarkusTestWithRestrictedResource + : prefixQuarkusTest; + return prefix + testClassName; + }); } return prefixNonQuarkusTest + testClassName; })); } + private boolean hasRestrictedResource(ClassDescriptor classDescriptor) { + return classDescriptor.findRepeatableAnnotations(QuarkusTestResource.class).stream() + .anyMatch(res -> res.restrictToAnnotatedClass() || isMetaTestResource(res, classDescriptor)); + } + + private boolean isMetaTestResource(QuarkusTestResource resource, ClassDescriptor classDescriptor) { + return Arrays.stream(classDescriptor.getTestClass().getAnnotationsByType(QuarkusTestResource.class)) + .map(QuarkusTestResource::value) + .noneMatch(resource.value()::equals); + } + /** * Template method that provides an optional custom order key for the given {@code classDescriptor}. * diff --git a/test-framework/junit5/src/test/java/io/quarkus/test/junit/util/QuarkusTestProfileAwareClassOrdererTest.java b/test-framework/junit5/src/test/java/io/quarkus/test/junit/util/QuarkusTestProfileAwareClassOrdererTest.java index bdd49032f378e..25b974da082e8 100644 --- a/test-framework/junit5/src/test/java/io/quarkus/test/junit/util/QuarkusTestProfileAwareClassOrdererTest.java +++ b/test-framework/junit5/src/test/java/io/quarkus/test/junit/util/QuarkusTestProfileAwareClassOrdererTest.java @@ -21,6 +21,8 @@ import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; +import io.quarkus.test.common.QuarkusTestResource; +import io.quarkus.test.common.QuarkusTestResourceLifecycleManager; import io.quarkus.test.junit.QuarkusTest; import io.quarkus.test.junit.QuarkusTestProfile; import io.quarkus.test.junit.TestProfile; @@ -35,7 +37,7 @@ class QuarkusTestProfileAwareClassOrdererTest { @Test void singleClass() { - doReturn(Arrays.asList(descriptorMock(Test1.class))) + doReturn(Arrays.asList(descriptorMock(Test01.class))) .when(contextMock).getClassDescriptors(); underTest.orderClasses(contextMock); @@ -45,20 +47,26 @@ void singleClass() { @Test void allVariants() { - ClassDescriptor quarkusTest1Desc = quarkusDescriptorMock(Test1.class, null); - ClassDescriptor quarkusTest2Desc = quarkusDescriptorMock(Test2.class, null); - ClassDescriptor quarkusTestWithProfile1Desc = quarkusDescriptorMock(Test3.class, Profile1.class); - ClassDescriptor quarkusTestWithProfile2Test4Desc = quarkusDescriptorMock(Test4.class, Profile2.class); - ClassDescriptor quarkusTestWithProfile2Test5Desc = quarkusDescriptorMock(Test5.class, Profile2.class); - ClassDescriptor nonQuarkusTest6Desc = descriptorMock(Test6.class); - ClassDescriptor nonQuarkusTest7Desc = descriptorMock(Test7.class); + ClassDescriptor quarkusTest1Desc = quarkusDescriptorMock(Test01.class, null); + ClassDescriptor quarkusTestWithUnrestrictedResourceDesc = quarkusDescriptorMock(Test02.class, Manager3.class, false); + ClassDescriptor quarkusTest2Desc = quarkusDescriptorMock(Test03.class, null); + ClassDescriptor quarkusTestWithProfile1Desc = quarkusDescriptorMock(Test04.class, Profile1.class); + ClassDescriptor quarkusTestWithProfile2Test4Desc = quarkusDescriptorMock(Test05.class, Profile2.class); + ClassDescriptor quarkusTestWithProfile2Test5Desc = quarkusDescriptorMock(Test06.class, Profile2.class); + ClassDescriptor quarkusTestWithRestrictedResourceDesc = quarkusDescriptorMock(Test07.class, Manager2.class, true); + ClassDescriptor quarkusTestWithMetaResourceDesc = quarkusDescriptorMock(Test08.class, Manager1.class, false); + ClassDescriptor nonQuarkusTest1Desc = descriptorMock(Test09.class); + ClassDescriptor nonQuarkusTest2Desc = descriptorMock(Test10.class); List input = Arrays.asList( - nonQuarkusTest7Desc, + quarkusTestWithRestrictedResourceDesc, + nonQuarkusTest2Desc, quarkusTestWithProfile2Test5Desc, quarkusTest2Desc, - nonQuarkusTest6Desc, + nonQuarkusTest1Desc, + quarkusTestWithMetaResourceDesc, quarkusTest1Desc, quarkusTestWithProfile2Test4Desc, + quarkusTestWithUnrestrictedResourceDesc, quarkusTestWithProfile1Desc); doReturn(input).when(contextMock).getClassDescriptors(); @@ -66,18 +74,21 @@ void allVariants() { assertThat(input).containsExactly( quarkusTest1Desc, + quarkusTestWithUnrestrictedResourceDesc, quarkusTest2Desc, quarkusTestWithProfile1Desc, quarkusTestWithProfile2Test4Desc, quarkusTestWithProfile2Test5Desc, - nonQuarkusTest6Desc, - nonQuarkusTest7Desc); + quarkusTestWithRestrictedResourceDesc, + quarkusTestWithMetaResourceDesc, + nonQuarkusTest1Desc, + nonQuarkusTest2Desc); } @Test void configuredPrefix() { - ClassDescriptor quarkusTestDesc = quarkusDescriptorMock(Test1.class, null); - ClassDescriptor nonQuarkusTestDesc = descriptorMock(Test2.class); + ClassDescriptor quarkusTestDesc = quarkusDescriptorMock(Test01.class, null); + ClassDescriptor nonQuarkusTestDesc = descriptorMock(Test03.class); List input = Arrays.asList(quarkusTestDesc, nonQuarkusTestDesc); doReturn(input).when(contextMock).getClassDescriptors(); @@ -92,8 +103,8 @@ void configuredPrefix() { @Test void customOrderKey() { - ClassDescriptor quarkusTest1Desc = quarkusDescriptorMock(Test1.class, null); - ClassDescriptor quarkusTest2Desc = quarkusDescriptorMock(Test2.class, null); + ClassDescriptor quarkusTest1Desc = quarkusDescriptorMock(Test01.class, null); + ClassDescriptor quarkusTest2Desc = quarkusDescriptorMock(Test03.class, null); List input = Arrays.asList(quarkusTest1Desc, quarkusTest2Desc); doReturn(input).when(contextMock).getClassDescriptors(); @@ -125,30 +136,61 @@ private ClassDescriptor quarkusDescriptorMock(Class testClass, Class testClass, + Class managerClass, boolean restrictToAnnotatedClass) { + ClassDescriptor mock = descriptorMock(testClass); + when(mock.isAnnotated(QuarkusTest.class)).thenReturn(true); + QuarkusTestResource resourceMock = Mockito.mock(QuarkusTestResource.class, withSettings().lenient()); + doReturn(managerClass).when(resourceMock).value(); + when(resourceMock.restrictToAnnotatedClass()).thenReturn(restrictToAnnotatedClass); + when(mock.findRepeatableAnnotations(QuarkusTestResource.class)).thenReturn(List.of(resourceMock)); + return mock; + } + + private static class Test01 { + } + + // this single made-up test class needs an actual annotation since the orderer will have do the meta-check directly because ClassDescriptor does not offer any details whether an annotation is directly annotated or meta-annotated + @QuarkusTestResource(Manager3.class) + private static class Test02 { + } + + private static class Test03 { + } + + private static class Test04 { + } - private static class Test2 { - }; + private static class Test05 { + } - private static class Test3 { - }; + private static class Test06 { + } - private static class Test4 { - }; + private static class Test07 { + } - private static class Test5 { - }; + private static class Test08 { + } - private static class Test6 { - }; + private static class Test09 { + } - private static class Test7 { - }; + private static class Test10 { + } private static class Profile1 implements QuarkusTestProfile { } private static class Profile2 implements QuarkusTestProfile { } + + private static interface Manager1 extends QuarkusTestResourceLifecycleManager { + } + + private static interface Manager2 extends QuarkusTestResourceLifecycleManager { + } + + private static interface Manager3 extends QuarkusTestResourceLifecycleManager { + } } From c3c59f6e4e3ef497c20dd662b5cc387032fdc410 Mon Sep 17 00:00:00 2001 From: Falko Modler Date: Sun, 21 Nov 2021 23:13:21 +0100 Subject: [PATCH 2/3] Do not order `@Nested` tests in `QuarkusTestProfileAwareClassOrderer` --- .../test/junit/util/QuarkusTestProfileAwareClassOrderer.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/util/QuarkusTestProfileAwareClassOrderer.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/util/QuarkusTestProfileAwareClassOrderer.java index 2e1d1450ad78b..9ec802da34003 100644 --- a/test-framework/junit5/src/main/java/io/quarkus/test/junit/util/QuarkusTestProfileAwareClassOrderer.java +++ b/test-framework/junit5/src/main/java/io/quarkus/test/junit/util/QuarkusTestProfileAwareClassOrderer.java @@ -7,6 +7,7 @@ import org.junit.jupiter.api.ClassDescriptor; import org.junit.jupiter.api.ClassOrderer; import org.junit.jupiter.api.ClassOrdererContext; +import org.junit.jupiter.api.Nested; import io.quarkus.test.common.QuarkusTestResource; import io.quarkus.test.junit.QuarkusIntegrationTest; @@ -38,6 +39,7 @@ * Limitations: *
    *
  • Only JUnit5 test classes are subject to ordering, e.g. ArchUnit test classes are not passed to this orderer.
  • + *
  • This orderer does not handle {@link Nested} test classes.
  • *
*/ public class QuarkusTestProfileAwareClassOrderer implements ClassOrderer { @@ -54,7 +56,8 @@ public class QuarkusTestProfileAwareClassOrderer implements ClassOrderer { @Override public void orderClasses(ClassOrdererContext context) { - if (context.getClassDescriptors().size() <= 1) { + // don't do anything if there is just one test class or the current order request is for @Nested tests + if (context.getClassDescriptors().size() <= 1 || context.getClassDescriptors().get(0).isAnnotated(Nested.class)) { return; } var prefixQuarkusTest = context From 5f1fd225f7394370b879a4c9db08935fab7ed3d2 Mon Sep 17 00:00:00 2001 From: Falko Modler Date: Mon, 22 Nov 2021 00:15:49 +0100 Subject: [PATCH 3/3] Activate `QuarkusTestProfileAwareClassOrderer` by default --- .../junit5/src/main/resources/junit-platform.properties | 1 + 1 file changed, 1 insertion(+) create mode 100644 test-framework/junit5/src/main/resources/junit-platform.properties diff --git a/test-framework/junit5/src/main/resources/junit-platform.properties b/test-framework/junit5/src/main/resources/junit-platform.properties new file mode 100644 index 0000000000000..0823c6b870646 --- /dev/null +++ b/test-framework/junit5/src/main/resources/junit-platform.properties @@ -0,0 +1 @@ +junit.jupiter.testclass.order.default=io.quarkus.test.junit.util.QuarkusTestProfileAwareClassOrderer \ No newline at end of file