diff --git a/core/includes/install.inc b/core/includes/install.inc
index 31d472a..af7ee41 100644
--- a/core/includes/install.inc
+++ b/core/includes/install.inc
@@ -210,7 +210,7 @@ function drupal_rewrite_settings($settings = array(), $settings_file = NULL) {
}
$variable_names['$'. $setting] = $setting;
}
- $contents = file_get_contents(DRUPAL_ROOT . '/' . $settings_file);
+ $contents = file_get_contents($settings_file);
if ($contents !== FALSE) {
// Initialize the contents for the settings.php file if it is empty.
if (trim($contents) === '') {
@@ -315,7 +315,7 @@ function drupal_rewrite_settings($settings = array(), $settings_file = NULL) {
}
// Write the new settings file.
- if (file_put_contents(DRUPAL_ROOT . '/' . $settings_file, $buffer) === FALSE) {
+ if (file_put_contents($settings_file, $buffer) === FALSE) {
throw new Exception(t('Failed to modify %settings. Verify the file permissions.', array('%settings' => $settings_file)));
}
else {
diff --git a/core/lib/Drupal/Core/Config/FileStorage.php b/core/lib/Drupal/Core/Config/FileStorage.php
index ec199e7..268aee5 100644
--- a/core/lib/Drupal/Core/Config/FileStorage.php
+++ b/core/lib/Drupal/Core/Config/FileStorage.php
@@ -200,19 +200,23 @@ public function decode($raw) {
* Implements Drupal\Core\Config\StorageInterface::listAll().
*/
public function listAll($prefix = '') {
- // glob() silently ignores the error of a non-existing search directory,
- // even with the GLOB_ERR flag.
$dir = $this->getCollectionDirectory();
- if (!file_exists($dir)) {
+ if (!is_dir($dir)) {
return array();
}
$extension = '.' . static::getFileExtension();
- // \GlobIterator on Windows requires an absolute path.
- $files = new \GlobIterator(realpath($dir) . '/' . $prefix . '*' . $extension);
+
+ // glob() directly calls into libc glob(), which is not aware of PHP stream
+ // wrappers. Same for \GlobIterator (which additionally requires an absolute
+ // realpath() on Windows).
+ // @see https://github.com/mikey179/vfsStream/issues/2
+ $files = scandir($dir);
$names = array();
foreach ($files as $file) {
- $names[] = $file->getBasename($extension);
+ if ($file[0] !== '.' && fnmatch($prefix . '*' . $extension, $file)) {
+ $names[] = basename($file, $extension);
+ }
}
return $names;
@@ -306,13 +310,15 @@ protected function getAllCollectionNamesHelper($directory) {
$collections[] = $collection . '.' . $sub_collection;
}
}
- // Check that the collection is valid by searching if for configuration
+ // Check that the collection is valid by searching it for configuration
// objects. A directory without any configuration objects is not a valid
// collection.
- // \GlobIterator on Windows requires an absolute path.
- $files = new \GlobIterator(realpath($directory . '/' . $collection) . '/*.' . $this->getFileExtension());
- if (count($files)) {
- $collections[] = $collection;
+ // @see \Drupal\Core\Config\FileStorage::listAll()
+ foreach (scandir($directory . '/' . $collection) as $file) {
+ if ($file[0] !== '.' && fnmatch('*.' . $this->getFileExtension(), $file)) {
+ $collections[] = $collection;
+ break;
+ }
}
}
}
diff --git a/core/lib/Drupal/Core/Config/InstallStorage.php b/core/lib/Drupal/Core/Config/InstallStorage.php
index f3a3298..1f20f89 100644
--- a/core/lib/Drupal/Core/Config/InstallStorage.php
+++ b/core/lib/Drupal/Core/Config/InstallStorage.php
@@ -197,10 +197,17 @@ public function getComponentNames(array $list) {
// We don't have to use ExtensionDiscovery here because our list of
// extensions was already obtained through an ExtensionDiscovery scan.
$directory = $this->getComponentFolder($extension_object);
- if (file_exists($directory)) {
- $files = new \GlobIterator(\Drupal::root() . '/' . $directory . '/*' . $extension);
+ if (is_dir($directory)) {
+ // glob() directly calls into libc glob(), which is not aware of PHP
+ // stream wrappers. Same for \GlobIterator (which additionally requires
+ // an absolute realpath() on Windows).
+ // @see https://github.com/mikey179/vfsStream/issues/2
+ $files = scandir($directory);
+
foreach ($files as $file) {
- $folders[$file->getBasename($extension)] = $directory;
+ if ($file[0] !== '.' && fnmatch('*' . $extension, $file)) {
+ $folders[basename($file, $extension)] = $directory;
+ }
}
}
}
@@ -217,10 +224,17 @@ public function getCoreNames() {
$extension = '.' . $this->getFileExtension();
$folders = array();
$directory = $this->getCoreFolder();
- if (file_exists($directory)) {
- $files = new \GlobIterator(\Drupal::root() . '/' . $directory . '/*' . $extension);
+ if (is_dir($directory)) {
+ // glob() directly calls into libc glob(), which is not aware of PHP
+ // stream wrappers. Same for \GlobIterator (which additionally requires an
+ // absolute realpath() on Windows).
+ // @see https://github.com/mikey179/vfsStream/issues/2
+ $files = scandir($directory);
+
foreach ($files as $file) {
- $folders[$file->getBasename($extension)] = $directory;
+ if ($file[0] !== '.' && fnmatch('*' . $extension, $file)) {
+ $folders[basename($file, $extension)] = $directory;
+ }
}
}
return $folders;
diff --git a/core/lib/Drupal/Core/DrupalKernel.php b/core/lib/Drupal/Core/DrupalKernel.php
index 0f103e6..814325e 100644
--- a/core/lib/Drupal/Core/DrupalKernel.php
+++ b/core/lib/Drupal/Core/DrupalKernel.php
@@ -15,6 +15,7 @@
use Drupal\Core\Config\NullStorage;
use Drupal\Core\Database\Database;
use Drupal\Core\DependencyInjection\ContainerBuilder;
+use Drupal\Core\DependencyInjection\ServiceModifierInterface;
use Drupal\Core\DependencyInjection\ServiceProviderInterface;
use Drupal\Core\DependencyInjection\YamlFileLoader;
use Drupal\Core\Extension\ExtensionDiscovery;
@@ -153,13 +154,16 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface {
protected $serviceYamls;
/**
- * List of discovered service provider class names.
+ * List of discovered service provider class names or objects.
*
* This is a nested array whose top-level keys are 'app' and 'site', denoting
* the origin of a service provider. Site-specific providers have to be
* collected separately, because they need to be processed last, so as to be
* able to override services from application service providers.
*
+ * Allowing objects is for example used to allow
+ * \Drupal\Tests\KernelTestBase to register itself as service provider.
+ *
* @var array
*/
protected $serviceProviderClasses;
@@ -432,6 +436,21 @@ public function getContainer() {
/**
* {@inheritdoc}
*/
+ public function setContainer(ContainerInterface $container = NULL) {
+ if (isset($this->container)) {
+ throw new \Exception('The container should not override an existing container.');
+ }
+ if ($this->booted) {
+ throw new \Exception('The container cannot be set after a booted kernel.');
+ }
+
+ $this->container = $container;
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
public function loadLegacyIncludes() {
require_once $this->root . '/core/includes/common.inc';
require_once $this->root . '/core/includes/database.inc';
@@ -524,7 +543,7 @@ public function discoverServiceProviders() {
// Add site-specific service providers.
if (!empty($GLOBALS['conf']['container_service_providers'])) {
foreach ($GLOBALS['conf']['container_service_providers'] as $class) {
- if (class_exists($class)) {
+ if ((is_string($class) && class_exists($class)) || (is_object($class) && ($class instanceof ServiceProviderInterface || $class instanceof ServiceModifierInterface))) {
$this->serviceProviderClasses['site'][] = $class;
}
}
@@ -769,6 +788,13 @@ protected function initializeContainer() {
}
}
+ // If we haven't booted yet but there is a container, then we're asked to
+ // boot the container injected via setContainer().
+ // @see \Drupal\Tests\KernelTestBase::setUp()
+ if (isset($this->container) && !$this->booted) {
+ $container = $this->container;
+ }
+
// If the module list hasn't already been set in updateModules and we are
// not forcing a rebuild, then try and load the container from the disk.
if (empty($this->moduleList) && !$this->containerNeedsRebuild) {
@@ -784,6 +810,7 @@ protected function initializeContainer() {
}
}
+ // If there is still no container, build a new one from scratch.
if (!isset($container)) {
$container = $this->compileContainer();
}
@@ -1172,7 +1199,12 @@ protected function initializeServiceProviders() {
);
foreach ($this->serviceProviderClasses as $origin => $classes) {
foreach ($classes as $name => $class) {
- $this->serviceProviders[$origin][$name] = new $class;
+ if (!is_object($class)) {
+ $this->serviceProviders[$origin][$name] = new $class;
+ }
+ else {
+ $this->serviceProviders[$origin][$name] = $class;
+ }
}
}
}
diff --git a/core/lib/Drupal/Core/DrupalKernelInterface.php b/core/lib/Drupal/Core/DrupalKernelInterface.php
index 892952a..12c32f6 100644
--- a/core/lib/Drupal/Core/DrupalKernelInterface.php
+++ b/core/lib/Drupal/Core/DrupalKernelInterface.php
@@ -7,6 +7,7 @@
namespace Drupal\Core;
+use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpFoundation\Request;
@@ -16,7 +17,7 @@
* This interface extends Symfony's KernelInterface and adds methods for
* responding to modules being enabled or disabled during its lifetime.
*/
-interface DrupalKernelInterface extends HttpKernelInterface {
+interface DrupalKernelInterface extends HttpKernelInterface, ContainerAwareInterface {
/**
* Boots the current kernel.
diff --git a/core/lib/Drupal/Core/PhpStorage/PhpStorageFactory.php b/core/lib/Drupal/Core/PhpStorage/PhpStorageFactory.php
index 69598e1..5ca0971 100644
--- a/core/lib/Drupal/Core/PhpStorage/PhpStorageFactory.php
+++ b/core/lib/Drupal/Core/PhpStorage/PhpStorageFactory.php
@@ -52,7 +52,7 @@ static function get($name) {
$configuration['bin'] = $name;
}
if (!isset($configuration['directory'])) {
- $configuration['directory'] = DRUPAL_ROOT . '/' . PublicStream::basePath() . '/php';
+ $configuration['directory'] = PublicStream::basePath() . '/php';
}
return new $class($configuration);
}
diff --git a/core/lib/Drupal/Core/StreamWrapper/LocalStream.php b/core/lib/Drupal/Core/StreamWrapper/LocalStream.php
index 5c60942..021c541 100644
--- a/core/lib/Drupal/Core/StreamWrapper/LocalStream.php
+++ b/core/lib/Drupal/Core/StreamWrapper/LocalStream.php
@@ -127,6 +127,15 @@ protected function getLocalPath($uri = NULL) {
$uri = $this->uri;
}
$path = $this->getDirectoryPath() . '/' . $this->getTarget($uri);
+
+ // In PHPUnit tests, the base path for local streams may be a virtual
+ // filesystem stream wrapper URI, in which case this local stream acts like
+ // a proxy. realpath() is not supported by vfsStream, because a virtual
+ // file system does not have a real filepath.
+ if (strpos($path, 'vfs://') === 0) {
+ return $path;
+ }
+
$realpath = realpath($path);
if (!$realpath) {
// This file does not yet exist.
diff --git a/core/modules/ckeditor/src/Plugin/Editor/CKEditor.php b/core/modules/ckeditor/src/Plugin/Editor/CKEditor.php
index 0e237b1..113523b 100644
--- a/core/modules/ckeditor/src/Plugin/Editor/CKEditor.php
+++ b/core/modules/ckeditor/src/Plugin/Editor/CKEditor.php
@@ -331,10 +331,12 @@ public function getLangcodes() {
if (empty($langcodes)) {
$langcodes = array();
// Collect languages included with CKEditor based on file listing.
- $ckeditor_languages = new \GlobIterator(\Drupal::root() . '/core/assets/vendor/ckeditor/lang/*.js');
- foreach ($ckeditor_languages as $language_file) {
- $langcode = $language_file->getBasename('.js');
- $langcodes[$langcode] = $langcode;
+ $files = scandir('core/assets/vendor/ckeditor/lang');
+ foreach ($files as $file) {
+ if ($file[0] !== '.' && fnmatch('*.js', $file)) {
+ $langcode = basename($file, '.js');
+ $langcodes[$langcode] = $langcode;
+ }
}
\Drupal::cache()->set('ckeditor.langcodes', $langcodes);
}
diff --git a/core/modules/simpletest/src/KernelTestBase.php b/core/modules/simpletest/src/KernelTestBase.php
index 481b031..70049a0 100644
--- a/core/modules/simpletest/src/KernelTestBase.php
+++ b/core/modules/simpletest/src/KernelTestBase.php
@@ -33,6 +33,9 @@
* Additional modules needed in a test may be loaded and added to the fixed
* module list.
*
+ * @deprecated in Drupal 8.0.x, will be removed before Drupal 8.2.x. Use
+ * \Drupal\Tests\KernelTestBase instead.
+ *
* @see \Drupal\simpletest\KernelTestBase::$modules
* @see \Drupal\simpletest\KernelTestBase::enableModules()
*
diff --git a/core/modules/simpletest/src/RandomGeneratorTrait.php b/core/modules/simpletest/src/RandomGeneratorTrait.php
new file mode 100644
index 0000000..5a50439
--- /dev/null
+++ b/core/modules/simpletest/src/RandomGeneratorTrait.php
@@ -0,0 +1,134 @@
+') character to ensure coverage for special
+ * characters and avoid the introduction of random test failures.
+ *
+ * @param int $length
+ * Length of random string to generate.
+ *
+ * @return string
+ * Pseudo-randomly generated unique string including special characters.
+ *
+ * @see \Drupal\Component\Utility\Random::string()
+ */
+ public function randomString($length = 8) {
+ if ($length < 4) {
+ return $this->getRandomGenerator()->string($length, TRUE, array($this, 'randomStringValidate'));
+ }
+
+ // To prevent the introduction of random test failures, ensure that the
+ // returned string contains a character that needs to be escaped in HTML by
+ // injecting an ampersand into it.
+ $replacement_pos = floor($length / 2);
+ // Remove 2 from the length to account for the ampersand and greater than
+ // characters.
+ $string = $this->getRandomGenerator()->string($length - 2, TRUE, array($this, 'randomStringValidate'));
+ return substr_replace($string, '>&', $replacement_pos, 0);
+ }
+
+ /**
+ * Callback for random string validation.
+ *
+ * @see \Drupal\Component\Utility\Random::string()
+ *
+ * @param string $string
+ * The random string to validate.
+ *
+ * @return bool
+ * TRUE if the random string is valid, FALSE if not.
+ */
+ public function randomStringValidate($string) {
+ // Consecutive spaces causes issues for
+ // \Drupal\simpletest\WebTestBase::assertLink().
+ if (preg_match('/\s{2,}/', $string)) {
+ return FALSE;
+ }
+ // Starting with a space means that length might not be what is expected.
+ // Starting with an @ sign causes CURL to fail if used in conjunction with a
+ // file upload, see https://drupal.org/node/2174997.
+ if (preg_match('/^(\s|@)/', $string)) {
+ return FALSE;
+ }
+ // Ending with a space means that length might not be what is expected.
+ if (preg_match('/\s$/', $string)) {
+ return FALSE;
+ }
+ return TRUE;
+ }
+
+ /**
+ * Generates a unique random string containing letters and numbers.
+ *
+ * Do not use this method when testing unvalidated user input. Instead, use
+ * \Drupal\simpletest\TestBase::randomString().
+ *
+ * @param int $length
+ * Length of random string to generate.
+ *
+ * @return string
+ * Randomly generated unique string.
+ *
+ * @see \Drupal\Component\Utility\Random::name()
+ */
+ protected function randomMachineName($length = 8) {
+ return $this->getRandomGenerator()->name($length, TRUE);
+ }
+
+ /**
+ * Generates a random PHP object.
+ *
+ * @param int $size
+ * The number of random keys to add to the object.
+ *
+ * @return \stdClass
+ * The generated object, with the specified number of random keys. Each key
+ * has a random string value.
+ *
+ * @see \Drupal\Component\Utility\Random::object()
+ */
+ public function randomObject($size = 4) {
+ return $this->getRandomGenerator()->object($size);
+ }
+
+ /**
+ * Gets the random generator for the utility methods.
+ *
+ * @return \Drupal\Component\Utility\Random
+ * The random generator.
+ */
+ protected function getRandomGenerator() {
+ if (!is_object($this->randomGenerator)) {
+ $this->randomGenerator = new Random();
+ }
+ return $this->randomGenerator;
+ }
+
+}
diff --git a/core/modules/simpletest/src/TestBase.php b/core/modules/simpletest/src/TestBase.php
index 4104b4b..5e76459 100644
--- a/core/modules/simpletest/src/TestBase.php
+++ b/core/modules/simpletest/src/TestBase.php
@@ -28,6 +28,7 @@
abstract class TestBase {
use SessionTestTrait;
+ use RandomGeneratorTrait;
/**
* The test run ID.
@@ -284,13 +285,6 @@
protected $configImporter;
/**
- * The random generator.
- *
- * @var \Drupal\Component\Utility\Random
- */
- protected $randomGenerator;
-
- /**
* Set to TRUE to strict check all configuration saved.
*
* @see \Drupal\Core\Config\Testing\ConfigSchemaChecker
@@ -1418,119 +1412,6 @@ protected function settingsSet($name, $value) {
}
/**
- * Generates a pseudo-random string of ASCII characters of codes 32 to 126.
- *
- * Do not use this method when special characters are not possible (e.g., in
- * machine or file names that have already been validated); instead, use
- * \Drupal\simpletest\TestBase::randomMachineName(). If $length is greater
- * than 3 the random string will include at least one ampersand ('&') and
- * at least one greater than ('>') character to ensure coverage for special
- * characters and avoid the introduction of random test failures.
- *
- * @param int $length
- * Length of random string to generate.
- *
- * @return string
- * Pseudo-randomly generated unique string including special characters.
- *
- * @see \Drupal\Component\Utility\Random::string()
- */
- public function randomString($length = 8) {
- if ($length < 4) {
- return $this->getRandomGenerator()->string($length, TRUE, array($this, 'randomStringValidate'));
- }
-
- // To prevent the introduction of random test failures, ensure that the
- // returned string contains a character that needs to be escaped in HTML by
- // injecting an ampersand into it.
- $replacement_pos = floor($length / 2);
- // Remove 2 from the length to account for the ampersand and greater than
- // characters.
- $string = $this->getRandomGenerator()->string($length - 2, TRUE, array($this, 'randomStringValidate'));
- return substr_replace($string, '>&', $replacement_pos, 0);
- }
-
- /**
- * Callback for random string validation.
- *
- * @see \Drupal\Component\Utility\Random::string()
- *
- * @param string $string
- * The random string to validate.
- *
- * @return bool
- * TRUE if the random string is valid, FALSE if not.
- */
- public function randomStringValidate($string) {
- // Consecutive spaces causes issues for
- // Drupal\simpletest\WebTestBase::assertLink().
- if (preg_match('/\s{2,}/', $string)) {
- return FALSE;
- }
-
- // Starting with a space means that length might not be what is expected.
- // Starting with an @ sign causes CURL to fail if used in conjunction with a
- // file upload. See https://www.drupal.org/node/2174997.
- if (preg_match('/^(\s|@)/', $string)) {
- return FALSE;
- }
-
- // Ending with a space means that length might not be what is expected.
- if (preg_match('/\s$/', $string)) {
- return FALSE;
- }
-
- return TRUE;
- }
-
- /**
- * Generates a unique random string containing letters and numbers.
- *
- * Do not use this method when testing unvalidated user input. Instead, use
- * \Drupal\simpletest\TestBase::randomString().
- *
- * @param int $length
- * Length of random string to generate.
- *
- * @return string
- * Randomly generated unique string.
- *
- * @see \Drupal\Component\Utility\Random::name()
- */
- public function randomMachineName($length = 8) {
- return $this->getRandomGenerator()->name($length, TRUE);
- }
-
- /**
- * Generates a random PHP object.
- *
- * @param int $size
- * The number of random keys to add to the object.
- *
- * @return \stdClass
- * The generated object, with the specified number of random keys. Each key
- * has a random string value.
- *
- * @see \Drupal\Component\Utility\Random::object()
- */
- public function randomObject($size = 4) {
- return $this->getRandomGenerator()->object($size);
- }
-
- /**
- * Gets the random generator for the utility methods.
- *
- * @return \Drupal\Component\Utility\Random
- * The random generator
- */
- protected function getRandomGenerator() {
- if (!is_object($this->randomGenerator)) {
- $this->randomGenerator = new Random();
- }
- return $this->randomGenerator;
- }
-
- /**
* Converts a list of possible parameters into a stack of permutations.
*
* Takes a list of parameters containing possible values, and converts all of
diff --git a/core/modules/simpletest/src/TestDiscovery.php b/core/modules/simpletest/src/TestDiscovery.php
index f71027d..630cca7 100644
--- a/core/modules/simpletest/src/TestDiscovery.php
+++ b/core/modules/simpletest/src/TestDiscovery.php
@@ -81,6 +81,7 @@ public function registerTestNamespaces() {
// Add PHPUnit test namespaces of Drupal core.
$this->testNamespaces['Drupal\\Tests\\'] = [DRUPAL_ROOT . '/core/tests/Drupal/Tests'];
+ $this->testNamespaces['Drupal\\KernelTests\\'] = [DRUPAL_ROOT . '/core/tests/Drupal/KernelTests'];
$this->testNamespaces['Drupal\\FunctionalTests\\'] = [DRUPAL_ROOT . '/core/tests/Drupal/FunctionalTests'];
$this->availableExtensions = array();
@@ -98,6 +99,7 @@ public function registerTestNamespaces() {
// Add PHPUnit test namespaces.
$this->testNamespaces["Drupal\\Tests\\$name\\Unit\\"][] = "$base_path/tests/src/Unit";
+ $this->testNamespaces["Drupal\\Tests\\$name\\Kernel\\"][] = "$base_path/tests/src/Kernel";
$this->testNamespaces["Drupal\\Tests\\$name\\Functional\\"][] = "$base_path/tests/src/Functional";
}
diff --git a/core/modules/simpletest/tests/src/Unit/TestBaseTest.php b/core/modules/simpletest/tests/src/Unit/TestBaseTest.php
index f4a005f..cea275c 100644
--- a/core/modules/simpletest/tests/src/Unit/TestBaseTest.php
+++ b/core/modules/simpletest/tests/src/Unit/TestBaseTest.php
@@ -117,7 +117,7 @@ public function testRandomString($length) {
$this->assertEquals($length, strlen($string));
// randomString() should always include an ampersand ('&') and a
// greater than ('>') if $length is greater than 3.
- if ($length > 3) {
+ if ($length > 4) {
$this->assertContains('&', $string);
$this->assertContains('>', $string);
}
diff --git a/core/modules/system/src/Tests/Extension/ModuleHandlerTest.php b/core/modules/system/tests/src/Kernel/Extension/ModuleHandlerTest.php
similarity index 91%
rename from core/modules/system/src/Tests/Extension/ModuleHandlerTest.php
rename to core/modules/system/tests/src/Kernel/Extension/ModuleHandlerTest.php
index a9fa3b2..125a11b 100644
--- a/core/modules/system/src/Tests/Extension/ModuleHandlerTest.php
+++ b/core/modules/system/tests/src/Kernel/Extension/ModuleHandlerTest.php
@@ -5,11 +5,11 @@
* Contains \Drupal\system\Tests\Extension\ModuleHandlerTest.
*/
-namespace Drupal\system\Tests\Extension;
+namespace Drupal\Tests\system\Kernel\Extension;
use Drupal\Core\DependencyInjection\ContainerBuilder;
-use Drupal\simpletest\KernelTestBase;
use \Drupal\Core\Extension\ModuleUninstallValidatorException;
+use Drupal\Tests\KernelTestBase;
/**
* Tests ModuleHandler functionality.
@@ -21,10 +21,13 @@ class ModuleHandlerTest extends KernelTestBase {
/**
* {@inheritdoc}
*/
- public static $modules = array('system');
-
- public function setUp() {
+ protected function setUp() {
parent::setUp();
+
+ // @todo ModuleInstaller calls system_rebuild_module_data which is part of
+ // system.module, see https://www.drupal.org/node/2208429.
+ include_once $this->root . '/core/modules/system/system.module';
+
// Set up the state values so we know where to find the files when running
// drupal_get_filename().
// @todo Remove as part of https://www.drupal.org/node/2186491
@@ -34,8 +37,8 @@ public function setUp() {
/**
* {@inheritdoc}
*/
- public function containerBuild(ContainerBuilder $container) {
- parent::containerBuild($container);
+ public function register(ContainerBuilder $container) {
+ parent::register($container);
// Put a fake route bumper on the container to be called during uninstall.
$container
->register('router.dumper', 'Drupal\Core\Routing\NullMatcherDumper');
@@ -45,24 +48,9 @@ public function containerBuild(ContainerBuilder $container) {
* The basic functionality of retrieving enabled modules.
*/
function testModuleList() {
- // Prime the drupal_get_filename() static cache with the location of the
- // testing profile as it is not the currently active profile and we don't
- // yet have any cached way to retrieve its location.
- // @todo Remove as part of https://www.drupal.org/node/2186491
- drupal_get_filename('profile', 'testing', 'core/profiles/testing/testing.info.yml');
- // Build a list of modules, sorted alphabetically.
- $profile_info = install_profile_info('testing', 'en');
- $module_list = $profile_info['dependencies'];
+ $module_list = array();
- // Installation profile is a module that is expected to be loaded.
- $module_list[] = 'testing';
-
- sort($module_list);
- // Compare this list to the one returned by the module handler. We expect
- // them to match, since all default profile modules have a weight equal to 0
- // (except for block.module, which has a lower weight but comes first in
- // the alphabet anyway).
- $this->assertModuleList($module_list, 'Testing profile');
+ $this->assertModuleList($module_list, 'Initial');
// Try to install a new module.
$this->moduleInstaller()->install(array('ban'));
@@ -98,7 +86,6 @@ function testModuleList() {
protected function assertModuleList(Array $expected_values, $condition) {
$expected_values = array_values(array_unique($expected_values));
$enabled_modules = array_keys($this->container->get('module_handler')->getModuleList());
- $enabled_modules = sort($enabled_modules);
$this->assertEqual($expected_values, $enabled_modules, format_string('@condition: extension handler returns correct results', array('@condition' => $condition)));
}
@@ -196,7 +183,7 @@ function testDependencyResolution() {
function testUninstallProfileDependency() {
$profile = 'minimal';
$dependency = 'dblog';
- $this->settingsSet('install_profile', $profile);
+ $this->setSetting('install_profile', $profile);
// Prime the drupal_get_filename() static cache with the location of the
// minimal profile as it is not the currently active profile and we don't
// yet have any cached way to retrieve its location.
diff --git a/core/modules/system/src/Tests/PhpStorage/PhpStorageFactoryTest.php b/core/modules/system/tests/src/Kernel/PhpStorage/PhpStorageFactoryTest.php
similarity index 86%
rename from core/modules/system/src/Tests/PhpStorage/PhpStorageFactoryTest.php
rename to core/modules/system/tests/src/Kernel/PhpStorage/PhpStorageFactoryTest.php
index 580bfa1..5f866c0 100644
--- a/core/modules/system/src/Tests/PhpStorage/PhpStorageFactoryTest.php
+++ b/core/modules/system/tests/src/Kernel/PhpStorage/PhpStorageFactoryTest.php
@@ -5,14 +5,14 @@
* Contains \Drupal\system\Tests\PhpStorage\PhpStorageFactoryTest.
*/
-namespace Drupal\system\Tests\PhpStorage;
+namespace Drupal\Tests\system\Kernel\PhpStorage;
use Drupal\Component\PhpStorage\MTimeProtectedFileStorage;
use Drupal\Core\PhpStorage\PhpStorageFactory;
use Drupal\Core\Site\Settings;
use Drupal\Core\StreamWrapper\PublicStream;
-use Drupal\simpletest\KernelTestBase;
use Drupal\system\PhpStorage\MockPhpStorage;
+use Drupal\Tests\KernelTestBase;
/**
* Tests the PHP storage factory.
@@ -23,6 +23,18 @@
class PhpStorageFactoryTest extends KernelTestBase {
/**
+ * {@inheritdoc}
+ */
+ protected function setUp() {
+ parent::setUp();
+
+ // Empty the PHP storage settings, as KernelTestBase sets it by default.
+ $settings = Settings::getAll();
+ unset($settings['php_storage']);
+ new Settings($settings);
+ }
+
+ /**
* Tests the get() method with no settings.
*/
public function testGetNoSettings() {
@@ -59,7 +71,7 @@ public function testGetOverride() {
$this->setSettings('test', array('directory' => NULL));
$php = PhpStorageFactory::get('test');
$this->assertTrue($php instanceof MockPhpStorage, 'An MockPhpStorage instance was returned from overridden settings.');
- $this->assertIdentical(\Drupal::root() . '/' . PublicStream::basePath() . '/php', $php->getConfigurationValue('directory'), 'Default file directory was used.');
+ $this->assertIdentical(PublicStream::basePath() . '/php', $php->getConfigurationValue('directory'), 'Default file directory was used.');
// Test that a default storage class is set if it's empty.
$this->setSettings('test', array('class' => NULL));
diff --git a/core/phpunit.xml.dist b/core/phpunit.xml.dist
index 743bf08..15b6316 100644
--- a/core/phpunit.xml.dist
+++ b/core/phpunit.xml.dist
@@ -22,6 +22,16 @@
./drush/tests
+
+ ./tests/Drupal/KernelTests
+ ./modules/*/tests/src/Kernel
+ ../modules/*/tests/src/Kernel
+ ../sites/*/modules/*/tests/src/Kernel
+
+ ./vendor
+
+ ./drush/tests
+ ./tests/Drupal/FunctionalTests./modules/*/tests/src/Functional
diff --git a/core/scripts/run-tests.sh b/core/scripts/run-tests.sh
index 0ccdc35..0755cf2 100755
--- a/core/scripts/run-tests.sh
+++ b/core/scripts/run-tests.sh
@@ -709,7 +709,7 @@ function simpletest_script_command($test_id, $test_class) {
* @see simpletest_script_run_one_test()
*/
function simpletest_script_cleanup($test_id, $test_class, $exitcode) {
- if (strpos($test_class, 'Drupal\\Tests\\') === 0) {
+ if (is_subclass_of($test_class, '\PHPUnit_Framework_TestCase')) {
// PHPUnit test, move on.
return;
}
diff --git a/core/tests/Drupal/KernelTests/KernelTestBaseTest.php b/core/tests/Drupal/KernelTests/KernelTestBaseTest.php
new file mode 100644
index 0000000..66c09bf
--- /dev/null
+++ b/core/tests/Drupal/KernelTests/KernelTestBaseTest.php
@@ -0,0 +1,216 @@
+assertSame(realpath(__DIR__ . '/../../../../'), getcwd());
+ }
+
+ /**
+ * @covers ::bootEnvironment
+ */
+ public function testBootEnvironment() {
+ $this->assertRegExp('/^simpletest\d{6}$/', $this->databasePrefix);
+ $this->assertStringStartsWith('vfs://root/sites/simpletest/', $this->siteDirectory);
+ $this->assertEquals(array(
+ 'root' => array(
+ 'sites' => array(
+ 'simpletest' => array(
+ substr($this->databasePrefix, 10) => array(
+ 'files' => array(
+ 'config' => array(
+ 'active' => array(),
+ 'staging' => array(),
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ ), vfsStream::inspect(new vfsStreamStructureVisitor())->getStructure());
+ }
+
+ /**
+ * @covers ::getDatabaseConnectionInfo
+ */
+ public function testGetDatabaseConnectionInfoWithOutManualSetDbUrl() {
+ $this->setUp();
+
+ $options = $this->container->get('database')->getConnectionOptions();
+ $this->assertSame($this->databasePrefix, $options['prefix']['default']);
+ }
+
+ /**
+ * @covers ::getDatabaseConnectionInfo
+ */
+ public function testGetDatabaseConnectionInfoWithManualSetDbUrl() {
+ if (!file_exists('/tmp')) {
+ $this->markTestSkipped();
+ }
+ putenv('SIMPLETEST_DB=sqlite://localhost//tmp/test2.sqlite');
+ $this->setUp();
+
+ $options = $this->container->get('database')->getConnectionOptions();
+ $this->assertNotEqual('', $options['prefix']['default']);
+ }
+
+ /**
+ * @covers ::setUp
+ */
+ public function testSetUp() {
+ $this->assertTrue($this->container->has('request_stack'));
+ $this->assertTrue($this->container->initialized('request_stack'));
+ $request = $this->container->get('request_stack')->getCurrentRequest();
+ $this->assertNotEmpty($request);
+ $this->assertEquals('/', $request->getPathInfo());
+
+ $this->assertSame($request, \Drupal::request());
+
+ $this->assertEquals($this, $GLOBALS['conf']['container_service_providers']['test']);
+
+ $GLOBALS['destroy-me'] = TRUE;
+ $this->assertArrayHasKey('destroy-me', $GLOBALS);
+
+ $schema = $this->container->get('database')->schema();
+ $schema->createTable('foo', array(
+ 'fields' => array(
+ 'number' => array(
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ ),
+ ),
+ ));
+ $this->assertTrue($schema->tableExists('foo'));
+ }
+
+ /**
+ * @covers ::setUp
+ * @depends testSetUp
+ */
+ public function testSetUpDoesNotLeak() {
+ $this->assertArrayNotHasKey('destroy-me', $GLOBALS);
+
+ // Ensure that we have a different database prefix.
+ $schema = $this->container->get('database')->schema();
+ $this->assertFalse($schema->tableExists('foo'));
+ }
+
+ /**
+ * @covers ::register
+ */
+ public function testRegister() {
+ // Verify that this container is identical to the actual container.
+ $this->assertInstanceOf('Symfony\Component\DependencyInjection\ContainerInterface', $this->container);
+ $this->assertSame($this->container, \Drupal::getContainer());
+
+ // The request service should never exist.
+ $this->assertFalse($this->container->has('request'));
+
+ // Verify that there is a request stack.
+ $request = $this->container->get('request_stack')->getCurrentRequest();
+ $this->assertInstanceOf('Symfony\Component\HttpFoundation\Request', $request);
+ $this->assertSame($request, \Drupal::request());
+
+ // Trigger a container rebuild.
+ $this->enableModules(array('system'));
+
+ // Verify that this container is identical to the actual container.
+ $this->assertInstanceOf('Symfony\Component\DependencyInjection\ContainerInterface', $this->container);
+ $this->assertSame($this->container, \Drupal::getContainer());
+
+ // The request service should never exist.
+ $this->assertFalse($this->container->has('request'));
+
+ // Verify that there is a request stack (and that it persisted).
+ $new_request = $this->container->get('request_stack')->getCurrentRequest();
+ $this->assertInstanceOf('Symfony\Component\HttpFoundation\Request', $new_request);
+ $this->assertSame($new_request, \Drupal::request());
+ $this->assertSame($request, $new_request);
+ }
+
+ /**
+ * @covers ::getCompiledContainerBuilder
+ */
+ public function testCompiledContainer() {
+ $this->enableModules(['system', 'user']);
+ $this->installConfig('user');
+ }
+
+ /**
+ * @covers ::getCompiledContainerBuilder
+ * @depends testCompiledContainer
+ */
+ public function testCompiledContainerIsDestructed() {
+ $this->enableModules(['system', 'user']);
+ $this->installConfig('user');
+ }
+
+ /**
+ * @covers ::render
+ */
+ public function testRender() {
+ $type = 'processed_text';
+ $element_info = $this->container->get('element_info');
+ $this->assertSame(['#defaults_loaded' => TRUE], $element_info->getInfo($type));
+
+ $this->enableModules(array('filter'));
+
+ $this->assertNotSame($element_info, $this->container->get('element_info'));
+ $this->assertNotEmpty($this->container->get('element_info')->getInfo($type));
+
+ $build = array(
+ '#type' => 'html_tag',
+ '#tag' => 'h3',
+ '#value' => 'Inner',
+ );
+ $expected = "
Inner
\n";
+
+ $this->assertEquals('core', \Drupal::theme()->getActiveTheme()->getName());
+ $output = \Drupal::service('renderer')->renderRoot($build);
+ $this->assertEquals('core', \Drupal::theme()->getActiveTheme()->getName());
+
+ $this->assertEquals($expected, $build['#children']);
+ $this->assertEquals($expected, $output);
+ }
+
+ /**
+ * @covers ::render
+ */
+ public function testRenderWithTheme() {
+ $this->enableModules(array('system'));
+
+ $build = array(
+ '#type' => 'textfield',
+ '#name' => 'test',
+ );
+ $expected = '/' . preg_quote('assertArrayNotHasKey('theme', $GLOBALS);
+ $output = \Drupal::service('renderer')->renderRoot($build);
+ $this->assertEquals('core', \Drupal::theme()->getActiveTheme()->getName());
+
+ $this->assertRegExp($expected, $build['#children']);
+ $this->assertRegExp($expected, $output);
+ }
+
+}
diff --git a/core/tests/Drupal/Tests/AssertLegacyTrait.php b/core/tests/Drupal/Tests/AssertLegacyTrait.php
new file mode 100644
index 0000000..03e25e7
--- /dev/null
+++ b/core/tests/Drupal/Tests/AssertLegacyTrait.php
@@ -0,0 +1,129 @@
+assertEquals($expected, $actual, $message);
+ }
+
+ /**
+ * @see \Drupal\simpletest\TestBase::assertNotEqual()
+ *
+ * @deprecated Scheduled for removal in Drupal 9.0.0. Use
+ * self::assertNotEquals() instead.
+ */
+ protected function assertNotEqual($actual, $expected, $message = '') {
+ $this->assertNotEquals($expected, $actual, $message);
+ }
+
+ /**
+ * @see \Drupal\simpletest\TestBase::assertIdentical()
+ *
+ * @deprecated Scheduled for removal in Drupal 9.0.0. Use self::assertSame()
+ * instead.
+ */
+ protected function assertIdentical($actual, $expected, $message = '') {
+ $this->assertSame($expected, $actual, $message);
+ }
+
+ /**
+ * @see \Drupal\simpletest\TestBase::assertNotIdentical()
+ *
+ * @deprecated Scheduled for removal in Drupal 9.0.0. Use
+ * self::assertNotSame() instead.
+ */
+ protected function assertNotIdentical($actual, $expected, $message = '') {
+ $this->assertNotSame($expected, $actual, $message);
+ }
+
+ /**
+ * @see \Drupal\simpletest\TestBase::assertIdenticalObject()
+ *
+ * @deprecated Scheduled for removal in Drupal 9.0.0. Use self::assertEquals()
+ * instead.
+ */
+ protected function assertIdenticalObject($actual, $expected, $message = '') {
+ // Note: ::assertSame checks whether its the same object. ::assertEquals
+ // though compares
+
+ $this->assertEquals($expected, $actual, $message);
+ }
+
+ /**
+ * @see \Drupal\simpletest\TestBase::pass()
+ *
+ * @deprecated Scheduled for removal in Drupal 9.0.0. Use self::assertTrue()
+ * instead.
+ */
+ protected function pass($message) {
+ $this->assertTrue(TRUE, $message);
+ }
+
+ /**
+ * @see \Drupal\simpletest\TestBase::verbose()
+ */
+ protected function verbose($message) {
+ if (in_array('--debug', $_SERVER['argv'], TRUE)) {
+ // Write directly to STDOUT to not produce unexpected test output.
+ // The STDOUT stream does not obey output buffering.
+ fwrite(STDOUT, $message . "\n");
+ }
+ }
+
+}
diff --git a/core/tests/Drupal/Tests/KernelTestBase.php b/core/tests/Drupal/Tests/KernelTestBase.php
new file mode 100644
index 0000000..8d5f56d
--- /dev/null
+++ b/core/tests/Drupal/Tests/KernelTestBase.php
@@ -0,0 +1,1051 @@
+ ['parsedFiles'],
+ 'Drupal\Core\DependencyInjection\YamlFileLoader' => ['yaml'],
+ 'Drupal\Core\Extension\ExtensionDiscovery' => ['files'],
+ 'Drupal\Core\Extension\InfoParser' => ['parsedInfos'],
+ // Drupal::$container cannot be serialized.
+ 'Drupal' => ['container'],
+ // Settings cannot be serialized.
+ 'Drupal\Core\Site\Settings' => ['instance'],
+ ];
+
+ /**
+ * {@inheritdoc}
+ *
+ * Do not forward any global state from the parent process to the processes
+ * that run the actual tests.
+ *
+ * @see self::runTestInSeparateProcess
+ */
+ protected $preserveGlobalState = FALSE;
+
+ /**
+ * @var \Composer\Autoload\Classloader
+ */
+ protected $classLoader;
+
+ /**
+ * @var string
+ */
+ protected $siteDirectory;
+
+ /**
+ * @var string
+ */
+ protected $databasePrefix;
+
+ /**
+ * @var \Drupal\Core\DependencyInjection\ContainerBuilder
+ */
+ protected $container;
+
+ /**
+ * @var \Drupal\Core\DependencyInjection\ContainerBuilder
+ */
+ private static $initialContainerBuilder;
+
+ /**
+ * 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 class
+ * hierarchy are merged.
+ *
+ * @see \Drupal\Tests\KernelTestBase::enableModules()
+ * @see \Drupal\Tests\KernelTestBase::bootKernel()
+ *
+ * @var array
+ */
+ public static $modules = array();
+
+ /**
+ * The virtual filesystem root directory.
+ *
+ * @var \org\bovigo\vfs\vfsStreamDirectory
+ */
+ protected $vfsRoot;
+
+ /**
+ * @var int
+ */
+ protected $expectedLogSeverity;
+
+ /**
+ * @var string
+ */
+ protected $expectedLogMessage;
+
+ /**
+ * @todo Move into Config test base class.
+ * @var \Drupal\Core\Config\ConfigImporter
+ */
+ protected $configImporter;
+
+ /**
+ * The app root.
+ *
+ * @var string
+ */
+ protected $root;
+
+ /**
+ * Set to TRUE to strict check all configuration saved.
+ *
+ * @see \Drupal\Core\Config\Testing\ConfigSchemaChecker
+ *
+ * @var bool
+ */
+ protected $strictConfigSchema = TRUE;
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function setUpBeforeClass() {
+ parent::setUpBeforeClass();
+
+ // Change the current dir to DRUPAL_ROOT.
+ chdir(static::getDrupalRoot());
+ }
+
+ /**
+ * Returns the drupal root directory.
+ *
+ * @return string
+ */
+ protected static function getDrupalRoot() {
+ return dirname(dirname(substr(__DIR__, 0, -strlen(__NAMESPACE__))));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function setUp() {
+ parent::setUp();
+
+ $this->root = static::getDrupalRoot();
+ $this->bootEnvironment();
+ $this->bootKernel();
+ }
+
+ /**
+ * Bootstraps a basic test environment.
+ *
+ * Should not be called by tests. Only visible for DrupalKernel integration
+ * tests.
+ *
+ * @see \Drupal\system\Tests\DrupalKernel\DrupalKernelTest
+ * @internal
+ */
+ protected function bootEnvironment() {
+ $this->streamWrappers = array();
+ \Drupal::unsetContainer();
+
+ // @see /core/tests/bootstrap.php
+ $this->classLoader = $GLOBALS['loader'];
+
+ require_once $this->root . '/core/includes/bootstrap.inc';
+
+ // Set up virtual filesystem.
+ // Ensure that the generated test site directory does not exist already,
+ // which may happen with a large amount of concurrent threads and
+ // long-running tests.
+ do {
+ $suffix = mt_rand(100000, 999999);
+ $this->siteDirectory = 'sites/simpletest/' . $suffix;
+ $this->databasePrefix = 'simpletest' . $suffix;
+ } while (is_dir($this->root . '/' . $this->siteDirectory));
+
+ $this->vfsRoot = vfsStream::setup('root', NULL, array(
+ 'sites' => array(
+ 'simpletest' => array(
+ $suffix => array(),
+ ),
+ ),
+ ));
+ $this->siteDirectory = vfsStream::url('root/sites/simpletest/' . $suffix);
+
+ mkdir($this->siteDirectory . '/files', 0775);
+ mkdir($this->siteDirectory . '/files/config/' . CONFIG_ACTIVE_DIRECTORY, 0775, TRUE);
+ mkdir($this->siteDirectory . '/files/config/' . CONFIG_STAGING_DIRECTORY, 0775, TRUE);
+
+ // Ensure that all code that relies on drupal_valid_test_ua() can still be
+ // safely executed. This primarily affects the (test) site directory
+ // resolution (used by e.g. LocalStream and PhpStorage).
+ $this->databasePrefix = 'simpletest' . $suffix;
+ drupal_valid_test_ua($this->databasePrefix);
+
+ $settings = array(
+ 'hash_salt' => get_class($this),
+ 'file_public_path' => $this->siteDirectory . '/files',
+ // Disable Twig template caching/dumping.
+ 'twig_cache' => FALSE,
+ // @see \Drupal\Tests\KernelTestBase::register()
+ );
+ new Settings($settings);
+
+ $GLOBALS['config_directories'] = array(
+ CONFIG_ACTIVE_DIRECTORY => $this->siteDirectory . '/files/config/active',
+ CONFIG_STAGING_DIRECTORY => $this->siteDirectory . '/files/config/staging',
+ );
+
+ foreach (Database::getAllConnectionInfo() as $key => $targets) {
+ Database::removeConnection($key);
+ }
+ Database::addConnectionInfo('default', 'default', $this->getDatabaseConnectionInfo()['default']);
+ }
+
+ /**
+ * Bootstraps a kernel for a test.
+ */
+ private function bootKernel() {
+ $this->setSetting('container_yamls', []);
+ // Allow for test-specific overrides.
+ $settings_services_file = $this->root . '/sites/default' . '/testing.services.yml';
+ if (file_exists($settings_services_file)) {
+ // Copy the testing-specific service overrides in place.
+ $testing_services_file = $this->root . '/' . $this->siteDirectory . '/services.yml';
+ copy($settings_services_file, $testing_services_file);
+ $this->setSetting('container_yamls', [$testing_services_file]);
+ }
+
+ // Allow for global test environment overrides.
+ if (file_exists($test_env = $this->root . '/sites/default/testing.services.yml')) {
+ $GLOBALS['conf']['container_yamls']['testing'] = $test_env;
+ }
+ // Add this test class as a service provider.
+ $GLOBALS['conf']['container_service_providers']['test'] = $this;
+
+ $modules = self::getModulesToEnable(get_class($this));
+
+ // Prepare a precompiled container for all tests of this class.
+ // Substantially improves performance, since ContainerBuilder::compile()
+ // is very expensive. Encourages testing best practices (small tests).
+ // Normally a setUpBeforeClass() operation, but object scope is required to
+ // inject $this test class instance as a service provider (see above).
+ $rc = new \ReflectionClass(get_class($this));
+ $test_method_count = count(array_filter($rc->getMethods(), function ($method) {
+ // PHPUnit's @test annotations are intentionally ignored/not supported.
+ return strpos($method->getName(), 'test') === 0;
+ }));
+ if ($test_method_count > 1 && !$this->isTestInIsolation()) {
+ // Clone a precompiled, empty ContainerBuilder instance for each test.
+ $container = $this->getCompiledContainerBuilder($modules);
+ }
+
+ // Bootstrap the kernel. Do not use createFromRequest() to retain Settings.
+ $kernel = new DrupalKernel('testing', $this->classLoader, FALSE);
+ $kernel->setSitePath($this->siteDirectory);
+ // Boot the precompiled container. The kernel will enhance it with synthetic
+ // services.
+ if (isset($container)) {
+ $kernel->setContainer($container);
+ unset($container);
+ }
+ // Boot a new one-time container from scratch. Ensure to set the module list
+ // upfront to avoid a subsequent rebuild.
+ elseif ($modules && $extensions = $this->getExtensionsForModules($modules)) {
+ $kernel->updateModules($extensions, $extensions);
+ }
+ // DrupalKernel::boot() is not sufficient as it does not invoke preHandle(),
+ // which is required to initialize legacy global variables.
+ $request = Request::create('/');
+ $kernel->prepareLegacyRequest($request);
+
+ // register() is only called if a new container was built/compiled.
+ $this->container = $kernel->getContainer();
+
+ if ($modules) {
+ $this->container->get('module_handler')->loadAll();
+ }
+
+ // Write the core.extension configuration.
+ // Required for ConfigInstaller::installDefaultConfig() to work.
+ $this->container->get('config.storage')->write('core.extension', array(
+ 'module' => array_fill_keys($modules, 0),
+ 'theme' => array(),
+ ));
+
+ $settings = Settings::getAll();
+ $settings['php_storage']['default'] = [
+ 'class' => '\Drupal\Component\PhpStorage\FileStorage',
+ ];
+ new Settings($settings);
+ }
+
+ /**
+ * Configuration accessor for tests. Returns non-overridden configuration.
+ *
+ * @param string $name
+ * The configuration name.
+ *
+ * @return \Drupal\Core\Config\Config
+ * The configuration object with original configuration data.
+ */
+ protected function config($name) {
+ return $this->container->get('config.factory')->getEditable($name);
+ }
+
+ /**
+ * Returns the Database connection info to be used for this test.
+ *
+ * This method only exists for tests of the Database component itself, because
+ * they require multiple database connections. Each SQLite :memory: connection
+ * creates a new/separate database in memory. A shared-memory SQLite file URI
+ * triggers PHP open_basedir/allow_url_fopen/allow_url_include restrictions.
+ * Due to that, Database tests are running against a SQLite database that is
+ * located in an actual file in the system's temporary directory.
+ *
+ * Other tests should not override this method.
+ *
+ * @return array
+ * A Database connection info array.
+ *
+ * @internal
+ */
+ protected function getDatabaseConnectionInfo() {
+ // If the test is run with argument dburl then use it.
+ $db_url = getenv('SIMPLETEST_DB');
+ if (!empty($db_url)) {
+ $database = Database::convertDbUrlToConnectionInfo($db_url, $this->root);
+ Database::addConnectionInfo('default', 'default', $database);
+ }
+
+ // Clone the current connection and replace the current prefix.
+ $connection_info = Database::getConnectionInfo('default');
+ if (is_null($connection_info)) {
+ throw new \InvalidArgumentException('There is no database connection so no tests can be run. You must provide a SIMPLETEST_DB environment variable, like "sqlite://localhost//tmp/test.sqlite", to run PHPUnit based functional tests outside of run-tests.sh.');
+ }
+ else {
+ Database::renameConnection('default', 'simpletest_original_default');
+ foreach ($connection_info as $target => $value) {
+ // Replace the full table prefix definition to ensure that no table
+ // prefixes of the test runner leak into the test.
+ $connection_info[$target]['prefix'] = array(
+ 'default' => $value['prefix']['default'] . $this->databasePrefix,
+ );
+ }
+ }
+ return $connection_info;
+ }
+
+ /**
+ * Prepares a precompiled ContainerBuilder for all tests of this class.
+ *
+ * Avoids repetitive calls to ContainerBuilder::compile(), which is very slow.
+ *
+ * Based on the (always identical) list of $modules to enable, an initial
+ * container is compiled, all instantiated services are reset/removed, and
+ * this precompiled container is stored in a static class property. (Static,
+ * because PHPUnit instantiates a new class instance for each test *method*.)
+ *
+ * This method is not invoked if there is only a single test method. It is
+ * also not invoked for tests running in process isolation (since each test
+ * method runs in a separate process).
+ *
+ * The ContainerBuilder is not dumped into the filesystem (which would yield
+ * an actually compiled Container class), because
+ *
+ * 1. PHP code cannot be unloaded, so e.g. 900 tests would load 900 different,
+ * full Container classes into memory, quickly exceeding any sensible
+ * memory consumption (GigaBytes).
+ * 2. Dumping a Container class requires to actually write to the system's
+ * temporary directory. This is not really easy with vfs, because vfs
+ * doesn't support yet "include 'vfs://container.php'.". Maybe we could fix
+ * that upstream.
+ * 3. PhpDumper is very slow on its own.
+ *
+ * @param string[] $modules
+ * The list of modules to enable.
+ *
+ * @return \Drupal\Core\DependencyInjection\ContainerBuilder
+ * A clone of the precompiled, empty service container.
+ */
+ private function getCompiledContainerBuilder(array $modules) {
+ if (!isset(self::$initialContainerBuilder)) {
+ $kernel = new DrupalKernel('testing', $this->classLoader, FALSE);
+ $kernel->setSitePath($this->siteDirectory);
+ if ($modules && $extensions = $this->getExtensionsForModules($modules)) {
+ $kernel->updateModules($extensions, $extensions);
+ }
+ $kernel->boot();
+ self::$initialContainerBuilder = $kernel->getContainer();
+
+ // Remove all instantiated services, so the container is safe for cloning.
+ // Technically, ContainerBuilder::set($id, NULL) removes each definition,
+ // but the container is compiled/frozen already.
+ foreach (self::$initialContainerBuilder->getServiceIds() as $id) {
+ self::$initialContainerBuilder->set($id, NULL);
+ }
+
+ // Destruct and trigger garbage collection.
+ \Drupal::unsetContainer();
+ $kernel->shutdown();
+ $kernel = NULL;
+ // @see register()
+ $this->container = NULL;
+ }
+
+ $container = clone self::$initialContainerBuilder;
+
+ return $container;
+ }
+
+ /**
+ * Returns Extension objects for $modules to enable.
+ *
+ * @param string[] $modules
+ * The list of modules to enable.
+ *
+ * @return \Drupal\Core\Extension\Extension[]
+ * Extension objects for $modules, keyed by module name.
+ *
+ * @throws \PHPUnit_Framework_Exception
+ * If a module is not available.
+ *
+ * @see \Drupal\Tests\KernelTestBase::enableModules()
+ * @see \Drupal\Core\Extension\ModuleHandler::add()
+ */
+ private function getExtensionsForModules(array $modules) {
+ $extensions = array();
+ $discovery = new ExtensionDiscovery($this->root);
+ $discovery->setProfileDirectories(array());
+ $list = $discovery->scan('module');
+ foreach ($modules as $name) {
+ if (!isset($list[$name])) {
+ throw new \PHPUnit_Framework_Exception("Unavailable module: '$name'. If this module needs to be downloaded separately, annotate the test class with '@requires module $name'.");
+ }
+ $extensions[$name] = $list[$name];
+ }
+ return $extensions;
+ }
+
+ /**
+ * Registers test-specific services.
+ *
+ * Extend this method in your test to register additional services. This
+ * method is called whenever the kernel is rebuilt.
+ *
+ * @param \Drupal\Core\DependencyInjection\ContainerBuilder $container
+ * The service container to enhance.
+ *
+ * @see \Drupal\Tests\KernelTestBase::bootKernel()
+ */
+ public function register(ContainerBuilder $container) {
+ // Keep the container object around for tests.
+ $this->container = $container;
+
+ $container
+ ->register('flood', 'Drupal\Core\Flood\MemoryBackend')
+ ->addArgument(new Reference('request_stack'));
+ $container
+ ->register('lock', 'Drupal\Core\Lock\NullLockBackend');
+ $container
+ ->register('cache_factory', 'Drupal\Core\Cache\MemoryBackendFactory');
+ $container
+ ->register('keyvalue.memory', 'Drupal\Core\KeyValueStore\KeyValueMemoryFactory')
+ // Must persist container rebuilds, or all data would vanish otherwise.
+ ->addTag('persist');
+ $container
+ ->setAlias('keyvalue', 'keyvalue.memory');
+
+ if ($this->strictConfigSchema) {
+ $container
+ ->register('simpletest.config_schema_checker', 'Drupal\Core\Config\Testing\ConfigSchemaChecker')
+ ->addArgument(new Reference('config.typed'))
+ ->addTag('event_subscriber');
+ }
+
+ 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.
+ $container->getDefinition('path_processor_alias')
+ ->clearTag('path_processor_inbound')
+ ->clearTag('path_processor_outbound');
+ }
+
+ if ($container->hasDefinition('password')) {
+ $container->getDefinition('password')
+ ->setArguments(array(1));
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function assertPostConditions() {
+ // Execute registered Drupal shutdown functions prior to tearing down.
+ // @see _drupal_shutdown_function()
+ $callbacks = &drupal_register_shutdown_function();
+ while ($callback = array_shift($callbacks)) {
+ call_user_func_array($callback['callback'], $callback['arguments']);
+ }
+
+ // Shut down the kernel (if bootKernel() was called).
+ // @see \Drupal\system\Tests\DrupalKernel\DrupalKernelTest
+ if ($this->container) {
+ $this->container->get('kernel')->shutdown();
+ }
+
+ // Fail in case any (new) shutdown functions exist.
+ $this->assertCount(0, drupal_register_shutdown_function(), 'Unexpected Drupal shutdown callbacks exist after running shutdown functions.');
+
+ parent::assertPostConditions();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function tearDown() {
+ // Destroy the testing kernel.
+ if (isset($this->kernel)) {
+ $this->kernel->shutdown();
+ }
+
+ // Free up memory: Own properties.
+ $this->classLoader = NULL;
+ $this->vfsRoot = NULL;
+ $this->configImporter = NULL;
+
+ // Free up memory: Custom test class properties.
+ // Note: Private properties cannot be cleaned up.
+ $rc = new \ReflectionClass(__CLASS__);
+ $blacklist = array();
+ foreach ($rc->getProperties() as $property) {
+ $blacklist[$property->name] = $property->getDeclaringClass()->name;
+ }
+ $rc = new \ReflectionClass($this);
+ foreach ($rc->getProperties(\ReflectionProperty::IS_PUBLIC | \ReflectionProperty::IS_PROTECTED) as $property) {
+ if (!$property->isStatic() && !isset($blacklist[$property->name])) {
+ $this->{$property->name} = NULL;
+ }
+ }
+
+ // Clean up statics, container, and settings.
+ if (function_exists('drupal_static_reset')) {
+ drupal_static_reset();
+ }
+ \Drupal::unsetContainer();
+ $this->container = NULL;
+ new Settings(array());
+
+ // Destroy the database connection, which for example removes the memory
+ // from sqlite in memory.
+ foreach (Database::getAllConnectionInfo() as $key => $targets) {
+ Database::removeConnection($key);
+ }
+
+ parent::tearDown();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function tearDownAfterClass() {
+ // Free up memory: Precompiled container.
+ self::$initialContainerBuilder = NULL;
+ parent::tearDownAfterClass();
+ }
+
+ /**
+ * Installs default configuration for a given list of modules.
+ *
+ * @param string|string[] $modules
+ * A list of modules for which to install default configuration.
+ *
+ * @throws \LogicException
+ * If any module in $modules is not enabled.
+ */
+ protected function installConfig($modules) {
+ foreach ((array) $modules as $module) {
+ if (!$this->container->get('module_handler')->moduleExists($module)) {
+ throw new \LogicException("$module module is not enabled.");
+ }
+ $this->container->get('config.installer')->installDefaultConfig('module', $module);
+ }
+ }
+
+ /**
+ * Installs database tables 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.
+ *
+ * @throws \LogicException
+ * If $module is not enabled or the table schema cannot be found.
+ */
+ protected function installSchema($module, $tables) {
+ // drupal_get_module_schema() 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 \LogicException("$module module is not enabled.");
+ }
+ $tables = (array) $tables;
+ foreach ($tables as $table) {
+ $schema = drupal_get_module_schema($module, $table);
+ if (empty($schema)) {
+ throw new \LogicException("$module module does not define a schema for table '$table'.");
+ }
+ $this->container->get('database')->schema()->createTable($table, $schema);
+ }
+ }
+
+ /**
+ * Installs the storage schema for a specific entity type.
+ *
+ * @param string $entity_type_id
+ * The ID of the entity type.
+ */
+ protected function installEntitySchema($entity_type_id) {
+ /** @var \Drupal\Core\Entity\EntityManagerInterface $entity_manager */
+ $entity_manager = $this->container->get('entity.manager');
+ $entity_type = $entity_manager->getDefinition($entity_type_id);
+ $entity_manager->onEntityTypeCreate($entity_type);
+
+ // For test runs, the most common storage backend is a SQL database. For
+ // this case, ensure the tables got created.
+ $storage = $entity_manager->getStorage($entity_type_id);
+ if ($storage instanceof SqlEntityStorageInterface) {
+ $tables = $storage->getTableMapping()->getTableNames();
+ $db_schema = $this->container->get('database')->schema();
+ $all_tables_exist = TRUE;
+ foreach ($tables as $table) {
+ if (!$db_schema->tableExists($table)) {
+ $this->fail(SafeMarkup::format('Installed entity type table for the %entity_type entity type: %table', array(
+ '%entity_type' => $entity_type_id,
+ '%table' => $table,
+ )));
+ $all_tables_exist = FALSE;
+ }
+ }
+ if ($all_tables_exist) {
+ $this->pass(SafeMarkup::format('Installed entity type tables for the %entity_type entity type: %tables', array(
+ '%entity_type' => $entity_type_id,
+ '%tables' => '{' . implode('}, {', $tables) . '}',
+ )));
+ }
+ }
+ }
+
+ /**
+ * Enables modules for this test.
+ *
+ * @param string[] $modules
+ * A list of modules to enable. Dependencies are not resolved; i.e.,
+ * multiple modules have to be specified individually. The modules are only
+ * added to the active module list and loaded; i.e., their database schema
+ * is not installed. hook_install() is not invoked. A custom module weight
+ * is not applied.
+ *
+ * @throws \LogicException
+ * If any module in $modules is already enabled.
+ * @throws \RuntimeException
+ * If a module is not enabled after enabling it.
+ */
+ protected function enableModules(array $modules) {
+ $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
+ if ($trace[1]['function'] === 'setUp') {
+ trigger_error('KernelTestBase::enableModules() should not be called from setUp(). Use the $modules property instead.', E_DEPRECATED);
+ }
+ unset($trace);
+
+ // Perform an ExtensionDiscovery scan as this function may receive a
+ // profile that is not the current profile, and we don't yet have a cached
+ // way to receive inactive profile information.
+ // @todo Remove as part of https://www.drupal.org/node/2186491
+ $listing = new ExtensionDiscovery(\Drupal::root());
+ $module_list = $listing->scan('module');
+ // In ModuleHandlerTest we pass in a profile as if it were a module.
+ $module_list += $listing->scan('profile');
+
+ // 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 = $this->container->get('config.storage');
+ $extension_config = $active_storage->read('core.extension');
+
+ foreach ($modules as $module) {
+ if ($module_handler->moduleExists($module)) {
+ throw new \LogicException("$module module is already enabled.");
+ }
+ $module_handler->addModule($module, $module_list[$module]->getPath());
+ // Maintain the list of enabled modules in configuration.
+ $extension_config['module'][$module] = 0;
+ }
+ $active_storage->write('core.extension', $extension_config);
+
+ // Update the kernel to make their services available.
+ $extensions = $module_handler->getModuleList();
+ $this->container->get('kernel')->updateModules($extensions, $extensions);
+
+ // Ensure isLoaded() is TRUE in order to make
+ // \Drupal\Core\Theme\ThemeManagerInterface::render() 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();
+ foreach ($modules as $module) {
+ if (!$module_handler->moduleExists($module)) {
+ throw new \RuntimeException("$module module is not enabled after enabling it.");
+ }
+ }
+ }
+
+ /**
+ * Disables modules for this test.
+ *
+ * @param string[] $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 enabled modules is still loaded. The modules are only
+ * removed from the active module list.
+ *
+ * @throws \LogicException
+ * If any module in $modules is already disabled.
+ * @throws \RuntimeException
+ * If a module is not disabled after disabling it.
+ */
+ 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->config('core.extension');
+ foreach ($modules as $module) {
+ if (!$module_handler->moduleExists($module)) {
+ throw new \LogicException("$module module cannot be disabled because it is not enabled.");
+ }
+ 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->container->get('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();
+ foreach ($modules as $module) {
+ if ($module_handler->moduleExists($module)) {
+ throw new \RuntimeException("$module module is not disabled after disabling it.");
+ }
+ }
+ }
+
+ /**
+ * Renders a render array.
+ *
+ * @param array $elements
+ * The elements to render.
+ *
+ * @return string
+ * The rendered string output (typically HTML).
+ */
+ protected function render(array &$elements) {
+ $content = $this->container->get('renderer')->render($elements);
+ drupal_process_attached($elements);
+ $this->setRawContent($content);
+ $this->verbose('
' . SafeMarkup::checkPlain($content));
+ return $content;
+ }
+
+ /**
+ * Sets an in-memory Settings variable.
+ *
+ * @param string $name
+ * The name of the setting to set.
+ * @param bool|string|int|array|null $value
+ * The value to set. Note that array values are replaced entirely; use
+ * \Drupal\Core\Site\Settings::get() to perform custom merges.
+ */
+ protected function setSetting($name, $value) {
+ $settings = Settings::getAll();
+ $settings[$name] = $value;
+ new Settings($settings);
+ }
+
+ /**
+ * Returns a ConfigImporter object to import test configuration.
+ *
+ * @return \Drupal\Core\Config\ConfigImporter
+ *
+ * @todo Move into Config-specific test base class.
+ */
+ protected function configImporter() {
+ if (!$this->configImporter) {
+ // Set up the ConfigImporter object for testing.
+ $storage_comparer = new StorageComparer(
+ $this->container->get('config.storage.staging'),
+ $this->container->get('config.storage'),
+ $this->container->get('config.manager')
+ );
+ $this->configImporter = new ConfigImporter(
+ $storage_comparer,
+ $this->container->get('event_dispatcher'),
+ $this->container->get('config.manager'),
+ $this->container->get('lock'),
+ $this->container->get('config.typed'),
+ $this->container->get('module_handler'),
+ $this->container->get('module_installer'),
+ $this->container->get('theme_handler'),
+ $this->container->get('string_translation')
+ );
+ }
+ // Always recalculate the changelist when called.
+ return $this->configImporter->reset();
+ }
+
+ /**
+ * Copies configuration objects from a source storage to a target storage.
+ *
+ * @param \Drupal\Core\Config\StorageInterface $source_storage
+ * The source config storage.
+ * @param \Drupal\Core\Config\StorageInterface $target_storage
+ * The target config storage.
+ *
+ * @todo Move into Config-specific test base class.
+ */
+ protected function copyConfig(StorageInterface $source_storage, StorageInterface $target_storage) {
+ $target_storage->deleteAll();
+ foreach ($source_storage->listAll() as $name) {
+ $target_storage->write($name, $source_storage->read($name));
+ }
+ }
+
+ /**
+ * Stops test execution.
+ */
+ protected function stop() {
+ $this->getTestResultObject()->stop();
+ }
+
+ /**
+ * Dumps the current state of the virtual filesystem to STDOUT.
+ */
+ protected function vfsDump() {
+ vfsStream::inspect(new vfsStreamPrintVisitor());
+ }
+
+ /**
+ * Returns the modules to enable for this test.
+ *
+ * @param string $class
+ * The fully-qualified class name of this test.
+ *
+ * @return array
+ */
+ private static function getModulesToEnable($class) {
+ $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.
+ $modules = array_reverse($modules);
+ return call_user_func_array('array_merge_recursive', $modules);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function prepareTemplate(\Text_Template $template) {
+ $bootstrap_globals = '';
+
+ // Fix missing bootstrap.php when $preserveGlobalState is FALSE.
+ // @see https://github.com/sebastianbergmann/phpunit/pull/797
+ $bootstrap_globals .= '$__PHPUNIT_BOOTSTRAP = ' . var_export($GLOBALS['__PHPUNIT_BOOTSTRAP'], TRUE) . ";\n";
+
+ // Avoid repetitive test namespace discoveries to improve performance.
+ // @see /core/tests/bootstrap.php
+ $bootstrap_globals .= '$namespaces = ' . var_export($GLOBALS['namespaces'], TRUE) . ";\n";
+
+ $template->setVar(array(
+ 'constants' => '',
+ 'included_files' => '',
+ 'globals' => $bootstrap_globals,
+ ));
+ }
+
+ /**
+ * Returns whether the current test runs in isolation.
+ *
+ * @return bool
+ *
+ * @see https://github.com/sebastianbergmann/phpunit/pull/1350
+ */
+ protected function isTestInIsolation() {
+ return function_exists('__phpunit_run_isolated_test');
+ }
+
+ /**
+ * BC: Automatically resolve former KernelTestBase class properties.
+ *
+ * Test authors should follow the provided instructions and adjust their tests
+ * accordingly.
+ *
+ * @deprecated in Drupal 8.0.x, will be removed before Drupal 8.2.0.
+ */
+ public function __get($name) {
+ if (in_array($name, array(
+ 'public_files_directory',
+ 'private_files_directory',
+ 'temp_files_directory',
+ 'translation_files_directory',
+ ))) {
+ // @comment it in again.
+ trigger_error(sprintf("KernelTestBase::\$%s no longer exists. Use the regular API method to retrieve it instead (e.g., Settings).", $name), E_USER_DEPRECATED);
+ switch ($name) {
+ case 'public_files_directory':
+ return Settings::get('file_public_path', conf_path() . '/files');
+
+ case 'private_files_directory':
+ return $this->container->get('config.factory')->get('system.file')->get('path.private');
+
+ case 'temp_files_directory':
+ return file_directory_temp();
+
+ case 'translation_files_directory':
+ return Settings::get('file_public_path', conf_path() . '/translations');
+ }
+ }
+
+ if ($name === 'configDirectories') {
+ trigger_error(sprintf("KernelTestBase::\$%s no longer exists. Use config_get_config_directory() directly instead.", $name), E_DEPRECATED);
+ return array(
+ CONFIG_ACTIVE_DIRECTORY => config_get_config_directory(CONFIG_ACTIVE_DIRECTORY),
+ CONFIG_STAGING_DIRECTORY => config_get_config_directory(CONFIG_STAGING_DIRECTORY),
+ );
+ }
+
+ $denied = array(
+ // @see \Drupal\simpletest\TestBase
+ 'testId',
+ 'timeLimit',
+ 'results',
+ 'assertions',
+ 'skipClasses',
+ 'verbose',
+ 'verboseId',
+ 'verboseClassName',
+ 'verboseDirectory',
+ 'verboseDirectoryUrl',
+ 'dieOnFail',
+ 'kernel',
+ // @see \Drupal\simpletest\TestBase::prepareEnvironment()
+ 'generatedTestFiles',
+ // @see \Drupal\simpletest\KernelTestBase::containerBuild()
+ 'keyValueFactory',
+ );
+ if (in_array($name, $denied) || strpos($name, 'original') === 0) {
+ throw new \RuntimeException(sprintf('TestBase::$%s property no longer exists', $name));
+ }
+ }
+
+}
diff --git a/core/tests/bootstrap.php b/core/tests/bootstrap.php
index fd980a4..81679ee 100644
--- a/core/tests/bootstrap.php
+++ b/core/tests/bootstrap.php
@@ -20,9 +20,10 @@ function drupal_phpunit_find_extension_directories($scan_directory) {
$extensions = array();
$dirs = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($scan_directory, \RecursiveDirectoryIterator::FOLLOW_SYMLINKS));
foreach ($dirs as $dir) {
- if (strpos($dir->getPathname(), 'info.yml') !== FALSE) {
+ if (strpos($dir->getPathname(), '.info.yml') !== FALSE) {
// Cut off ".info.yml" from the filename for use as the extension name.
- $extensions[substr($dir->getFilename(), 0, -9)] = $dir->getPathInfo()->getRealPath();
+ $extensions[substr($dir->getFilename(), 0, -9)] = $dir->getPathInfo()
+ ->getRealPath();
}
}
return $extensions;
@@ -35,10 +36,19 @@ function drupal_phpunit_find_extension_directories($scan_directory) {
* An array of directories under which contributed extensions may exist.
*/
function drupal_phpunit_contrib_extension_directory_roots() {
- $sites_path = __DIR__ . '/../../sites';
- $paths = array();
+ $root = dirname(dirname(__DIR__));
+ $paths = array(
+ $root . '/core/modules',
+ $root . '/core/profiles',
+ $root . '/modules',
+ $root . '/profiles',
+ );
+ $sites_path = $root . '/sites';
// Note this also checks sites/../modules and sites/../profiles.
foreach (scandir($sites_path) as $site) {
+ if ($site[0] === '.' || $site === 'simpletest') {
+ continue;
+ }
$path = "$sites_path/$site";
$paths[] = is_dir("$path/modules") ? realpath("$path/modules") : NULL;
$paths[] = is_dir("$path/profiles") ? realpath("$path/profiles") : NULL;
@@ -49,37 +59,42 @@ function drupal_phpunit_contrib_extension_directory_roots() {
/**
* Registers the namespace for each extension directory with the autoloader.
*
- * @param Composer\Autoload\ClassLoader $loader
- * The supplied autoloader.
* @param array $dirs
* An associative array of extension directories, keyed by extension name.
+ *
+ * @return array
+ * An associative array of extension directories, keyed by their namespace.
*/
-function drupal_phpunit_register_extension_dirs(Composer\Autoload\ClassLoader $loader, $dirs) {
+function drupal_phpunit_get_extension_namespaces($dirs) {
+ $namespaces = array();
foreach ($dirs as $extension => $dir) {
if (is_dir($dir . '/src')) {
// Register the PSR-4 directory for module-provided classes.
- $loader->addPsr4('Drupal\\' . $extension . '\\', $dir . '/src');
+ $namespaces['Drupal\\' . $extension . '\\'][] = $dir . '/src';
}
if (is_dir($dir . '/tests/src')) {
// Register the PSR-4 directory for PHPUnit test classes.
- $loader->addPsr4('Drupal\\Tests\\' . $extension . '\\', $dir . '/tests/src');
+ $namespaces['Drupal\\Tests\\' . $extension . '\\'][] = $dir . '/tests/src';
}
}
+ return $namespaces;
}
// Start with classes in known locations.
$loader = require __DIR__ . '/../../autoload.php';
$loader->add('Drupal\\Tests', __DIR__);
-// Scan for arbitrary extension namespaces from core and contrib.
-$extension_roots = array_merge(array(
- __DIR__ . '/../modules',
- __DIR__ . '/../profiles',
-), drupal_phpunit_contrib_extension_directory_roots());
+if (!isset($GLOBALS['namespaces'])) {
+ // Scan for arbitrary extension namespaces from core and contrib.
+ $extension_roots = drupal_phpunit_contrib_extension_directory_roots();
-$dirs = array_map('drupal_phpunit_find_extension_directories', $extension_roots);
-$dirs = array_reduce($dirs, 'array_merge', array());
-drupal_phpunit_register_extension_dirs($loader, $dirs);
+ $dirs = array_map('drupal_phpunit_find_extension_directories', $extension_roots);
+ $dirs = array_reduce($dirs, 'array_merge', array());
+ $GLOBALS['namespaces'] = drupal_phpunit_get_extension_namespaces($dirs);
+}
+foreach ($GLOBALS['namespaces'] as $prefix => $paths) {
+ $loader->addPsr4($prefix, $paths);
+}
// Set sane locale settings, to ensure consistent string, dates, times and
// numbers handling.
@@ -89,3 +104,6 @@ function drupal_phpunit_register_extension_dirs(Composer\Autoload\ClassLoader $l
// Set the default timezone. While this doesn't cause any tests to fail, PHP
// complains if 'date.timezone' is not set in php.ini.
date_default_timezone_set('UTC');
+
+// Clean up.
+unset($extension_roots, $dirs);