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