KernelTestBase.php 25 KB

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