2015-09-04 20:20:09 +00:00
< ? php
namespace Drupal\KernelTests ;
2015-10-08 18:40:12 +00:00
use Drupal\Component\FileCache\ApcuFileCacheBackend ;
use Drupal\Component\FileCache\FileCache ;
use Drupal\Component\FileCache\FileCacheFactory ;
2015-09-04 20:20:09 +00:00
use Drupal\Component\Utility\Html ;
use Drupal\Component\Utility\SafeMarkup ;
use Drupal\Core\Config\ConfigImporter ;
use Drupal\Core\Config\StorageComparer ;
use Drupal\Core\Config\StorageInterface ;
use Drupal\Core\Database\Database ;
use Drupal\Core\DependencyInjection\ContainerBuilder ;
use Drupal\Core\DependencyInjection\ServiceProviderInterface ;
use Drupal\Core\DrupalKernel ;
use Drupal\Core\Entity\Sql\SqlEntityStorageInterface ;
use Drupal\Core\Extension\ExtensionDiscovery ;
use Drupal\Core\Site\Settings ;
use Drupal\simpletest\AssertContentTrait ;
2015-10-08 18:40:12 +00:00
use Drupal\simpletest\AssertHelperTrait ;
2015-09-04 20:20:09 +00:00
use Drupal\simpletest\RandomGeneratorTrait ;
2016-04-20 16:56:34 +00:00
use Drupal\simpletest\TestServiceProvider ;
2015-09-04 20:20:09 +00:00
use Symfony\Component\DependencyInjection\Reference ;
use Symfony\Component\HttpFoundation\Request ;
use org\bovigo\vfs\vfsStream ;
use org\bovigo\vfs\visitor\vfsStreamPrintVisitor ;
/**
* Base class for functional 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 .
*
* Unlike \Drupal\Tests\UnitTestCase , modules specified in the $modules
* property are automatically added to the service container for each test .
* 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 .
*
* Unlike \Drupal\simpletest\WebTestBase , the modules are only loaded , but not
* installed . Modules have to be installed manually , if needed .
*
* @ see \Drupal\Tests\KernelTestBase :: $modules
* @ see \Drupal\Tests\KernelTestBase :: enableModules ()
*
* @ todo Extend :: setRequirementsFromAnnotation () and :: checkRequirements () to
* account for '@requires module' .
*/
abstract class KernelTestBase extends \PHPUnit_Framework_TestCase implements ServiceProviderInterface {
use AssertLegacyTrait ;
use AssertContentTrait ;
2015-10-08 18:40:12 +00:00
use AssertHelperTrait ;
2015-09-04 20:20:09 +00:00
use RandomGeneratorTrait ;
/**
* { @ inheritdoc }
*
* Back up and restore any global variables that may be changed by tests .
*
* @ see self :: runTestInSeparateProcess
*/
protected $backupGlobals = TRUE ;
/**
* { @ inheritdoc }
*
* Kernel tests are run in separate processes to prevent collisions between
* code that may be loaded by tests .
*/
protected $runTestInSeparateProcess = TRUE ;
/**
* { @ inheritdoc }
*
* Back up and restore static class properties that may be changed by tests .
*
* @ see self :: runTestInSeparateProcess
*/
protected $backupStaticAttributes = TRUE ;
/**
* { @ inheritdoc }
*
* Contains a few static class properties for performance .
*/
protected $backupStaticAttributesBlacklist = [
// Ignore static discovery/parser caches to speed up tests.
'Drupal\Component\Discovery\YamlDiscovery' => [ '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 ;
2016-04-20 16:56:34 +00:00
/**
* An array of config object names that are excluded from schema checking .
*
* @ var string []
*/
protected static $configSchemaCheckerExclusions = array (
// Following are used to test lack of or partial schema. Where partial
// schema is provided, that is explicitly tested in specific tests.
'config_schema_test.noschema' ,
'config_schema_test.someschema' ,
'config_schema_test.schema_data_types' ,
'config_schema_test.no_schema_data_types' ,
// Used to test application of schema to filtering of configuration.
'config_test.dynamic.system' ,
);
2015-09-04 20:20:09 +00:00
/**
* { @ 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 ();
2015-10-08 18:40:12 +00:00
$this -> initFileCache ();
2015-09-04 20:20:09 +00:00
$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 ();
2016-01-07 00:31:26 +00:00
$this -> classLoader = require $this -> root . '/autoload.php' ;
2015-09-04 20:20:09 +00:00
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 );
2015-10-08 18:40:12 +00:00
mkdir ( $this -> siteDirectory . '/files/config/' . CONFIG_SYNC_DIRECTORY , 0775 , TRUE );
2015-09-04 20:20:09 +00:00
// 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\KernelTests\KernelTestBase::register()
);
new Settings ( $settings );
$GLOBALS [ 'config_directories' ] = array (
2015-10-08 18:40:12 +00:00
CONFIG_SYNC_DIRECTORY => $this -> siteDirectory . '/files/config/sync' ,
2015-09-04 20:20:09 +00:00
);
foreach ( Database :: getAllConnectionInfo () as $key => $targets ) {
Database :: removeConnection ( $key );
}
Database :: addConnectionInfo ( 'default' , 'default' , $this -> getDatabaseConnectionInfo ()[ 'default' ]);
}
2016-04-20 16:56:34 +00:00
/**
* @ return string
*/
public function getDatabasePrefix () {
return $this -> databasePrefix ;
}
2015-09-04 20:20:09 +00:00
/**
* 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 ();
2015-10-08 18:40:12 +00:00
// Ensure database tasks have been run.
require_once __DIR__ . '/../../../includes/install.inc' ;
$connection = Database :: getConnection ();
$errors = db_installer_object ( $connection -> driver ()) -> runTasks ();
if ( ! empty ( $errors )) {
$this -> fail ( 'Failed to run installer database tasks: ' . implode ( ', ' , $errors ));
}
2015-09-04 20:20:09 +00:00
if ( $modules ) {
$this -> container -> get ( 'module_handler' ) -> loadAll ();
}
2016-04-20 16:56:34 +00:00
$this -> container -> get ( 'request_stack' ) -> push ( $request );
// Setup the destion to the be frontpage by default.
\Drupal :: destination () -> set ( '/' );
2015-09-04 20:20:09 +00:00
// 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 );
2016-04-07 18:19:57 +00:00
// Manually configure the test mail collector implementation to prevent
// tests from sending out emails and collect them in state instead.
// While this should be enforced via settings.php prior to installation,
// some tests expect to be able to test mail system implementations.
$GLOBALS [ 'config' ][ 'system.mail' ][ 'interface' ][ 'default' ] = 'test_mail_collector' ;
2015-09-04 20:20:09 +00:00
}
/**
* 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' );
2015-10-08 18:40:12 +00:00
if ( empty ( $db_url )) {
2016-01-07 00:31:26 +00:00
throw new \Exception ( 'There is no database connection so no tests can be run. You must provide a SIMPLETEST_DB environment variable to run PHPUnit based functional tests outside of run-tests.sh. See https://www.drupal.org/node/2116263#skipped-tests for more information.' );
2015-10-08 18:40:12 +00:00
}
else {
2015-09-04 20:20:09 +00:00
$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' );
2015-10-08 18:40:12 +00:00
if ( ! empty ( $connection_info )) {
2015-09-04 20:20:09 +00:00
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 ;
}
2015-10-08 18:40:12 +00:00
/**
* Initializes the FileCache component .
*
* We can not use the Settings object in a component , that ' s why we have to do
* it here instead of \Drupal\Component\FileCache\FileCacheFactory .
*/
protected function initFileCache () {
$configuration = Settings :: get ( 'file_cache' );
// Provide a default configuration, if not set.
if ( ! isset ( $configuration [ 'default' ])) {
$configuration [ 'default' ] = [
'class' => FileCache :: class ,
'cache_backend_class' => NULL ,
'cache_backend_configuration' => [],
];
// @todo Use extension_loaded('apcu') for non-testbot
// https://www.drupal.org/node/2447753.
2016-01-07 00:31:26 +00:00
if ( function_exists ( 'apcu_fetch' )) {
2015-10-08 18:40:12 +00:00
$configuration [ 'default' ][ 'cache_backend_class' ] = ApcuFileCacheBackend :: class ;
}
}
FileCacheFactory :: setConfiguration ( $configuration );
FileCacheFactory :: setPrefix ( Settings :: getApcuPrefix ( 'file_cache' , $this -> root ));
}
2015-09-04 20:20:09 +00:00
/**
* 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' ))
2016-04-20 16:56:34 +00:00
-> addArgument ( $this -> getConfigSchemaExclusions ())
2015-09-04 20:20:09 +00:00
-> 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 ));
}
2016-04-20 16:56:34 +00:00
TestServiceProvider :: addRouteProvider ( $container );
}
/**
* Gets the config schema exclusions for this test .
*
* @ return string []
* An array of config object names that are excluded from schema checking .
*/
protected function getConfigSchemaExclusions () {
$class = get_class ( $this );
$exceptions = [];
while ( $class ) {
if ( property_exists ( $class , 'configSchemaCheckerExclusions' )) {
$exceptions = array_merge ( $exceptions , $class :: $configSchemaCheckerExclusions );
}
$class = get_parent_class ( $class );
}
// Filter out any duplicates.
return array_unique ( $exceptions );
2015-09-04 20:20:09 +00:00
}
/**
* { @ 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 ();
}
// Remove all prefixed tables.
$original_connection_info = Database :: getConnectionInfo ( 'simpletest_original_default' );
$original_prefix = $original_connection_info [ 'default' ][ 'prefix' ][ 'default' ];
$test_connection_info = Database :: getConnectionInfo ( 'default' );
$test_prefix = $test_connection_info [ 'default' ][ 'prefix' ][ 'default' ];
if ( $original_prefix != $test_prefix ) {
$tables = Database :: getConnection () -> schema () -> findTables ( '%' );
foreach ( $tables as $table ) {
if ( Database :: getConnection () -> schema () -> dropTable ( $table )) {
unset ( $tables [ $table ]);
}
}
}
// 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 ;
}
}
2015-10-08 18:40:12 +00:00
// Clean FileCache cache.
FileCache :: reset ();
2015-09-04 20:20:09 +00:00
// Clean up statics, container, and settings.
if ( function_exists ( 'drupal_static_reset' )) {
drupal_static_reset ();
}
\Drupal :: unsetContainer ();
$this -> container = NULL ;
new Settings ( array ());
parent :: tearDown ();
}
/**
* @ after
*
* Additional tear down method to close the connection at the end .
*/
public function tearDownCloseDatabaseConnection () {
// Destroy the database connection, which for example removes the memory
// from sqlite in memory.
foreach ( Database :: getAllConnectionInfo () as $key => $targets ) {
Database :: removeConnection ( $key );
}
}
/**
* { @ 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 )) {
2016-04-20 16:56:34 +00:00
// BC layer to avoid some contrib tests to fail.
// @todo Remove the BC layer before 8.1.x release.
// @see https://www.drupal.org/node/2670360
// @see https://www.drupal.org/node/2670454
if ( $module == 'system' ) {
continue ;
}
2015-09-04 20:20:09 +00:00
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 .
*
2016-01-07 00:31:26 +00:00
* To install test modules outside of the testing environment , add
* @ code
* $settings [ 'extension_discovery_scan_tests' ] = TRUE ;
* @ encode
* to your settings . php .
*
2015-09-04 20:20:09 +00:00
* @ 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_USER_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 );
2016-01-07 00:31:26 +00:00
// Ensure isLoaded() is TRUE in order to make
// \Drupal\Core\Theme\ThemeManagerInterface::render() work.
2015-09-04 20:20:09 +00:00
// 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 ) {
2016-04-20 16:56:34 +00:00
// \Drupal\Core\Render\BareHtmlPageRenderer::renderBarePage calls out to
// system_page_attachments() directly.
if ( ! \Drupal :: moduleHandler () -> moduleExists ( 'system' )) {
throw new \Exception ( __METHOD__ . ' requires system module to be installed.' );
}
2015-10-08 18:40:12 +00:00
// Use the bare HTML page renderer to render our links.
$renderer = $this -> container -> get ( 'bare_html_page_renderer' );
2016-04-20 16:56:34 +00:00
$response = $renderer -> renderBarePage ( $elements , '' , 'maintenance_page' );
2015-10-08 18:40:12 +00:00
// Glean the content from the response object.
$content = $response -> getContent ();
2015-09-04 20:20:09 +00:00
$this -> setRawContent ( $content );
$this -> verbose ( '<pre style="white-space: pre-wrap">' . Html :: escape ( $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 (
2015-10-08 18:40:12 +00:00
$this -> container -> get ( 'config.storage.sync' ),
2015-09-04 20:20:09 +00:00
$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' :
2015-10-08 18:40:12 +00:00
return Settings :: get ( 'file_public_path' , \Drupal :: service ( 'site.path' ) . '/files' );
2015-09-04 20:20:09 +00:00
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' :
2015-10-08 18:40:12 +00:00
return Settings :: get ( 'file_public_path' , \Drupal :: service ( 'site.path' ) . '/translations' );
2015-09-04 20:20:09 +00:00
}
}
if ( $name === 'configDirectories' ) {
trigger_error ( sprintf ( " KernelTestBase:: \$ %s no longer exists. Use config_get_config_directory() directly instead. " , $name ), E_USER_DEPRECATED );
return array (
2015-10-08 18:40:12 +00:00
CONFIG_SYNC_DIRECTORY => config_get_config_directory ( CONFIG_SYNC_DIRECTORY ),
2015-09-04 20:20:09 +00:00
);
}
$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 ));
}
}
2016-01-07 00:31:26 +00:00
/**
* Prevents serializing any properties .
*
* Kernel tests are run in a separate process . To do this PHPUnit creates a
* script to run the test . If it fails , the test result object will contain a
* stack trace which includes the test object . It will attempt to serialize
* it . Returning an empty array prevents it from serializing anything it
* should not .
*
* @ return array
* An empty array .
*
* @ see vendor / phpunit / phpunit / src / Util / PHP / Template / TestCaseMethod . tpl . dist
*/
public function __sleep () {
return [];
}
2016-04-07 18:19:57 +00:00
/**
* { @ inheritdoc }
*/
public static function assertEquals ( $expected , $actual , $message = '' , $delta = 0.0 , $maxDepth = 10 , $canonicalize = false , $ignoreCase = false ) {
$expected = static :: castSafeStrings ( $expected );
$actual = static :: castSafeStrings ( $actual );
parent :: assertEquals ( $expected , $actual , $message , $delta , $maxDepth , $canonicalize , $ignoreCase );
}
2015-09-04 20:20:09 +00:00
}