@@ -93,6 +93,12 @@ class Autoloader
9393 */
9494 protected $ helpers = ['url ' ];
9595
96+ /**
97+ * Track whether Composer namespaces have been loaded to prevent
98+ * re-loading in parallel test execution contexts.
99+ */
100+ private bool $ composerNamespacesLoaded = false ;
101+
96102 public function __construct (private readonly string $ composerPath = COMPOSER_PATH )
97103 {
98104 }
@@ -109,6 +115,8 @@ public function initialize(Autoload $config, Modules $modules)
109115 $ this ->classmap = [];
110116 $ this ->files = [];
111117
118+ $ this ->composerNamespacesLoaded = false ;
119+
112120 // We have to have one or the other, though we don't enforce the need
113121 // to have both present in order to work.
114122 if ($ config ->psr4 === [] && $ config ->classmap === []) {
@@ -150,9 +158,11 @@ private function loadComposerAutoloader(Modules $modules): void
150158 $ composer = include $ this ->composerPath ;
151159
152160 // Should we load through Composer's namespaces, also?
153- if ($ modules ->discoverInComposer ) {
161+ // Guard against repeated loading in parallel execution environments
162+ if ($ modules ->discoverInComposer && ! $ this ->composerNamespacesLoaded ) {
154163 $ composerPackages = $ modules ->composerPackages ;
155164 $ this ->loadComposerNamespaces ($ composer , $ composerPackages ?? []);
165+ $ this ->composerNamespacesLoaded = true ;
156166 }
157167
158168 unset($ composer );
@@ -371,6 +381,24 @@ public function sanitizeFilename(string $filename): string
371381 return $ cleanFilename ;
372382 }
373383
384+ /**
385+ * Convert a path to absolute path if it isn't already.
386+ * Prevents issues with relative paths in parallel execution.
387+ */
388+ private function makePathAbsolute (string $ path ): string
389+ {
390+ if (str_starts_with ($ path , '/ ' ) || (strlen ($ path ) > 1 && $ path [1 ] === ': ' )) {
391+ return $ path ; // Already absolute
392+ }
393+
394+ // Make relative to vendor directory
395+ if (defined ('VENDORPATH ' )) {
396+ return VENDORPATH . ltrim ($ path , '/ \\' );
397+ }
398+
399+ return $ path ;
400+ }
401+
374402 /**
375403 * @param array{only?: list<string>, exclude?: list<string>} $composerPackages
376404 */
@@ -441,7 +469,8 @@ private function loadComposerNamespaces(ClassLoader $composer, array $composerPa
441469
442470 if ($ add ) {
443471 // Composer stores namespaces with trailing slash. We don't.
444- $ newPaths [rtrim ($ namespace , '\\ ' )] = $ srcPaths ;
472+ // Ensure all paths are absolute to prevent issues in parallel execution
473+ $ newPaths [rtrim ($ namespace , '\\ ' )] = array_map ($ this ->makePathAbsolute (...), $ srcPaths );
445474 }
446475 }
447476
0 commit comments