From 1f4686eb71886e656f4a983faa779954aeef53e6 Mon Sep 17 00:00:00 2001 From: Matt Glaman Date: Wed, 5 Aug 2026 08:33:26 -0500 Subject: [PATCH 1/2] Fix module sort comparator and drop Drupal 8/9 dead code in autoloader The usort callback never returned a negative value, so test-module ordering was implementation-defined. Replace it with a valid comparator that sorts _test extensions last. Remove the PhpUnit8 ClassWriter shim (removed in Drupal 10) and the Drush 8 directory-depth branch (composer requires drush ^11 || ^12 || ^13). Co-Authored-By: Claude Fable 5 --- src/Drupal/DrupalAutoloader.php | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/src/Drupal/DrupalAutoloader.php b/src/Drupal/DrupalAutoloader.php index 0c6f2efc..9acc7a93 100644 --- a/src/Drupal/DrupalAutoloader.php +++ b/src/Drupal/DrupalAutoloader.php @@ -6,11 +6,9 @@ use Drupal\Component\DependencyInjection\Container as DrupalContainer; use Drupal\Core\DependencyInjection\ContainerNotInitializedException; use Drupal\Core\DrupalKernelInterface; -use Drupal\TestTools\PhpUnitCompatibility\PhpUnit8\ClassWriter; use DrupalFinder\DrupalFinderComposerRuntime; use Drush\Drush; use PHPStan\DependencyInjection\Container; -use PHPUnit\Framework\Test; use ReflectionClass; use RuntimeException; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -24,7 +22,6 @@ use function dirname; use function file_exists; use function in_array; -use function interface_exists; use function is_array; use function is_dir; use function is_string; @@ -124,8 +121,12 @@ public function register(Container $container): void $extensionDiscovery->setProfileDirectories($profile_directories); $this->moduleData = array_merge($extensionDiscovery->scan('module'), $profiles); - usort($this->moduleData, static function (Extension $a, Extension $b) { - return strpos($a->getName(), '_test') !== false ? 10 : 0; + // Sort test extensions after regular ones so that their namespaces + // and services do not take precedence during registration. + usort($this->moduleData, static function (Extension $a, Extension $b): int { + $aIsTest = strpos($a->getName(), '_test') !== false ? 1 : 0; + $bIsTest = strpos($b->getName(), '_test') !== false ? 1 : 0; + return $aIsTest <=> $bIsTest; }); $this->themeData = $extensionDiscovery->scan('theme'); $this->addCoreTestNamespaces(); @@ -193,11 +194,7 @@ public function register(Container $container): void if (class_exists(Drush::class)) { $reflect = new ReflectionClass(Drush::class); if ($reflect->getFileName() !== false) { - $levels = 2; - if (Drush::getMajorVersion() < 9) { - $levels = 3; - } - $drushDir = dirname($reflect->getFileName(), $levels); + $drushDir = dirname($reflect->getFileName(), 2); foreach (Finder::create()->files()->name('*.inc')->in($drushDir . '/includes') as $file) { require_once $file->getPathname(); } @@ -259,11 +256,6 @@ class: Drupal\jsonapi\Routing\JsonApiParamEnhancer $service_map = $container->getByType(ServiceMap::class); $service_map->setDrupalServices($this->serviceMap); - if (interface_exists(Test::class) - && class_exists('Drupal\TestTools\PhpUnitCompatibility\PhpUnit8\ClassWriter')) { - ClassWriter::mutateTestBase($this->autoloader); - } - $extension_map = $container->getByType(ExtensionMap::class); $extension_map->setExtensions($this->moduleData, $this->themeData, $profiles); } From a26d82c601dd87ad1b54c84ccf55948688f1bb46 Mon Sep 17 00:00:00 2001 From: Matt Glaman Date: Tue, 8 Sep 2026 13:58:01 -0500 Subject: [PATCH 2/2] State the real reason for sorting test extensions last and simplify the comparator The sort exists so test modules' .module files load after their parent module's. Test modules stub parent functions behind function_exists() guards, and loading them first turns the parent's declaration into a compile error the bootstrap cannot catch. Namespaces are keyed by module name, so order never affected them. Co-Authored-By: Claude Fable 5.1 --- src/Drupal/DrupalAutoloader.php | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Drupal/DrupalAutoloader.php b/src/Drupal/DrupalAutoloader.php index 9acc7a93..0bf28fd6 100644 --- a/src/Drupal/DrupalAutoloader.php +++ b/src/Drupal/DrupalAutoloader.php @@ -25,8 +25,8 @@ use function is_array; use function is_dir; use function is_string; +use function str_contains; use function str_replace; -use function strpos; use function strtr; use function trigger_error; use function ucwords; @@ -121,12 +121,13 @@ public function register(Container $container): void $extensionDiscovery->setProfileDirectories($profile_directories); $this->moduleData = array_merge($extensionDiscovery->scan('module'), $profiles); - // Sort test extensions after regular ones so that their namespaces - // and services do not take precedence during registration. + // Load test extensions after regular ones. Test modules stub functions + // from their parent module behind function_exists() guards, so if the + // test module's .module file loads first the parent's unconditional + // declaration is a compile error that loadAndCatchErrors() cannot + // intercept. usort($this->moduleData, static function (Extension $a, Extension $b): int { - $aIsTest = strpos($a->getName(), '_test') !== false ? 1 : 0; - $bIsTest = strpos($b->getName(), '_test') !== false ? 1 : 0; - return $aIsTest <=> $bIsTest; + return str_contains($a->getName(), '_test') <=> str_contains($b->getName(), '_test'); }); $this->themeData = $extensionDiscovery->scan('theme'); $this->addCoreTestNamespaces();