Commit 53656a33 authored by catch's avatar catch

Issue #1774388 by sun, chx, tim.plunkett: Unit Tests Remastered.

parent 742c9368
......@@ -2,10 +2,10 @@
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Database\Database;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Symfony\Component\ClassLoader\UniversalClassLoader;
use Symfony\Component\ClassLoader\ApcUniversalClassLoader;
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\Exception\RuntimeException as DependencyInjectionRuntimeException;
use Symfony\Component\HttpFoundation\Request;
......
......@@ -66,9 +66,9 @@ function drupal_get_schema($table = NULL, $rebuild = FALSE) {
* If TRUE, the schema will be rebuilt instead of retrieved from the cache.
*/
function drupal_get_complete_schema($rebuild = FALSE) {
static $schema = array();
static $schema;
if (empty($schema) || $rebuild) {
if (!isset($schema) || $rebuild) {
// Try to load the schema from cache.
if (!$rebuild && $cached = cache()->get('schema')) {
$schema = $cached->data;
......@@ -100,15 +100,15 @@ function drupal_get_complete_schema($rebuild = FALSE) {
_drupal_schema_initialize($current, $module);
$schema = array_merge($schema, $current);
}
drupal_alter('schema', $schema);
if ($rebuild) {
cache()->invalidateTags(array('schema' => TRUE));
}
// If the schema is empty, avoid saving it: some database engines require
// the schema to perform queries, and this could lead to infinite loops.
if (!empty($schema) && (drupal_get_bootstrap_phase() == DRUPAL_BOOTSTRAP_FULL)) {
cache()->set('schema', $schema);
}
if ($rebuild) {
cache()->invalidateTags(array('schema' => TRUE));
cache()->set('schema', $schema, CacheBackendInterface::CACHE_PERMANENT, array('schema' => TRUE));
}
}
}
......@@ -284,8 +284,11 @@ function drupal_get_schema_unprocessed($module, $table = NULL) {
module_load_install($module);
$schema = module_invoke($module, 'schema');
if (isset($table) && isset($schema[$table])) {
return $schema[$table];
if (isset($table)) {
if (isset($schema[$table])) {
return $schema[$table];
}
return array();
}
elseif (!empty($schema)) {
return $schema;
......
......@@ -195,7 +195,7 @@ protected function moduleData($module) {
/**
* Implements Drupal\Core\DrupalKernelInterface::updateModules().
*/
public function updateModules(array $module_list, array $module_paths = array()) {
public function updateModules(array $module_list, array $module_paths = array(), ContainerBuilder $base_container = NULL) {
$this->newModuleList = $module_list;
foreach ($module_paths as $module => $path) {
$this->moduleData[$module] = (object) array('uri' => $path);
......@@ -204,7 +204,7 @@ public function updateModules(array $module_list, array $module_paths = array())
// list will take effect when boot() is called. If we have already booted,
// then reboot in order to refresh the bundle list and container.
if ($this->booted) {
drupal_container(NULL, TRUE);
drupal_container($base_container, TRUE);
$this->booted = FALSE;
$this->boot();
}
......
......@@ -7,7 +7,9 @@
namespace Drupal\simpletest;
use Drupal\Core\DrupalKernel;
use Drupal\Core\Database\Database;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Base test case class for Drupal unit tests.
......@@ -62,6 +64,13 @@ abstract class DrupalUnitTestBase extends UnitTestBase {
private $themeFiles;
private $themeData;
/**
* Base service container for rebooting DrupalKernel.
*
* @var \Drupal\Core\DependencyInjection\ContainerBuilder
*/
private $baseContainer;
/**
* Sets up Drupal unit test environment.
*
......@@ -69,8 +78,6 @@ abstract class DrupalUnitTestBase extends UnitTestBase {
* @see DrupalUnitTestBase
*/
protected function setUp() {
global $conf;
// Copy/prime extension file lists once to avoid filesystem scans.
if (!isset($this->moduleFiles)) {
$this->moduleFiles = state()->get('system.module.files') ?: array();
......@@ -80,20 +87,21 @@ protected function setUp() {
parent::setUp();
// Provide a minimal, partially mocked environment for unit tests.
$conf['lock_backend'] = 'Drupal\Core\Lock\NullLockBackend';
$conf['cache_classes'] = array('cache' => 'Drupal\Core\Cache\MemoryBackend');
$this->container
->register('config.storage', 'Drupal\Core\Config\FileStorage')
->addArgument($this->configDirectories[CONFIG_ACTIVE_DIRECTORY]);
$conf['keyvalue_default'] = 'keyvalue.memory';
$this->container
->register('keyvalue.memory', 'Drupal\Core\KeyValueStore\KeyValueMemoryFactory');
// Build a minimal, partially mocked environment for unit tests.
$this->setUpContainer();
state()->set('system.module.files', $this->moduleFiles);
state()->set('system.theme.files', $this->themeFiles);
state()->set('system.theme.data', $this->themeData);
// Back up the base container for enableModules().
$this->baseContainer = clone $this->container;
// Bootstrap the kernel.
$this->kernel = new DrupalKernel('testing', TRUE, drupal_classloader());
$this->kernel->boot();
$this->container = drupal_container();
// Ensure that the module list is initially empty.
$this->moduleList = array();
// Collect and set a fixed module list.
......@@ -108,19 +116,71 @@ protected function setUp() {
$this->enableModules($modules, FALSE);
}
/**
* Sets up the base service container for this test.
*
* Extend this method in your test to register additional service overrides
* that need to persist a DrupalKernel reboot. This method is only called once
* for each test.
*
* @see DrupalUnitTestBase::setUp()
* @see DrupalUnitTestBase::enableModules()
*/
protected function setUpContainer() {
global $conf;
$conf['lock_backend'] = 'Drupal\Core\Lock\NullLockBackend';
$conf['cache_classes'] = array('cache' => 'Drupal\Core\Cache\MemoryBackend');
$this->container
->register('config.storage', 'Drupal\Core\Config\FileStorage')
->addArgument($this->configDirectories[CONFIG_ACTIVE_DIRECTORY]);
$conf['keyvalue_default'] = 'keyvalue.memory';
$this->container
->register('keyvalue.memory', 'Drupal\Core\KeyValueStore\KeyValueMemoryFactory');
}
/**
* Overrides TestBase::tearDown().
*/
protected function tearDown() {
// Ensure that TestBase::tearDown() gets a working container.
$this->container = $this->baseContainer;
parent::tearDown();
}
/**
* Installs a specific table from a module schema definition.
*
* Use this to install a particular table from System module.
*
* @param string $module
* The name of the module that defines the table's schema.
* @param string $table
* The name of the table to install.
*/
protected function installSchema($module, $table) {
require_once DRUPAL_ROOT . '/' . drupal_get_path('module', $module) . "/$module.install";
$function = $module . '_schema';
$schema = $function();
Database::getConnection()->schema()->createTable($table, $schema[$table]);
// drupal_get_schema_unprocessed() is technically able to install a schema
// of a non-enabled module, but its ability to load the module's .install
// file depends on many other factors. To prevent differences in test
// behavior and non-reproducible test failures, we only allow the schema of
// explicitly loaded/enabled modules to be installed.
if (!module_exists($module)) {
throw new \RuntimeException(format_string("'@module' module is not enabled.", array(
'@module' => $module,
)));
}
$schema = drupal_get_schema_unprocessed($module, $table);
if (empty($schema)) {
throw new \RuntimeException(format_string("Unable to retrieve '@module' module schema for '@table' table.", array(
'@module' => $module,
'@table' => $table,
)));
}
Database::getConnection()->schema()->createTable($table, $schema);
// We need to refresh the schema cache, as any call to drupal_get_schema()
// would not know of/return the schema otherwise.
// @todo Refactor Schema API to make this obsolete.
drupal_get_schema(NULL, TRUE);
}
/**
......@@ -132,7 +192,8 @@ protected function installSchema($module, $table) {
* has no effect, since we are operating with a fixed module list.
*
* @param array $modules
* A list of modules to enable.
* A list of modules to enable. Dependencies are not resolved; i.e.,
* multiple modules have to be specified with dependent modules first.
* @param bool $install
* (optional) Whether to install the list of modules via module_enable().
* Defaults to TRUE. If FALSE, the new modules are only added to the fixed
......@@ -143,18 +204,33 @@ protected function installSchema($module, $table) {
*/
protected function enableModules(array $modules, $install = TRUE) {
// Set the modules in the fixed module_list().
$new_enabled = array();
foreach ($modules as $module) {
$this->moduleList[$module]['filename'] = drupal_get_filename('module', $module);
}
module_list(NULL, $this->moduleList);
// Call module_enable() to enable (install) the new modules.
if ($install) {
module_enable($modules);
$new_enabled[$module] = dirname($this->moduleList[$module]['filename']);
module_list(NULL, $this->moduleList);
// Call module_enable() to enable (install) the new module.
if ($install) {
// module_enable() reboots DrupalKernel, but that builds an entirely new
// ContainerBuilder, retrieving a fresh base container from
// drupal_container(), which means that all of the service overrides
// from DrupalUnitTestBase::setUpContainer() are lost, in turn triggering
// invalid service reference errors; e.g., in TestBase::tearDown().
// Since DrupalKernel also replaces the container in drupal_container()
// after (re-)booting, we have to re-inject a new copy of our initial
// base container that was built in setUpContainer().
drupal_container(clone $this->baseContainer);
module_enable(array($module), FALSE);
}
}
// Otherwise, only ensure that the new modules are loaded.
else {
if (!$install) {
module_load_all(FALSE, TRUE);
module_implements_reset();
}
$kernel = $this->container->get('kernel');
$kernel->updateModules($this->moduleList, $new_enabled, clone $this->baseContainer);
}
}
<?php
/**
* @file
* Contains Drupal\simpletest\Tests\DrupalUnitTestBaseTest.
*/
namespace Drupal\simpletest\Tests;
use Drupal\simpletest\DrupalUnitTestBase;
/**
* Tests DrupalUnitTestBase functionality.
*/
class DrupalUnitTestBaseTest extends DrupalUnitTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('entity_test');
public static function getInfo() {
return array(
'name' => 'DrupalUnitTestBase',
'description' => 'Tests DrupalUnitTestBase functionality.',
'group' => 'SimpleTest',
);
}
/**
* Tests expected behavior of setUp().
*/
function testSetUp() {
$module = 'entity_test';
$table = 'entity_test';
// Verify that specified $modules have been loaded.
$this->assertTrue(function_exists('entity_test_permission'), "$module.module was loaded.");
// Verify that there is a fixed module list.
$this->assertIdentical(module_list(), array($module => $module));
$this->assertIdentical(module_implements('permission'), array($module));
// Verify that no modules have been installed.
$this->assertFalse(db_table_exists($table), "'$table' database table not found.");
}
/**
* Tests expected load behavior of enableModules().
*/
function testEnableModulesLoad() {
$module = 'field_test';
// Verify that the module does not exist yet.
$this->assertFalse(module_exists($module), "$module module not found.");
$list = module_list();
$this->assertFalse(in_array($module, $list), "$module module in module_list() not found.");
$list = module_list('permission');
$this->assertFalse(in_array($module, $list), "{$module}_permission() in module_implements() not found.");
// Enable the module.
$this->enableModules(array($module), FALSE);
// Verify that the module exists.
$this->assertTrue(module_exists($module), "$module module found.");
$list = module_list();
$this->assertTrue(in_array($module, $list), "$module module in module_list() found.");
$list = module_list('permission');
$this->assertTrue(in_array($module, $list), "{$module}_permission() in module_implements() found.");
}
/**
* Tests expected installation behavior of enableModules().
*/
function testEnableModulesInstall() {
$module = 'filter';
$table = 'filter';
// @todo Remove after configuration system conversion.
$this->enableModules(array('system'), FALSE);
$this->installSchema('system', 'variable');
// Verify that the module does not exist yet.
$this->assertFalse(module_exists($module), "$module module not found.");
$list = module_list();
$this->assertFalse(in_array($module, $list), "$module module in module_list() not found.");
$list = module_list('permission');
$this->assertFalse(in_array($module, $list), "{$module}_permission() in module_implements() not found.");
$this->assertFalse(db_table_exists($table), "'$table' database table not found.");
$schema = drupal_get_schema($table);
$this->assertFalse($schema, "'$table' table schema not found.");
// Enable the module.
$this->enableModules(array($module));
// Verify that the enabled module exists.
$this->assertTrue(module_exists($module), "$module module found.");
$list = module_list();
$this->assertTrue(in_array($module, $list), "$module module in module_list() found.");
$list = module_list('permission');
$this->assertTrue(in_array($module, $list), "{$module}_permission() in module_implements() found.");
$this->assertTrue(db_table_exists($table), "'$table' database table found.");
$schema = drupal_get_schema($table);
$this->assertTrue($schema, "'$table' table schema found.");
}
/**
* Tests installing of multiple modules via enableModules().
*
* Regression test: Each passed module has to be enabled and installed on its
* own, in the same way as module_enable() enables only one module after the
* other.
*/
function testEnableModulesInstallMultiple() {
// Field retrieves entity type plugins, and EntityTypeManager calls into
// hook_entity_info_alter(). If both modules would be first enabled together
// instead of each on its own, then Node module's alter implementation
// would be called and this simply blows up. To further complicate matters,
// additionally install Comment module, whose entity bundles depend on node
// types.
$this->enableModules(array('field', 'node', 'comment'));
$this->pass('Comment module was installed.');
}
/**
* Tests installing modules via enableModules() with DepedencyInjection services.
*/
function testEnableModulesInstallContainer() {
// Install Node module.
// @todo field_sql_storage and field should technically not be necessary
// for an entity query.
$this->enableModules(array('field_sql_storage', 'field', 'node'));
// Perform an entity query against node.
$query = entity_query('node');
// Disable node access checks, since User module is not enabled.
$query->accessCheck(FALSE);
$query->condition('nid', 1);
$query->execute();
$this->pass('Entity field query was executed.');
}
/**
* Tests expected behavior of installSchema().
*/
function testInstallSchema() {
$module = 'entity_test';
$table = 'entity_test';
// Verify that we can install a table from the module schema.
$this->installSchema($module, $table);
$this->assertTrue(db_table_exists($table), "'$table' database table found.");
// Verify that the schema is known to Schema API.
$schema = drupal_get_schema();
$this->assertTrue($schema[$table], "'$table' table found in schema.");
$schema = drupal_get_schema($table);
$this->assertTrue($schema, "'$table' table schema found.");
// Verify that a table from a unknown module cannot be installed.
$module = 'database_test';
$table = 'test';
try {
$this->installSchema($module, $table);
$this->fail('Exception for non-retrievable schema found.');
}
catch (\Exception $e) {
$this->pass('Exception for non-retrievable schema found.');
}
$this->assertFalse(db_table_exists($table), "'$table' database table not found.");
$schema = drupal_get_schema($table);
$this->assertFalse($schema, "'$table' table schema not found.");
// Verify that the same table can be installed after enabling the module.
$this->enableModules(array($module), FALSE);
$this->installSchema($module, $table);
$this->assertTrue(db_table_exists($table), "'$table' database table found.");
$schema = drupal_get_schema($table);
$this->assertTrue($schema, "'$table' table schema found.");
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment