4fda0022b7d59a652a76ebb9de0295d3024c39ec
[yaffs-website] / web / core / tests / Drupal / Tests / Core / Extension / ModuleHandlerTest.php
1 <?php
2
3 namespace Drupal\Tests\Core\Extension;
4
5 use Drupal\Core\Cache\CacheBackendInterface;
6 use Drupal\Core\Extension\Extension;
7 use Drupal\Core\Extension\ModuleHandler;
8 use Drupal\Tests\UnitTestCase;
9
10 /**
11  * @coversDefaultClass \Drupal\Core\Extension\ModuleHandler
12  * @runTestsInSeparateProcesses
13  *
14  * @group Extension
15  */
16 class ModuleHandlerTest extends UnitTestCase {
17
18   /**
19    * The mocked cache backend.
20    *
21    * @var \Drupal\Core\Cache\CacheBackendInterface|\PHPUnit_Framework_MockObject_MockObject
22    */
23   protected $cacheBackend;
24
25   /**
26    * {@inheritdoc}
27    *
28    * @covers ::__construct
29    */
30   protected function setUp() {
31     parent::setUp();
32     // We can mock the cache handler here, but not the module handler.
33     $this->cacheBackend = $this->getMock(CacheBackendInterface::class);
34   }
35
36   /**
37    * Get a module handler object to test.
38    *
39    * Since we have to run these tests in separate processes, we have to use
40    * test objects which are serializable. Since ModuleHandler will populate
41    * itself with Extension objects, and since Extension objects will try to
42    * access DRUPAL_ROOT when they're unserialized, we can't store our mocked
43    * ModuleHandler objects as a property in unit tests. They must be generated
44    * by the test method by calling this method.
45    *
46    * @return \Drupal\Core\Extension\ModuleHandler
47    *   The module handler to test.
48    */
49   protected function getModuleHandler() {
50     $module_handler = new ModuleHandler($this->root, [
51       'module_handler_test' => [
52         'type' => 'module',
53         'pathname' => 'core/tests/Drupal/Tests/Core/Extension/modules/module_handler_test/module_handler_test.info.yml',
54         'filename' => 'module_handler_test.module',
55       ]
56     ], $this->cacheBackend);
57     return $module_handler;
58   }
59
60   /**
61    * Test loading a module.
62    *
63    * @covers ::load
64    */
65   public function testLoadModule() {
66     $module_handler = $this->getModuleHandler();
67     $this->assertFalse(function_exists('module_handler_test_hook'));
68     $this->assertTrue($module_handler->load('module_handler_test'));
69     $this->assertTrue(function_exists('module_handler_test_hook'));
70
71     $module_handler->addModule('module_handler_test_added', 'core/tests/Drupal/Tests/Core/Extension/modules/module_handler_test_added');
72     $this->assertFalse(function_exists('module_handler_test_added_hook'), 'Function does not exist before being loaded.');
73     $this->assertTrue($module_handler->load('module_handler_test_added'));
74     $this->assertTrue(function_exists('module_handler_test_added_helper'), 'Function exists after being loaded.');
75     $this->assertTrue($module_handler->load('module_handler_test_added'));
76
77     $this->assertFalse($module_handler->load('module_handler_test_dne'), 'Non-existent modules returns false.');
78   }
79
80   /**
81    * Test loading all modules.
82    *
83    * @covers ::loadAll
84    */
85   public function testLoadAllModules() {
86     $module_handler = $this->getModuleHandler();
87     $module_handler->addModule('module_handler_test_all1', 'core/tests/Drupal/Tests/Core/Extension/modules/module_handler_test_all1');
88     $module_handler->addModule('module_handler_test_all2', 'core/tests/Drupal/Tests/Core/Extension/modules/module_handler_test_all2');
89     $this->assertFalse(function_exists('module_handler_test_all1_hook'), 'Function does not exist before being loaded.');
90     $this->assertFalse(function_exists('module_handler_test_all2_hook'), 'Function does not exist before being loaded.');
91     $module_handler->loadAll();
92     $this->assertTrue(function_exists('module_handler_test_all1_hook'), 'Function exists after being loaded.');
93     $this->assertTrue(function_exists('module_handler_test_all2_hook'), 'Function exists after being loaded.');
94   }
95
96   /**
97    * Test reload method.
98    *
99    * @covers ::reload
100    */
101   public function testModuleReloading() {
102     $module_handler = $this->getMockBuilder(ModuleHandler::class)
103       ->setConstructorArgs([
104         $this->root,
105         [
106           'module_handler_test' => [
107             'type' => 'module',
108             'pathname' => 'core/tests/Drupal/Tests/Core/Extension/modules/module_handler_test/module_handler_test.info.yml',
109             'filename' => 'module_handler_test.module',
110           ]
111         ], $this->cacheBackend
112       ])
113       ->setMethods(['load'])
114       ->getMock();
115     // First reload.
116     $module_handler->expects($this->at(0))
117       ->method('load')
118       ->with($this->equalTo('module_handler_test'));
119     // Second reload.
120     $module_handler->expects($this->at(1))
121       ->method('load')
122       ->with($this->equalTo('module_handler_test'));
123     $module_handler->expects($this->at(2))
124       ->method('load')
125       ->with($this->equalTo('module_handler_test_added'));
126     $module_handler->reload();
127     $module_handler->addModule('module_handler_test_added', 'core/tests/Drupal/Tests/Core/Extension/modules/module_handler_test_added');
128     $module_handler->reload();
129   }
130
131   /**
132    * Test isLoaded accessor.
133    *
134    * @covers ::isLoaded
135    */
136   public function testIsLoaded() {
137     $module_handler = $this->getModuleHandler();
138     $this->assertFalse($module_handler->isLoaded());
139     $module_handler->loadAll();
140     $this->assertTrue($module_handler->isLoaded());
141   }
142
143   /**
144    * Confirm we get back the modules set in the constructor.
145    *
146    * @covers ::getModuleList
147    */
148   public function testGetModuleList() {
149     $this->assertEquals($this->getModuleHandler()->getModuleList(), [
150       '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'),
151     ]);
152   }
153
154   /**
155    * Confirm we get back a module from the module list
156    *
157    * @covers ::getModule
158    */
159   public function testGetModuleWithExistingModule() {
160     $this->assertEquals($this->getModuleHandler()->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'));
161   }
162
163   /**
164    * @covers ::getModule
165    */
166   public function testGetModuleWithNonExistingModule() {
167     $this->setExpectedException(\InvalidArgumentException::class);
168     $this->getModuleHandler()->getModule('claire_alice_watch_my_little_pony_module_that_does_not_exist');
169   }
170
171   /**
172    * Ensure setting the module list replaces the module list and resets internal structures.
173    *
174    * @covers ::setModuleList
175    */
176   public function testSetModuleList() {
177     $fixture_module_handler = $this->getModuleHandler();
178     $module_handler = $this->getMockBuilder(ModuleHandler::class)
179       ->setConstructorArgs([
180         $this->root, [], $this->cacheBackend
181       ])
182       ->setMethods(['resetImplementations'])
183       ->getMock();
184
185     // Ensure we reset implementations when settings a new modules list.
186     $module_handler->expects($this->once())->method('resetImplementations');
187
188     // Make sure we're starting empty.
189     $this->assertEquals($module_handler->getModuleList(), []);
190
191     // Replace the list with a prebuilt list.
192     $module_handler->setModuleList($fixture_module_handler->getModuleList());
193
194     // Ensure those changes are stored.
195     $this->assertEquals($fixture_module_handler->getModuleList(), $module_handler->getModuleList());
196   }
197
198   /**
199    * Test adding a module.
200    *
201    * @covers ::addModule
202    * @covers ::add
203    */
204   public function testAddModule() {
205
206     $module_handler = $this->getMockBuilder(ModuleHandler::class)
207       ->setConstructorArgs([
208         $this->root, [], $this->cacheBackend
209       ])
210       ->setMethods(['resetImplementations'])
211       ->getMock();
212
213     // Ensure we reset implementations when settings a new modules list.
214     $module_handler->expects($this->once())->method('resetImplementations');
215
216     $module_handler->addModule('module_handler_test', 'core/tests/Drupal/Tests/Core/Extension/modules/module_handler_test');
217     $this->assertTrue($module_handler->moduleExists('module_handler_test'));
218   }
219
220   /**
221    * Test adding a profile.
222    *
223    * @covers ::addProfile
224    * @covers ::add
225    */
226   public function testAddProfile() {
227
228     $module_handler = $this->getMockBuilder(ModuleHandler::class)
229       ->setConstructorArgs([
230         $this->root, [], $this->cacheBackend
231       ])
232       ->setMethods(['resetImplementations'])
233       ->getMock();
234
235     // Ensure we reset implementations when settings a new modules list.
236     $module_handler->expects($this->once())->method('resetImplementations');
237
238     // @todo this should probably fail since its a module not a profile.
239     $module_handler->addProfile('module_handler_test', 'core/tests/Drupal/Tests/Core/Extension/modules/module_handler_test');
240     $this->assertTrue($module_handler->moduleExists('module_handler_test'));
241   }
242
243   /**
244    * Test module exists returns correct module status.
245    *
246    * @covers ::moduleExists
247    */
248   public function testModuleExists() {
249     $module_handler = $this->getModuleHandler();
250     $this->assertTrue($module_handler->moduleExists('module_handler_test'));
251     $this->assertFalse($module_handler->moduleExists('module_handler_test_added'));
252   }
253
254   /**
255    * @covers ::loadAllIncludes
256    */
257   public function testLoadAllIncludes() {
258     $this->assertTrue(TRUE);
259     $module_handler = $this->getMockBuilder(ModuleHandler::class)
260       ->setConstructorArgs([
261         $this->root,
262         [
263           'module_handler_test' => [
264             'type' => 'module',
265             'pathname' => 'core/tests/Drupal/Tests/Core/Extension/modules/module_handler_test/module_handler_test.info.yml',
266             'filename' => 'module_handler_test.module',
267           ]
268         ], $this->cacheBackend
269       ])
270       ->setMethods(['loadInclude'])
271       ->getMock();
272
273     // Ensure we reset implementations when settings a new modules list.
274     $module_handler->expects($this->once())->method('loadInclude');
275     $module_handler->loadAllIncludes('hook');
276   }
277
278   /**
279    * @covers ::loadInclude
280    *
281    * Note we load code, so isolate the test.
282    *
283    * @runInSeparateProcess
284    * @preserveGlobalState disabled
285    */
286   public function testLoadInclude() {
287     $module_handler = $this->getModuleHandler();
288     // Include exists.
289     $this->assertEquals(__DIR__ . '/modules/module_handler_test/hook_include.inc', $module_handler->loadInclude('module_handler_test', 'inc', 'hook_include'));
290     $this->assertTrue(function_exists('module_handler_test_hook_include'));
291     // Include doesn't exist.
292     $this->assertFalse($module_handler->loadInclude('module_handler_test', 'install'));
293   }
294
295   /**
296    * Test invoke methods when module is enabled.
297    *
298    * @covers ::invoke
299    */
300   public function testInvokeModuleEnabled() {
301     $module_handler = $this->getModuleHandler();
302     $this->assertTrue($module_handler->invoke('module_handler_test', 'hook', [TRUE]), 'Installed module runs hook.');
303     $this->assertFalse($module_handler->invoke('module_handler_test', 'hook', [FALSE]), 'Installed module runs hook.');
304     $this->assertNull($module_handler->invoke('module_handler_test_fake', 'hook', [FALSE]), 'Installed module runs hook.');
305   }
306
307   /**
308    * Test implementations methods when module is enabled.
309    *
310    * @covers ::implementsHook
311    * @covers ::loadAllIncludes
312    */
313   public function testImplementsHookModuleEnabled() {
314     $module_handler = $this->getModuleHandler();
315     $this->assertTrue($module_handler->implementsHook('module_handler_test', 'hook'), 'Installed module implementation found.');
316
317     $module_handler->addModule('module_handler_test_added', 'core/tests/Drupal/Tests/Core/Extension/modules/module_handler_test_added');
318     $this->assertTrue($module_handler->implementsHook('module_handler_test_added', 'hook'), 'Runtime added module with implementation in include found.');
319
320     $module_handler->addModule('module_handler_test_no_hook', 'core/tests/Drupal/Tests/Core/Extension/modules/module_handler_test_added');
321     $this->assertFalse($module_handler->implementsHook('module_handler_test_no_hook', 'hook', [TRUE]), 'Missing implementation not found.');
322   }
323
324   /**
325    * Test getImplementations.
326    *
327    * @covers ::getImplementations
328    * @covers ::getImplementationInfo
329    * @covers ::buildImplementationInfo
330    */
331   public function testGetImplementations() {
332     $this->assertEquals(['module_handler_test'], $this->getModuleHandler()->getImplementations('hook'));
333   }
334
335   /**
336    * Test getImplementations.
337    *
338    * @covers ::getImplementations
339    * @covers ::getImplementationInfo
340    */
341   public function testCachedGetImplementations() {
342     $this->cacheBackend->expects($this->exactly(1))
343       ->method('get')
344       ->will($this->onConsecutiveCalls(
345         (object) ['data' => ['hook' => ['module_handler_test' => 'test']]]
346       ));
347
348     // Ensure buildImplementationInfo doesn't get called and that we work off cached results.
349     $module_handler = $this->getMockBuilder(ModuleHandler::class)
350       ->setConstructorArgs([
351         $this->root, [
352           'module_handler_test' => [
353             'type' => 'module',
354             'pathname' => 'core/tests/Drupal/Tests/Core/Extension/modules/module_handler_test/module_handler_test.info.yml',
355             'filename' => 'module_handler_test.module',
356           ]
357         ], $this->cacheBackend
358       ])
359       ->setMethods(['buildImplementationInfo', 'loadInclude'])
360       ->getMock();
361     $module_handler->load('module_handler_test');
362
363     $module_handler->expects($this->never())->method('buildImplementationInfo');
364     $module_handler->expects($this->once())->method('loadInclude');
365     $this->assertEquals(['module_handler_test'], $module_handler->getImplementations('hook'));
366   }
367
368   /**
369    * Test getImplementations.
370    *
371    * @covers ::getImplementations
372    * @covers ::getImplementationInfo
373    */
374   public function testCachedGetImplementationsMissingMethod() {
375     $this->cacheBackend->expects($this->exactly(1))
376       ->method('get')
377       ->will($this->onConsecutiveCalls((object) [
378         'data' => [
379           'hook' => [
380             'module_handler_test' => [],
381             'module_handler_test_missing' => [],
382           ],
383         ],
384       ]));
385
386     // Ensure buildImplementationInfo doesn't get called and that we work off cached results.
387     $module_handler = $this->getMockBuilder(ModuleHandler::class)
388       ->setConstructorArgs([
389         $this->root, [
390           'module_handler_test' => [
391             'type' => 'module',
392             'pathname' => 'core/tests/Drupal/Tests/Core/Extension/modules/module_handler_test/module_handler_test.info.yml',
393             'filename' => 'module_handler_test.module',
394           ]
395         ], $this->cacheBackend
396       ])
397       ->setMethods(['buildImplementationInfo'])
398       ->getMock();
399     $module_handler->load('module_handler_test');
400
401     $module_handler->expects($this->never())->method('buildImplementationInfo');
402     $this->assertEquals(['module_handler_test'], $module_handler->getImplementations('hook'));
403   }
404
405   /**
406    * Test invoke all.
407    *
408    * @covers ::invokeAll
409    */
410   public function testInvokeAll() {
411     $module_handler = $this->getModuleHandler();
412     $module_handler->addModule('module_handler_test_all1', 'core/tests/Drupal/Tests/Core/Extension/modules/module_handler_test_all1');
413     $module_handler->addModule('module_handler_test_all2', 'core/tests/Drupal/Tests/Core/Extension/modules/module_handler_test_all2');
414     $this->assertEquals([TRUE, TRUE, TRUE], $module_handler->invokeAll('hook', [TRUE]));
415   }
416
417   /**
418    * Test that write cache calls through to cache library correctly.
419    *
420    * @covers ::writeCache
421    */
422   public function testWriteCache() {
423     $module_handler = $this->getModuleHandler();
424     $this->cacheBackend
425       ->expects($this->exactly(2))
426       ->method('get')
427       ->will($this->returnValue(NULL));
428     $this->cacheBackend
429       ->expects($this->exactly(2))
430       ->method('set')
431       ->with($this->logicalOr('module_implements', 'hook_info'));
432     $module_handler->getImplementations('hook');
433     $module_handler->writeCache();
434   }
435
436   /**
437    * Test hook_hook_info() fetching through getHookInfo().
438    *
439    * @covers ::getHookInfo
440    * @covers ::buildHookInfo
441    */
442   public function testGetHookInfo() {
443     $module_handler = $this->getModuleHandler();
444     // Set up some synthetic results.
445     $this->cacheBackend
446       ->expects($this->exactly(2))
447       ->method('get')
448       ->will($this->onConsecutiveCalls(
449         NULL,
450         (object) ['data' => ['hook_foo' => ['group' => 'hook']]]
451       ));
452
453     // Results from building from mocked environment.
454     $this->assertEquals([
455       'hook' => ['group' => 'hook'],
456     ], $module_handler->getHookInfo());
457
458     // Reset local cache so we get our synthetic result from the cache handler.
459     $module_handler->resetImplementations();
460     $this->assertEquals([
461       'hook_foo' => ['group' => 'hook'],
462     ], $module_handler->getHookInfo());
463   }
464
465   /**
466    * Test internal implementation cache reset.
467    *
468    * @covers ::resetImplementations
469    */
470   public function testResetImplementations() {
471     $module_handler = $this->getModuleHandler();
472     // Prime caches
473     $module_handler->getImplementations('hook');
474     $module_handler->getHookInfo();
475
476     // Reset all caches internal and external.
477     $this->cacheBackend
478       ->expects($this->once())
479       ->method('delete')
480       ->with('hook_info');
481     $this->cacheBackend
482       ->expects($this->exactly(2))
483       ->method('set')
484       // reset sets module_implements to array() and getHookInfo later
485       // populates hook_info.
486       ->with($this->logicalOr('module_implements', 'hook_info'));
487     $module_handler->resetImplementations();
488
489     // Request implementation and ensure hook_info and module_implements skip
490     // local caches.
491     $this->cacheBackend
492       ->expects($this->exactly(2))
493       ->method('get')
494       ->with($this->logicalOr('module_implements', 'hook_info'));
495     $module_handler->getImplementations('hook');
496   }
497
498   /**
499    * @dataProvider dependencyProvider
500    * @covers ::parseDependency
501    */
502   public function testDependencyParsing($dependency, $expected) {
503     $version = ModuleHandler::parseDependency($dependency);
504     $this->assertEquals($expected, $version);
505   }
506
507   /**
508    * Provider for testing dependency parsing.
509    */
510   public function dependencyProvider() {
511     return [
512       ['system', ['name' => 'system']],
513       ['taxonomy', ['name' => 'taxonomy']],
514       ['views', ['name' => 'views']],
515       ['views_ui(8.x-1.0)', ['name' => 'views_ui', 'original_version' => ' (8.x-1.0)', 'versions' => [['op' => '=', 'version' => '1.0']]]],
516       // Not supported?.
517       // 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')))),
518       ['views_ui(8.x-1.1-alpha12)', ['name' => 'views_ui', 'original_version' => ' (8.x-1.1-alpha12)', 'versions' => [['op' => '=', 'version' => '1.1-alpha12']]]],
519       ['views_ui(8.x-1.1-beta8)', ['name' => 'views_ui', 'original_version' => ' (8.x-1.1-beta8)', 'versions' => [['op' => '=', 'version' => '1.1-beta8']]]],
520       ['views_ui(8.x-1.1-rc11)', ['name' => 'views_ui', 'original_version' => ' (8.x-1.1-rc11)', 'versions' => [['op' => '=', 'version' => '1.1-rc11']]]],
521       ['views_ui(8.x-1.12)', ['name' => 'views_ui', 'original_version' => ' (8.x-1.12)', 'versions' => [['op' => '=', 'version' => '1.12']]]],
522       ['views_ui(8.x-1.x)', ['name' => 'views_ui', 'original_version' => ' (8.x-1.x)', 'versions' => [['op' => '<', 'version' => '2.x'], ['op' => '>=', 'version' => '1.x']]]],
523       ['views_ui( <= 8.x-1.x)', ['name' => 'views_ui', 'original_version' => ' ( <= 8.x-1.x)', 'versions' => [['op' => '<=', 'version' => '2.x']]]],
524       ['views_ui(<= 8.x-1.x)', ['name' => 'views_ui', 'original_version' => ' (<= 8.x-1.x)', 'versions' => [['op' => '<=', 'version' => '2.x']]]],
525       ['views_ui( <=8.x-1.x)', ['name' => 'views_ui', 'original_version' => ' ( <=8.x-1.x)', 'versions' => [['op' => '<=', 'version' => '2.x']]]],
526       ['views_ui(>8.x-1.x)', ['name' => 'views_ui', 'original_version' => ' (>8.x-1.x)', 'versions' => [['op' => '>', 'version' => '2.x']]]],
527       ['drupal:views_ui(>8.x-1.x)', ['project' => 'drupal', 'name' => 'views_ui', 'original_version' => ' (>8.x-1.x)', 'versions' => [['op' => '>', 'version' => '2.x']]]],
528     ];
529   }
530
531   /**
532    * @covers ::getModuleDirectories
533    */
534   public function testGetModuleDirectories() {
535     $module_handler = $this->getModuleHandler();
536     $module_handler->setModuleList([]);
537     $module_handler->addModule('module', 'place');
538     $this->assertEquals(['module' => $this->root . '/place'], $module_handler->getModuleDirectories());
539   }
540
541 }