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