Commit 6c91ce87 authored by alexpott's avatar alexpott

Issue #2254203 by sun, neclimdul: Fix test performance of Drupal\system\Tests\Module\ModuleApiTest.

parent f02bf6cf
......@@ -2,51 +2,61 @@
/**
* @file
* Definition of Drupal\system\Tests\Module\ModuleApiTest.
* Contains \Drupal\system\Tests\Extension\ModuleHandlerTest.
*/
namespace Drupal\system\Tests\Module;
namespace Drupal\system\Tests\Extension;
use Drupal\simpletest\WebTestBase;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\simpletest\KernelTestBase;
use Symfony\Component\HttpFoundation\Response;
/**
* Unit tests for the module API.
* Tests ModuleHandler functionality.
*/
class ModuleApiTest extends WebTestBase {
// Requires Standard profile modules/dependencies.
protected $profile = 'standard';
class ModuleHandlerTest extends KernelTestBase {
public static function getInfo() {
return array(
'name' => 'Module API',
'description' => 'Test low-level module functions.',
'group' => 'Module',
'name' => 'Module handler',
'description' => 'Tests ModuleHandler functionality.',
'group' => 'Extension',
);
}
/**
* {@inheritdoc}
*/
public function containerBuild(ContainerBuilder $container) {
parent::containerBuild($container);
// Put a fake route bumper on the container to be called during uninstall.
$container
->register('router.dumper', 'Drupal\Core\Routing\NullMatcherDumper');
}
/**
* The basic functionality of retrieving enabled modules.
*/
function testModuleList() {
// Build a list of modules, sorted alphabetically.
$profile_info = install_profile_info('standard', 'en');
$profile_info = install_profile_info('testing', 'en');
$module_list = $profile_info['dependencies'];
// Installation profile is a module that is expected to be loaded.
$module_list[] = 'standard';
$module_list[] = 'testing';
sort($module_list);
// Compare this list to the one returned by the extension handler. We expect
// 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, t('Standard profile'));
$this->assertModuleList($module_list, 'Testing profile');
// Try to install a new module.
\Drupal::moduleHandler()->install(array('ban'));
$this->moduleHandler()->install(array('ban'));
$module_list[] = 'ban';
sort($module_list);
$this->assertModuleList($module_list, t('After adding a module'));
$this->assertModuleList($module_list, 'After adding a module');
// Try to mess with the module weights.
module_set_weight('ban', 20);
......@@ -54,24 +64,24 @@ function testModuleList() {
// Move ban to the end of the array.
unset($module_list[array_search('ban', $module_list)]);
$module_list[] = 'ban';
$this->assertModuleList($module_list, t('After changing weights'));
$this->assertModuleList($module_list, 'After changing weights');
// Test the fixed list feature.
$fixed_list = array(
'system' => 'core/modules/system/system.module',
'menu' => 'core/modules/menu/menu.module',
);
$this->container->get('module_handler')->setModuleList($fixed_list);
$this->moduleHandler()->setModuleList($fixed_list);
$new_module_list = array_combine(array_keys($fixed_list), array_keys($fixed_list));
$this->assertModuleList($new_module_list, t('When using a fixed list'));
}
/**
* Assert that the extension handler returns the expected values.
*
* @param $expected_values
* @param array $expected_values
* The expected values, sorted by weight and module name.
* @param $condition
*/
protected function assertModuleList(Array $expected_values, $condition) {
$expected_values = array_values(array_unique($expected_values));
......@@ -81,117 +91,65 @@ protected function assertModuleList(Array $expected_values, $condition) {
}
/**
* Test \Drupal::moduleHandler()->getImplementations() caching.
*/
function testModuleImplements() {
// Clear the cache.
\Drupal::cache('bootstrap')->delete('module_implements');
$this->assertFalse(\Drupal::cache('bootstrap')->get('module_implements'), 'The module implements cache is empty.');
$this->drupalGet('');
$this->assertTrue(\Drupal::cache('bootstrap')->get('module_implements'), 'The module implements cache is populated after requesting a page.');
// Test again with an authenticated user.
$this->user = $this->drupalCreateUser();
$this->drupalLogin($this->user);
\Drupal::cache('bootstrap')->delete('module_implements');
$this->drupalGet('');
$this->assertTrue(\Drupal::cache('bootstrap')->get('module_implements'), 'The module implements cache is populated after requesting a page.');
// Prime ModuleHandler's hook implementation cache by invoking a random hook
// name. The subsequent \Drupal\Core\Extension\ModuleHandler::install()
// below will only call into setModuleList(), but will not explicitly reset
// the hook implementation cache, as that is expected to happen implicitly
// by setting the module list. This verifies that the hook implementation
// cache is cleared whenever setModuleList() is called.
$module_handler = \Drupal::moduleHandler();
$module_handler->invokeAll('test');
// Make sure group include files are detected properly even when the file is
// already loaded when the cache is rebuilt.
// For that activate the module_test which provides the file to load.
\Drupal::moduleHandler()->install(array('module_test'));
$module_handler->loadAll();
module_load_include('inc', 'module_test', 'module_test.file');
$modules = $module_handler->getImplementations('test_hook');
$this->assertTrue(in_array('module_test', $modules), 'Hook found.');
}
/**
* Test that moduleHandler()->invoke() can load a hook defined in
* hook_hook_info().
* Tests dependency resolution.
*
* Intentionally using fake dependencies added via hook_system_info_alter()
* for modules that normally do not have any dependencies.
*
* To simplify things further, all of the manipulated modules are either
* purely UI-facing or live at the "bottom" of all dependency chains.
*
* @see module_test_system_info_alter()
* @see https://drupal.org/files/issues/dep.gv__0.png
*/
function testModuleInvoke() {
\Drupal::moduleHandler()->install(array('module_test'), FALSE);
$this->resetAll();
$this->drupalGet('module-test/hook-dynamic-loading-invoke');
$this->assertText('success!', 'moduleHandler()->invoke() dynamically loads a hook defined in hook_hook_info().');
}
function testDependencyResolution() {
$this->enableModules(array('module_test'));
$this->assertTrue($this->moduleHandler()->moduleExists('module_test'), 'Test module is enabled.');
/**
* Test that \Drupal::moduleHandler()->invokeAll() can load a hook defined in hook_hook_info().
*/
function testModuleInvokeAll() {
\Drupal::moduleHandler()->install(array('module_test'), FALSE);
$this->resetAll();
$this->drupalGet('module-test/hook-dynamic-loading-invoke-all');
$this->assertText('success!', '\Drupal::moduleHandler()->invokeAll() dynamically loads a hook defined in hook_hook_info().');
}
// Ensure that modules are not enabled.
$this->assertFalse($this->moduleHandler()->moduleExists('color'), 'Color module is disabled.');
$this->assertFalse($this->moduleHandler()->moduleExists('config'), 'Config module is disabled.');
$this->assertFalse($this->moduleHandler()->moduleExists('help'), 'Help module is disabled.');
/**
* Test dependency resolution.
*/
function testDependencyResolution() {
// Enable the test module, and make sure that other modules we are testing
// are not already enabled. (If they were, the tests below would not work
// correctly.)
\Drupal::moduleHandler()->install(array('module_test'), FALSE);
$this->assertTrue(\Drupal::moduleHandler()->moduleExists('module_test'), 'Test module is enabled.');
$this->assertFalse(\Drupal::moduleHandler()->moduleExists('forum'), 'Forum module is disabled.');
$this->assertFalse(\Drupal::moduleHandler()->moduleExists('ban'), 'Ban module is disabled.');
$this->assertFalse(\Drupal::moduleHandler()->moduleExists('xmlrpc'), 'XML-RPC module is disabled.');
// First, create a fake missing dependency. Forum depends on ban, which
// depends on a made-up module, foo. Nothing should be installed.
// Create a missing fake dependency.
// Color will depend on Config, which depends on a non-existing module Foo.
// Nothing should be installed.
\Drupal::state()->set('module_test.dependency', 'missing dependency');
drupal_static_reset('system_rebuild_module_data');
$result = \Drupal::moduleHandler()->install(array('forum'));
$this->assertFalse($result, '\Drupal\Core\Extension\ModuleHandler::install() returns FALSE if dependencies are missing.');
$this->assertFalse(\Drupal::moduleHandler()->moduleExists('forum'), '\Drupal\Core\Extension\ModuleHandler::install() aborts if dependencies are missing.');
// Now, fix the missing dependency. Forum module depends on ban, but ban
// depends on the XML-RPC module.
// \Drupal\Core\Extension\ModuleHandler::install() should work.
$result = $this->moduleHandler()->install(array('color'));
$this->assertFalse($result, 'ModuleHandler::install() returns FALSE if dependencies are missing.');
$this->assertFalse($this->moduleHandler()->moduleExists('color'), 'ModuleHandler::install() aborts if dependencies are missing.');
// Fix the missing dependency.
// Forum module depends on Ban. Ban depends on XML-RPC module.
\Drupal::state()->set('module_test.dependency', 'dependency');
drupal_static_reset('system_rebuild_module_data');
$result = \Drupal::moduleHandler()->install(array('forum'));
$this->assertTrue($result, '\Drupal\Core\Extension\ModuleHandler::install() returns the correct value.');
$result = $this->moduleHandler()->install(array('color'));
$this->assertTrue($result, 'ModuleHandler::install() returns the correct value.');
// Verify that the fake dependency chain was installed.
$this->assertTrue(\Drupal::moduleHandler()->moduleExists('ban') && \Drupal::moduleHandler()->moduleExists('xmlrpc'), 'Dependency chain was installed.');
$this->assertTrue($this->moduleHandler()->moduleExists('config') && $this->moduleHandler()->moduleExists('help'), 'Dependency chain was installed.');
// Verify that the original module was installed.
$this->assertTrue(\Drupal::moduleHandler()->moduleExists('forum'), 'Module installation with unlisted dependencies succeeded.');
// Finally, verify that the modules were enabled in the correct order.
$this->assertTrue($this->moduleHandler()->moduleExists('color'), 'Module installation with dependencies succeeded.');
// Verify that the modules were enabled in the correct order.
$module_order = \Drupal::state()->get('module_test.install_order') ?: array();
$this->assertEqual($module_order, array('xmlrpc', 'ban', 'forum'), 'Modules were enabled in the correct order.');
$this->assertEqual($module_order, array('help', 'config', 'color'));
// Now uninstall all three modules explicitly, but in the incorrect order,
// and make sure that drupal_uninstall_modules() uninstalled them in the
// Uninstall all three modules explicitly, but in the incorrect order,
// and make sure that ModuleHandler::uninstall() uninstalled them in the
// correct sequence.
$result = module_uninstall(array('ban', 'xmlrpc', 'forum'));
$this->assertTrue($result, 'module_uninstall() returns the correct value.');
foreach (array('forum', 'ban', 'xmlrpc') as $module) {
$this->assertEqual(drupal_get_installed_schema_version($module), SCHEMA_UNINSTALLED, format_string('The @module module was uninstalled.', array('@module' => $module)));
}
$uninstalled_modules = \Drupal::state()->get('module_test.uninstall_order') ?: array();
$this->assertEqual($uninstalled_modules, array('forum', 'ban', 'xmlrpc'), 'Modules were uninstalled in the correct order by module_uninstall().');
$result = $this->moduleHandler()->uninstall(array('config', 'help', 'color'));
$this->assertTrue($result, 'ModuleHandler::uninstall() returned TRUE.');
// Uninstall the profile module from above, and make sure that the profile
// itself is not on the list of dependent modules to be uninstalled.
$result = module_uninstall(array('comment'));
$this->assertTrue($result, 'module_uninstall() returns the correct value.');
$this->assertEqual(drupal_get_installed_schema_version('comment'), SCHEMA_UNINSTALLED, 'Comment module was uninstalled.');
foreach (array('color', 'config', 'help') as $module) {
$this->assertEqual(drupal_get_installed_schema_version($module), SCHEMA_UNINSTALLED, "$module module was uninstalled.");
}
$uninstalled_modules = \Drupal::state()->get('module_test.uninstall_order') ?: array();
$this->assertTrue(in_array('comment', $uninstalled_modules), 'Comment module is in the list of uninstalled modules.');
$this->assertFalse(in_array($this->profile, $uninstalled_modules), 'The installation profile is not in the list of uninstalled modules.');
$this->assertEqual($uninstalled_modules, array('color', 'config', 'help'), 'Modules were uninstalled in the correct order.');
// Enable forum module again, which should enable both the ban module and
// XML-RPC module. But, this time do it with ban module declaring a
......@@ -199,22 +157,54 @@ function testDependencyResolution() {
// sure that Drupal\Core\Extension\ModuleHandler::install() still works.
\Drupal::state()->set('module_test.dependency', 'version dependency');
drupal_static_reset('system_rebuild_module_data');
$result = \Drupal::moduleHandler()->install(array('forum'));
$this->assertTrue($result, '\Drupal\Core\Extension\ModuleHandler::install() returns the correct value.');
$result = $this->moduleHandler()->install(array('color'));
$this->assertTrue($result, 'ModuleHandler::install() returns the correct value.');
// Verify that the fake dependency chain was installed.
$this->assertTrue(\Drupal::moduleHandler()->moduleExists('ban') && \Drupal::moduleHandler()->moduleExists('xmlrpc'), 'Dependency chain was installed.');
$this->assertTrue($this->moduleHandler()->moduleExists('config') && $this->moduleHandler()->moduleExists('help'), 'Dependency chain was installed.');
// Verify that the original module was installed.
$this->assertTrue(\Drupal::moduleHandler()->moduleExists('forum'), 'Module installation with version dependencies succeeded.');
$this->assertTrue($this->moduleHandler()->moduleExists('color'), 'Module installation with version dependencies succeeded.');
// Finally, verify that the modules were enabled in the correct order.
$enable_order = \Drupal::state()->get('module_test.install_order') ?: array();
$xmlrpc_position = array_search('xmlrpc', $enable_order);
$ban_position = array_search('ban', $enable_order);
$forum_position = array_search('forum', $enable_order);
$xmlrpc_before_ban = $xmlrpc_position !== FALSE && $ban_position !== FALSE && $xmlrpc_position < $ban_position;
$ban_before_forum = $ban_position !== FALSE && $forum_position !== FALSE && $ban_position < $forum_position;
$help_position = array_search('help', $enable_order);
$config_position = array_search('config', $enable_order);
$color_position = array_search('color', $enable_order);
$xmlrpc_before_ban = $help_position !== FALSE && $config_position !== FALSE && $help_position < $config_position;
$ban_before_forum = $config_position !== FALSE && $color_position !== FALSE && $config_position < $color_position;
$this->assertTrue($xmlrpc_before_ban && $ban_before_forum, 'Modules were enabled in the correct order.');
}
/**
* Tests uninstalling a module that is a "dependency" of a profile.
*/
function testUninstallProfileDependency() {
$profile = 'minimal';
$dependency = 'dblog';
$this->settingsSet('install_profile', $profile);
$this->enableModules(array('module_test', $profile));
drupal_static_reset('system_rebuild_module_data');
$data = system_rebuild_module_data();
$this->assertTrue(isset($data[$profile]->requires[$dependency]));
$this->moduleHandler()->install(array($dependency));
$this->assertTrue($this->moduleHandler()->moduleExists($dependency));
// Uninstall the profile module "dependency".
$result = $this->moduleHandler()->uninstall(array($dependency));
$this->assertTrue($result, 'ModuleHandler::uninstall() returns TRUE.');
$this->assertFalse($this->moduleHandler()->moduleExists($dependency));
$this->assertEqual(drupal_get_installed_schema_version($dependency), SCHEMA_UNINSTALLED, "$dependency module was uninstalled.");
// Verify that the installation profile itself was not uninstalled.
$uninstalled_modules = \Drupal::state()->get('module_test.uninstall_order') ?: array();
$this->assertTrue(in_array($dependency, $uninstalled_modules), "$dependency module is in the list of uninstalled modules.");
$this->assertFalse(in_array($profile, $uninstalled_modules), 'The installation profile is not in the list of uninstalled modules.');
}
/**
* Tests whether the correct module metadata is returned.
*/
......@@ -242,4 +232,14 @@ function testThemeMetaData() {
// Ensure the mtime field contains a number that is greater than zero.
$this->assertTrue(is_numeric($test_mtime) && ($test_mtime > 0), 'The bartik.info.yml file modification time field contains a timestamp.');
}
/**
* Returns the ModuleHandler.
*
* @return \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected function moduleHandler() {
return $this->container->get('module_handler');
}
}
......@@ -111,8 +111,9 @@ function testEnableRequirementsFailureDependency() {
}
/**
* Tests that module dependencies are enabled in the correct order via the
* UI. Dependencies should be enabled before their dependents.
* Tests that module dependencies are enabled in the correct order in the UI.
*
* Dependencies should be enabled before their dependents.
*/
function testModuleEnableOrder() {
\Drupal::moduleHandler()->install(array('module_test'), FALSE);
......@@ -120,34 +121,24 @@ function testModuleEnableOrder() {
$this->assertModules(array('module_test'), TRUE);
\Drupal::state()->set('module_test.dependency', 'dependency');
// module_test creates a dependency chain:
// - forum depends on node, taxonomy, comment, datetime, history, and
// ban (via module_test)
// - node depends on text
// - text depends on filter
// - taxonomy depends on options
// - ban depends on xmlrpc (via module_test)
// The correct enable order is:
$expected_order = array('filter', 'text', 'options', 'xmlrpc', 'ban', 'node', 'datetime', 'comment', 'history', 'taxonomy', 'forum');
// - color depends on config
// - config depends on help
$expected_order = array('help', 'config', 'color');
// Enable the modules through the UI, verifying that the dependency chain
// is correct.
$edit = array();
$edit['modules[Core][forum][enable]'] = 'forum';
$edit['modules[Core][color][enable]'] = 'color';
$this->drupalPostForm('admin/modules', $edit, t('Save configuration'));
$this->assertModules(array('forum'), FALSE);
$this->assertText(t('You must enable the Node, Text, Filter, History, Taxonomy, Options, Comment, Datetime, Ban, XML-RPC modules to install Forum.'));
$edit['modules[Core][node][enable]'] = 'node';
$edit['modules[Field types][text][enable]'] = 'text';
$edit['modules[Core][filter][enable]'] = 'filter';
$edit['modules[Core][history][enable]'] = 'history';
$edit['modules[Field types][options][enable]'] = 'options';
$edit['modules[Core][taxonomy][enable]'] = 'taxonomy';
$edit['modules[Core][comment][enable]'] = 'comment';
$edit['modules[Field types][datetime][enable]'] = 'datetime';
$edit['modules[Core][ban][enable]'] = 'ban';
$edit['modules[Core][xmlrpc][enable]'] = 'xmlrpc';
$this->assertModules(array('color'), FALSE);
// Note that dependencies are sorted alphabetically in the confirmation
// message.
$this->assertText(t('You must enable the Configuration Manager, Help modules to install Color.'));
$edit['modules[Core][config][enable]'] = 'config';
$edit['modules[Core][help][enable]'] = 'help';
$this->drupalPostForm('admin/modules', $edit, t('Save configuration'));
$this->assertModules(array('forum', 'ban', 'node', 'text', 'filter', 'xmlrpc', 'datetime', 'comment', 'history', 'taxonomy', 'options'), TRUE);
$this->assertModules(array('color', 'config', 'help'), TRUE);
// Check the actual order which is saved by module_test_modules_enabled().
$module_order = \Drupal::state()->get('module_test.install_order') ?: array();
......
......@@ -18,36 +18,36 @@ function module_test_permission() {
*/
function module_test_system_info_alter(&$info, Extension $file, $type) {
if (\Drupal::state()->get('module_test.dependency') == 'missing dependency') {
if ($file->getName() == 'forum') {
// Make forum module depend on ban.
$info['dependencies'][] = 'ban';
if ($file->getName() == 'color') {
// Make color module depend on config.
$info['dependencies'][] = 'config';
}
elseif ($file->getName() == 'ban') {
// Make ban depend on a made-up module.
elseif ($file->getName() == 'config') {
// Make config module depend on a non-existing module.
$info['dependencies'][] = 'foo';
}
}
elseif (\Drupal::state()->get('module_test.dependency') == 'dependency') {
if ($file->getName() == 'forum') {
// Make the forum module depend on ban.
$info['dependencies'][] = 'ban';
if ($file->getName() == 'color') {
// Make color module depend on config.
$info['dependencies'][] = 'config';
}
elseif ($file->getName() == 'ban') {
// Make ban depend on xmlrpc module.
$info['dependencies'][] = 'xmlrpc';
elseif ($file->getName() == 'config') {
// Make config module depend on help module.
$info['dependencies'][] = 'help';
}
}
elseif (\Drupal::state()->get('module_test.dependency') == 'version dependency') {
if ($file->getName() == 'forum') {
// Make the forum module depend on ban.
$info['dependencies'][] = 'ban';
if ($file->getName() == 'color') {
// Make color module depend on config.
$info['dependencies'][] = 'config';
}
elseif ($file->getName() == 'ban') {
// Make ban depend on a specific version of xmlrpc module.
$info['dependencies'][] = 'xmlrpc (1.x)';
elseif ($file->getName() == 'config') {
// Make config module depend on a specific version of help module.
$info['dependencies'][] = 'help (1.x)';
}
elseif ($file->getName() == 'xmlrpc') {
// Set xmlrpc module to a version compatible with the above.
elseif ($file->getName() == 'help') {
// Set help module to a version compatible with the above.
$info['version'] = '8.x-1.0';
}
}
......
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