1<?php 2 3/** 4 * @file 5 * Autoloader for Drupal PHPUnit testing. 6 * 7 * @see phpunit.xml.dist 8 */ 9 10use Drupal\Component\Assertion\Handle; 11use PHPUnit\Framework\AssertionFailedError; 12use PHPUnit\Framework\Constraint\Count; 13use PHPUnit\Framework\Error\Error; 14use PHPUnit\Framework\Error\Warning; 15use PHPUnit\Framework\ExpectationFailedException; 16use PHPUnit\Framework\Exception; 17use PHPUnit\Framework\MockObject\Matcher\InvokedRecorder; 18use PHPUnit\Framework\SkippedTestError; 19use PHPUnit\Framework\TestCase; 20use PHPUnit\Util\Test; 21use PHPUnit\Util\Xml; 22 23/** 24 * Finds all valid extension directories recursively within a given directory. 25 * 26 * @param string $scan_directory 27 * The directory that should be recursively scanned. 28 * 29 * @return array 30 * An associative array of extension directories found within the scanned 31 * directory, keyed by extension name. 32 */ 33function drupal_phpunit_find_extension_directories($scan_directory) { 34 $extensions = []; 35 $dirs = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($scan_directory, \RecursiveDirectoryIterator::FOLLOW_SYMLINKS)); 36 foreach ($dirs as $dir) { 37 if (strpos($dir->getPathname(), '.info.yml') !== FALSE) { 38 // Cut off ".info.yml" from the filename for use as the extension name. We 39 // use getRealPath() so that we can scan extensions represented by 40 // directory aliases. 41 $extensions[substr($dir->getFilename(), 0, -9)] = $dir->getPathInfo() 42 ->getRealPath(); 43 } 44 } 45 return $extensions; 46} 47 48/** 49 * Returns directories under which contributed extensions may exist. 50 * 51 * @param string $root 52 * (optional) Path to the root of the Drupal installation. 53 * 54 * @return array 55 * An array of directories under which contributed extensions may exist. 56 */ 57function drupal_phpunit_contrib_extension_directory_roots($root = NULL) { 58 if ($root === NULL) { 59 $root = dirname(dirname(__DIR__)); 60 } 61 $paths = [ 62 $root . '/core/modules', 63 $root . '/core/profiles', 64 $root . '/modules', 65 $root . '/profiles', 66 $root . '/themes', 67 ]; 68 $sites_path = $root . '/sites'; 69 // Note this also checks sites/../modules and sites/../profiles. 70 foreach (scandir($sites_path) as $site) { 71 if ($site[0] === '.' || $site === 'simpletest') { 72 continue; 73 } 74 $path = "$sites_path/$site"; 75 $paths[] = is_dir("$path/modules") ? realpath("$path/modules") : NULL; 76 $paths[] = is_dir("$path/profiles") ? realpath("$path/profiles") : NULL; 77 $paths[] = is_dir("$path/themes") ? realpath("$path/themes") : NULL; 78 } 79 return array_filter($paths, 'file_exists'); 80} 81 82/** 83 * Registers the namespace for each extension directory with the autoloader. 84 * 85 * @param array $dirs 86 * An associative array of extension directories, keyed by extension name. 87 * 88 * @return array 89 * An associative array of extension directories, keyed by their namespace. 90 */ 91function drupal_phpunit_get_extension_namespaces($dirs) { 92 $suite_names = ['Unit', 'Kernel', 'Functional', 'Build', 'FunctionalJavascript']; 93 $namespaces = []; 94 foreach ($dirs as $extension => $dir) { 95 if (is_dir($dir . '/src')) { 96 // Register the PSR-4 directory for module-provided classes. 97 $namespaces['Drupal\\' . $extension . '\\'][] = $dir . '/src'; 98 } 99 $test_dir = $dir . '/tests/src'; 100 if (is_dir($test_dir)) { 101 foreach ($suite_names as $suite_name) { 102 $suite_dir = $test_dir . '/' . $suite_name; 103 if (is_dir($suite_dir)) { 104 // Register the PSR-4 directory for PHPUnit-based suites. 105 $namespaces['Drupal\\Tests\\' . $extension . '\\' . $suite_name . '\\'][] = $suite_dir; 106 } 107 } 108 // Extensions can have a \Drupal\extension\Traits namespace for 109 // cross-suite trait code. 110 $trait_dir = $test_dir . '/Traits'; 111 if (is_dir($trait_dir)) { 112 $namespaces['Drupal\\Tests\\' . $extension . '\\Traits\\'][] = $trait_dir; 113 } 114 } 115 } 116 return $namespaces; 117} 118 119// We define the COMPOSER_INSTALL constant, so that PHPUnit knows where to 120// autoload from. This is needed for tests run in isolation mode, because 121// phpunit.xml.dist is located in a non-default directory relative to the 122// PHPUnit executable. 123if (!defined('PHPUNIT_COMPOSER_INSTALL')) { 124 define('PHPUNIT_COMPOSER_INSTALL', __DIR__ . '/../../autoload.php'); 125} 126 127/** 128 * Populate class loader with additional namespaces for tests. 129 * 130 * We run this in a function to avoid setting the class loader to a global 131 * that can change. This change can cause unpredictable false positives for 132 * phpunit's global state change watcher. The class loader can be retrieved from 133 * composer at any time by requiring autoload.php. 134 */ 135function drupal_phpunit_populate_class_loader() { 136 137 /** @var \Composer\Autoload\ClassLoader $loader */ 138 $loader = require __DIR__ . '/../../autoload.php'; 139 140 // Start with classes in known locations. 141 $loader->add('Drupal\\BuildTests', __DIR__); 142 $loader->add('Drupal\\Tests', __DIR__); 143 $loader->add('Drupal\\TestSite', __DIR__); 144 $loader->add('Drupal\\KernelTests', __DIR__); 145 $loader->add('Drupal\\FunctionalTests', __DIR__); 146 $loader->add('Drupal\\FunctionalJavascriptTests', __DIR__); 147 $loader->add('Drupal\\TestTools', __DIR__); 148 149 if (!isset($GLOBALS['namespaces'])) { 150 // Scan for arbitrary extension namespaces from core and contrib. 151 $extension_roots = drupal_phpunit_contrib_extension_directory_roots(); 152 153 $dirs = array_map('drupal_phpunit_find_extension_directories', $extension_roots); 154 $dirs = array_reduce($dirs, 'array_merge', []); 155 $GLOBALS['namespaces'] = drupal_phpunit_get_extension_namespaces($dirs); 156 } 157 foreach ($GLOBALS['namespaces'] as $prefix => $paths) { 158 $loader->addPsr4($prefix, $paths); 159 } 160 161 return $loader; 162} 163 164// Do class loader population. 165drupal_phpunit_populate_class_loader(); 166 167// Set sane locale settings, to ensure consistent string, dates, times and 168// numbers handling. 169// @see \Drupal\Core\DrupalKernel::bootEnvironment() 170setlocale(LC_ALL, 'C'); 171 172// Set appropriate configuration for multi-byte strings. 173mb_internal_encoding('utf-8'); 174mb_language('uni'); 175 176// Set the default timezone. While this doesn't cause any tests to fail, PHP 177// complains if 'date.timezone' is not set in php.ini. The Australia/Sydney 178// timezone is chosen so all tests are run using an edge case scenario (UTC+10 179// and DST). This choice is made to prevent timezone related regressions and 180// reduce the fragility of the testing system in general. 181date_default_timezone_set('Australia/Sydney'); 182 183// Runtime assertions. PHPUnit follows the php.ini assert.active setting for 184// runtime assertions. By default this setting is on. Ensure exceptions are 185// thrown if an assert fails, but this call does not turn runtime assertions on 186// if they weren't on already. 187Handle::register(); 188 189// PHPUnit 4 to PHPUnit 6 bridge. Tests written for PHPUnit 4 need to work on 190// PHPUnit 6 with a minimum of fuss. 191// @todo provided for BC; remove in Drupal 9. 192class_alias(AssertionFailedError::class, '\PHPUnit_Framework_AssertionFailedError'); 193class_alias(Count::class, '\PHPUnit_Framework_Constraint_Count'); 194class_alias(Error::class, '\PHPUnit_Framework_Error'); 195class_alias(Warning::class, '\PHPUnit_Framework_Error_Warning'); 196class_alias(ExpectationFailedException::class, '\PHPUnit_Framework_ExpectationFailedException'); 197class_alias(Exception::class, '\PHPUnit_Framework_Exception'); 198class_alias(InvokedRecorder::class, '\PHPUnit_Framework_MockObject_Matcher_InvokedRecorder'); 199class_alias(SkippedTestError::class, '\PHPUnit_Framework_SkippedTestError'); 200class_alias(TestCase::class, '\PHPUnit_Framework_TestCase'); 201class_alias(Test::class, '\PHPUnit_Util_Test'); 202class_alias(Xml::class, '\PHPUnit_Util_XML'); 203