bootstrap.php 6.32 KB
Newer Older
1 2
<?php

3 4 5 6 7 8
/**
 * @file
 * Autoloader for Drupal PHPUnit testing.
 *
 * @see phpunit.xml.dist
 */
9

10
use Drupal\Component\Assertion\Handle;
11
use Drupal\TestTools\PhpUnitCompatibility\PhpUnit8\ClassWriter;
12

13 14 15 16 17 18 19 20 21 22
/**
 * Finds all valid extension directories recursively within a given directory.
 *
 * @param string $scan_directory
 *   The directory that should be recursively scanned.
 * @return array
 *   An associative array of extension directories found within the scanned
 *   directory, keyed by extension name.
 */
function drupal_phpunit_find_extension_directories($scan_directory) {
23
  $extensions = [];
24 25
  $dirs = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($scan_directory, \RecursiveDirectoryIterator::FOLLOW_SYMLINKS));
  foreach ($dirs as $dir) {
26
    if (strpos($dir->getPathname(), '.info.yml') !== FALSE) {
27 28 29
      // Cut off ".info.yml" from the filename for use as the extension name. We
      // use getRealPath() so that we can scan extensions represented by
      // directory aliases.
30 31
      $extensions[substr($dir->getFilename(), 0, -9)] = $dir->getPathInfo()
        ->getRealPath();
32 33
    }
  }
34 35 36 37 38 39
  return $extensions;
}

/**
 * Returns directories under which contributed extensions may exist.
 *
40 41 42
 * @param string $root
 *   (optional) Path to the root of the Drupal installation.
 *
43 44 45
 * @return array
 *   An array of directories under which contributed extensions may exist.
 */
46 47 48 49
function drupal_phpunit_contrib_extension_directory_roots($root = NULL) {
  if ($root === NULL) {
    $root = dirname(dirname(__DIR__));
  }
50
  $paths = [
51 52 53 54
    $root . '/core/modules',
    $root . '/core/profiles',
    $root . '/modules',
    $root . '/profiles',
55
    $root . '/themes',
56
  ];
57
  $sites_path = $root . '/sites';
58 59
  // Note this also checks sites/../modules and sites/../profiles.
  foreach (scandir($sites_path) as $site) {
60 61 62
    if ($site[0] === '.' || $site === 'simpletest') {
      continue;
    }
63 64 65
    $path = "$sites_path/$site";
    $paths[] = is_dir("$path/modules") ? realpath("$path/modules") : NULL;
    $paths[] = is_dir("$path/profiles") ? realpath("$path/profiles") : NULL;
66
    $paths[] = is_dir("$path/themes") ? realpath("$path/themes") : NULL;
67
  }
68
  return array_filter($paths, 'file_exists');
69
}
70

71 72 73 74 75
/**
 * Registers the namespace for each extension directory with the autoloader.
 *
 * @param array $dirs
 *   An associative array of extension directories, keyed by extension name.
76 77 78
 *
 * @return array
 *   An associative array of extension directories, keyed by their namespace.
79
 */
80
function drupal_phpunit_get_extension_namespaces($dirs) {
81
  $suite_names = ['Unit', 'Kernel', 'Functional', 'Build', 'FunctionalJavascript'];
82
  $namespaces = [];
83
  foreach ($dirs as $extension => $dir) {
84
    if (is_dir($dir . '/src')) {
85
      // Register the PSR-4 directory for module-provided classes.
86
      $namespaces['Drupal\\' . $extension . '\\'][] = $dir . '/src';
87
    }
88 89 90 91 92 93 94 95 96 97 98 99 100 101 102
    $test_dir = $dir . '/tests/src';
    if (is_dir($test_dir)) {
      foreach ($suite_names as $suite_name) {
        $suite_dir = $test_dir . '/' . $suite_name;
        if (is_dir($suite_dir)) {
          // Register the PSR-4 directory for PHPUnit-based suites.
          $namespaces['Drupal\\Tests\\' . $extension . '\\' . $suite_name . '\\'][] = $suite_dir;
        }
      }
      // Extensions can have a \Drupal\extension\Traits namespace for
      // cross-suite trait code.
      $trait_dir = $test_dir . '/Traits';
      if (is_dir($trait_dir)) {
        $namespaces['Drupal\\Tests\\' . $extension . '\\Traits\\'][] = $trait_dir;
      }
103
    }
104
  }
105
  return $namespaces;
106 107
}

108 109 110 111 112 113 114
// We define the COMPOSER_INSTALL constant, so that PHPUnit knows where to
// autoload from. This is needed for tests run in isolation mode, because
// phpunit.xml.dist is located in a non-default directory relative to the
// PHPUnit executable.
if (!defined('PHPUNIT_COMPOSER_INSTALL')) {
  define('PHPUNIT_COMPOSER_INSTALL', __DIR__ . '/../../autoload.php');
}
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129

/**
 * Populate class loader with additional namespaces for tests.
 *
 * We run this in a function to avoid setting the class loader to a global
 * that can change. This change can cause unpredictable false positives for
 * phpunit's global state change watcher. The class loader can be retrieved from
 * composer at any time by requiring autoload.php.
 */
function drupal_phpunit_populate_class_loader() {

  /** @var \Composer\Autoload\ClassLoader $loader */
  $loader = require __DIR__ . '/../../autoload.php';

  // Start with classes in known locations.
130
  $loader->add('Drupal\\BuildTests', __DIR__);
131
  $loader->add('Drupal\\Tests', __DIR__);
132
  $loader->add('Drupal\\TestSite', __DIR__);
133
  $loader->add('Drupal\\KernelTests', __DIR__);
134 135
  $loader->add('Drupal\\FunctionalTests', __DIR__);
  $loader->add('Drupal\\FunctionalJavascriptTests', __DIR__);
136
  $loader->add('Drupal\\TestTools', __DIR__);
137 138 139 140 141 142

  if (!isset($GLOBALS['namespaces'])) {
    // Scan for arbitrary extension namespaces from core and contrib.
    $extension_roots = drupal_phpunit_contrib_extension_directory_roots();

    $dirs = array_map('drupal_phpunit_find_extension_directories', $extension_roots);
143
    $dirs = array_reduce($dirs, 'array_merge', []);
144 145 146 147 148 149 150
    $GLOBALS['namespaces'] = drupal_phpunit_get_extension_namespaces($dirs);
  }
  foreach ($GLOBALS['namespaces'] as $prefix => $paths) {
    $loader->addPsr4($prefix, $paths);
  }

  return $loader;
151
}
152 153

// Do class loader population.
154 155 156
$loader = drupal_phpunit_populate_class_loader();

ClassWriter::mutateTestBase($loader);
157

158 159
// Set sane locale settings, to ensure consistent string, dates, times and
// numbers handling.
160
// @see \Drupal\Core\DrupalKernel::bootEnvironment()
161
setlocale(LC_ALL, 'C');
162

163 164 165 166
// Set appropriate configuration for multi-byte strings.
mb_internal_encoding('utf-8');
mb_language('uni');

167
// Set the default timezone. While this doesn't cause any tests to fail, PHP
168 169 170 171 172
// complains if 'date.timezone' is not set in php.ini. The Australia/Sydney
// timezone is chosen so all tests are run using an edge case scenario (UTC+10
// and DST). This choice is made to prevent timezone related regressions and
// reduce the fragility of the testing system in general.
date_default_timezone_set('Australia/Sydney');
173 174

// Runtime assertions. PHPUnit follows the php.ini assert.active setting for
175 176 177
// runtime assertions. By default this setting is on. Ensure exceptions are
// thrown if an assert fails, but this call does not turn runtime assertions on
// if they weren't on already.
178
Handle::register();