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