annotate core/tests/bootstrap.php @ 19:fa3358dc1485 tip

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