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