8
8
import java .io .InputStream ;
9
9
import java .lang .reflect .Constructor ;
10
10
import java .lang .reflect .InvocationTargetException ;
11
+ import java .lang .reflect .Method ;
11
12
import java .lang .reflect .Modifier ;
12
13
import java .nio .file .Files ;
13
14
import java .nio .file .Path ;
@@ -89,6 +90,9 @@ public class JunitTestRunner {
89
90
public static final DotName QUARKUS_TEST = DotName .createSimple ("io.quarkus.test.junit.QuarkusTest" );
90
91
public static final DotName QUARKUS_MAIN_TEST = DotName .createSimple ("io.quarkus.test.junit.main.QuarkusMainTest" );
91
92
public static final DotName QUARKUS_INTEGRATION_TEST = DotName .createSimple ("io.quarkus.test.junit.QuarkusIntegrationTest" );
93
+ public static final DotName QUARKUS_COMPONENT_TEST = DotName .createSimple ("io.quarkus.test.component.QuarkusComponentTest" );
94
+ public static final DotName QUARKUS_COMPONENT_TEST_EXTENSION = DotName
95
+ .createSimple ("io.quarkus.test.component.QuarkusComponentTestExtension" );
92
96
public static final DotName TEST_PROFILE = DotName .createSimple ("io.quarkus.test.junit.TestProfile" );
93
97
public static final DotName TEST = DotName .createSimple (Test .class .getName ());
94
98
public static final DotName REPEATED_TEST = DotName .createSimple (RepeatedTest .class .getName ());
@@ -99,6 +103,7 @@ public class JunitTestRunner {
99
103
public static final DotName NESTED = DotName .createSimple (Nested .class .getName ());
100
104
private static final String ARCHUNIT_FIELDSOURCE_FQCN = "com.tngtech.archunit.junit.FieldSource" ;
101
105
private static final String FACADE_CLASS_LOADER_NAME = "io.quarkus.test.junit.classloading.FacadeClassLoader" ;
106
+ private static final String TEST_DISCOVERY_PROPERTY = "quarkus.continuous-tests-discovery" ;
102
107
103
108
private final long runId ;
104
109
private final DevModeContext .ModuleInfo moduleInfo ;
@@ -568,6 +573,9 @@ private DiscoveryResult discoverTestClasses() {
568
573
//for now this is out of scope, we are just going to do annotation based discovery
569
574
//we will need to fix this sooner rather than later though
570
575
576
+ // Set the system property that is used for QuarkusComponentTest
577
+ System .setProperty (TEST_DISCOVERY_PROPERTY , "true" );
578
+
571
579
if (moduleInfo .getTest ().isEmpty ()) {
572
580
return DiscoveryResult .EMPTY ;
573
581
}
@@ -613,6 +621,22 @@ private DiscoveryResult discoverTestClasses() {
613
621
}
614
622
}
615
623
624
+ Set <String > quarkusComponentTestClasses = new HashSet <>();
625
+ for (AnnotationInstance a : index .getAnnotations (QUARKUS_COMPONENT_TEST )) {
626
+ DotName name = a .target ().asClass ().name ();
627
+ quarkusComponentTestClasses .add (name .toString ());
628
+ for (ClassInfo subclass : index .getAllKnownSubclasses (name )) {
629
+ quarkusComponentTestClasses .add (subclass .name ().toString ());
630
+ }
631
+ }
632
+ for (ClassInfo clazz : index .getKnownUsers (QUARKUS_COMPONENT_TEST_EXTENSION )) {
633
+ DotName name = clazz .name ();
634
+ quarkusComponentTestClasses .add (name .toString ());
635
+ for (ClassInfo subclass : index .getAllKnownSubclasses (name )) {
636
+ quarkusComponentTestClasses .add (subclass .name ().toString ());
637
+ }
638
+ }
639
+
616
640
// The FacadeClassLoader approach of loading test classes with the classloader we will use to run them can only work for `@QuarkusTest` and not main or integration tests
617
641
// Most logic in the JUnitRunner counts main tests as quarkus tests, so do a (mildly irritating) special pass to get the ones which are strictly @QuarkusTest
618
642
@@ -676,7 +700,8 @@ private DiscoveryResult discoverTestClasses() {
676
700
for (DotName testClass : allTestClasses ) {
677
701
String name = testClass .toString ();
678
702
if (integrationTestClasses .contains (name )
679
- || quarkusTestClasses .contains (name )) {
703
+ || quarkusTestClasses .contains (name )
704
+ || quarkusComponentTestClasses .contains (name )) {
680
705
continue ;
681
706
}
682
707
var enclosing = enclosingClasses .get (testClass );
@@ -705,11 +730,14 @@ private DiscoveryResult discoverTestClasses() {
705
730
// if we didn't find any test classes, let's return early
706
731
// Make sure you also update the logic for the non-empty case above if you adjust this part
707
732
if (testType == TestType .ALL ) {
708
- if (unitTestClasses .isEmpty () && quarkusTestClasses .isEmpty ()) {
733
+ if (unitTestClasses .isEmpty ()
734
+ && quarkusTestClasses .isEmpty ()
735
+ && quarkusComponentTestClasses .isEmpty ()) {
709
736
return DiscoveryResult .EMPTY ;
710
737
}
711
738
} else if (testType == TestType .UNIT ) {
712
- if (unitTestClasses .isEmpty ()) {
739
+ if (unitTestClasses .isEmpty ()
740
+ && quarkusComponentTestClasses .isEmpty ()) {
713
741
return DiscoveryResult .EMPTY ;
714
742
}
715
743
} else if (quarkusTestClasses .isEmpty ()) {
@@ -784,8 +812,9 @@ public String apply(Class<?> aClass) {
784
812
return testProfile .value ().asClass ().name ().toString () + "$$" + aClass .getName ();
785
813
}
786
814
}));
815
+
787
816
QuarkusClassLoader cl = null ;
788
- if (!unitTestClasses .isEmpty ()) {
817
+ if (!unitTestClasses .isEmpty () || ! quarkusComponentTestClasses . isEmpty () ) {
789
818
//we need to work the unit test magic
790
819
//this is a lot more complex
791
820
//we need to transform the classes to make the tracing magic work
@@ -810,6 +839,7 @@ public String apply(Class<?> aClass) {
810
839
cl = testApplication .createDeploymentClassLoader ();
811
840
deploymentClassLoader = cl ;
812
841
cl .reset (Collections .emptyMap (), transformedClasses );
842
+
813
843
for (String i : unitTestClasses ) {
814
844
try {
815
845
utClasses .add (cl .loadClass (i ));
@@ -820,6 +850,30 @@ public String apply(Class<?> aClass) {
820
850
}
821
851
}
822
852
853
+ if (!quarkusComponentTestClasses .isEmpty ()) {
854
+ try {
855
+ // We use the deployment class loader to load the test class
856
+ Class <?> qcfcClazz = cl .loadClass ("io.quarkus.test.component.QuarkusComponentFacadeClassLoaderProvider" );
857
+ Constructor <?> c = qcfcClazz .getConstructor (Class .class , Set .class );
858
+ Method getClassLoader = qcfcClazz .getMethod ("getClassLoader" , String .class , ClassLoader .class );
859
+ for (String componentTestClass : quarkusComponentTestClasses ) {
860
+ try {
861
+ Class <?> testClass = cl .loadClass (componentTestClass );
862
+ Object ecl = c .newInstance (testClass , classesToTransform );
863
+ ClassLoader excl = (ClassLoader ) getClassLoader .invoke (ecl , componentTestClass , cl );
864
+ utClasses .add (excl .loadClass (componentTestClass ));
865
+ } catch (Exception e ) {
866
+ log .debug (e );
867
+ log .warnf ("Failed to load component test class %s, it will not be executed this run." ,
868
+ componentTestClass );
869
+ }
870
+ }
871
+ } catch (ClassNotFoundException | IllegalArgumentException
872
+ | SecurityException | NoSuchMethodException e ) {
873
+ log .warn (
874
+ "Failed to load QuarkusComponentFacadeClassLoaderProvider, component test classes will not be executed this run." );
875
+ }
876
+ }
823
877
}
824
878
825
879
if (classLoaderToClose != null ) {
@@ -832,6 +886,9 @@ public String apply(Class<?> aClass) {
832
886
}
833
887
}
834
888
889
+ // Unset the system property that is used for QuarkusComponentTest
890
+ System .clearProperty (TEST_DISCOVERY_PROPERTY );
891
+
835
892
// Make sure you also update the logic for the empty case above if you adjust this part
836
893
if (testType == TestType .ALL ) {
837
894
//run unit style tests first
0 commit comments