44import org .junit .platform .launcher .LauncherDiscoveryRequest ;
55import org .junit .platform .launcher .LauncherSession ;
66import org .junit .platform .launcher .LauncherSessionListener ;
7+ import org .junit .platform .launcher .TestExecutionListener ;
8+ import org .junit .platform .launcher .TestPlan ;
79
810import io .quarkus .test .junit .classloading .FacadeClassLoader ;
911
10- public class CustomLauncherInterceptor implements LauncherDiscoveryListener , LauncherSessionListener {
12+ public class CustomLauncherInterceptor
13+ implements LauncherDiscoveryListener , LauncherSessionListener , TestExecutionListener {
1114
1215 private static FacadeClassLoader facadeLoader = null ;
1316 // Also use a static variable to store a 'first' starting state that we can reset to
@@ -93,7 +96,6 @@ public void launcherDiscoveryStarted(LauncherDiscoveryRequest request) {
9396 System .setProperty ("java.util.concurrent.ForkJoinPool.common.threadFactory" ,
9497 "io.quarkus.bootstrap.forkjoin.QuarkusForkJoinWorkerThreadFactory" );
9598 }
96-
9799 }
98100
99101 private void adjustContextClassLoader () {
@@ -108,7 +110,6 @@ private void adjustContextClassLoader() {
108110
109111 @ Override
110112 public void launcherDiscoveryFinished (LauncherDiscoveryRequest request ) {
111-
112113 if (!isProductionModeTests ()) {
113114 // We need to support two somewhat incompatible scenarios.
114115 // If there are user extensions present which implement `ExecutionCondition`, and they call config in `evaluateExecutionCondition`,
@@ -129,7 +130,10 @@ public void launcherDiscoveryFinished(LauncherDiscoveryRequest request) {
129130
130131 @ Override
131132 public void launcherSessionClosed (LauncherSession session ) {
133+ clearContextClassloader ();
134+ }
132135
136+ private static void clearContextClassloader () {
133137 try {
134138 // Tidy up classloaders we created, but not ones created upstream
135139 // Also make sure to reset the TCCL so we don't leave a closed classloader on the thread
@@ -150,4 +154,37 @@ public void launcherSessionClosed(LauncherSession session) {
150154 throw new RuntimeException ("Failed to close custom classloader" , e );
151155 }
152156 }
157+
158+ /**
159+ * Called when the execution of the {@link TestPlan} has started,
160+ * <em>before</em> any test has been executed.
161+ *
162+ * <p>
163+ * Called from the same thread as {@link #testPlanExecutionFinished(TestPlan)}.
164+ *
165+ * In continuous testing, the test plan listener seems not to be called, perhaps because of a different execution model.
166+ *
167+ * @param testPlan describes the tree of tests about to be executed
168+ */
169+ public void testPlanExecutionStarted (TestPlan testPlan ) {
170+ // Do nothing, but have the method here for symmetry :)
171+ }
172+
173+ /**
174+ * Called when the execution of the {@link TestPlan} has finished,
175+ * <em>after</em> all tests have been executed.
176+ *
177+ * <p>
178+ * Called from the same thread as {@link #testPlanExecutionStarted(TestPlan)}.
179+ *
180+ * If tests failed and are rerun by surefire, the same session will be used for all runs, so we need to get rid of the
181+ * FacadeClassLoader associated with the previous run, since its app will be closed and its classloaders will all be stale.
182+ *
183+ * In continuous testing, the test plan listener seems not to be called, perhaps because of a different execution model.
184+ *
185+ * @param testPlan describes the tree of tests that have been executed
186+ */
187+ public void testPlanExecutionFinished (TestPlan testPlan ) {
188+ clearContextClassloader ();
189+ }
153190}
0 commit comments