diff --git a/core/lib/Drupal/Core/DrupalKernel.php b/core/lib/Drupal/Core/DrupalKernel.php index 2b788dccac4bf615837524a8f3585a6870e45fbc..9603d38d99a8649874342c3f8779abf746d02387 100644 --- a/core/lib/Drupal/Core/DrupalKernel.php +++ b/core/lib/Drupal/Core/DrupalKernel.php @@ -116,13 +116,6 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface { */ protected $allowDumping; - /** - * Whether the container can be loaded. - * - * @var bool - */ - protected $allowLoading; - /** * Whether the container needs to be dumped once booting is complete. * @@ -178,16 +171,12 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface { * @param bool $allow_dumping * (optional) FALSE to stop the container from being written to or read * from disk. Defaults to TRUE. - * @param bool $allow_loading - * (optional) FALSE to prevent the kernel attempting to read the dependency - * injection container from disk. Defaults to $allow_dumping. */ - public function __construct($environment, ClassLoader $class_loader, $allow_dumping = TRUE, $allow_loading = NULL) { + public function __construct($environment, ClassLoader $class_loader, $allow_dumping = TRUE) { $this->environment = $environment; $this->booted = FALSE; $this->classLoader = $class_loader; $this->allowDumping = $allow_dumping; - $this->allowLoading = isset($allow_loading) ? $allow_loading : $allow_dumping; } /** @@ -355,12 +344,6 @@ public function updateModules(array $module_list, array $module_filenames = arra foreach ($module_filenames as $name => $extension) { $this->moduleData[$name] = $extension; } - - // This method is called whenever the list of modules changed. Therefore - // disable loading of a dumped container from the disk, because it is - // guaranteed to be out of date and needs to be rebuilt anyway. - $this->allowLoading = FALSE; - // If we haven't yet booted, we don't need to do anything: the new module // list will take effect when boot() is called. If we have already booted, // then reboot in order to refresh the serviceProvider list and container. @@ -414,7 +397,7 @@ protected function initializeContainer() { $class = $this->getClassName(); $cache_file = $class . '.php'; - if ($this->allowLoading) { + if ($this->allowDumping) { // First, try to load. if (!class_exists($class, FALSE)) { $this->storage()->load($cache_file); @@ -434,12 +417,33 @@ protected function initializeContainer() { $this->moduleList = $this->newModuleList; unset($this->newModuleList); } + // Second, check if some other request -- for example on another web + // frontend or during the installer -- changed the list of enabled modules. if (isset($this->container)) { // All namespaces must be registered before we attempt to use any service // from the container. + $container_modules = $this->container->getParameter('container.modules'); + $namespaces_before = $this->classLoader->getPrefixesPsr4(); $this->registerNamespacesPsr4($this->container->getParameter('container.namespaces')); + + // If 'container.modules' is wrong, the container must be rebuilt. + if (!isset($this->moduleList)) { + $this->moduleList = $this->container->get('config.factory')->get('core.extension')->get('module') ?: array(); + } + if (array_keys($this->moduleList) !== array_keys($container_modules)) { + $persist = $this->getServicesToPersist(); + unset($this->container); + // Revert the class loader to its prior state. However, + // registerNamespaces() performs a merge rather than replace, so to + // effectively remove erroneous registrations, we must replace them with + // empty arrays. + $namespaces_after = $this->classLoader->getPrefixesPsr4(); + $namespaces_before += array_fill_keys(array_diff(array_keys($namespaces_after), array_keys($namespaces_before)), array()); + $this->registerNamespacesPsr4($namespaces_before); + } } - else { + + if (!isset($this->container)) { $this->container = $this->buildContainer(); $this->persistServices($persist); diff --git a/core/modules/hal/lib/Drupal/hal/Tests/NormalizerTestBase.php b/core/modules/hal/lib/Drupal/hal/Tests/NormalizerTestBase.php index 407e4e337ea11c9a2cc9358cc66e93b2ab5ca3d4..98d24ce57aac48f29c1a35dc7ada7f4cb736845c 100644 --- a/core/modules/hal/lib/Drupal/hal/Tests/NormalizerTestBase.php +++ b/core/modules/hal/lib/Drupal/hal/Tests/NormalizerTestBase.php @@ -57,7 +57,7 @@ abstract class NormalizerTestBase extends DrupalUnitTestBase { protected $entityClass = 'Drupal\entity_test\Entity\EntityTest'; /** - * {@inheritdoc} + * Overrides \Drupal\simpletest\DrupalUnitTestBase::setup(). */ function setUp() { parent::setUp(); diff --git a/core/modules/simpletest/lib/Drupal/simpletest/DrupalUnitTestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/DrupalUnitTestBase.php index 21c53d9820aded567fa65aac96ebf512131aafa7..d6bd64fc6961c66752502fa2c5b4889476449f3c 100644 --- a/core/modules/simpletest/lib/Drupal/simpletest/DrupalUnitTestBase.php +++ b/core/modules/simpletest/lib/Drupal/simpletest/DrupalUnitTestBase.php @@ -7,11 +7,462 @@ namespace Drupal\simpletest; +use Drupal\Core\DependencyInjection\ContainerBuilder; +use Drupal\Core\DrupalKernel; +use Drupal\Core\KeyValueStore\KeyValueMemoryFactory; +use Drupal\Core\Language\Language; +use Symfony\Component\DependencyInjection\Reference; +use Drupal\Core\Database\Database; +use Symfony\Component\HttpFoundation\Request; + /** - * Base class for integration tests. + * Base test case class for Drupal unit tests. + * + * Tests extending this base class can access files and the database, but the + * entire environment is initially empty. Drupal runs in a minimal mocked + * environment, comparable to the one in the installer or update.php. + * + * The module/hook system is functional and operates on a fixed module list. + * Additional modules needed in a test may be loaded and added to the fixed + * module list. * - * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0. - * Use \Drupal\simpletest\KernelTestBase. + * @see \DrupalUnitTestBase::$modules + * @see \DrupalUnitTestBase::enableModules() */ -abstract class DrupalUnitTestBase extends KernelTestBase { +abstract class DrupalUnitTestBase extends UnitTestBase { + + /** + * Modules to enable. + * + * Test classes extending this class, and any classes in the hierarchy up to + * this class, may specify individual lists of modules to enable by setting + * this property. The values of all properties in all classes in the hierarchy + * are merged. + * + * Unlike UnitTestBase::setUp(), any modules specified in the $modules + * property are automatically loaded and set as the fixed module list. + * + * Unlike WebTestBase::setUp(), the specified modules are loaded only, but not + * automatically installed. Modules need to be installed manually, if needed. + * + * @see \DrupalUnitTestBase::enableModules() + * @see \DrupalUnitTestBase::setUp() + * + * @var array + */ + public static $modules = array(); + + private $moduleFiles; + private $themeFiles; + + /** + * The configuration directories for this test run. + * + * @var array + */ + protected $configDirectories = array(); + + /** + * A KeyValueMemoryFactory instance to use when building the container. + * + * @var \Drupal\Core\KeyValueStore\KeyValueMemoryFactory. + */ + protected $keyValueFactory; + + /** + * A list of stream wrappers that have been registered for this test. + * + * @see \Drupal\simpletest\DrupalUnitTestBase::registerStreamWrapper() + * + * @var array + */ + private $streamWrappers = array(); + + /** + * Overrides \Drupal\simpletest\UnitTestBase::__construct(). + */ + function __construct($test_id = NULL) { + parent::__construct($test_id); + $this->skipClasses[__CLASS__] = TRUE; + } + + /** + * Overrides TestBase::beforePrepareEnvironment(). + */ + protected function beforePrepareEnvironment() { + // Copy/prime extension file lists once to avoid filesystem scans. + if (!isset($this->moduleFiles)) { + $this->moduleFiles = \Drupal::state()->get('system.module.files') ?: array(); + $this->themeFiles = \Drupal::state()->get('system.theme.files') ?: array(); + } + } + + /** + * Create and set new configuration directories. + * + * @see config_get_config_directory() + */ + protected function prepareConfigDirectories() { + $this->configDirectories = array(); + include_once DRUPAL_ROOT . '/core/includes/install.inc'; + foreach (array(CONFIG_ACTIVE_DIRECTORY, CONFIG_STAGING_DIRECTORY) as $type) { + // Assign the relative path to the global variable. + $path = $this->siteDirectory . '/config_' . $type; + $GLOBALS['config_directories'][$type] = $path; + // Ensure the directory can be created and is writeable. + if (!install_ensure_config_directory($type)) { + throw new \RuntimeException("Failed to create '$type' config directory $path"); + } + // Provide the already resolved path for tests. + $this->configDirectories[$type] = $path; + } + } + + /** + * Sets up Drupal unit test environment. + */ + protected function setUp() { + $this->keyValueFactory = new KeyValueMemoryFactory(); + + parent::setUp(); + + // Create and set new configuration directories. + $this->prepareConfigDirectories(); + + // Build a minimal, partially mocked environment for unit tests. + $this->containerBuild(\Drupal::getContainer()); + // Make sure it survives kernel rebuilds. + $GLOBALS['conf']['container_service_providers']['TestServiceProvider'] = 'Drupal\simpletest\TestServiceProvider'; + + \Drupal::state()->set('system.module.files', $this->moduleFiles); + \Drupal::state()->set('system.theme.files', $this->themeFiles); + + // Bootstrap the kernel. + // No need to dump it; this test runs in-memory. + $this->kernel = new DrupalKernel('unit_testing', drupal_classloader(), FALSE); + $this->kernel->boot(); + + $request = Request::create('/'); + $this->container->set('request', $request); + $this->container->get('request_stack')->push($request); + + // Create a minimal core.extension configuration object so that the list of + // enabled modules can be maintained allowing + // \Drupal\Core\Config\ConfigInstaller::installDefaultConfig() to work. + // Write directly to active storage to avoid early instantiation of + // the event dispatcher which can prevent modules from registering events. + \Drupal::service('config.storage')->write('core.extension', array('module' => array(), 'theme' => array())); + + // Collect and set a fixed module list. + $class = get_class($this); + $modules = array(); + while ($class) { + if (property_exists($class, 'modules')) { + // Only add the modules, if the $modules property was not inherited. + $rp = new \ReflectionProperty($class, 'modules'); + if ($rp->class == $class) { + $modules[$class] = $class::$modules; + } + } + $class = get_parent_class($class); + } + // Modules have been collected in reverse class hierarchy order; modules + // defined by base classes should be sorted first. Then, merge the results + // together. + if ($modules) { + $modules = array_reverse($modules); + $modules = call_user_func_array('array_merge_recursive', $modules); + $this->enableModules($modules, FALSE); + } + // In order to use theme functions default theme config needs to exist. + \Drupal::config('system.theme')->set('default', 'stark'); + + // Tests based on this class are entitled to use Drupal's File and + // StreamWrapper APIs. + // @todo Move StreamWrapper management into DrupalKernel. + // @see https://drupal.org/node/2028109 + $this->streamWrappers = array(); + // The public stream wrapper only depends on the file_public_path setting, + // which is provided by UnitTestBase::setUp(). + $this->registerStreamWrapper('public', 'Drupal\Core\StreamWrapper\PublicStream'); + // The temporary stream wrapper is able to operate both with and without + // configuration. + $this->registerStreamWrapper('temporary', 'Drupal\Core\StreamWrapper\TemporaryStream'); + } + + protected function tearDown() { + if ($this->kernel instanceof DrupalKernel) { + $this->kernel->shutdown(); + } + // Before tearing down the test environment, ensure that no stream wrapper + // of this test leaks into the parent environment. Unlike all other global + // state variables in Drupal, stream wrappers are a global state construct + // of PHP core, which has to be maintained manually. + // @todo Move StreamWrapper management into DrupalKernel. + // @see https://drupal.org/node/2028109 + foreach ($this->streamWrappers as $scheme => $type) { + $this->unregisterStreamWrapper($scheme, $type); + } + parent::tearDown(); + } + + /** + * Sets up the base service container for this test. + * + * Extend this method in your test to register additional service overrides + * that need to persist a DrupalKernel reboot. This method is called whenever + * the kernel is rebuilt. + * + * @see \DrupalUnitTestBase::setUp() + * @see \DrupalUnitTestBase::enableModules() + * @see \DrupalUnitTestBase::disableModules() + */ + public function containerBuild(ContainerBuilder $container) { + // Keep the container object around for tests. + $this->container = $container; + + // Set the default language on the minimal container. + $this->container->setParameter('language.default_values', Language::$defaultValues); + + $container->register('lock', 'Drupal\Core\Lock\NullLockBackend'); + $container->register('cache_factory', 'Drupal\Core\Cache\MemoryBackendFactory'); + + $container + ->register('config.storage.active', 'Drupal\Core\Config\DatabaseStorage') + ->addArgument(Database::getConnection()) + ->addArgument('config'); + + $this->settingsSet('keyvalue_default', 'keyvalue.memory'); + $container->set('keyvalue.memory', $this->keyValueFactory); + if (!$container->has('keyvalue')) { + // TestBase::setUp puts a completely empty container in + // $this->container which is somewhat the mirror of the empty + // environment being set up. Unit tests need not to waste time with + // getting a container set up for them. Drupal Unit Tests might just get + // away with a simple container holding the absolute bare minimum. When + // a kernel is overridden then there's no need to re-register the keyvalue + // service but when a test is happy with the superminimal container put + // together here, it still might a keyvalue storage for anything using + // \Drupal::state() -- that's why a memory service was added in the first + // place. + $container->register('settings', 'Drupal\Core\Site\Settings') + ->setFactoryClass('Drupal\Core\Site\Settings') + ->setFactoryMethod('getInstance'); + + $container + ->register('keyvalue', 'Drupal\Core\KeyValueStore\KeyValueFactory') + ->addArgument(new Reference('service_container')) + ->addArgument(new Reference('settings')); + + $container->register('state', 'Drupal\Core\State\State') + ->addArgument(new Reference('keyvalue')); + } + + if ($container->hasDefinition('path_processor_alias')) { + // Prevent the alias-based path processor, which requires a url_alias db + // table, from being registered to the path processor manager. We do this + // by removing the tags that the compiler pass looks for. This means the + // url generator can safely be used within DUTB tests. + $definition = $container->getDefinition('path_processor_alias'); + $definition->clearTag('path_processor_inbound')->clearTag('path_processor_outbound'); + } + + if ($container->hasDefinition('password')) { + $container->getDefinition('password')->setArguments(array(1)); + } + + $request = Request::create('/'); + $this->container->set('request', $request); + } + + /** + * Installs default configuration for a given list of modules. + * + * @param array $modules + * A list of modules for which to install default configuration. + */ + protected function installConfig(array $modules) { + foreach ($modules as $module) { + if (!$this->container->get('module_handler')->moduleExists($module)) { + throw new \RuntimeException(format_string("'@module' module is not enabled.", array( + '@module' => $module, + ))); + } + \Drupal::service('config.installer')->installDefaultConfig('module', $module); + } + $this->pass(format_string('Installed default config: %modules.', array( + '%modules' => implode(', ', $modules), + ))); + } + + /** + * Installs a specific table from a module schema definition. + * + * @param string $module + * The name of the module that defines the table's schema. + * @param string|array $tables + * The name or an array of the names of the tables to install. + */ + protected function installSchema($module, $tables) { + // drupal_get_schema_unprocessed() is technically able to install a schema + // of a non-enabled module, but its ability to load the module's .install + // file depends on many other factors. To prevent differences in test + // behavior and non-reproducible test failures, we only allow the schema of + // explicitly loaded/enabled modules to be installed. + if (!$this->container->get('module_handler')->moduleExists($module)) { + throw new \RuntimeException(format_string("'@module' module is not enabled.", array( + '@module' => $module, + ))); + } + $tables = (array) $tables; + foreach ($tables as $table) { + $schema = drupal_get_schema_unprocessed($module, $table); + if (empty($schema)) { + throw new \RuntimeException(format_string("Unknown '@table' table schema in '@module' module.", array( + '@module' => $module, + '@table' => $table, + ))); + } + $this->container->get('database')->schema()->createTable($table, $schema); + } + // We need to refresh the schema cache, as any call to drupal_get_schema() + // would not know of/return the schema otherwise. + // @todo Refactor Schema API to make this obsolete. + drupal_get_schema(NULL, TRUE); + $this->pass(format_string('Installed %module tables: %tables.', array( + '%tables' => '{' . implode('}, {', $tables) . '}', + '%module' => $module, + ))); + } + + /** + * Enables modules for this test. + * + * @param array $modules + * A list of modules to enable. Dependencies are not resolved; i.e., + * multiple modules have to be specified with dependent modules first. + * The new modules are only added to the active module list and loaded. + */ + protected function enableModules(array $modules) { + // Set the list of modules in the extension handler. + $module_handler = $this->container->get('module_handler'); + + // Write directly to active storage to avoid early instantiation of + // the event dispatcher which can prevent modules from registering events. + $active_storage = \Drupal::service('config.storage'); + $extensions = $active_storage->read('core.extension'); + + foreach ($modules as $module) { + $module_handler->addModule($module, drupal_get_path('module', $module)); + // Maintain the list of enabled modules in configuration. + $extensions['module'][$module] = 0; + } + $active_storage->write('core.extension', $extensions); + + // Update the kernel to make their services available. + $module_filenames = $module_handler->getModuleList(); + $this->kernel->updateModules($module_filenames, $module_filenames); + + // Ensure isLoaded() is TRUE in order to make _theme() work. + // Note that the kernel has rebuilt the container; this $module_handler is + // no longer the $module_handler instance from above. + $module_handler = $this->container->get('module_handler'); + $module_handler->reload(); + $this->pass(format_string('Enabled modules: %modules.', array( + '%modules' => implode(', ', $modules), + ))); + } + + /** + * Disables modules for this test. + * + * @param array $modules + * A list of modules to disable. Dependencies are not resolved; i.e., + * multiple modules have to be specified with dependent modules first. + * Code of previously active modules is still loaded. The modules are only + * removed from the active module list. + */ + protected function disableModules(array $modules) { + // Unset the list of modules in the extension handler. + $module_handler = $this->container->get('module_handler'); + $module_filenames = $module_handler->getModuleList(); + $extension_config = $this->container->get('config.factory')->get('core.extension'); + foreach ($modules as $module) { + unset($module_filenames[$module]); + $extension_config->clear('module.' . $module); + } + $extension_config->save(); + $module_handler->setModuleList($module_filenames); + $module_handler->resetImplementations(); + // Update the kernel to remove their services. + $this->kernel->updateModules($module_filenames, $module_filenames); + + // Ensure isLoaded() is TRUE in order to make _theme() work. + // Note that the kernel has rebuilt the container; this $module_handler is + // no longer the $module_handler instance from above. + $module_handler = $this->container->get('module_handler'); + $module_handler->reload(); + $this->pass(format_string('Disabled modules: %modules.', array( + '%modules' => implode(', ', $modules), + ))); + } + + /** + * Registers a stream wrapper for this test. + * + * @param string $scheme + * The scheme to register. + * @param string $class + * The fully qualified class name to register. + * @param int $type + * The Drupal Stream Wrapper API type. Defaults to + * STREAM_WRAPPERS_LOCAL_NORMAL. + */ + protected function registerStreamWrapper($scheme, $class, $type = STREAM_WRAPPERS_LOCAL_NORMAL) { + if (isset($this->streamWrappers[$scheme])) { + $this->unregisterStreamWrapper($scheme); + } + $this->streamWrappers[$scheme] = $type; + if (($type & STREAM_WRAPPERS_LOCAL) == STREAM_WRAPPERS_LOCAL) { + stream_wrapper_register($scheme, $class); + } + else { + stream_wrapper_register($scheme, $class, STREAM_IS_URL); + } + // @todo Revamp Drupal's stream wrapper API for D8. + // @see https://drupal.org/node/2028109 + $wrappers = &drupal_static('file_get_stream_wrappers', array()); + $wrappers[STREAM_WRAPPERS_ALL][$scheme] = array( + 'type' => $type, + 'class' => $class, + ); + if (($type & STREAM_WRAPPERS_WRITE_VISIBLE) == STREAM_WRAPPERS_WRITE_VISIBLE) { + $wrappers[STREAM_WRAPPERS_WRITE_VISIBLE][$scheme] = $wrappers[STREAM_WRAPPERS_ALL][$scheme]; + } + } + + /** + * Unregisters a stream wrapper previously registered by this test. + * + * DrupalUnitTestBase::tearDown() automatically cleans up all registered + * stream wrappers, so this usually does not have to be called manually. + * + * @param string $scheme + * The scheme to unregister. + * @param int $type + * The Drupal Stream Wrapper API type of the scheme to unregister. + */ + protected function unregisterStreamWrapper($scheme, $type) { + stream_wrapper_unregister($scheme); + unset($this->streamWrappers[$scheme]); + // @todo Revamp Drupal's stream wrapper API for D8. + // @see https://drupal.org/node/2028109 + $wrappers = &drupal_static('file_get_stream_wrappers', array()); + foreach ($wrappers as $filter => $schemes) { + if (is_int($filter) && (($filter & $type) == $filter)) { + unset($wrappers[$filter][$scheme]); + } + } + } + } diff --git a/core/modules/simpletest/lib/Drupal/simpletest/KernelTestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/KernelTestBase.php deleted file mode 100644 index 1188822ea574e735fc8788d45b40964c6ed642b8..0000000000000000000000000000000000000000 --- a/core/modules/simpletest/lib/Drupal/simpletest/KernelTestBase.php +++ /dev/null @@ -1,471 +0,0 @@ -<?php - -/** - * @file - * Contains \Drupal\simpletest\KernelTestBase. - */ - -namespace Drupal\simpletest; - -use Drupal\Core\Database\Database; -use Drupal\Core\DependencyInjection\ContainerBuilder; -use Drupal\Core\DrupalKernel; -use Drupal\Core\KeyValueStore\KeyValueMemoryFactory; -use Drupal\Core\Language\Language; -use Symfony\Component\DependencyInjection\Reference; -use Symfony\Component\HttpFoundation\Request; - -/** - * Base class for integration tests. - * - * Tests extending this base class can access files and the database, but the - * entire environment is initially empty. Drupal runs in a minimal mocked - * environment, comparable to the one in the early installer. - * - * The module/hook system is functional and operates on a fixed module list. - * Additional modules needed in a test may be loaded and added to the fixed - * module list. - * - * @see \Drupal\simpletest\KernelTestBase::$modules - * @see \Drupal\simpletest\KernelTestBase::enableModules() - */ -abstract class KernelTestBase extends UnitTestBase { - - /** - * Modules to enable. - * - * Test classes extending this class, and any classes in the hierarchy up to - * this class, may specify individual lists of modules to enable by setting - * this property. The values of all properties in all classes in the hierarchy - * are merged. - * - * Unlike UnitTestBase::setUp(), any modules specified in the $modules - * property are automatically loaded and set as the fixed module list. - * - * Unlike WebTestBase::setUp(), the specified modules are loaded only, but not - * automatically installed. Modules need to be installed manually, if needed. - * - * @see \Drupal\simpletest\KernelTestBase::enableModules() - * @see \Drupal\simpletest\KernelTestBase::setUp() - * - * @var array - */ - public static $modules = array(); - - private $moduleFiles; - private $themeFiles; - - /** - * The configuration directories for this test run. - * - * @var array - */ - protected $configDirectories = array(); - - /** - * A KeyValueMemoryFactory instance to use when building the container. - * - * @var \Drupal\Core\KeyValueStore\KeyValueMemoryFactory. - */ - protected $keyValueFactory; - - /** - * A list of stream wrappers that have been registered for this test. - * - * @see \Drupal\simpletest\KernelTestBase::registerStreamWrapper() - * - * @var array - */ - private $streamWrappers = array(); - - /** - * {@inheritdoc} - */ - function __construct($test_id = NULL) { - parent::__construct($test_id); - $this->skipClasses[__CLASS__] = TRUE; - } - - /** - * {@inheritdoc} - */ - protected function beforePrepareEnvironment() { - // Copy/prime extension file lists once to avoid filesystem scans. - if (!isset($this->moduleFiles)) { - $this->moduleFiles = \Drupal::state()->get('system.module.files') ?: array(); - $this->themeFiles = \Drupal::state()->get('system.theme.files') ?: array(); - } - } - - /** - * Create and set new configuration directories. - * - * @see config_get_config_directory() - */ - protected function prepareConfigDirectories() { - $this->configDirectories = array(); - include_once DRUPAL_ROOT . '/core/includes/install.inc'; - foreach (array(CONFIG_ACTIVE_DIRECTORY, CONFIG_STAGING_DIRECTORY) as $type) { - // Assign the relative path to the global variable. - $path = $this->siteDirectory . '/config_' . $type; - $GLOBALS['config_directories'][$type] = $path; - // Ensure the directory can be created and is writeable. - if (!install_ensure_config_directory($type)) { - throw new \RuntimeException("Failed to create '$type' config directory $path"); - } - // Provide the already resolved path for tests. - $this->configDirectories[$type] = $path; - } - } - - /** - * {@inheritdoc} - */ - protected function setUp() { - $this->keyValueFactory = new KeyValueMemoryFactory(); - - parent::setUp(); - - // Create and set new configuration directories. - $this->prepareConfigDirectories(); - - // Build a minimal, partially mocked environment for unit tests. - $this->containerBuild(\Drupal::getContainer()); - // Make sure it survives kernel rebuilds. - $GLOBALS['conf']['container_service_providers']['TestServiceProvider'] = 'Drupal\simpletest\TestServiceProvider'; - - \Drupal::state()->set('system.module.files', $this->moduleFiles); - \Drupal::state()->set('system.theme.files', $this->themeFiles); - - // Bootstrap the kernel. - // No need to dump it; this test runs in-memory. - $this->kernel = new DrupalKernel('unit_testing', drupal_classloader(), FALSE); - $this->kernel->boot(); - - $request = Request::create('/'); - $this->container->set('request', $request); - $this->container->get('request_stack')->push($request); - - // Create a minimal core.extension configuration object so that the list of - // enabled modules can be maintained allowing - // \Drupal\Core\Config\ConfigInstaller::installDefaultConfig() to work. - // Write directly to active storage to avoid early instantiation of - // the event dispatcher which can prevent modules from registering events. - \Drupal::service('config.storage')->write('core.extension', array('module' => array(), 'theme' => array())); - - // Collect and set a fixed module list. - $class = get_class($this); - $modules = array(); - while ($class) { - if (property_exists($class, 'modules')) { - // Only add the modules, if the $modules property was not inherited. - $rp = new \ReflectionProperty($class, 'modules'); - if ($rp->class == $class) { - $modules[$class] = $class::$modules; - } - } - $class = get_parent_class($class); - } - // Modules have been collected in reverse class hierarchy order; modules - // defined by base classes should be sorted first. Then, merge the results - // together. - if ($modules) { - $modules = array_reverse($modules); - $modules = call_user_func_array('array_merge_recursive', $modules); - $this->enableModules($modules, FALSE); - } - // In order to use theme functions default theme config needs to exist. - \Drupal::config('system.theme')->set('default', 'stark'); - - // Tests based on this class are entitled to use Drupal's File and - // StreamWrapper APIs. - // @todo Move StreamWrapper management into DrupalKernel. - // @see https://drupal.org/node/2028109 - $this->streamWrappers = array(); - // The public stream wrapper only depends on the file_public_path setting, - // which is provided by UnitTestBase::setUp(). - $this->registerStreamWrapper('public', 'Drupal\Core\StreamWrapper\PublicStream'); - // The temporary stream wrapper is able to operate both with and without - // configuration. - $this->registerStreamWrapper('temporary', 'Drupal\Core\StreamWrapper\TemporaryStream'); - } - - /** - * {@inheritdoc} - */ - protected function tearDown() { - if ($this->kernel instanceof DrupalKernel) { - $this->kernel->shutdown(); - } - // Before tearing down the test environment, ensure that no stream wrapper - // of this test leaks into the parent environment. Unlike all other global - // state variables in Drupal, stream wrappers are a global state construct - // of PHP core, which has to be maintained manually. - // @todo Move StreamWrapper management into DrupalKernel. - // @see https://drupal.org/node/2028109 - foreach ($this->streamWrappers as $scheme => $type) { - $this->unregisterStreamWrapper($scheme, $type); - } - parent::tearDown(); - } - - /** - * Sets up the base service container for this test. - * - * Extend this method in your test to register additional service overrides - * that need to persist a DrupalKernel reboot. This method is called whenever - * the kernel is rebuilt. - * - * @see \Drupal\simpletest\KernelTestBase::setUp() - * @see \Drupal\simpletest\KernelTestBase::enableModules() - * @see \Drupal\simpletest\KernelTestBase::disableModules() - */ - public function containerBuild(ContainerBuilder $container) { - // Keep the container object around for tests. - $this->container = $container; - - // Set the default language on the minimal container. - $this->container->setParameter('language.default_values', Language::$defaultValues); - - $container->register('lock', 'Drupal\Core\Lock\NullLockBackend'); - $container->register('cache_factory', 'Drupal\Core\Cache\MemoryBackendFactory'); - - $container - ->register('config.storage.active', 'Drupal\Core\Config\DatabaseStorage') - ->addArgument(Database::getConnection()) - ->addArgument('config'); - - $this->settingsSet('keyvalue_default', 'keyvalue.memory'); - $container->set('keyvalue.memory', $this->keyValueFactory); - if (!$container->has('keyvalue')) { - // TestBase::setUp puts a completely empty container in - // $this->container which is somewhat the mirror of the empty - // environment being set up. Unit tests need not to waste time with - // getting a container set up for them. Drupal Unit Tests might just get - // away with a simple container holding the absolute bare minimum. When - // a kernel is overridden then there's no need to re-register the keyvalue - // service but when a test is happy with the superminimal container put - // together here, it still might a keyvalue storage for anything using - // \Drupal::state() -- that's why a memory service was added in the first - // place. - $container->register('settings', 'Drupal\Core\Site\Settings') - ->setFactoryClass('Drupal\Core\Site\Settings') - ->setFactoryMethod('getInstance'); - - $container - ->register('keyvalue', 'Drupal\Core\KeyValueStore\KeyValueFactory') - ->addArgument(new Reference('service_container')) - ->addArgument(new Reference('settings')); - - $container->register('state', 'Drupal\Core\State\State') - ->addArgument(new Reference('keyvalue')); - } - - if ($container->hasDefinition('path_processor_alias')) { - // Prevent the alias-based path processor, which requires a url_alias db - // table, from being registered to the path processor manager. We do this - // by removing the tags that the compiler pass looks for. This means the - // url generator can safely be used within tests. - $definition = $container->getDefinition('path_processor_alias'); - $definition->clearTag('path_processor_inbound')->clearTag('path_processor_outbound'); - } - - if ($container->hasDefinition('password')) { - $container->getDefinition('password')->setArguments(array(1)); - } - - $request = Request::create('/'); - $this->container->set('request', $request); - } - - /** - * Installs default configuration for a given list of modules. - * - * @param array $modules - * A list of modules for which to install default configuration. - */ - protected function installConfig(array $modules) { - foreach ($modules as $module) { - if (!$this->container->get('module_handler')->moduleExists($module)) { - throw new \RuntimeException(format_string("'@module' module is not enabled.", array( - '@module' => $module, - ))); - } - \Drupal::service('config.installer')->installDefaultConfig('module', $module); - } - $this->pass(format_string('Installed default config: %modules.', array( - '%modules' => implode(', ', $modules), - ))); - } - - /** - * Installs a specific table from a module schema definition. - * - * @param string $module - * The name of the module that defines the table's schema. - * @param string|array $tables - * The name or an array of the names of the tables to install. - */ - protected function installSchema($module, $tables) { - // drupal_get_schema_unprocessed() is technically able to install a schema - // of a non-enabled module, but its ability to load the module's .install - // file depends on many other factors. To prevent differences in test - // behavior and non-reproducible test failures, we only allow the schema of - // explicitly loaded/enabled modules to be installed. - if (!$this->container->get('module_handler')->moduleExists($module)) { - throw new \RuntimeException(format_string("'@module' module is not enabled.", array( - '@module' => $module, - ))); - } - $tables = (array) $tables; - foreach ($tables as $table) { - $schema = drupal_get_schema_unprocessed($module, $table); - if (empty($schema)) { - throw new \RuntimeException(format_string("Unknown '@table' table schema in '@module' module.", array( - '@module' => $module, - '@table' => $table, - ))); - } - $this->container->get('database')->schema()->createTable($table, $schema); - } - // We need to refresh the schema cache, as any call to drupal_get_schema() - // would not know of/return the schema otherwise. - // @todo Refactor Schema API to make this obsolete. - drupal_get_schema(NULL, TRUE); - $this->pass(format_string('Installed %module tables: %tables.', array( - '%tables' => '{' . implode('}, {', $tables) . '}', - '%module' => $module, - ))); - } - - /** - * Enables modules for this test. - * - * @param array $modules - * A list of modules to enable. Dependencies are not resolved; i.e., - * multiple modules have to be specified with dependent modules first. - * The new modules are only added to the active module list and loaded. - */ - protected function enableModules(array $modules) { - // Set the list of modules in the extension handler. - $module_handler = $this->container->get('module_handler'); - - // Write directly to active storage to avoid early instantiation of - // the event dispatcher which can prevent modules from registering events. - $active_storage = \Drupal::service('config.storage'); - $extensions = $active_storage->read('core.extension'); - - foreach ($modules as $module) { - $module_handler->addModule($module, drupal_get_path('module', $module)); - // Maintain the list of enabled modules in configuration. - $extensions['module'][$module] = 0; - } - $active_storage->write('core.extension', $extensions); - - // Update the kernel to make their services available. - $module_filenames = $module_handler->getModuleList(); - $this->kernel->updateModules($module_filenames, $module_filenames); - - // Ensure isLoaded() is TRUE in order to make _theme() work. - // Note that the kernel has rebuilt the container; this $module_handler is - // no longer the $module_handler instance from above. - $module_handler = $this->container->get('module_handler'); - $module_handler->reload(); - $this->pass(format_string('Enabled modules: %modules.', array( - '%modules' => implode(', ', $modules), - ))); - } - - /** - * Disables modules for this test. - * - * @param array $modules - * A list of modules to disable. Dependencies are not resolved; i.e., - * multiple modules have to be specified with dependent modules first. - * Code of previously active modules is still loaded. The modules are only - * removed from the active module list. - */ - protected function disableModules(array $modules) { - // Unset the list of modules in the extension handler. - $module_handler = $this->container->get('module_handler'); - $module_filenames = $module_handler->getModuleList(); - $extension_config = $this->container->get('config.factory')->get('core.extension'); - foreach ($modules as $module) { - unset($module_filenames[$module]); - $extension_config->clear('module.' . $module); - } - $extension_config->save(); - $module_handler->setModuleList($module_filenames); - $module_handler->resetImplementations(); - // Update the kernel to remove their services. - $this->kernel->updateModules($module_filenames, $module_filenames); - - // Ensure isLoaded() is TRUE in order to make _theme() work. - // Note that the kernel has rebuilt the container; this $module_handler is - // no longer the $module_handler instance from above. - $module_handler = $this->container->get('module_handler'); - $module_handler->reload(); - $this->pass(format_string('Disabled modules: %modules.', array( - '%modules' => implode(', ', $modules), - ))); - } - - /** - * Registers a stream wrapper for this test. - * - * @param string $scheme - * The scheme to register. - * @param string $class - * The fully qualified class name to register. - * @param int $type - * The Drupal Stream Wrapper API type. Defaults to - * STREAM_WRAPPERS_LOCAL_NORMAL. - */ - protected function registerStreamWrapper($scheme, $class, $type = STREAM_WRAPPERS_LOCAL_NORMAL) { - if (isset($this->streamWrappers[$scheme])) { - $this->unregisterStreamWrapper($scheme); - } - $this->streamWrappers[$scheme] = $type; - if (($type & STREAM_WRAPPERS_LOCAL) == STREAM_WRAPPERS_LOCAL) { - stream_wrapper_register($scheme, $class); - } - else { - stream_wrapper_register($scheme, $class, STREAM_IS_URL); - } - // @todo Revamp Drupal's stream wrapper API for D8. - // @see https://drupal.org/node/2028109 - $wrappers = &drupal_static('file_get_stream_wrappers', array()); - $wrappers[STREAM_WRAPPERS_ALL][$scheme] = array( - 'type' => $type, - 'class' => $class, - ); - if (($type & STREAM_WRAPPERS_WRITE_VISIBLE) == STREAM_WRAPPERS_WRITE_VISIBLE) { - $wrappers[STREAM_WRAPPERS_WRITE_VISIBLE][$scheme] = $wrappers[STREAM_WRAPPERS_ALL][$scheme]; - } - } - - /** - * Unregisters a stream wrapper previously registered by this test. - * - * KernelTestBase::tearDown() automatically cleans up all registered - * stream wrappers, so this usually does not have to be called manually. - * - * @param string $scheme - * The scheme to unregister. - * @param int $type - * The Drupal Stream Wrapper API type of the scheme to unregister. - */ - protected function unregisterStreamWrapper($scheme, $type) { - stream_wrapper_unregister($scheme); - unset($this->streamWrappers[$scheme]); - // @todo Revamp Drupal's stream wrapper API for D8. - // @see https://drupal.org/node/2028109 - $wrappers = &drupal_static('file_get_stream_wrappers', array()); - foreach ($wrappers as $filter => $schemes) { - if (is_int($filter) && (($filter & $type) == $filter)) { - unset($wrappers[$filter][$scheme]); - } - } - } - -} diff --git a/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php index 023cf8e53bcc8f531227ef6a20beb1adba4b01e5..19dd044022dad2620a8b11363aa4dea620b1e902 100644 --- a/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php +++ b/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php @@ -970,7 +970,7 @@ private function changeDatabasePrefix() { /** * Act on global state information before the environment is altered for a test. * - * Allows e.g. KernelTestBase to prime system/extension info from the + * Allows e.g. DrupalUnitTestBase to prime system/extension info from the * parent site (and inject it into the test environment so as to improve * performance). */ @@ -1058,7 +1058,7 @@ private function prepareEnvironment() { // Unregister all custom stream wrappers of the parent site. // Availability of Drupal stream wrappers varies by test base class: // - UnitTestBase operates in a completely empty environment. - // - KernelTestBase supports and maintains stream wrappers in a custom + // - DrupalUnitTestBase supports and maintains stream wrappers in a custom // way. // - WebTestBase re-initializes Drupal stream wrappers after installation. // The original stream wrappers are restored after the test run. diff --git a/core/modules/simpletest/lib/Drupal/simpletest/Tests/KernelTestBaseTest.php b/core/modules/simpletest/lib/Drupal/simpletest/Tests/DrupalUnitTestBaseTest.php similarity index 97% rename from core/modules/simpletest/lib/Drupal/simpletest/Tests/KernelTestBaseTest.php rename to core/modules/simpletest/lib/Drupal/simpletest/Tests/DrupalUnitTestBaseTest.php index 0a97a6356293147b0eed0492ba0cc5fa434a7a13..6c13c8a9f4184ff468f338582366e849db03bbb3 100644 --- a/core/modules/simpletest/lib/Drupal/simpletest/Tests/KernelTestBaseTest.php +++ b/core/modules/simpletest/lib/Drupal/simpletest/Tests/DrupalUnitTestBaseTest.php @@ -2,17 +2,17 @@ /** * @file - * Contains \Drupal\simpletest\Tests\KernelTestBaseTest. + * Contains \Drupal\simpletest\Tests\DrupalUnitTestBaseTest. */ namespace Drupal\simpletest\Tests; -use Drupal\simpletest\KernelTestBase; +use Drupal\simpletest\DrupalUnitTestBase; /** - * Tests KernelTestBase functionality. + * Tests DrupalUnitTestBase functionality. */ -class KernelTestBaseTest extends KernelTestBase { +class DrupalUnitTestBaseTest extends DrupalUnitTestBase { /** * Modules to enable. @@ -23,8 +23,8 @@ class KernelTestBaseTest extends KernelTestBase { public static function getInfo() { return array( - 'name' => 'KernelTestBase', - 'description' => 'Tests KernelTestBase functionality.', + 'name' => 'DrupalUnitTestBase', + 'description' => 'Tests DrupalUnitTestBase functionality.', 'group' => 'SimpleTest', ); } diff --git a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php index a120883ac2e38732b4b67d31d0f9302a9cf8eb2a..340b5556a60c99e493992e162d384b7669eca912 100644 --- a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php +++ b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php @@ -1094,7 +1094,7 @@ protected function rebuildContainer($environment = 'prod') { $request_stack = \Drupal::service('request_stack'); } - $this->kernel = new DrupalKernel($environment, drupal_classloader(), TRUE, FALSE); + $this->kernel = new DrupalKernel($environment, drupal_classloader(), FALSE); $this->kernel->boot(); // DrupalKernel replaces the container in \Drupal::getContainer() with a // different object, so we need to replace the instance on this test class. diff --git a/core/modules/system/lib/Drupal/system/Tests/DrupalKernel/DrupalKernelTest.php b/core/modules/system/lib/Drupal/system/Tests/DrupalKernel/DrupalKernelTest.php index 17e4b4b9f030ec77e7d6176802edaa9771e6a46c..aaca572fbb0b11d0be876cfa79d6ba81e3fe2db4 100644 --- a/core/modules/system/lib/Drupal/system/Tests/DrupalKernel/DrupalKernelTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/DrupalKernel/DrupalKernelTest.php @@ -27,7 +27,7 @@ public static function getInfo() { function setUp() { // DrupalKernel relies on global $config_directories and requires those // directories to exist. Therefore, create the directories, but do not - // invoke KernelTestBase::setUp(), since that would set up further + // invoke DrupalUnitTestBase::setUp(), since that would set up further // environment aspects, which would distort this test, because it tests // the DrupalKernel (re-)building itself. $this->prepareConfigDirectories(); @@ -56,6 +56,7 @@ function testCompileDIC() { // Instantiate it a second time and we should get the compiled Container // class. $kernel = new DrupalKernel('testing', $classloader); + $kernel->updateModules($module_enabled); $kernel->boot(); $container = $kernel->getContainer(); $refClass = new \ReflectionClass($container); @@ -63,10 +64,6 @@ function testCompileDIC() { $refClass->getParentClass()->getName() == 'Drupal\Core\DependencyInjection\Container' && !$refClass->isSubclassOf('Symfony\Component\DependencyInjection\ContainerBuilder'); $this->assertTrue($is_compiled_container); - // Verify that the list of modules is the same for the initial and the - // compiled container. - $module_list = array_keys($container->get('module_handler')->getModuleList()); - $this->assertEqual(array_values($module_enabled), $module_list); // Now use the read-only storage implementation, simulating a "production" // environment. @@ -74,6 +71,7 @@ function testCompileDIC() { $php_storage['service_container']['class'] = 'Drupal\Component\PhpStorage\FileReadOnlyStorage'; $this->settingsSet('php_storage', $php_storage); $kernel = new DrupalKernel('testing', $classloader); + $kernel->updateModules($module_enabled); $kernel->boot(); $container = $kernel->getContainer(); $refClass = new \ReflectionClass($container); @@ -81,10 +79,6 @@ function testCompileDIC() { $refClass->getParentClass()->getName() == 'Drupal\Core\DependencyInjection\Container' && !$refClass->isSubclassOf('Symfony\Component\DependencyInjection\ContainerBuilder'); $this->assertTrue($is_compiled_container); - // Verify that the list of modules is the same for the initial and the - // compiled container. - $module_list = array_keys($container->get('module_handler')->getModuleList()); - $this->assertEqual(array_values($module_enabled), $module_list); // Test that our synthetic services are there. $classloader = $container->get('class_loader'); $refClass = new \ReflectionClass($classloader); diff --git a/core/modules/text/lib/Drupal/text/Tests/Formatter/TextPlainUnitTest.php b/core/modules/text/lib/Drupal/text/Tests/Formatter/TextPlainUnitTest.php index 1c35b242c5030bbd3d7d3cb863af438895c54172..8e37920c5a9f99e46ee1e9315557e6c459199e7a 100644 --- a/core/modules/text/lib/Drupal/text/Tests/Formatter/TextPlainUnitTest.php +++ b/core/modules/text/lib/Drupal/text/Tests/Formatter/TextPlainUnitTest.php @@ -15,7 +15,7 @@ /** * Tests the text_plain field formatter. * - * @todo Move assertion helper methods into KernelTestBase. + * @todo Move assertion helper methods into DrupalUnitTestBase. * @todo Move field helper methods, $modules, and setUp() into a new * FieldPluginUnitTestBase. */ diff --git a/core/modules/views/lib/Drupal/views/Tests/ViewUnitTestBase.php b/core/modules/views/lib/Drupal/views/Tests/ViewUnitTestBase.php index fa14ec930cbe7221f54e26e71b62157a701e96fb..620e4fe14fd1851e39e7be6870985c60b5f7afdd 100644 --- a/core/modules/views/lib/Drupal/views/Tests/ViewUnitTestBase.php +++ b/core/modules/views/lib/Drupal/views/Tests/ViewUnitTestBase.php @@ -21,6 +21,7 @@ * ViewTestBase instead. * * @see \Drupal\views\Tests\ViewTestBase + * @see \Drupal\simpletest\DrupalUnitTestBase */ abstract class ViewUnitTestBase extends DrupalUnitTestBase {