Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cover @QuarkusTestResource & @QuarkusMainTest in QuarkusTestProfileAwareClassOrderer, skip @Nested, activate globally #21610

Merged
merged 3 commits into from
Nov 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,22 +1,30 @@
package io.quarkus.test.junit.util;

import java.util.Arrays;
import java.util.Comparator;
import java.util.Optional;

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;
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).
* <p/>
* 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).<br/>
* 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).
* <p/>
* 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
Expand All @@ -30,31 +38,39 @@
* <p/>
* Limitations:
* <ul>
* <li>This orderer does not (yet) consider {@linkplain io.quarkus.test.common.QuarkusTestResource#restrictToAnnotatedClass()
* test resources that are restricted to the annotated class}.</li>
* <li>Only JUnit5 test classes are subject to ordering, e.g. ArchUnit test classes are not passed to this orderer.</li>
* <li>This orderer does not handle {@link Nested} test classes.</li>
* </ul>
*/
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
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.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 -> {
Expand All @@ -64,16 +80,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}.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
junit.jupiter.testclass.order.default=io.quarkus.test.junit.util.QuarkusTestProfileAwareClassOrderer
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
Expand All @@ -45,39 +47,48 @@ 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<ClassDescriptor> input = Arrays.asList(
nonQuarkusTest7Desc,
quarkusTestWithRestrictedResourceDesc,
nonQuarkusTest2Desc,
quarkusTestWithProfile2Test5Desc,
quarkusTest2Desc,
nonQuarkusTest6Desc,
nonQuarkusTest1Desc,
quarkusTestWithMetaResourceDesc,
quarkusTest1Desc,
quarkusTestWithProfile2Test4Desc,
quarkusTestWithUnrestrictedResourceDesc,
quarkusTestWithProfile1Desc);
doReturn(input).when(contextMock).getClassDescriptors();

underTest.orderClasses(contextMock);

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<ClassDescriptor> input = Arrays.asList(quarkusTestDesc, nonQuarkusTestDesc);
doReturn(input).when(contextMock).getClassDescriptors();

Expand All @@ -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<ClassDescriptor> input = Arrays.asList(quarkusTest1Desc, quarkusTest2Desc);
doReturn(input).when(contextMock).getClassDescriptors();

Expand Down Expand Up @@ -125,30 +136,61 @@ private ClassDescriptor quarkusDescriptorMock(Class<?> testClass, Class<? extend
return mock;
}

private static class Test1 {
};
private ClassDescriptor quarkusDescriptorMock(Class<?> testClass,
Class<? extends QuarkusTestResourceLifecycleManager> 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 {
}
}