3 namespace Drupal\Tests\Core\Extension;
5 use Drupal\Core\Extension\Extension;
6 use Drupal\Core\Extension\ModuleHandler;
7 use Drupal\Tests\UnitTestCase;
10 * @coversDefaultClass \Drupal\Core\Extension\ModuleHandler
13 class ModuleHandlerTest extends UnitTestCase {
16 * The mocked cache backend.
18 * @var \Drupal\Core\Cache\CacheBackendInterface|\PHPUnit_Framework_MockObject_MockObject
20 protected $cacheBackend;
23 * The tested module handler.
25 * @var \Drupal\Core\Extension\ModuleHandler
27 protected $moduleHandler;
32 * @covers ::__construct
34 protected function setUp() {
37 $this->cacheBackend = $this->getMock('Drupal\Core\Cache\CacheBackendInterface');
38 $this->moduleHandler = new ModuleHandler($this->root, [
39 'module_handler_test' => [
41 'pathname' => 'core/tests/Drupal/Tests/Core/Extension/modules/module_handler_test/module_handler_test.info.yml',
42 'filename' => 'module_handler_test.module',
44 ], $this->cacheBackend);
48 * Test loading a module.
52 public function testLoadModule() {
53 $this->assertFalse(function_exists('module_handler_test_hook'));
54 $this->assertTrue($this->moduleHandler->load('module_handler_test'));
55 $this->assertTrue(function_exists('module_handler_test_hook'));
57 $this->moduleHandler->addModule('module_handler_test_added', 'core/tests/Drupal/Tests/Core/Extension/modules/module_handler_test_added');
58 $this->assertFalse(function_exists('module_handler_test_added_hook'), 'Function does not exist before being loaded.');
59 $this->assertTrue($this->moduleHandler->load('module_handler_test_added'));
60 $this->assertTrue(function_exists('module_handler_test_added_helper'), 'Function exists after being loaded.');
61 $this->assertTrue($this->moduleHandler->load('module_handler_test_added'));
63 $this->assertFalse($this->moduleHandler->load('module_handler_test_dne'), 'Non-existent modules returns false.');
67 * Test loading all modules.
71 public function testLoadAllModules() {
72 $this->moduleHandler->addModule('module_handler_test_all1', 'core/tests/Drupal/Tests/Core/Extension/modules/module_handler_test_all1');
73 $this->moduleHandler->addModule('module_handler_test_all2', 'core/tests/Drupal/Tests/Core/Extension/modules/module_handler_test_all2');
74 $this->assertFalse(function_exists('module_handler_test_all1_hook'), 'Function does not exist before being loaded.');
75 $this->assertFalse(function_exists('module_handler_test_all2_hook'), 'Function does not exist before being loaded.');
76 $this->moduleHandler->loadAll();
77 $this->assertTrue(function_exists('module_handler_test_all1_hook'), 'Function exists after being loaded.');
78 $this->assertTrue(function_exists('module_handler_test_all2_hook'), 'Function exists after being loaded.');
86 public function testModuleReloading() {
87 $module_handler = $this->getMockBuilder('Drupal\Core\Extension\ModuleHandler')
88 ->setConstructorArgs([
91 'module_handler_test' => [
93 'pathname' => 'core/tests/Drupal/Tests/Core/Extension/modules/module_handler_test/module_handler_test.info.yml',
94 'filename' => 'module_handler_test.module',
96 ], $this->cacheBackend
98 ->setMethods(['load'])
101 $module_handler->expects($this->at(0))
103 ->with($this->equalTo('module_handler_test'));
105 $module_handler->expects($this->at(1))
107 ->with($this->equalTo('module_handler_test'));
108 $module_handler->expects($this->at(2))
110 ->with($this->equalTo('module_handler_test_added'));
111 $module_handler->reload();
112 $module_handler->addModule('module_handler_test_added', 'core/tests/Drupal/Tests/Core/Extension/modules/module_handler_test_added');
113 $module_handler->reload();
117 * Test isLoaded accessor.
121 public function testIsLoaded() {
122 $this->assertFalse($this->moduleHandler->isLoaded());
123 $this->moduleHandler->loadAll();
124 $this->assertTrue($this->moduleHandler->isLoaded());
128 * Confirm we get back the modules set in the constructor.
130 * @covers ::getModuleList
132 public function testGetModuleList() {
133 $this->assertEquals($this->moduleHandler->getModuleList(), [
134 'module_handler_test' => new Extension($this->root, 'module', 'core/tests/Drupal/Tests/Core/Extension/modules/module_handler_test/module_handler_test.info.yml', 'module_handler_test.module'),
139 * Confirm we get back a module from the module list
141 * @covers ::getModule
143 public function testGetModuleWithExistingModule() {
144 $this->assertEquals($this->moduleHandler->getModule('module_handler_test'), new Extension($this->root, 'module', 'core/tests/Drupal/Tests/Core/Extension/modules/module_handler_test/module_handler_test.info.yml', 'module_handler_test.module'));
148 * @covers ::getModule
150 public function testGetModuleWithNonExistingModule() {
151 $this->setExpectedException(\InvalidArgumentException::class);
152 $this->moduleHandler->getModule('claire_alice_watch_my_little_pony_module_that_does_not_exist');
156 * Ensure setting the module list replaces the module list and resets internal structures.
158 * @covers ::setModuleList
160 public function testSetModuleList() {
161 $module_handler = $this->getMockBuilder('Drupal\Core\Extension\ModuleHandler')
162 ->setConstructorArgs([
163 $this->root, [], $this->cacheBackend
165 ->setMethods(['resetImplementations'])
168 // Ensure we reset implementations when settings a new modules list.
169 $module_handler->expects($this->once())->method('resetImplementations');
171 // Make sure we're starting empty.
172 $this->assertEquals($module_handler->getModuleList(), []);
174 // Replace the list with a prebuilt list.
175 $module_handler->setModuleList($this->moduleHandler->getModuleList());
177 // Ensure those changes are stored.
178 $this->assertEquals($this->moduleHandler->getModuleList(), $module_handler->getModuleList());
182 * Test adding a module.
184 * @covers ::addModule
187 public function testAddModule() {
189 $module_handler = $this->getMockBuilder('Drupal\Core\Extension\ModuleHandler')
190 ->setConstructorArgs([
191 $this->root, [], $this->cacheBackend
193 ->setMethods(['resetImplementations'])
196 // Ensure we reset implementations when settings a new modules list.
197 $module_handler->expects($this->once())->method('resetImplementations');
199 $module_handler->addModule('module_handler_test', 'core/tests/Drupal/Tests/Core/Extension/modules/module_handler_test');
200 $this->assertTrue($module_handler->moduleExists('module_handler_test'));
204 * Test adding a profile.
206 * @covers ::addProfile
209 public function testAddProfile() {
211 $module_handler = $this->getMockBuilder('Drupal\Core\Extension\ModuleHandler')
212 ->setConstructorArgs([
213 $this->root, [], $this->cacheBackend
215 ->setMethods(['resetImplementations'])
218 // Ensure we reset implementations when settings a new modules list.
219 $module_handler->expects($this->once())->method('resetImplementations');
221 // @todo this should probably fail since its a module not a profile.
222 $module_handler->addProfile('module_handler_test', 'core/tests/Drupal/Tests/Core/Extension/modules/module_handler_test');
223 $this->assertTrue($module_handler->moduleExists('module_handler_test'));
227 * Test module exists returns correct module status.
229 * @covers ::moduleExists
231 public function testModuleExists() {
232 $this->assertTrue($this->moduleHandler->moduleExists('module_handler_test'));
233 $this->assertFalse($this->moduleHandler->moduleExists('module_handler_test_added'));
237 * @covers ::loadAllIncludes
239 public function testLoadAllIncludes() {
240 $this->assertTrue(TRUE);
241 $module_handler = $this->getMockBuilder('Drupal\Core\Extension\ModuleHandler')
242 ->setConstructorArgs([
245 'module_handler_test' => [
247 'pathname' => 'core/tests/Drupal/Tests/Core/Extension/modules/module_handler_test/module_handler_test.info.yml',
248 'filename' => 'module_handler_test.module',
250 ], $this->cacheBackend
252 ->setMethods(['loadInclude'])
255 // Ensure we reset implementations when settings a new modules list.
256 $module_handler->expects($this->once())->method('loadInclude');
257 $module_handler->loadAllIncludes('hook');
261 * @covers ::loadInclude
263 * Note we load code, so isolate the test.
265 * @runInSeparateProcess
266 * @preserveGlobalState disabled
268 public function testLoadInclude() {
270 $this->assertEquals(__DIR__ . '/modules/module_handler_test/hook_include.inc', $this->moduleHandler->loadInclude('module_handler_test', 'inc', 'hook_include'));
271 $this->assertTrue(function_exists('module_handler_test_hook_include'));
272 // Include doesn't exist.
273 $this->assertFalse($this->moduleHandler->loadInclude('module_handler_test', 'install'));
277 * Test invoke methods when module is enabled.
281 public function testInvokeModuleEnabled() {
282 $this->assertTrue($this->moduleHandler->invoke('module_handler_test', 'hook', [TRUE]), 'Installed module runs hook.');
283 $this->assertFalse($this->moduleHandler->invoke('module_handler_test', 'hook', [FALSE]), 'Installed module runs hook.');
284 $this->assertNull($this->moduleHandler->invoke('module_handler_test_fake', 'hook', [FALSE]), 'Installed module runs hook.');
288 * Test implementations methods when module is enabled.
290 * @covers ::implementsHook
291 * @covers ::loadAllIncludes
293 public function testImplementsHookModuleEnabled() {
294 $this->assertTrue($this->moduleHandler->implementsHook('module_handler_test', 'hook'), 'Installed module implementation found.');
296 $this->moduleHandler->addModule('module_handler_test_added', 'core/tests/Drupal/Tests/Core/Extension/modules/module_handler_test_added');
297 $this->assertTrue($this->moduleHandler->implementsHook('module_handler_test_added', 'hook'), 'Runtime added module with implementation in include found.');
299 $this->moduleHandler->addModule('module_handler_test_no_hook', 'core/tests/Drupal/Tests/Core/Extension/modules/module_handler_test_added');
300 $this->assertFalse($this->moduleHandler->implementsHook('module_handler_test_no_hook', 'hook', [TRUE]), 'Missing implementation not found.');
304 * Test getImplementations.
306 * @covers ::getImplementations
307 * @covers ::getImplementationInfo
308 * @covers ::buildImplementationInfo
310 public function testGetImplementations() {
311 $this->assertEquals(['module_handler_test'], $this->moduleHandler->getImplementations('hook'));
315 * Test getImplementations.
317 * @covers ::getImplementations
318 * @covers ::getImplementationInfo
320 public function testCachedGetImplementations() {
321 $this->cacheBackend->expects($this->exactly(1))
323 ->will($this->onConsecutiveCalls(
324 (object) ['data' => ['hook' => ['module_handler_test' => 'test']]]
327 // Ensure buildImplementationInfo doesn't get called and that we work off cached results.
328 $module_handler = $this->getMockBuilder('Drupal\Core\Extension\ModuleHandler')
329 ->setConstructorArgs([
331 'module_handler_test' => [
333 'pathname' => 'core/tests/Drupal/Tests/Core/Extension/modules/module_handler_test/module_handler_test.info.yml',
334 'filename' => 'module_handler_test.module',
336 ], $this->cacheBackend
338 ->setMethods(['buildImplementationInfo', 'loadInclude'])
341 $module_handler->expects($this->never())->method('buildImplementationInfo');
342 $module_handler->expects($this->once())->method('loadInclude');
343 $this->assertEquals(['module_handler_test'], $module_handler->getImplementations('hook'));
347 * Test getImplementations.
349 * @covers ::getImplementations
350 * @covers ::getImplementationInfo
352 public function testCachedGetImplementationsMissingMethod() {
353 $this->cacheBackend->expects($this->exactly(1))
355 ->will($this->onConsecutiveCalls(
356 (object) ['data' => ['hook' => [
357 'module_handler_test' => [],
358 'module_handler_test_missing' => [],
362 // Ensure buildImplementationInfo doesn't get called and that we work off cached results.
363 $module_handler = $this->getMockBuilder('Drupal\Core\Extension\ModuleHandler')
364 ->setConstructorArgs([
366 'module_handler_test' => [
368 'pathname' => 'core/tests/Drupal/Tests/Core/Extension/modules/module_handler_test/module_handler_test.info.yml',
369 'filename' => 'module_handler_test.module',
371 ], $this->cacheBackend
373 ->setMethods(['buildImplementationInfo'])
376 $module_handler->expects($this->never())->method('buildImplementationInfo');
377 $this->assertEquals(['module_handler_test'], $module_handler->getImplementations('hook'));
383 * @covers ::invokeAll
385 public function testInvokeAll() {
386 $this->moduleHandler->addModule('module_handler_test_all1', 'core/tests/Drupal/Tests/Core/Extension/modules/module_handler_test_all1');
387 $this->moduleHandler->addModule('module_handler_test_all2', 'core/tests/Drupal/Tests/Core/Extension/modules/module_handler_test_all2');
388 $this->assertEquals([TRUE, TRUE, TRUE], $this->moduleHandler->invokeAll('hook', [TRUE]));
392 * Test that write cache calls through to cache library correctly.
394 * @covers ::writeCache
396 public function testWriteCache() {
398 ->expects($this->exactly(2))
400 ->will($this->returnValue(NULL));
402 ->expects($this->exactly(2))
404 ->with($this->logicalOr('module_implements', 'hook_info'));
405 $this->moduleHandler->getImplementations('hook');
406 $this->moduleHandler->writeCache();
410 * Test hook_hook_info() fetching through getHookInfo().
412 * @covers ::getHookInfo
413 * @covers ::buildHookInfo
415 public function testGetHookInfo() {
417 // Set up some synthetic results.
419 ->expects($this->exactly(2))
421 ->will($this->onConsecutiveCalls(
424 ['hook_foo' => ['group' => 'hook']]])
427 // Results from building from mocked environment.
428 $this->assertEquals([
429 'hook' => ['group' => 'hook'],
430 ], $this->moduleHandler->getHookInfo());
432 // Reset local cache so we get our synthetic result from the cache handler.
433 $this->moduleHandler->resetImplementations();
434 $this->assertEquals([
435 'hook_foo' => ['group' => 'hook'],
436 ], $this->moduleHandler->getHookInfo());
440 * Test internal implementation cache reset.
442 * @covers ::resetImplementations
444 public function testResetImplementations() {
447 $this->moduleHandler->getImplementations('hook');
448 $this->moduleHandler->getHookInfo();
450 // Reset all caches internal and external.
452 ->expects($this->once())
456 ->expects($this->exactly(2))
458 // reset sets module_implements to array() and getHookInfo later
459 // populates hook_info.
460 ->with($this->logicalOr('module_implements', 'hook_info'));
461 $this->moduleHandler->resetImplementations();
463 // Request implementation and ensure hook_info and module_implements skip
466 ->expects($this->exactly(2))
468 ->with($this->logicalOr('module_implements', 'hook_info'));
469 $this->moduleHandler->getImplementations('hook');
473 * @dataProvider dependencyProvider
474 * @covers ::parseDependency
476 public function testDependencyParsing($dependency, $expected) {
477 $version = ModuleHandler::parseDependency($dependency);
478 $this->assertEquals($expected, $version);
482 * Provider for testing dependency parsing.
484 public function dependencyProvider() {
486 ['system', ['name' => 'system']],
487 ['taxonomy', ['name' => 'taxonomy']],
488 ['views', ['name' => 'views']],
489 ['views_ui(8.x-1.0)', ['name' => 'views_ui', 'original_version' => ' (8.x-1.0)', 'versions' => [['op' => '=', 'version' => '1.0']]]],
491 // array('views_ui(8.x-1.1-beta)', array('name' => 'views_ui', 'original_version' => ' (8.x-1.1-beta)', 'versions' => array(array('op' => '=', 'version' => '1.1-beta')))),
492 ['views_ui(8.x-1.1-alpha12)', ['name' => 'views_ui', 'original_version' => ' (8.x-1.1-alpha12)', 'versions' => [['op' => '=', 'version' => '1.1-alpha12']]]],
493 ['views_ui(8.x-1.1-beta8)', ['name' => 'views_ui', 'original_version' => ' (8.x-1.1-beta8)', 'versions' => [['op' => '=', 'version' => '1.1-beta8']]]],
494 ['views_ui(8.x-1.1-rc11)', ['name' => 'views_ui', 'original_version' => ' (8.x-1.1-rc11)', 'versions' => [['op' => '=', 'version' => '1.1-rc11']]]],
495 ['views_ui(8.x-1.12)', ['name' => 'views_ui', 'original_version' => ' (8.x-1.12)', 'versions' => [['op' => '=', 'version' => '1.12']]]],
496 ['views_ui(8.x-1.x)', ['name' => 'views_ui', 'original_version' => ' (8.x-1.x)', 'versions' => [['op' => '<', 'version' => '2.x'], ['op' => '>=', 'version' => '1.x']]]],
497 ['views_ui( <= 8.x-1.x)', ['name' => 'views_ui', 'original_version' => ' ( <= 8.x-1.x)', 'versions' => [['op' => '<=', 'version' => '2.x']]]],
498 ['views_ui(<= 8.x-1.x)', ['name' => 'views_ui', 'original_version' => ' (<= 8.x-1.x)', 'versions' => [['op' => '<=', 'version' => '2.x']]]],
499 ['views_ui( <=8.x-1.x)', ['name' => 'views_ui', 'original_version' => ' ( <=8.x-1.x)', 'versions' => [['op' => '<=', 'version' => '2.x']]]],
500 ['views_ui(>8.x-1.x)', ['name' => 'views_ui', 'original_version' => ' (>8.x-1.x)', 'versions' => [['op' => '>', 'version' => '2.x']]]],
501 ['drupal:views_ui(>8.x-1.x)', ['project' => 'drupal', 'name' => 'views_ui', 'original_version' => ' (>8.x-1.x)', 'versions' => [['op' => '>', 'version' => '2.x']]]],
506 * @covers ::getModuleDirectories
508 public function testGetModuleDirectories() {
509 $this->moduleHandler->setModuleList([]);
510 $this->moduleHandler->addModule('module', 'place');
511 $this->assertEquals(['module' => $this->root . '/place'], $this->moduleHandler->getModuleDirectories());