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