annotate core/tests/Drupal/KernelTests/KernelTestBase.php @ 12:7a779792577d

Update Drupal core to v8.4.5 (via Composer)
author Chris Cannam
date Fri, 23 Feb 2018 15:52:07 +0000
parents 4c8ae668cc8c
children 1fec387a4317
rev   line source
Chris@0 1 <?php
Chris@0 2
Chris@0 3 namespace Drupal\KernelTests;
Chris@0 4
Chris@0 5 use Drupal\Component\FileCache\ApcuFileCacheBackend;
Chris@0 6 use Drupal\Component\FileCache\FileCache;
Chris@0 7 use Drupal\Component\FileCache\FileCacheFactory;
Chris@0 8 use Drupal\Component\Utility\Html;
Chris@0 9 use Drupal\Component\Utility\SafeMarkup;
Chris@0 10 use Drupal\Core\Config\Development\ConfigSchemaChecker;
Chris@0 11 use Drupal\Core\Database\Database;
Chris@0 12 use Drupal\Core\DependencyInjection\ContainerBuilder;
Chris@0 13 use Drupal\Core\DependencyInjection\ServiceProviderInterface;
Chris@0 14 use Drupal\Core\DrupalKernel;
Chris@0 15 use Drupal\Core\Entity\Sql\SqlEntityStorageInterface;
Chris@0 16 use Drupal\Core\Extension\ExtensionDiscovery;
Chris@0 17 use Drupal\Core\Language\Language;
Chris@0 18 use Drupal\Core\Site\Settings;
Chris@0 19 use Drupal\Core\Test\TestDatabase;
Chris@0 20 use Drupal\simpletest\AssertContentTrait;
Chris@0 21 use Drupal\Tests\AssertHelperTrait;
Chris@0 22 use Drupal\Tests\ConfigTestTrait;
Chris@12 23 use Drupal\Tests\PhpunitCompatibilityTrait;
Chris@0 24 use Drupal\Tests\RandomGeneratorTrait;
Chris@0 25 use Drupal\Tests\TestRequirementsTrait;
Chris@0 26 use Drupal\simpletest\TestServiceProvider;
Chris@0 27 use PHPUnit\Framework\TestCase;
Chris@0 28 use Symfony\Component\DependencyInjection\Reference;
Chris@0 29 use Symfony\Component\HttpFoundation\Request;
Chris@0 30 use org\bovigo\vfs\vfsStream;
Chris@0 31 use org\bovigo\vfs\visitor\vfsStreamPrintVisitor;
Chris@0 32
Chris@0 33 /**
Chris@0 34 * Base class for functional integration tests.
Chris@0 35 *
Chris@0 36 * This base class should be useful for testing some types of integrations which
Chris@0 37 * don't require the overhead of a fully-installed Drupal instance, but which
Chris@0 38 * have many dependencies on parts of Drupal which can't or shouldn't be mocked.
Chris@0 39 *
Chris@0 40 * This base class partially boots a fixture Drupal. The state of the fixture
Chris@0 41 * Drupal is comparable to the state of a system during the early part of the
Chris@0 42 * installation process.
Chris@0 43 *
Chris@0 44 * Tests extending this base class can access services and the database, but the
Chris@0 45 * system is initially empty. This Drupal runs in a minimal mocked filesystem
Chris@0 46 * which operates within vfsStream.
Chris@0 47 *
Chris@0 48 * Modules specified in the $modules property are added to the service container
Chris@0 49 * for each test. The module/hook system is functional. Additional modules
Chris@0 50 * needed in a test should override $modules. Modules specified in this way will
Chris@0 51 * be added to those specified in superclasses.
Chris@0 52 *
Chris@0 53 * Unlike \Drupal\Tests\BrowserTestBase, the modules are not installed. They are
Chris@0 54 * loaded such that their services and hooks are available, but the install
Chris@0 55 * process has not been performed.
Chris@0 56 *
Chris@0 57 * Other modules can be made available in this way using
Chris@0 58 * KernelTestBase::enableModules().
Chris@0 59 *
Chris@0 60 * Some modules can be brought into a fully-installed state using
Chris@0 61 * KernelTestBase::installConfig(), KernelTestBase::installSchema(), and
Chris@0 62 * KernelTestBase::installEntitySchema(). Alternately, tests which need modules
Chris@0 63 * to be fully installed could inherit from \Drupal\Tests\BrowserTestBase.
Chris@0 64 *
Chris@0 65 * @see \Drupal\Tests\KernelTestBase::$modules
Chris@0 66 * @see \Drupal\Tests\KernelTestBase::enableModules()
Chris@0 67 * @see \Drupal\Tests\KernelTestBase::installConfig()
Chris@0 68 * @see \Drupal\Tests\KernelTestBase::installEntitySchema()
Chris@0 69 * @see \Drupal\Tests\KernelTestBase::installSchema()
Chris@0 70 * @see \Drupal\Tests\BrowserTestBase
Chris@0 71 */
Chris@0 72 abstract class KernelTestBase extends TestCase implements ServiceProviderInterface {
Chris@0 73
Chris@0 74 use AssertLegacyTrait;
Chris@0 75 use AssertContentTrait;
Chris@0 76 use AssertHelperTrait;
Chris@0 77 use RandomGeneratorTrait;
Chris@0 78 use ConfigTestTrait;
Chris@0 79 use TestRequirementsTrait;
Chris@12 80 use PhpunitCompatibilityTrait;
Chris@0 81
Chris@0 82 /**
Chris@0 83 * {@inheritdoc}
Chris@0 84 *
Chris@0 85 * Back up and restore any global variables that may be changed by tests.
Chris@0 86 *
Chris@0 87 * @see self::runTestInSeparateProcess
Chris@0 88 */
Chris@0 89 protected $backupGlobals = TRUE;
Chris@0 90
Chris@0 91 /**
Chris@0 92 * {@inheritdoc}
Chris@0 93 *
Chris@0 94 * Kernel tests are run in separate processes because they allow autoloading
Chris@0 95 * of code from extensions. Running the test in a separate process isolates
Chris@0 96 * this behavior from other tests. Subclasses should not override this
Chris@0 97 * property.
Chris@0 98 */
Chris@0 99 protected $runTestInSeparateProcess = TRUE;
Chris@0 100
Chris@0 101 /**
Chris@0 102 * {@inheritdoc}
Chris@0 103 *
Chris@0 104 * Back up and restore static class properties that may be changed by tests.
Chris@0 105 *
Chris@0 106 * @see self::runTestInSeparateProcess
Chris@0 107 */
Chris@0 108 protected $backupStaticAttributes = TRUE;
Chris@0 109
Chris@0 110 /**
Chris@0 111 * {@inheritdoc}
Chris@0 112 *
Chris@0 113 * Contains a few static class properties for performance.
Chris@0 114 */
Chris@0 115 protected $backupStaticAttributesBlacklist = [
Chris@0 116 // Ignore static discovery/parser caches to speed up tests.
Chris@0 117 'Drupal\Component\Discovery\YamlDiscovery' => ['parsedFiles'],
Chris@0 118 'Drupal\Core\DependencyInjection\YamlFileLoader' => ['yaml'],
Chris@0 119 'Drupal\Core\Extension\ExtensionDiscovery' => ['files'],
Chris@0 120 'Drupal\Core\Extension\InfoParser' => ['parsedInfos'],
Chris@0 121 // Drupal::$container cannot be serialized.
Chris@0 122 'Drupal' => ['container'],
Chris@0 123 // Settings cannot be serialized.
Chris@0 124 'Drupal\Core\Site\Settings' => ['instance'],
Chris@0 125 ];
Chris@0 126
Chris@0 127 /**
Chris@0 128 * {@inheritdoc}
Chris@0 129 *
Chris@0 130 * Do not forward any global state from the parent process to the processes
Chris@0 131 * that run the actual tests.
Chris@0 132 *
Chris@0 133 * @see self::runTestInSeparateProcess
Chris@0 134 */
Chris@0 135 protected $preserveGlobalState = FALSE;
Chris@0 136
Chris@0 137 /**
Chris@0 138 * @var \Composer\Autoload\Classloader
Chris@0 139 */
Chris@0 140 protected $classLoader;
Chris@0 141
Chris@0 142 /**
Chris@0 143 * @var string
Chris@0 144 */
Chris@0 145 protected $siteDirectory;
Chris@0 146
Chris@0 147 /**
Chris@0 148 * @var string
Chris@0 149 */
Chris@0 150 protected $databasePrefix;
Chris@0 151
Chris@0 152 /**
Chris@0 153 * @var \Drupal\Core\DependencyInjection\ContainerBuilder
Chris@0 154 */
Chris@0 155 protected $container;
Chris@0 156
Chris@0 157 /**
Chris@0 158 * Modules to enable.
Chris@0 159 *
Chris@0 160 * The test runner will merge the $modules lists from this class, the class
Chris@0 161 * it extends, and so on up the class hierarchy. It is not necessary to
Chris@0 162 * include modules in your list that a parent class has already declared.
Chris@0 163 *
Chris@0 164 * @see \Drupal\Tests\KernelTestBase::enableModules()
Chris@0 165 * @see \Drupal\Tests\KernelTestBase::bootKernel()
Chris@0 166 *
Chris@0 167 * @var array
Chris@0 168 */
Chris@0 169 protected static $modules = [];
Chris@0 170
Chris@0 171 /**
Chris@0 172 * The virtual filesystem root directory.
Chris@0 173 *
Chris@0 174 * @var \org\bovigo\vfs\vfsStreamDirectory
Chris@0 175 */
Chris@0 176 protected $vfsRoot;
Chris@0 177
Chris@0 178 /**
Chris@0 179 * @var int
Chris@0 180 */
Chris@0 181 protected $expectedLogSeverity;
Chris@0 182
Chris@0 183 /**
Chris@0 184 * @var string
Chris@0 185 */
Chris@0 186 protected $expectedLogMessage;
Chris@0 187
Chris@0 188 /**
Chris@0 189 * @todo Move into Config test base class.
Chris@0 190 * @var \Drupal\Core\Config\ConfigImporter
Chris@0 191 */
Chris@0 192 protected $configImporter;
Chris@0 193
Chris@0 194 /**
Chris@0 195 * The app root.
Chris@0 196 *
Chris@0 197 * @var string
Chris@0 198 */
Chris@0 199 protected $root;
Chris@0 200
Chris@0 201 /**
Chris@0 202 * Set to TRUE to strict check all configuration saved.
Chris@0 203 *
Chris@0 204 * @see \Drupal\Core\Config\Development\ConfigSchemaChecker
Chris@0 205 *
Chris@0 206 * @var bool
Chris@0 207 */
Chris@0 208 protected $strictConfigSchema = TRUE;
Chris@0 209
Chris@0 210 /**
Chris@0 211 * An array of config object names that are excluded from schema checking.
Chris@0 212 *
Chris@0 213 * @var string[]
Chris@0 214 */
Chris@0 215 protected static $configSchemaCheckerExclusions = [
Chris@0 216 // Following are used to test lack of or partial schema. Where partial
Chris@0 217 // schema is provided, that is explicitly tested in specific tests.
Chris@0 218 'config_schema_test.noschema',
Chris@0 219 'config_schema_test.someschema',
Chris@0 220 'config_schema_test.schema_data_types',
Chris@0 221 'config_schema_test.no_schema_data_types',
Chris@0 222 // Used to test application of schema to filtering of configuration.
Chris@0 223 'config_test.dynamic.system',
Chris@0 224 ];
Chris@0 225
Chris@0 226 /**
Chris@0 227 * {@inheritdoc}
Chris@0 228 */
Chris@0 229 public static function setUpBeforeClass() {
Chris@0 230 parent::setUpBeforeClass();
Chris@0 231
Chris@0 232 // Change the current dir to DRUPAL_ROOT.
Chris@0 233 chdir(static::getDrupalRoot());
Chris@0 234 }
Chris@0 235
Chris@0 236 /**
Chris@0 237 * {@inheritdoc}
Chris@0 238 */
Chris@0 239 protected function setUp() {
Chris@0 240 parent::setUp();
Chris@0 241
Chris@0 242 $this->root = static::getDrupalRoot();
Chris@0 243 $this->initFileCache();
Chris@0 244 $this->bootEnvironment();
Chris@0 245 $this->bootKernel();
Chris@0 246 }
Chris@0 247
Chris@0 248 /**
Chris@0 249 * Bootstraps a basic test environment.
Chris@0 250 *
Chris@0 251 * Should not be called by tests. Only visible for DrupalKernel integration
Chris@0 252 * tests.
Chris@0 253 *
Chris@0 254 * @see \Drupal\system\Tests\DrupalKernel\DrupalKernelTest
Chris@0 255 * @internal
Chris@0 256 */
Chris@0 257 protected function bootEnvironment() {
Chris@0 258 $this->streamWrappers = [];
Chris@0 259 \Drupal::unsetContainer();
Chris@0 260
Chris@0 261 $this->classLoader = require $this->root . '/autoload.php';
Chris@0 262
Chris@0 263 require_once $this->root . '/core/includes/bootstrap.inc';
Chris@0 264
Chris@0 265 // Set up virtual filesystem.
Chris@0 266 Database::addConnectionInfo('default', 'test-runner', $this->getDatabaseConnectionInfo()['default']);
Chris@0 267 $test_db = new TestDatabase();
Chris@0 268 $this->siteDirectory = $test_db->getTestSitePath();
Chris@0 269
Chris@0 270 // Ensure that all code that relies on drupal_valid_test_ua() can still be
Chris@0 271 // safely executed. This primarily affects the (test) site directory
Chris@0 272 // resolution (used by e.g. LocalStream and PhpStorage).
Chris@0 273 $this->databasePrefix = $test_db->getDatabasePrefix();
Chris@0 274 drupal_valid_test_ua($this->databasePrefix);
Chris@0 275
Chris@0 276 $settings = [
Chris@0 277 'hash_salt' => get_class($this),
Chris@0 278 'file_public_path' => $this->siteDirectory . '/files',
Chris@0 279 // Disable Twig template caching/dumping.
Chris@0 280 'twig_cache' => FALSE,
Chris@0 281 // @see \Drupal\KernelTests\KernelTestBase::register()
Chris@0 282 ];
Chris@0 283 new Settings($settings);
Chris@0 284
Chris@0 285 $this->setUpFilesystem();
Chris@0 286
Chris@0 287 foreach (Database::getAllConnectionInfo() as $key => $targets) {
Chris@0 288 Database::removeConnection($key);
Chris@0 289 }
Chris@0 290 Database::addConnectionInfo('default', 'default', $this->getDatabaseConnectionInfo()['default']);
Chris@0 291 }
Chris@0 292
Chris@0 293 /**
Chris@0 294 * Sets up the filesystem, so things like the file directory.
Chris@0 295 */
Chris@0 296 protected function setUpFilesystem() {
Chris@0 297 $test_db = new TestDatabase($this->databasePrefix);
Chris@0 298 $test_site_path = $test_db->getTestSitePath();
Chris@0 299
Chris@0 300 $this->vfsRoot = vfsStream::setup('root');
Chris@0 301 $this->vfsRoot->addChild(vfsStream::newDirectory($test_site_path));
Chris@0 302 $this->siteDirectory = vfsStream::url('root/' . $test_site_path);
Chris@0 303
Chris@0 304 mkdir($this->siteDirectory . '/files', 0775);
Chris@0 305 mkdir($this->siteDirectory . '/files/config/' . CONFIG_SYNC_DIRECTORY, 0775, TRUE);
Chris@0 306
Chris@0 307 $settings = Settings::getInstance() ? Settings::getAll() : [];
Chris@0 308 $settings['file_public_path'] = $this->siteDirectory . '/files';
Chris@0 309 new Settings($settings);
Chris@0 310
Chris@0 311 $GLOBALS['config_directories'] = [
Chris@0 312 CONFIG_SYNC_DIRECTORY => $this->siteDirectory . '/files/config/sync',
Chris@0 313 ];
Chris@0 314 }
Chris@0 315
Chris@0 316 /**
Chris@0 317 * @return string
Chris@0 318 */
Chris@0 319 public function getDatabasePrefix() {
Chris@0 320 return $this->databasePrefix;
Chris@0 321 }
Chris@0 322
Chris@0 323 /**
Chris@0 324 * Bootstraps a kernel for a test.
Chris@0 325 */
Chris@0 326 private function bootKernel() {
Chris@0 327 $this->setSetting('container_yamls', []);
Chris@0 328 // Allow for test-specific overrides.
Chris@12 329 $settings_services_file = $this->root . '/sites/default/testing.services.yml';
Chris@0 330 if (file_exists($settings_services_file)) {
Chris@0 331 // Copy the testing-specific service overrides in place.
Chris@12 332 $testing_services_file = $this->siteDirectory . '/services.yml';
Chris@0 333 copy($settings_services_file, $testing_services_file);
Chris@0 334 $this->setSetting('container_yamls', [$testing_services_file]);
Chris@0 335 }
Chris@0 336
Chris@0 337 // Allow for global test environment overrides.
Chris@0 338 if (file_exists($test_env = $this->root . '/sites/default/testing.services.yml')) {
Chris@0 339 $GLOBALS['conf']['container_yamls']['testing'] = $test_env;
Chris@0 340 }
Chris@0 341 // Add this test class as a service provider.
Chris@0 342 $GLOBALS['conf']['container_service_providers']['test'] = $this;
Chris@0 343
Chris@0 344 $modules = self::getModulesToEnable(get_class($this));
Chris@0 345
Chris@0 346 // Prepare a precompiled container for all tests of this class.
Chris@0 347 // Substantially improves performance, since ContainerBuilder::compile()
Chris@0 348 // is very expensive. Encourages testing best practices (small tests).
Chris@0 349 // Normally a setUpBeforeClass() operation, but object scope is required to
Chris@0 350 // inject $this test class instance as a service provider (see above).
Chris@0 351 $rc = new \ReflectionClass(get_class($this));
Chris@0 352 $test_method_count = count(array_filter($rc->getMethods(), function ($method) {
Chris@0 353 // PHPUnit's @test annotations are intentionally ignored/not supported.
Chris@0 354 return strpos($method->getName(), 'test') === 0;
Chris@0 355 }));
Chris@0 356
Chris@0 357 // Bootstrap the kernel. Do not use createFromRequest() to retain Settings.
Chris@0 358 $kernel = new DrupalKernel('testing', $this->classLoader, FALSE);
Chris@0 359 $kernel->setSitePath($this->siteDirectory);
Chris@0 360 // Boot a new one-time container from scratch. Ensure to set the module list
Chris@0 361 // upfront to avoid a subsequent rebuild.
Chris@0 362 if ($modules && $extensions = $this->getExtensionsForModules($modules)) {
Chris@0 363 $kernel->updateModules($extensions, $extensions);
Chris@0 364 }
Chris@0 365 // DrupalKernel::boot() is not sufficient as it does not invoke preHandle(),
Chris@0 366 // which is required to initialize legacy global variables.
Chris@0 367 $request = Request::create('/');
Chris@0 368 $kernel->prepareLegacyRequest($request);
Chris@0 369
Chris@0 370 // register() is only called if a new container was built/compiled.
Chris@0 371 $this->container = $kernel->getContainer();
Chris@0 372
Chris@0 373 // Ensure database tasks have been run.
Chris@0 374 require_once __DIR__ . '/../../../includes/install.inc';
Chris@0 375 $connection = Database::getConnection();
Chris@0 376 $errors = db_installer_object($connection->driver())->runTasks();
Chris@0 377 if (!empty($errors)) {
Chris@0 378 $this->fail('Failed to run installer database tasks: ' . implode(', ', $errors));
Chris@0 379 }
Chris@0 380
Chris@0 381 if ($modules) {
Chris@0 382 $this->container->get('module_handler')->loadAll();
Chris@0 383 }
Chris@0 384
Chris@0 385 $this->container->get('request_stack')->push($request);
Chris@0 386
Chris@0 387 // Setup the destion to the be frontpage by default.
Chris@0 388 \Drupal::destination()->set('/');
Chris@0 389
Chris@0 390 // Write the core.extension configuration.
Chris@0 391 // Required for ConfigInstaller::installDefaultConfig() to work.
Chris@0 392 $this->container->get('config.storage')->write('core.extension', [
Chris@0 393 'module' => array_fill_keys($modules, 0),
Chris@0 394 'theme' => [],
Chris@0 395 'profile' => '',
Chris@0 396 ]);
Chris@0 397
Chris@0 398 $settings = Settings::getAll();
Chris@0 399 $settings['php_storage']['default'] = [
Chris@0 400 'class' => '\Drupal\Component\PhpStorage\FileStorage',
Chris@0 401 ];
Chris@0 402 new Settings($settings);
Chris@0 403
Chris@0 404 // Manually configure the test mail collector implementation to prevent
Chris@0 405 // tests from sending out emails and collect them in state instead.
Chris@0 406 // While this should be enforced via settings.php prior to installation,
Chris@0 407 // some tests expect to be able to test mail system implementations.
Chris@0 408 $GLOBALS['config']['system.mail']['interface']['default'] = 'test_mail_collector';
Chris@0 409
Chris@0 410 // Manually configure the default file scheme so that modules that use file
Chris@0 411 // functions don't have to install system and its configuration.
Chris@0 412 // @see file_default_scheme()
Chris@0 413 $GLOBALS['config']['system.file']['default_scheme'] = 'public';
Chris@0 414 }
Chris@0 415
Chris@0 416 /**
Chris@0 417 * Configuration accessor for tests. Returns non-overridden configuration.
Chris@0 418 *
Chris@0 419 * @param string $name
Chris@0 420 * The configuration name.
Chris@0 421 *
Chris@0 422 * @return \Drupal\Core\Config\Config
Chris@0 423 * The configuration object with original configuration data.
Chris@0 424 */
Chris@0 425 protected function config($name) {
Chris@0 426 return $this->container->get('config.factory')->getEditable($name);
Chris@0 427 }
Chris@0 428
Chris@0 429 /**
Chris@0 430 * Returns the Database connection info to be used for this test.
Chris@0 431 *
Chris@0 432 * This method only exists for tests of the Database component itself, because
Chris@0 433 * they require multiple database connections. Each SQLite :memory: connection
Chris@0 434 * creates a new/separate database in memory. A shared-memory SQLite file URI
Chris@0 435 * triggers PHP open_basedir/allow_url_fopen/allow_url_include restrictions.
Chris@0 436 * Due to that, Database tests are running against a SQLite database that is
Chris@0 437 * located in an actual file in the system's temporary directory.
Chris@0 438 *
Chris@0 439 * Other tests should not override this method.
Chris@0 440 *
Chris@0 441 * @return array
Chris@0 442 * A Database connection info array.
Chris@0 443 *
Chris@0 444 * @internal
Chris@0 445 */
Chris@0 446 protected function getDatabaseConnectionInfo() {
Chris@0 447 // If the test is run with argument dburl then use it.
Chris@0 448 $db_url = getenv('SIMPLETEST_DB');
Chris@0 449 if (empty($db_url)) {
Chris@0 450 throw new \Exception('There is no database connection so no tests can be run. You must provide a SIMPLETEST_DB environment variable to run PHPUnit based functional tests outside of run-tests.sh. See https://www.drupal.org/node/2116263#skipped-tests for more information.');
Chris@0 451 }
Chris@0 452 else {
Chris@0 453 $database = Database::convertDbUrlToConnectionInfo($db_url, $this->root);
Chris@0 454 Database::addConnectionInfo('default', 'default', $database);
Chris@0 455 }
Chris@0 456
Chris@0 457 // Clone the current connection and replace the current prefix.
Chris@0 458 $connection_info = Database::getConnectionInfo('default');
Chris@0 459 if (!empty($connection_info)) {
Chris@0 460 Database::renameConnection('default', 'simpletest_original_default');
Chris@0 461 foreach ($connection_info as $target => $value) {
Chris@0 462 // Replace the full table prefix definition to ensure that no table
Chris@0 463 // prefixes of the test runner leak into the test.
Chris@0 464 $connection_info[$target]['prefix'] = [
Chris@0 465 'default' => $value['prefix']['default'] . $this->databasePrefix,
Chris@0 466 ];
Chris@0 467 }
Chris@0 468 }
Chris@0 469 return $connection_info;
Chris@0 470 }
Chris@0 471
Chris@0 472 /**
Chris@0 473 * Initializes the FileCache component.
Chris@0 474 *
Chris@0 475 * We can not use the Settings object in a component, that's why we have to do
Chris@0 476 * it here instead of \Drupal\Component\FileCache\FileCacheFactory.
Chris@0 477 */
Chris@0 478 protected function initFileCache() {
Chris@0 479 $configuration = Settings::get('file_cache');
Chris@0 480
Chris@0 481 // Provide a default configuration, if not set.
Chris@0 482 if (!isset($configuration['default'])) {
Chris@0 483 // @todo Use extension_loaded('apcu') for non-testbot
Chris@0 484 // https://www.drupal.org/node/2447753.
Chris@0 485 if (function_exists('apcu_fetch')) {
Chris@0 486 $configuration['default']['cache_backend_class'] = ApcuFileCacheBackend::class;
Chris@0 487 }
Chris@0 488 }
Chris@0 489 FileCacheFactory::setConfiguration($configuration);
Chris@0 490 FileCacheFactory::setPrefix(Settings::getApcuPrefix('file_cache', $this->root));
Chris@0 491 }
Chris@0 492
Chris@0 493 /**
Chris@0 494 * Returns Extension objects for $modules to enable.
Chris@0 495 *
Chris@0 496 * @param string[] $modules
Chris@0 497 * The list of modules to enable.
Chris@0 498 *
Chris@0 499 * @return \Drupal\Core\Extension\Extension[]
Chris@0 500 * Extension objects for $modules, keyed by module name.
Chris@0 501 *
Chris@0 502 * @throws \PHPUnit_Framework_Exception
Chris@0 503 * If a module is not available.
Chris@0 504 *
Chris@0 505 * @see \Drupal\Tests\KernelTestBase::enableModules()
Chris@0 506 * @see \Drupal\Core\Extension\ModuleHandler::add()
Chris@0 507 */
Chris@0 508 private function getExtensionsForModules(array $modules) {
Chris@0 509 $extensions = [];
Chris@0 510 $discovery = new ExtensionDiscovery($this->root);
Chris@0 511 $discovery->setProfileDirectories([]);
Chris@0 512 $list = $discovery->scan('module');
Chris@0 513 foreach ($modules as $name) {
Chris@0 514 if (!isset($list[$name])) {
Chris@0 515 throw new \PHPUnit_Framework_Exception("Unavailable module: '$name'. If this module needs to be downloaded separately, annotate the test class with '@requires module $name'.");
Chris@0 516 }
Chris@0 517 $extensions[$name] = $list[$name];
Chris@0 518 }
Chris@0 519 return $extensions;
Chris@0 520 }
Chris@0 521
Chris@0 522 /**
Chris@0 523 * Registers test-specific services.
Chris@0 524 *
Chris@0 525 * Extend this method in your test to register additional services. This
Chris@0 526 * method is called whenever the kernel is rebuilt.
Chris@0 527 *
Chris@0 528 * @param \Drupal\Core\DependencyInjection\ContainerBuilder $container
Chris@0 529 * The service container to enhance.
Chris@0 530 *
Chris@0 531 * @see \Drupal\Tests\KernelTestBase::bootKernel()
Chris@0 532 */
Chris@0 533 public function register(ContainerBuilder $container) {
Chris@0 534 // Keep the container object around for tests.
Chris@0 535 $this->container = $container;
Chris@0 536
Chris@0 537 $container
Chris@0 538 ->register('flood', 'Drupal\Core\Flood\MemoryBackend')
Chris@0 539 ->addArgument(new Reference('request_stack'));
Chris@0 540 $container
Chris@0 541 ->register('lock', 'Drupal\Core\Lock\NullLockBackend');
Chris@0 542 $container
Chris@0 543 ->register('cache_factory', 'Drupal\Core\Cache\MemoryBackendFactory');
Chris@0 544 $container
Chris@0 545 ->register('keyvalue.memory', 'Drupal\Core\KeyValueStore\KeyValueMemoryFactory')
Chris@0 546 // Must persist container rebuilds, or all data would vanish otherwise.
Chris@0 547 ->addTag('persist');
Chris@0 548 $container
Chris@0 549 ->setAlias('keyvalue', 'keyvalue.memory');
Chris@0 550
Chris@0 551 // Set the default language on the minimal container.
Chris@0 552 $container->setParameter('language.default_values', Language::$defaultValues);
Chris@0 553
Chris@0 554 if ($this->strictConfigSchema) {
Chris@0 555 $container
Chris@0 556 ->register('simpletest.config_schema_checker', ConfigSchemaChecker::class)
Chris@0 557 ->addArgument(new Reference('config.typed'))
Chris@0 558 ->addArgument($this->getConfigSchemaExclusions())
Chris@0 559 ->addTag('event_subscriber');
Chris@0 560 }
Chris@0 561
Chris@0 562 if ($container->hasDefinition('path_processor_alias')) {
Chris@0 563 // Prevent the alias-based path processor, which requires a url_alias db
Chris@0 564 // table, from being registered to the path processor manager. We do this
Chris@0 565 // by removing the tags that the compiler pass looks for. This means the
Chris@0 566 // url generator can safely be used within tests.
Chris@0 567 $container->getDefinition('path_processor_alias')
Chris@0 568 ->clearTag('path_processor_inbound')
Chris@0 569 ->clearTag('path_processor_outbound');
Chris@0 570 }
Chris@0 571
Chris@0 572 if ($container->hasDefinition('password')) {
Chris@0 573 $container->getDefinition('password')
Chris@0 574 ->setArguments([1]);
Chris@0 575 }
Chris@0 576 TestServiceProvider::addRouteProvider($container);
Chris@0 577 }
Chris@0 578
Chris@0 579 /**
Chris@0 580 * Gets the config schema exclusions for this test.
Chris@0 581 *
Chris@0 582 * @return string[]
Chris@0 583 * An array of config object names that are excluded from schema checking.
Chris@0 584 */
Chris@0 585 protected function getConfigSchemaExclusions() {
Chris@0 586 $class = get_class($this);
Chris@0 587 $exceptions = [];
Chris@0 588 while ($class) {
Chris@0 589 if (property_exists($class, 'configSchemaCheckerExclusions')) {
Chris@0 590 $exceptions = array_merge($exceptions, $class::$configSchemaCheckerExclusions);
Chris@0 591 }
Chris@0 592 $class = get_parent_class($class);
Chris@0 593 }
Chris@0 594 // Filter out any duplicates.
Chris@0 595 return array_unique($exceptions);
Chris@0 596 }
Chris@0 597
Chris@0 598 /**
Chris@0 599 * {@inheritdoc}
Chris@0 600 */
Chris@0 601 protected function assertPostConditions() {
Chris@0 602 // Execute registered Drupal shutdown functions prior to tearing down.
Chris@0 603 // @see _drupal_shutdown_function()
Chris@0 604 $callbacks = &drupal_register_shutdown_function();
Chris@0 605 while ($callback = array_shift($callbacks)) {
Chris@0 606 call_user_func_array($callback['callback'], $callback['arguments']);
Chris@0 607 }
Chris@0 608
Chris@0 609 // Shut down the kernel (if bootKernel() was called).
Chris@0 610 // @see \Drupal\KernelTests\Core\DrupalKernel\DrupalKernelTest
Chris@0 611 if ($this->container) {
Chris@0 612 $this->container->get('kernel')->shutdown();
Chris@0 613 }
Chris@0 614
Chris@0 615 // Fail in case any (new) shutdown functions exist.
Chris@0 616 $this->assertCount(0, drupal_register_shutdown_function(), 'Unexpected Drupal shutdown callbacks exist after running shutdown functions.');
Chris@0 617
Chris@0 618 parent::assertPostConditions();
Chris@0 619 }
Chris@0 620
Chris@0 621 /**
Chris@0 622 * {@inheritdoc}
Chris@0 623 */
Chris@0 624 protected function tearDown() {
Chris@0 625 // Destroy the testing kernel.
Chris@0 626 if (isset($this->kernel)) {
Chris@0 627 $this->kernel->shutdown();
Chris@0 628 }
Chris@0 629
Chris@0 630 // Remove all prefixed tables.
Chris@0 631 $original_connection_info = Database::getConnectionInfo('simpletest_original_default');
Chris@0 632 $original_prefix = $original_connection_info['default']['prefix']['default'];
Chris@0 633 $test_connection_info = Database::getConnectionInfo('default');
Chris@0 634 $test_prefix = $test_connection_info['default']['prefix']['default'];
Chris@0 635 if ($original_prefix != $test_prefix) {
Chris@0 636 $tables = Database::getConnection()->schema()->findTables('%');
Chris@0 637 foreach ($tables as $table) {
Chris@0 638 if (Database::getConnection()->schema()->dropTable($table)) {
Chris@0 639 unset($tables[$table]);
Chris@0 640 }
Chris@0 641 }
Chris@0 642 }
Chris@0 643
Chris@0 644 // Free up memory: Own properties.
Chris@0 645 $this->classLoader = NULL;
Chris@0 646 $this->vfsRoot = NULL;
Chris@0 647 $this->configImporter = NULL;
Chris@0 648
Chris@0 649 // Free up memory: Custom test class properties.
Chris@0 650 // Note: Private properties cannot be cleaned up.
Chris@0 651 $rc = new \ReflectionClass(__CLASS__);
Chris@0 652 $blacklist = [];
Chris@0 653 foreach ($rc->getProperties() as $property) {
Chris@0 654 $blacklist[$property->name] = $property->getDeclaringClass()->name;
Chris@0 655 }
Chris@0 656 $rc = new \ReflectionClass($this);
Chris@0 657 foreach ($rc->getProperties(\ReflectionProperty::IS_PUBLIC | \ReflectionProperty::IS_PROTECTED) as $property) {
Chris@0 658 if (!$property->isStatic() && !isset($blacklist[$property->name])) {
Chris@0 659 $this->{$property->name} = NULL;
Chris@0 660 }
Chris@0 661 }
Chris@0 662
Chris@0 663 // Clean FileCache cache.
Chris@0 664 FileCache::reset();
Chris@0 665
Chris@0 666 // Clean up statics, container, and settings.
Chris@0 667 if (function_exists('drupal_static_reset')) {
Chris@0 668 drupal_static_reset();
Chris@0 669 }
Chris@0 670 \Drupal::unsetContainer();
Chris@0 671 $this->container = NULL;
Chris@0 672 new Settings([]);
Chris@0 673
Chris@0 674 parent::tearDown();
Chris@0 675 }
Chris@0 676
Chris@0 677 /**
Chris@0 678 * @after
Chris@0 679 *
Chris@0 680 * Additional tear down method to close the connection at the end.
Chris@0 681 */
Chris@0 682 public function tearDownCloseDatabaseConnection() {
Chris@0 683 // Destroy the database connection, which for example removes the memory
Chris@0 684 // from sqlite in memory.
Chris@0 685 foreach (Database::getAllConnectionInfo() as $key => $targets) {
Chris@0 686 Database::removeConnection($key);
Chris@0 687 }
Chris@0 688 }
Chris@0 689
Chris@0 690 /**
Chris@0 691 * Installs default configuration for a given list of modules.
Chris@0 692 *
Chris@0 693 * @param string|string[] $modules
Chris@0 694 * A module or list of modules for which to install default configuration.
Chris@0 695 *
Chris@0 696 * @throws \LogicException
Chris@0 697 * If any module in $modules is not enabled.
Chris@0 698 */
Chris@0 699 protected function installConfig($modules) {
Chris@0 700 foreach ((array) $modules as $module) {
Chris@0 701 if (!$this->container->get('module_handler')->moduleExists($module)) {
Chris@0 702 throw new \LogicException("$module module is not enabled.");
Chris@0 703 }
Chris@0 704 $this->container->get('config.installer')->installDefaultConfig('module', $module);
Chris@0 705 }
Chris@0 706 }
Chris@0 707
Chris@0 708 /**
Chris@0 709 * Installs database tables from a module schema definition.
Chris@0 710 *
Chris@0 711 * @param string $module
Chris@0 712 * The name of the module that defines the table's schema.
Chris@0 713 * @param string|array $tables
Chris@0 714 * The name or an array of the names of the tables to install.
Chris@0 715 *
Chris@0 716 * @throws \LogicException
Chris@0 717 * If $module is not enabled or the table schema cannot be found.
Chris@0 718 */
Chris@0 719 protected function installSchema($module, $tables) {
Chris@0 720 // drupal_get_module_schema() is technically able to install a schema
Chris@0 721 // of a non-enabled module, but its ability to load the module's .install
Chris@0 722 // file depends on many other factors. To prevent differences in test
Chris@0 723 // behavior and non-reproducible test failures, we only allow the schema of
Chris@0 724 // explicitly loaded/enabled modules to be installed.
Chris@0 725 if (!$this->container->get('module_handler')->moduleExists($module)) {
Chris@0 726 throw new \LogicException("$module module is not enabled.");
Chris@0 727 }
Chris@0 728 $tables = (array) $tables;
Chris@0 729 foreach ($tables as $table) {
Chris@0 730 $schema = drupal_get_module_schema($module, $table);
Chris@0 731 if (empty($schema)) {
Chris@0 732 // BC layer to avoid some contrib tests to fail.
Chris@0 733 // @todo Remove the BC layer before 8.1.x release.
Chris@0 734 // @see https://www.drupal.org/node/2670360
Chris@0 735 // @see https://www.drupal.org/node/2670454
Chris@0 736 if ($module == 'system') {
Chris@0 737 continue;
Chris@0 738 }
Chris@0 739 throw new \LogicException("$module module does not define a schema for table '$table'.");
Chris@0 740 }
Chris@0 741 $this->container->get('database')->schema()->createTable($table, $schema);
Chris@0 742 }
Chris@0 743 }
Chris@0 744
Chris@0 745 /**
Chris@0 746 * Installs the storage schema for a specific entity type.
Chris@0 747 *
Chris@0 748 * @param string $entity_type_id
Chris@0 749 * The ID of the entity type.
Chris@0 750 */
Chris@0 751 protected function installEntitySchema($entity_type_id) {
Chris@0 752 /** @var \Drupal\Core\Entity\EntityManagerInterface $entity_manager */
Chris@0 753 $entity_manager = $this->container->get('entity.manager');
Chris@0 754 $entity_type = $entity_manager->getDefinition($entity_type_id);
Chris@0 755 $entity_manager->onEntityTypeCreate($entity_type);
Chris@0 756
Chris@0 757 // For test runs, the most common storage backend is a SQL database. For
Chris@0 758 // this case, ensure the tables got created.
Chris@0 759 $storage = $entity_manager->getStorage($entity_type_id);
Chris@0 760 if ($storage instanceof SqlEntityStorageInterface) {
Chris@0 761 $tables = $storage->getTableMapping()->getTableNames();
Chris@0 762 $db_schema = $this->container->get('database')->schema();
Chris@0 763 $all_tables_exist = TRUE;
Chris@0 764 foreach ($tables as $table) {
Chris@0 765 if (!$db_schema->tableExists($table)) {
Chris@0 766 $this->fail(SafeMarkup::format('Installed entity type table for the %entity_type entity type: %table', [
Chris@0 767 '%entity_type' => $entity_type_id,
Chris@0 768 '%table' => $table,
Chris@0 769 ]));
Chris@0 770 $all_tables_exist = FALSE;
Chris@0 771 }
Chris@0 772 }
Chris@0 773 if ($all_tables_exist) {
Chris@0 774 $this->pass(SafeMarkup::format('Installed entity type tables for the %entity_type entity type: %tables', [
Chris@0 775 '%entity_type' => $entity_type_id,
Chris@0 776 '%tables' => '{' . implode('}, {', $tables) . '}',
Chris@0 777 ]));
Chris@0 778 }
Chris@0 779 }
Chris@0 780 }
Chris@0 781
Chris@0 782 /**
Chris@0 783 * Enables modules for this test.
Chris@0 784 *
Chris@0 785 * This method does not install modules fully. Services and hooks for the
Chris@0 786 * module are available, but the install process is not performed.
Chris@0 787 *
Chris@0 788 * To install test modules outside of the testing environment, add
Chris@0 789 * @code
Chris@0 790 * $settings['extension_discovery_scan_tests'] = TRUE;
Chris@0 791 * @endcode
Chris@0 792 * to your settings.php.
Chris@0 793 *
Chris@0 794 * @param string[] $modules
Chris@0 795 * A list of modules to enable. Dependencies are not resolved; i.e.,
Chris@0 796 * multiple modules have to be specified individually. The modules are only
Chris@0 797 * added to the active module list and loaded; i.e., their database schema
Chris@0 798 * is not installed. hook_install() is not invoked. A custom module weight
Chris@0 799 * is not applied.
Chris@0 800 *
Chris@0 801 * @throws \LogicException
Chris@0 802 * If any module in $modules is already enabled.
Chris@0 803 * @throws \RuntimeException
Chris@0 804 * If a module is not enabled after enabling it.
Chris@0 805 */
Chris@0 806 protected function enableModules(array $modules) {
Chris@0 807 $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
Chris@0 808 if ($trace[1]['function'] === 'setUp') {
Chris@0 809 trigger_error('KernelTestBase::enableModules() should not be called from setUp(). Use the $modules property instead.', E_USER_DEPRECATED);
Chris@0 810 }
Chris@0 811 unset($trace);
Chris@0 812
Chris@0 813 // Perform an ExtensionDiscovery scan as this function may receive a
Chris@0 814 // profile that is not the current profile, and we don't yet have a cached
Chris@0 815 // way to receive inactive profile information.
Chris@0 816 // @todo Remove as part of https://www.drupal.org/node/2186491
Chris@0 817 $listing = new ExtensionDiscovery(\Drupal::root());
Chris@0 818 $module_list = $listing->scan('module');
Chris@0 819 // In ModuleHandlerTest we pass in a profile as if it were a module.
Chris@0 820 $module_list += $listing->scan('profile');
Chris@0 821
Chris@0 822 // Set the list of modules in the extension handler.
Chris@0 823 $module_handler = $this->container->get('module_handler');
Chris@0 824
Chris@0 825 // Write directly to active storage to avoid early instantiation of
Chris@0 826 // the event dispatcher which can prevent modules from registering events.
Chris@0 827 $active_storage = $this->container->get('config.storage');
Chris@0 828 $extension_config = $active_storage->read('core.extension');
Chris@0 829
Chris@0 830 foreach ($modules as $module) {
Chris@0 831 if ($module_handler->moduleExists($module)) {
Chris@12 832 continue;
Chris@0 833 }
Chris@0 834 $module_handler->addModule($module, $module_list[$module]->getPath());
Chris@0 835 // Maintain the list of enabled modules in configuration.
Chris@0 836 $extension_config['module'][$module] = 0;
Chris@0 837 }
Chris@0 838 $active_storage->write('core.extension', $extension_config);
Chris@0 839
Chris@0 840 // Update the kernel to make their services available.
Chris@0 841 $extensions = $module_handler->getModuleList();
Chris@0 842 $this->container->get('kernel')->updateModules($extensions, $extensions);
Chris@0 843
Chris@0 844 // Ensure isLoaded() is TRUE in order to make
Chris@0 845 // \Drupal\Core\Theme\ThemeManagerInterface::render() work.
Chris@0 846 // Note that the kernel has rebuilt the container; this $module_handler is
Chris@0 847 // no longer the $module_handler instance from above.
Chris@0 848 $module_handler = $this->container->get('module_handler');
Chris@0 849 $module_handler->reload();
Chris@0 850 foreach ($modules as $module) {
Chris@0 851 if (!$module_handler->moduleExists($module)) {
Chris@0 852 throw new \RuntimeException("$module module is not enabled after enabling it.");
Chris@0 853 }
Chris@0 854 }
Chris@0 855 }
Chris@0 856
Chris@0 857 /**
Chris@0 858 * Disables modules for this test.
Chris@0 859 *
Chris@0 860 * @param string[] $modules
Chris@0 861 * A list of modules to disable. Dependencies are not resolved; i.e.,
Chris@0 862 * multiple modules have to be specified with dependent modules first.
Chris@0 863 * Code of previously enabled modules is still loaded. The modules are only
Chris@0 864 * removed from the active module list.
Chris@0 865 *
Chris@0 866 * @throws \LogicException
Chris@0 867 * If any module in $modules is already disabled.
Chris@0 868 * @throws \RuntimeException
Chris@0 869 * If a module is not disabled after disabling it.
Chris@0 870 */
Chris@0 871 protected function disableModules(array $modules) {
Chris@0 872 // Unset the list of modules in the extension handler.
Chris@0 873 $module_handler = $this->container->get('module_handler');
Chris@0 874 $module_filenames = $module_handler->getModuleList();
Chris@0 875 $extension_config = $this->config('core.extension');
Chris@0 876 foreach ($modules as $module) {
Chris@0 877 if (!$module_handler->moduleExists($module)) {
Chris@0 878 throw new \LogicException("$module module cannot be disabled because it is not enabled.");
Chris@0 879 }
Chris@0 880 unset($module_filenames[$module]);
Chris@0 881 $extension_config->clear('module.' . $module);
Chris@0 882 }
Chris@0 883 $extension_config->save();
Chris@0 884 $module_handler->setModuleList($module_filenames);
Chris@0 885 $module_handler->resetImplementations();
Chris@0 886 // Update the kernel to remove their services.
Chris@0 887 $this->container->get('kernel')->updateModules($module_filenames, $module_filenames);
Chris@0 888
Chris@0 889 // Ensure isLoaded() is TRUE in order to make
Chris@0 890 // \Drupal\Core\Theme\ThemeManagerInterface::render() work.
Chris@0 891 // Note that the kernel has rebuilt the container; this $module_handler is
Chris@0 892 // no longer the $module_handler instance from above.
Chris@0 893 $module_handler = $this->container->get('module_handler');
Chris@0 894 $module_handler->reload();
Chris@0 895 foreach ($modules as $module) {
Chris@0 896 if ($module_handler->moduleExists($module)) {
Chris@0 897 throw new \RuntimeException("$module module is not disabled after disabling it.");
Chris@0 898 }
Chris@0 899 }
Chris@0 900 }
Chris@0 901
Chris@0 902 /**
Chris@0 903 * Renders a render array.
Chris@0 904 *
Chris@0 905 * @param array $elements
Chris@0 906 * The elements to render.
Chris@0 907 *
Chris@0 908 * @return string
Chris@0 909 * The rendered string output (typically HTML).
Chris@0 910 */
Chris@0 911 protected function render(array &$elements) {
Chris@0 912 // \Drupal\Core\Render\BareHtmlPageRenderer::renderBarePage calls out to
Chris@0 913 // system_page_attachments() directly.
Chris@0 914 if (!\Drupal::moduleHandler()->moduleExists('system')) {
Chris@0 915 throw new \Exception(__METHOD__ . ' requires system module to be installed.');
Chris@0 916 }
Chris@0 917
Chris@0 918 // Use the bare HTML page renderer to render our links.
Chris@0 919 $renderer = $this->container->get('bare_html_page_renderer');
Chris@0 920 $response = $renderer->renderBarePage($elements, '', 'maintenance_page');
Chris@0 921
Chris@0 922 // Glean the content from the response object.
Chris@0 923 $content = $response->getContent();
Chris@0 924 $this->setRawContent($content);
Chris@0 925 $this->verbose('<pre style="white-space: pre-wrap">' . Html::escape($content));
Chris@0 926 return $content;
Chris@0 927 }
Chris@0 928
Chris@0 929 /**
Chris@0 930 * Sets an in-memory Settings variable.
Chris@0 931 *
Chris@0 932 * @param string $name
Chris@0 933 * The name of the setting to set.
Chris@0 934 * @param bool|string|int|array|null $value
Chris@0 935 * The value to set. Note that array values are replaced entirely; use
Chris@0 936 * \Drupal\Core\Site\Settings::get() to perform custom merges.
Chris@0 937 */
Chris@0 938 protected function setSetting($name, $value) {
Chris@0 939 $settings = Settings::getInstance() ? Settings::getAll() : [];
Chris@0 940 $settings[$name] = $value;
Chris@0 941 new Settings($settings);
Chris@0 942 }
Chris@0 943
Chris@0 944 /**
Chris@0 945 * Stops test execution.
Chris@0 946 */
Chris@0 947 protected function stop() {
Chris@0 948 $this->getTestResultObject()->stop();
Chris@0 949 }
Chris@0 950
Chris@0 951 /**
Chris@0 952 * Dumps the current state of the virtual filesystem to STDOUT.
Chris@0 953 */
Chris@0 954 protected function vfsDump() {
Chris@0 955 vfsStream::inspect(new vfsStreamPrintVisitor());
Chris@0 956 }
Chris@0 957
Chris@0 958 /**
Chris@0 959 * Returns the modules to enable for this test.
Chris@0 960 *
Chris@0 961 * @param string $class
Chris@0 962 * The fully-qualified class name of this test.
Chris@0 963 *
Chris@0 964 * @return array
Chris@0 965 */
Chris@0 966 private static function getModulesToEnable($class) {
Chris@0 967 $modules = [];
Chris@0 968 while ($class) {
Chris@0 969 if (property_exists($class, 'modules')) {
Chris@0 970 // Only add the modules, if the $modules property was not inherited.
Chris@0 971 $rp = new \ReflectionProperty($class, 'modules');
Chris@0 972 if ($rp->class == $class) {
Chris@0 973 $modules[$class] = $class::$modules;
Chris@0 974 }
Chris@0 975 }
Chris@0 976 $class = get_parent_class($class);
Chris@0 977 }
Chris@0 978 // Modules have been collected in reverse class hierarchy order; modules
Chris@0 979 // defined by base classes should be sorted first. Then, merge the results
Chris@0 980 // together.
Chris@0 981 $modules = array_reverse($modules);
Chris@0 982 return call_user_func_array('array_merge_recursive', $modules);
Chris@0 983 }
Chris@0 984
Chris@0 985 /**
Chris@0 986 * {@inheritdoc}
Chris@0 987 */
Chris@0 988 protected function prepareTemplate(\Text_Template $template) {
Chris@0 989 $bootstrap_globals = '';
Chris@0 990
Chris@0 991 // Fix missing bootstrap.php when $preserveGlobalState is FALSE.
Chris@0 992 // @see https://github.com/sebastianbergmann/phpunit/pull/797
Chris@0 993 $bootstrap_globals .= '$__PHPUNIT_BOOTSTRAP = ' . var_export($GLOBALS['__PHPUNIT_BOOTSTRAP'], TRUE) . ";\n";
Chris@0 994
Chris@0 995 // Avoid repetitive test namespace discoveries to improve performance.
Chris@0 996 // @see /core/tests/bootstrap.php
Chris@0 997 $bootstrap_globals .= '$namespaces = ' . var_export($GLOBALS['namespaces'], TRUE) . ";\n";
Chris@0 998
Chris@0 999 $template->setVar([
Chris@0 1000 'constants' => '',
Chris@0 1001 'included_files' => '',
Chris@0 1002 'globals' => $bootstrap_globals,
Chris@0 1003 ]);
Chris@0 1004 }
Chris@0 1005
Chris@0 1006 /**
Chris@0 1007 * Returns whether the current test method is running in a separate process.
Chris@0 1008 *
Chris@0 1009 * Note that KernelTestBase will run in a separate process by default.
Chris@0 1010 *
Chris@0 1011 * @return bool
Chris@0 1012 *
Chris@0 1013 * @see \Drupal\KernelTests\KernelTestBase::$runTestInSeparateProcess
Chris@0 1014 * @see https://github.com/sebastianbergmann/phpunit/pull/1350
Chris@0 1015 *
Chris@0 1016 * @deprecated in Drupal 8.4.x, for removal before the Drupal 9.0.0 release.
Chris@0 1017 * KernelTestBase tests are always run in isolated processes.
Chris@0 1018 */
Chris@0 1019 protected function isTestInIsolation() {
Chris@0 1020 @trigger_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated in Drupal 8.4.x, for removal before the Drupal 9.0.0 release. KernelTestBase tests are always run in isolated processes.', E_USER_DEPRECATED);
Chris@0 1021 return function_exists('__phpunit_run_isolated_test');
Chris@0 1022 }
Chris@0 1023
Chris@0 1024 /**
Chris@0 1025 * BC: Automatically resolve former KernelTestBase class properties.
Chris@0 1026 *
Chris@0 1027 * Test authors should follow the provided instructions and adjust their tests
Chris@0 1028 * accordingly.
Chris@0 1029 *
Chris@0 1030 * @deprecated in Drupal 8.0.x, will be removed before Drupal 8.2.0.
Chris@0 1031 */
Chris@0 1032 public function __get($name) {
Chris@0 1033 if (in_array($name, [
Chris@0 1034 'public_files_directory',
Chris@0 1035 'private_files_directory',
Chris@0 1036 'temp_files_directory',
Chris@0 1037 'translation_files_directory',
Chris@0 1038 ])) {
Chris@0 1039 // @comment it in again.
Chris@0 1040 trigger_error(sprintf("KernelTestBase::\$%s no longer exists. Use the regular API method to retrieve it instead (e.g., Settings).", $name), E_USER_DEPRECATED);
Chris@0 1041 switch ($name) {
Chris@0 1042 case 'public_files_directory':
Chris@0 1043 return Settings::get('file_public_path', \Drupal::service('site.path') . '/files');
Chris@0 1044
Chris@0 1045 case 'private_files_directory':
Chris@0 1046 return Settings::get('file_private_path');
Chris@0 1047
Chris@0 1048 case 'temp_files_directory':
Chris@0 1049 return file_directory_temp();
Chris@0 1050
Chris@0 1051 case 'translation_files_directory':
Chris@0 1052 return Settings::get('file_public_path', \Drupal::service('site.path') . '/translations');
Chris@0 1053 }
Chris@0 1054 }
Chris@0 1055
Chris@0 1056 if ($name === 'configDirectories') {
Chris@0 1057 trigger_error(sprintf("KernelTestBase::\$%s no longer exists. Use config_get_config_directory() directly instead.", $name), E_USER_DEPRECATED);
Chris@0 1058 return [
Chris@0 1059 CONFIG_SYNC_DIRECTORY => config_get_config_directory(CONFIG_SYNC_DIRECTORY),
Chris@0 1060 ];
Chris@0 1061 }
Chris@0 1062
Chris@0 1063 $denied = [
Chris@0 1064 // @see \Drupal\simpletest\TestBase
Chris@0 1065 'testId',
Chris@0 1066 'timeLimit',
Chris@0 1067 'results',
Chris@0 1068 'assertions',
Chris@0 1069 'skipClasses',
Chris@0 1070 'verbose',
Chris@0 1071 'verboseId',
Chris@0 1072 'verboseClassName',
Chris@0 1073 'verboseDirectory',
Chris@0 1074 'verboseDirectoryUrl',
Chris@0 1075 'dieOnFail',
Chris@0 1076 'kernel',
Chris@0 1077 // @see \Drupal\simpletest\TestBase::prepareEnvironment()
Chris@0 1078 'generatedTestFiles',
Chris@0 1079 // Properties from the old KernelTestBase class that has been removed.
Chris@0 1080 'keyValueFactory',
Chris@0 1081 ];
Chris@0 1082 if (in_array($name, $denied) || strpos($name, 'original') === 0) {
Chris@0 1083 throw new \RuntimeException(sprintf('TestBase::$%s property no longer exists', $name));
Chris@0 1084 }
Chris@0 1085 }
Chris@0 1086
Chris@0 1087 /**
Chris@0 1088 * Prevents serializing any properties.
Chris@0 1089 *
Chris@0 1090 * Kernel tests are run in a separate process. To do this PHPUnit creates a
Chris@0 1091 * script to run the test. If it fails, the test result object will contain a
Chris@0 1092 * stack trace which includes the test object. It will attempt to serialize
Chris@0 1093 * it. Returning an empty array prevents it from serializing anything it
Chris@0 1094 * should not.
Chris@0 1095 *
Chris@0 1096 * @return array
Chris@0 1097 * An empty array.
Chris@0 1098 *
Chris@0 1099 * @see vendor/phpunit/phpunit/src/Util/PHP/Template/TestCaseMethod.tpl.dist
Chris@0 1100 */
Chris@0 1101 public function __sleep() {
Chris@0 1102 return [];
Chris@0 1103 }
Chris@0 1104
Chris@0 1105 /**
Chris@0 1106 * {@inheritdoc}
Chris@0 1107 */
Chris@0 1108 public static function assertEquals($expected, $actual, $message = '', $delta = 0.0, $maxDepth = 10, $canonicalize = FALSE, $ignoreCase = FALSE) {
Chris@0 1109 // Cast objects implementing MarkupInterface to string instead of
Chris@0 1110 // relying on PHP casting them to string depending on what they are being
Chris@0 1111 // comparing with.
Chris@0 1112 $expected = static::castSafeStrings($expected);
Chris@0 1113 $actual = static::castSafeStrings($actual);
Chris@0 1114 parent::assertEquals($expected, $actual, $message, $delta, $maxDepth, $canonicalize, $ignoreCase);
Chris@0 1115 }
Chris@0 1116
Chris@0 1117 }