44import org .jboss .arquillian .test .spi .TestRunnerAdaptorBuilder ;
55import org .junit .jupiter .api .extension .ExtensionContext ;
66
7- import static org .jboss .arquillian .junit5 .ContextStore .getContextStore ;
8-
97/**
10- * Manages the lifecycle of JUnit Jupiter test classes within the Arquillian framework.
11- * This class implements both {@link AutoCloseable} and {@link ExtensionContext.Store.CloseableResource}
12- * for junit5 prior to 5.13.0 to ensure proper resource cleanup.
8+ * Owns the {@link TestRunnerAdaptor} for a single JUnit Jupiter test class.
139 *
14- * <p>The manager is responsible for:</p>
15- * <ul>
16- * <li>Initializing and managing the Arquillian{@link TestRunnerAdaptor}</li>
17- * <li>Handling test suite lifecycle events (beforeSuite, afterSuite)</li>
18- * <li>Providing access to the test runner adaptor for test execution</li>
19- * <li>Managing initialization failures and error handling</li>
20- * </ul>
10+ * <p>The manager is cached in the root store under a {@link ExtensionContext.Namespace}
11+ * keyed by the test class, so each class gets its own {@code TestRunnerAdaptor}
12+ * and underlying {@code Manager}. This matters under parallel class execution:
13+ * a shared {@code Manager} would leak thread-local context activations between
14+ * classes running on different threads.</p>
2115 *
22- * <p>This class uses a singleton pattern per test context, ensuring that only one
23- * manager instance exists per test suite execution.</p>
16+ * <p>Implementing {@link ExtensionContext.Store.CloseableResource} lets JUnit
17+ * call {@link #close()} (and therefore {@code afterSuite}) when it tears down
18+ * the root context at the end of the run.</p>
2419 */
2520
2621public class JUnitJupiterTestClassLifecycleManager implements AutoCloseable ,
@@ -35,14 +30,23 @@ private JUnitJupiterTestClassLifecycleManager() {
3530 }
3631
3732 static JUnitJupiterTestClassLifecycleManager getManager (ExtensionContext context ) throws Exception {
38- ExtensionContext .Store store = getContextStore (context ).getRootStore ();
39- JUnitJupiterTestClassLifecycleManager instance = store .get (MANAGER_KEY , JUnitJupiterTestClassLifecycleManager .class );
40- if (instance == null ) {
41- instance = new JUnitJupiterTestClassLifecycleManager ();
42- store .put (MANAGER_KEY , instance );
43- instance .initializeAdaptor ();
44- }
45- // no, initialization has been attempted before and failed, refuse
33+ ExtensionContext .Store store = context .getRoot ().getStore (
34+ ExtensionContext .Namespace .create (
35+ JUnitJupiterTestClassLifecycleManager .class ,
36+ context .getRequiredTestClass ()));
37+ JUnitJupiterTestClassLifecycleManager instance = store .getOrComputeIfAbsent (
38+ MANAGER_KEY ,
39+ key -> {
40+ JUnitJupiterTestClassLifecycleManager mgr = new JUnitJupiterTestClassLifecycleManager ();
41+ try {
42+ mgr .initializeAdaptor ();
43+ } catch (Exception e ) {
44+ mgr .caughtInitializationException = e ;
45+ }
46+ return mgr ;
47+ },
48+ JUnitJupiterTestClassLifecycleManager .class );
49+ // initialization has been attempted before and failed, refuse
4650 // to do anything else
4751 if (instance .hasInitializationException ()) {
4852 instance .handleSuiteLevelFailure ();
0 commit comments