KernelTestBase.php 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651
  1. <?php
  2. namespace Drupal\simpletest;
  3. @trigger_error(__NAMESPACE__ . '\KernelTestBase is deprecated in Drupal 8.0.x, will be removed before Drupal 9.0.0. Use \Drupal\KernelTests\KernelTestBase instead.', E_USER_DEPRECATED);
  4. use Drupal\Component\Utility\Html;
  5. use Drupal\Component\Render\FormattableMarkup;
  6. use Drupal\Component\Utility\Variable;
  7. use Drupal\Core\Config\Development\ConfigSchemaChecker;
  8. use Drupal\Core\Database\Database;
  9. use Drupal\Core\DependencyInjection\ContainerBuilder;
  10. use Drupal\Core\DrupalKernel;
  11. use Drupal\Core\Entity\Sql\SqlEntityStorageInterface;
  12. use Drupal\Core\Extension\ExtensionDiscovery;
  13. use Drupal\Core\File\FileSystemInterface;
  14. use Drupal\Core\KeyValueStore\KeyValueMemoryFactory;
  15. use Drupal\Core\Language\Language;
  16. use Drupal\Core\Site\Settings;
  17. use Drupal\KernelTests\TestServiceProvider;
  18. use Symfony\Component\DependencyInjection\Parameter;
  19. use Drupal\Core\StreamWrapper\StreamWrapperInterface;
  20. use Symfony\Component\DependencyInjection\Reference;
  21. use Symfony\Component\HttpFoundation\Request;
  22. /**
  23. * Base class for functional integration tests.
  24. *
  25. * This base class should be useful for testing some types of integrations which
  26. * don't require the overhead of a fully-installed Drupal instance, but which
  27. * have many dependencies on parts of Drupal which can't or shouldn't be mocked.
  28. *
  29. * This base class partially boots a fixture Drupal. The state of the fixture
  30. * Drupal is comparable to the state of a system during the early part of the
  31. * installation process.
  32. *
  33. * Tests extending this base class can access services and the database, but the
  34. * system is initially empty. This Drupal runs in a minimal mocked filesystem
  35. * which operates within vfsStream.
  36. *
  37. * Modules specified in the $modules property are added to the service container
  38. * for each test. The module/hook system is functional. Additional modules
  39. * needed in a test should override $modules. Modules specified in this way will
  40. * be added to those specified in superclasses.
  41. *
  42. * Unlike \Drupal\Tests\BrowserTestBase, the modules are not installed. They are
  43. * loaded such that their services and hooks are available, but the install
  44. * process has not been performed.
  45. *
  46. * Other modules can be made available in this way using
  47. * KernelTestBase::enableModules().
  48. *
  49. * Some modules can be brought into a fully-installed state using
  50. * KernelTestBase::installConfig(), KernelTestBase::installSchema(), and
  51. * KernelTestBase::installEntitySchema(). Alternately, tests which need modules
  52. * to be fully installed could inherit from \Drupal\Tests\BrowserTestBase.
  53. *
  54. * @see \Drupal\Tests\KernelTestBase::$modules
  55. * @see \Drupal\Tests\KernelTestBase::enableModules()
  56. * @see \Drupal\Tests\KernelTestBase::installConfig()
  57. * @see \Drupal\Tests\KernelTestBase::installEntitySchema()
  58. * @see \Drupal\Tests\KernelTestBase::installSchema()
  59. * @see \Drupal\Tests\BrowserTestBase
  60. *
  61. * @deprecated in drupal:8.0.0 and is removed from drupal:9.0.0. Use
  62. * \Drupal\KernelTests\KernelTestBase instead.
  63. *
  64. * @ingroup testing
  65. */
  66. abstract class KernelTestBase extends TestBase {
  67. use AssertContentTrait;
  68. /**
  69. * Modules to enable.
  70. *
  71. * Test classes extending this class, and any classes in the hierarchy up to
  72. * this class, may specify individual lists of modules to enable by setting
  73. * this property. The values of all properties in all classes in the hierarchy
  74. * are merged.
  75. *
  76. * Any modules specified in the $modules property are automatically loaded and
  77. * set as the fixed module list.
  78. *
  79. * Unlike WebTestBase::setUp(), the specified modules are loaded only, but not
  80. * automatically installed. Modules need to be installed manually, if needed.
  81. *
  82. * @see \Drupal\simpletest\KernelTestBase::enableModules()
  83. * @see \Drupal\simpletest\KernelTestBase::setUp()
  84. *
  85. * @var array
  86. */
  87. public static $modules = [];
  88. private $moduleFiles;
  89. private $themeFiles;
  90. /**
  91. * The configuration directories for this test run.
  92. *
  93. * @var array
  94. */
  95. protected $configDirectories = [];
  96. /**
  97. * A KeyValueMemoryFactory instance to use when building the container.
  98. *
  99. * @var \Drupal\Core\KeyValueStore\KeyValueMemoryFactory
  100. */
  101. protected $keyValueFactory;
  102. /**
  103. * Array of registered stream wrappers.
  104. *
  105. * @var array
  106. */
  107. protected $streamWrappers = [];
  108. /**
  109. * {@inheritdoc}
  110. */
  111. public function __construct($test_id = NULL) {
  112. parent::__construct($test_id);
  113. $this->skipClasses[__CLASS__] = TRUE;
  114. }
  115. /**
  116. * {@inheritdoc}
  117. */
  118. protected function beforePrepareEnvironment() {
  119. // Copy/prime extension file lists once to avoid filesystem scans.
  120. if (!isset($this->moduleFiles)) {
  121. $this->moduleFiles = \Drupal::state()->get('system.module.files') ?: [];
  122. $this->themeFiles = \Drupal::state()->get('system.theme.files') ?: [];
  123. }
  124. }
  125. /**
  126. * Create and set new configuration directories.
  127. *
  128. * @see \Drupal\Core\Site\Settings::getConfigDirectory()
  129. *
  130. * @throws \RuntimeException
  131. * Thrown when the configuration sync directory cannot be created or made
  132. * writable.
  133. *
  134. * @return string
  135. * The config sync directory path.
  136. */
  137. protected function prepareConfigDirectories() {
  138. $this->configDirectories = [];
  139. // Assign the relative path to the global variable.
  140. $path = $this->siteDirectory . '/config_' . CONFIG_SYNC_DIRECTORY;
  141. // Ensure the directory can be created and is writable.
  142. if (!\Drupal::service('file_system')->prepareDirectory($path, FileSystemInterface::CREATE_DIRECTORY | FileSystemInterface::MODIFY_PERMISSIONS)) {
  143. throw new \RuntimeException("Failed to create '" . CONFIG_SYNC_DIRECTORY . "' config directory $path");
  144. }
  145. // Provide the already resolved path for tests.
  146. $this->configDirectories[CONFIG_SYNC_DIRECTORY] = $path;
  147. return $path;
  148. }
  149. /**
  150. * {@inheritdoc}
  151. */
  152. protected function setUp() {
  153. $this->keyValueFactory = new KeyValueMemoryFactory();
  154. // Back up settings from TestBase::prepareEnvironment().
  155. $settings = Settings::getAll();
  156. // Allow for test-specific overrides.
  157. $directory = DRUPAL_ROOT . '/' . $this->siteDirectory;
  158. $settings_services_file = DRUPAL_ROOT . '/' . $this->originalSite . '/testing.services.yml';
  159. $container_yamls = [];
  160. if (file_exists($settings_services_file)) {
  161. // Copy the testing-specific service overrides in place.
  162. $testing_services_file = $directory . '/services.yml';
  163. copy($settings_services_file, $testing_services_file);
  164. $container_yamls[] = $testing_services_file;
  165. }
  166. $settings_testing_file = DRUPAL_ROOT . '/' . $this->originalSite . '/settings.testing.php';
  167. if (file_exists($settings_testing_file)) {
  168. // Copy the testing-specific settings.php overrides in place.
  169. copy($settings_testing_file, $directory . '/settings.testing.php');
  170. }
  171. if (file_exists($directory . '/settings.testing.php')) {
  172. // Add the name of the testing class to settings.php and include the
  173. // testing specific overrides
  174. $hash_salt = Settings::getHashSalt();
  175. $test_class = get_class($this);
  176. $container_yamls_export = Variable::export($container_yamls);
  177. $php = <<<EOD
  178. <?php
  179. \$settings['hash_salt'] = '$hash_salt';
  180. \$settings['container_yamls'] = $container_yamls_export;
  181. \$test_class = '$test_class';
  182. include DRUPAL_ROOT . '/' . \$site_path . '/settings.testing.php';
  183. EOD;
  184. file_put_contents($directory . '/settings.php', $php);
  185. }
  186. // Add this test class as a service provider.
  187. // @todo Remove the indirection; implement ServiceProviderInterface instead.
  188. $GLOBALS['conf']['container_service_providers']['TestServiceProvider'] = TestServiceProvider::class;
  189. // Bootstrap a new kernel.
  190. $class_loader = require DRUPAL_ROOT . '/autoload.php';
  191. $this->kernel = new DrupalKernel('testing', $class_loader, FALSE);
  192. $request = Request::create('/');
  193. $site_path = DrupalKernel::findSitePath($request);
  194. $this->kernel->setSitePath($site_path);
  195. if (file_exists($directory . '/settings.testing.php')) {
  196. Settings::initialize(DRUPAL_ROOT, $site_path, $class_loader);
  197. }
  198. // Set the module list upfront to avoid setting the kernel into the
  199. // pre-installer mode.
  200. $this->kernel->updateModules([], []);
  201. $this->kernel->boot();
  202. // Ensure database install tasks have been run.
  203. require_once __DIR__ . '/../../../includes/install.inc';
  204. $connection = Database::getConnection();
  205. $errors = db_installer_object($connection->driver())->runTasks();
  206. if (!empty($errors)) {
  207. $this->fail('Failed to run installer database tasks: ' . implode(', ', $errors));
  208. }
  209. // Reboot the kernel because the container might contain a connection to the
  210. // database that has been closed during the database install tasks. This
  211. // prevents any services created during the first boot from having stale
  212. // database connections, for example, \Drupal\Core\Config\DatabaseStorage.
  213. $this->kernel->shutdown();
  214. // Set the module list upfront to avoid setting the kernel into the
  215. // pre-installer mode.
  216. $this->kernel->updateModules([], []);
  217. $this->kernel->boot();
  218. // Save the original site directory path, so that extensions in the
  219. // site-specific directory can still be discovered in the test site
  220. // environment.
  221. // @see \Drupal\Core\Extension\ExtensionDiscovery::scan()
  222. $settings['test_parent_site'] = $this->originalSite;
  223. // Create and set new configuration directories.
  224. $settings['config_sync_directory'] = $this->prepareConfigDirectories();
  225. // Restore and merge settings.
  226. // DrupalKernel::boot() initializes new Settings, and the containerBuild()
  227. // method sets additional settings.
  228. new Settings($settings + Settings::getAll());
  229. // Set the request scope.
  230. $this->container = $this->kernel->getContainer();
  231. $this->container->get('request_stack')->push($request);
  232. // Re-inject extension file listings into state, unless the key/value
  233. // service was overridden (in which case its storage does not exist yet).
  234. if ($this->container->get('keyvalue') instanceof KeyValueMemoryFactory) {
  235. $this->container->get('state')->set('system.module.files', $this->moduleFiles);
  236. $this->container->get('state')->set('system.theme.files', $this->themeFiles);
  237. }
  238. // Create a minimal core.extension configuration object so that the list of
  239. // enabled modules can be maintained allowing
  240. // \Drupal\Core\Config\ConfigInstaller::installDefaultConfig() to work.
  241. // Write directly to active storage to avoid early instantiation of
  242. // the event dispatcher which can prevent modules from registering events.
  243. \Drupal::service('config.storage')->write('core.extension', ['module' => [], 'theme' => [], 'profile' => '']);
  244. // Collect and set a fixed module list.
  245. $class = get_class($this);
  246. $modules = [];
  247. while ($class) {
  248. if (property_exists($class, 'modules')) {
  249. // Only add the modules, if the $modules property was not inherited.
  250. $rp = new \ReflectionProperty($class, 'modules');
  251. if ($rp->class == $class) {
  252. $modules[$class] = $class::$modules;
  253. }
  254. }
  255. $class = get_parent_class($class);
  256. }
  257. // Modules have been collected in reverse class hierarchy order; modules
  258. // defined by base classes should be sorted first. Then, merge the results
  259. // together.
  260. $modules = array_reverse($modules);
  261. $modules = call_user_func_array('array_merge_recursive', $modules);
  262. if ($modules) {
  263. $this->enableModules($modules);
  264. }
  265. // Tests based on this class are entitled to use Drupal's File and
  266. // StreamWrapper APIs.
  267. \Drupal::service('file_system')->prepareDirectory($this->publicFilesDirectory, FileSystemInterface::CREATE_DIRECTORY | FileSystemInterface::MODIFY_PERMISSIONS);
  268. $this->settingsSet('file_public_path', $this->publicFilesDirectory);
  269. $this->streamWrappers = [];
  270. $this->registerStreamWrapper('public', 'Drupal\Core\StreamWrapper\PublicStream');
  271. // The temporary stream wrapper is able to operate both with and without
  272. // configuration.
  273. $this->registerStreamWrapper('temporary', 'Drupal\Core\StreamWrapper\TemporaryStream');
  274. // Manually configure the test mail collector implementation to prevent
  275. // tests from sending out emails and collect them in state instead.
  276. // While this should be enforced via settings.php prior to installation,
  277. // some tests expect to be able to test mail system implementations.
  278. $GLOBALS['config']['system.mail']['interface']['default'] = 'test_mail_collector';
  279. }
  280. /**
  281. * {@inheritdoc}
  282. */
  283. protected function tearDown() {
  284. if ($this->kernel instanceof DrupalKernel) {
  285. $this->kernel->shutdown();
  286. }
  287. // Before tearing down the test environment, ensure that no stream wrapper
  288. // of this test leaks into the parent environment. Unlike all other global
  289. // state variables in Drupal, stream wrappers are a global state construct
  290. // of PHP core, which has to be maintained manually.
  291. // @todo Move StreamWrapper management into DrupalKernel.
  292. // @see https://www.drupal.org/node/2028109
  293. foreach ($this->streamWrappers as $scheme => $type) {
  294. $this->unregisterStreamWrapper($scheme, $type);
  295. }
  296. parent::tearDown();
  297. }
  298. /**
  299. * Sets up the base service container for this test.
  300. *
  301. * Extend this method in your test to register additional service overrides
  302. * that need to persist a DrupalKernel reboot. This method is called whenever
  303. * the kernel is rebuilt.
  304. *
  305. * @see \Drupal\simpletest\KernelTestBase::setUp()
  306. * @see \Drupal\simpletest\KernelTestBase::enableModules()
  307. * @see \Drupal\simpletest\KernelTestBase::disableModules()
  308. */
  309. public function containerBuild(ContainerBuilder $container) {
  310. // Keep the container object around for tests.
  311. $this->container = $container;
  312. // Set the default language on the minimal container.
  313. $this->container->setParameter('language.default_values', $this->defaultLanguageData());
  314. $container->register('lock', 'Drupal\Core\Lock\NullLockBackend');
  315. $container->register('cache_factory', 'Drupal\Core\Cache\MemoryBackendFactory');
  316. $container
  317. ->register('config.storage', 'Drupal\Core\Config\DatabaseStorage')
  318. ->addArgument(Database::getConnection())
  319. ->addArgument('config');
  320. if ($this->strictConfigSchema) {
  321. $container
  322. ->register('testing.config_schema_checker', ConfigSchemaChecker::class)
  323. ->addArgument(new Reference('config.typed'))
  324. ->addArgument($this->getConfigSchemaExclusions())
  325. ->addTag('event_subscriber');
  326. }
  327. $keyvalue_options = $container->getParameter('factory.keyvalue') ?: [];
  328. $keyvalue_options['default'] = 'keyvalue.memory';
  329. $container->setParameter('factory.keyvalue', $keyvalue_options);
  330. $container->set('keyvalue.memory', $this->keyValueFactory);
  331. if (!$container->has('keyvalue')) {
  332. // TestBase::setUp puts a completely empty container in
  333. // $this->container which is somewhat the mirror of the empty
  334. // environment being set up. Unit tests need not to waste time with
  335. // getting a container set up for them. Drupal Unit Tests might just get
  336. // away with a simple container holding the absolute bare minimum. When
  337. // a kernel is overridden then there's no need to re-register the keyvalue
  338. // service but when a test is happy with the superminimal container put
  339. // together here, it still might a keyvalue storage for anything using
  340. // \Drupal::state() -- that's why a memory service was added in the first
  341. // place.
  342. $container->register('settings', 'Drupal\Core\Site\Settings')
  343. ->setFactoryClass('Drupal\Core\Site\Settings')
  344. ->setFactoryMethod('getInstance');
  345. $container
  346. ->register('keyvalue', 'Drupal\Core\KeyValueStore\KeyValueFactory')
  347. ->addArgument(new Reference('service_container'))
  348. ->addArgument(new Parameter('factory.keyvalue'));
  349. $container->register('state', 'Drupal\Core\State\State')
  350. ->addArgument(new Reference('keyvalue'));
  351. }
  352. if ($container->hasDefinition('path_alias.path_processor')) {
  353. // The alias-based processor requires the path_alias entity schema to be
  354. // installed, so we prevent it from being registered to the path processor
  355. // manager. We do this by removing the tags that the compiler pass looks
  356. // for. This means that the URL generator can safely be used within tests.
  357. $definition = $container->getDefinition('path_alias.path_processor');
  358. $definition->clearTag('path_processor_inbound')->clearTag('path_processor_outbound');
  359. }
  360. if ($container->hasDefinition('password')) {
  361. $container->getDefinition('password')->setArguments([1]);
  362. }
  363. // Register the stream wrapper manager.
  364. $container
  365. ->register('stream_wrapper_manager', 'Drupal\Core\StreamWrapper\StreamWrapperManager')
  366. ->addArgument(new Reference('module_handler'))
  367. ->addMethodCall('setContainer', [new Reference('service_container')]);
  368. $request = Request::create('/');
  369. $container->get('request_stack')->push($request);
  370. }
  371. /**
  372. * Provides the data for setting the default language on the container.
  373. *
  374. * @return array
  375. * The data array for the default language.
  376. */
  377. protected function defaultLanguageData() {
  378. return Language::$defaultValues;
  379. }
  380. /**
  381. * Installs default configuration for a given list of modules.
  382. *
  383. * @param array $modules
  384. * A list of modules for which to install default configuration.
  385. *
  386. * @throws \RuntimeException
  387. * Thrown when any module listed in $modules is not enabled.
  388. */
  389. protected function installConfig(array $modules) {
  390. foreach ($modules as $module) {
  391. if (!$this->container->get('module_handler')->moduleExists($module)) {
  392. throw new \RuntimeException("'$module' module is not enabled");
  393. }
  394. \Drupal::service('config.installer')->installDefaultConfig('module', $module);
  395. }
  396. $this->pass(new FormattableMarkup('Installed default config: %modules.', [
  397. '%modules' => implode(', ', $modules),
  398. ]));
  399. }
  400. /**
  401. * Installs a specific table from a module schema definition.
  402. *
  403. * @param string $module
  404. * The name of the module that defines the table's schema.
  405. * @param string|array $tables
  406. * The name or an array of the names of the tables to install.
  407. *
  408. * @throws \RuntimeException
  409. * Thrown when $module is not enabled or when the table schema cannot be
  410. * found in the module specified.
  411. */
  412. protected function installSchema($module, $tables) {
  413. // drupal_get_module_schema() is technically able to install a schema
  414. // of a non-enabled module, but its ability to load the module's .install
  415. // file depends on many other factors. To prevent differences in test
  416. // behavior and non-reproducible test failures, we only allow the schema of
  417. // explicitly loaded/enabled modules to be installed.
  418. if (!$this->container->get('module_handler')->moduleExists($module)) {
  419. throw new \RuntimeException("'$module' module is not enabled");
  420. }
  421. $tables = (array) $tables;
  422. foreach ($tables as $table) {
  423. $schema = drupal_get_module_schema($module, $table);
  424. if (empty($schema)) {
  425. // BC layer to avoid some contrib tests to fail.
  426. // @todo Remove the BC layer before 8.1.x release.
  427. // @see https://www.drupal.org/node/2670360
  428. // @see https://www.drupal.org/node/2670454
  429. if ($module == 'system') {
  430. continue;
  431. }
  432. throw new \RuntimeException("Unknown '$table' table schema in '$module' module.");
  433. }
  434. $this->container->get('database')->schema()->createTable($table, $schema);
  435. }
  436. $this->pass(new FormattableMarkup('Installed %module tables: %tables.', [
  437. '%tables' => '{' . implode('}, {', $tables) . '}',
  438. '%module' => $module,
  439. ]));
  440. }
  441. /**
  442. * Installs the storage schema for a specific entity type.
  443. *
  444. * @param string $entity_type_id
  445. * The ID of the entity type.
  446. */
  447. protected function installEntitySchema($entity_type_id) {
  448. /** @var \Drupal\Core\Entity\EntityManagerInterface $entity_manager */
  449. $entity_manager = $this->container->get('entity.manager');
  450. $entity_type = $entity_manager->getDefinition($entity_type_id);
  451. $entity_manager->onEntityTypeCreate($entity_type);
  452. // For test runs, the most common storage backend is a SQL database. For
  453. // this case, ensure the tables got created.
  454. $storage = $entity_manager->getStorage($entity_type_id);
  455. if ($storage instanceof SqlEntityStorageInterface) {
  456. $tables = $storage->getTableMapping()->getTableNames();
  457. $db_schema = $this->container->get('database')->schema();
  458. $all_tables_exist = TRUE;
  459. foreach ($tables as $table) {
  460. if (!$db_schema->tableExists($table)) {
  461. $this->fail(new FormattableMarkup('Installed entity type table for the %entity_type entity type: %table', [
  462. '%entity_type' => $entity_type_id,
  463. '%table' => $table,
  464. ]));
  465. $all_tables_exist = FALSE;
  466. }
  467. }
  468. if ($all_tables_exist) {
  469. $this->pass(new FormattableMarkup('Installed entity type tables for the %entity_type entity type: %tables', [
  470. '%entity_type' => $entity_type_id,
  471. '%tables' => '{' . implode('}, {', $tables) . '}',
  472. ]));
  473. }
  474. }
  475. }
  476. /**
  477. * Enables modules for this test.
  478. *
  479. * To install test modules outside of the testing environment, add
  480. * @code
  481. * $settings['extension_discovery_scan_tests'] = TRUE;
  482. * @endcode
  483. * to your settings.php.
  484. *
  485. * @param array $modules
  486. * A list of modules to enable. Dependencies are not resolved; i.e.,
  487. * multiple modules have to be specified with dependent modules first.
  488. * The new modules are only added to the active module list and loaded.
  489. */
  490. protected function enableModules(array $modules) {
  491. // Perform an ExtensionDiscovery scan as this function may receive a
  492. // profile that is not the current profile, and we don't yet have a cached
  493. // way to receive inactive profile information.
  494. // @todo Remove as part of https://www.drupal.org/node/2186491
  495. $listing = new ExtensionDiscovery(\Drupal::root());
  496. $module_list = $listing->scan('module');
  497. // In ModuleHandlerTest we pass in a profile as if it were a module.
  498. $module_list += $listing->scan('profile');
  499. // Set the list of modules in the extension handler.
  500. $module_handler = $this->container->get('module_handler');
  501. // Write directly to active storage to avoid early instantiation of
  502. // the event dispatcher which can prevent modules from registering events.
  503. $active_storage = \Drupal::service('config.storage');
  504. $extensions = $active_storage->read('core.extension');
  505. foreach ($modules as $module) {
  506. $module_handler->addModule($module, $module_list[$module]->getPath());
  507. // Maintain the list of enabled modules in configuration.
  508. $extensions['module'][$module] = 0;
  509. }
  510. $active_storage->write('core.extension', $extensions);
  511. // Update the kernel to make their services available.
  512. $module_filenames = $module_handler->getModuleList();
  513. $this->kernel->updateModules($module_filenames, $module_filenames);
  514. // Ensure isLoaded() is TRUE in order to make
  515. // \Drupal\Core\Theme\ThemeManagerInterface::render() work.
  516. // Note that the kernel has rebuilt the container; this $module_handler is
  517. // no longer the $module_handler instance from above.
  518. $this->container->get('module_handler')->reload();
  519. $this->pass(new FormattableMarkup('Enabled modules: %modules.', [
  520. '%modules' => implode(', ', $modules),
  521. ]));
  522. }
  523. /**
  524. * Disables modules for this test.
  525. *
  526. * @param array $modules
  527. * A list of modules to disable. Dependencies are not resolved; i.e.,
  528. * multiple modules have to be specified with dependent modules first.
  529. * Code of previously active modules is still loaded. The modules are only
  530. * removed from the active module list.
  531. */
  532. protected function disableModules(array $modules) {
  533. // Unset the list of modules in the extension handler.
  534. $module_handler = $this->container->get('module_handler');
  535. $module_filenames = $module_handler->getModuleList();
  536. $extension_config = $this->config('core.extension');
  537. foreach ($modules as $module) {
  538. unset($module_filenames[$module]);
  539. $extension_config->clear('module.' . $module);
  540. }
  541. $extension_config->save();
  542. $module_handler->setModuleList($module_filenames);
  543. $module_handler->resetImplementations();
  544. // Update the kernel to remove their services.
  545. $this->kernel->updateModules($module_filenames, $module_filenames);
  546. // Ensure isLoaded() is TRUE in order to make
  547. // \Drupal\Core\Theme\ThemeManagerInterface::render() work.
  548. // Note that the kernel has rebuilt the container; this $module_handler is
  549. // no longer the $module_handler instance from above.
  550. $module_handler = $this->container->get('module_handler');
  551. $module_handler->reload();
  552. $this->pass(new FormattableMarkup('Disabled modules: %modules.', [
  553. '%modules' => implode(', ', $modules),
  554. ]));
  555. }
  556. /**
  557. * Registers a stream wrapper for this test.
  558. *
  559. * @param string $scheme
  560. * The scheme to register.
  561. * @param string $class
  562. * The fully qualified class name to register.
  563. * @param int $type
  564. * The Drupal Stream Wrapper API type. Defaults to
  565. * StreamWrapperInterface::NORMAL.
  566. */
  567. protected function registerStreamWrapper($scheme, $class, $type = StreamWrapperInterface::NORMAL) {
  568. $this->container->get('stream_wrapper_manager')->registerWrapper($scheme, $class, $type);
  569. }
  570. /**
  571. * Renders a render array.
  572. *
  573. * @param array $elements
  574. * The elements to render.
  575. *
  576. * @return string
  577. * The rendered string output (typically HTML).
  578. */
  579. protected function render(array &$elements) {
  580. // Use the bare HTML page renderer to render our links.
  581. $renderer = $this->container->get('bare_html_page_renderer');
  582. $response = $renderer->renderBarePage($elements, '', 'maintenance_page');
  583. // Glean the content from the response object.
  584. $content = $response->getContent();
  585. $this->setRawContent($content);
  586. $this->verbose('<pre style="white-space: pre-wrap">' . Html::escape($content));
  587. return $content;
  588. }
  589. }