1bde890b07dbc8dcd53ae2899114cb7a571eb1a0
[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\Extension\Extension;
6 use Drupal\Core\Extension\ModuleHandler;
7 use Drupal\Tests\UnitTestCase;
8
9 /**
10  * @coversDefaultClass \Drupal\Core\Extension\ModuleHandler
11  * @group Extension
12  */
13 class ModuleHandlerTest extends UnitTestCase {
14
15   /**
16    * The mocked cache backend.
17    *
18    * @var \Drupal\Core\Cache\CacheBackendInterface|\PHPUnit_Framework_MockObject_MockObject
19    */
20   protected $cacheBackend;
21
22   /**
23    * The tested module handler.
24    *
25    * @var \Drupal\Core\Extension\ModuleHandler
26    */
27   protected $moduleHandler;
28
29   /**
30    * {@inheritdoc}
31    *
32    * @covers ::__construct
33    */
34   protected function setUp() {
35     parent::setUp();
36
37     $this->cacheBackend = $this->getMock('Drupal\Core\Cache\CacheBackendInterface');
38     $this->moduleHandler = new ModuleHandler($this->root, [
39       'module_handler_test' => [
40         'type' => 'module',
41         'pathname' => 'core/tests/Drupal/Tests/Core/Extension/modules/module_handler_test/module_handler_test.info.yml',
42         'filename' => 'module_handler_test.module',
43       ]
44     ], $this->cacheBackend);
45   }
46
47   /**
48    * Test loading a module.
49    *
50    * @covers ::load
51    */
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'));
56
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'));
62
63     $this->assertFalse($this->moduleHandler->load('module_handler_test_dne'), 'Non-existent modules returns false.');
64   }
65
66   /**
67    * Test loading all modules.
68    *
69    * @covers ::loadAll
70    */
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.');
79   }
80
81   /**
82    * Test reload method.
83    *
84    * @covers ::reload
85    */
86   public function testModuleReloading() {
87     $module_handler = $this->getMockBuilder('Drupal\Core\Extension\ModuleHandler')
88       ->setConstructorArgs([
89         $this->root,
90         [
91           'module_handler_test' => [
92             'type' => 'module',
93             'pathname' => 'core/tests/Drupal/Tests/Core/Extension/modules/module_handler_test/module_handler_test.info.yml',
94             'filename' => 'module_handler_test.module',
95           ]
96         ], $this->cacheBackend
97       ])
98       ->setMethods(['load'])
99       ->getMock();
100     // First reload.
101     $module_handler->expects($this->at(0))
102       ->method('load')
103       ->with($this->equalTo('module_handler_test'));
104     // Second reload.
105     $module_handler->expects($this->at(1))
106       ->method('load')
107       ->with($this->equalTo('module_handler_test'));
108     $module_handler->expects($this->at(2))
109       ->method('load')
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();
114   }
115
116   /**
117    * Test isLoaded accessor.
118    *
119    * @covers ::isLoaded
120    */
121   public function testIsLoaded() {
122     $this->assertFalse($this->moduleHandler->isLoaded());
123     $this->moduleHandler->loadAll();
124     $this->assertTrue($this->moduleHandler->isLoaded());
125   }
126
127   /**
128    * Confirm we get back the modules set in the constructor.
129    *
130    * @covers ::getModuleList
131    */
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'),
135     ]);
136   }
137
138   /**
139    * Confirm we get back a module from the module list
140    *
141    * @covers ::getModule
142    */
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'));
145   }
146
147   /**
148    * @covers ::getModule
149    */
150   public function testGetModuleWithNonExistingModule() {
151     $this->setExpectedException(\InvalidArgumentException::class);
152     $this->moduleHandler->getModule('claire_alice_watch_my_little_pony_module_that_does_not_exist');
153   }
154
155   /**
156    * Ensure setting the module list replaces the module list and resets internal structures.
157    *
158    * @covers ::setModuleList
159    */
160   public function testSetModuleList() {
161     $module_handler = $this->getMockBuilder('Drupal\Core\Extension\ModuleHandler')
162       ->setConstructorArgs([
163         $this->root, [], $this->cacheBackend
164       ])
165       ->setMethods(['resetImplementations'])
166       ->getMock();
167
168     // Ensure we reset implementations when settings a new modules list.
169     $module_handler->expects($this->once())->method('resetImplementations');
170
171     // Make sure we're starting empty.
172     $this->assertEquals($module_handler->getModuleList(), []);
173
174     // Replace the list with a prebuilt list.
175     $module_handler->setModuleList($this->moduleHandler->getModuleList());
176
177     // Ensure those changes are stored.
178     $this->assertEquals($this->moduleHandler->getModuleList(), $module_handler->getModuleList());
179   }
180
181   /**
182    * Test adding a module.
183    *
184    * @covers ::addModule
185    * @covers ::add
186    */
187   public function testAddModule() {
188
189     $module_handler = $this->getMockBuilder('Drupal\Core\Extension\ModuleHandler')
190       ->setConstructorArgs([
191         $this->root, [], $this->cacheBackend
192       ])
193       ->setMethods(['resetImplementations'])
194       ->getMock();
195
196     // Ensure we reset implementations when settings a new modules list.
197     $module_handler->expects($this->once())->method('resetImplementations');
198
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'));
201   }
202
203   /**
204    * Test adding a profile.
205    *
206    * @covers ::addProfile
207    * @covers ::add
208    */
209   public function testAddProfile() {
210
211     $module_handler = $this->getMockBuilder('Drupal\Core\Extension\ModuleHandler')
212       ->setConstructorArgs([
213         $this->root, [], $this->cacheBackend
214       ])
215       ->setMethods(['resetImplementations'])
216       ->getMock();
217
218     // Ensure we reset implementations when settings a new modules list.
219     $module_handler->expects($this->once())->method('resetImplementations');
220
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'));
224   }
225
226   /**
227    * Test module exists returns correct module status.
228    *
229    * @covers ::moduleExists
230    */
231   public function testModuleExists() {
232     $this->assertTrue($this->moduleHandler->moduleExists('module_handler_test'));
233     $this->assertFalse($this->moduleHandler->moduleExists('module_handler_test_added'));
234   }
235
236   /**
237    * @covers ::loadAllIncludes
238    */
239   public function testLoadAllIncludes() {
240     $this->assertTrue(TRUE);
241     $module_handler = $this->getMockBuilder('Drupal\Core\Extension\ModuleHandler')
242       ->setConstructorArgs([
243         $this->root,
244         [
245           'module_handler_test' => [
246             'type' => 'module',
247             'pathname' => 'core/tests/Drupal/Tests/Core/Extension/modules/module_handler_test/module_handler_test.info.yml',
248             'filename' => 'module_handler_test.module',
249           ]
250         ], $this->cacheBackend
251       ])
252       ->setMethods(['loadInclude'])
253       ->getMock();
254
255     // Ensure we reset implementations when settings a new modules list.
256     $module_handler->expects($this->once())->method('loadInclude');
257     $module_handler->loadAllIncludes('hook');
258   }
259
260   /**
261    * @covers ::loadInclude
262    *
263    * Note we load code, so isolate the test.
264    *
265    * @runInSeparateProcess
266    * @preserveGlobalState disabled
267    */
268   public function testLoadInclude() {
269     // Include exists.
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'));
274   }
275
276   /**
277    * Test invoke methods when module is enabled.
278    *
279    * @covers ::invoke
280    */
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.');
285   }
286
287   /**
288    * Test implementations methods when module is enabled.
289    *
290    * @covers ::implementsHook
291    * @covers ::loadAllIncludes
292    */
293   public function testImplementsHookModuleEnabled() {
294     $this->assertTrue($this->moduleHandler->implementsHook('module_handler_test', 'hook'), 'Installed module implementation found.');
295
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.');
298
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.');
301   }
302
303   /**
304    * Test getImplementations.
305    *
306    * @covers ::getImplementations
307    * @covers ::getImplementationInfo
308    * @covers ::buildImplementationInfo
309    */
310   public function testGetImplementations() {
311     $this->assertEquals(['module_handler_test'], $this->moduleHandler->getImplementations('hook'));
312   }
313
314   /**
315    * Test getImplementations.
316    *
317    * @covers ::getImplementations
318    * @covers ::getImplementationInfo
319    */
320   public function testCachedGetImplementations() {
321     $this->cacheBackend->expects($this->exactly(1))
322       ->method('get')
323       ->will($this->onConsecutiveCalls(
324         (object) ['data' => ['hook' => ['module_handler_test' => 'test']]]
325       ));
326
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([
330         $this->root, [
331           'module_handler_test' => [
332             'type' => 'module',
333             'pathname' => 'core/tests/Drupal/Tests/Core/Extension/modules/module_handler_test/module_handler_test.info.yml',
334             'filename' => 'module_handler_test.module',
335           ]
336         ], $this->cacheBackend
337       ])
338       ->setMethods(['buildImplementationInfo', 'loadInclude'])
339       ->getMock();
340
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'));
344   }
345
346   /**
347    * Test getImplementations.
348    *
349    * @covers ::getImplementations
350    * @covers ::getImplementationInfo
351    */
352   public function testCachedGetImplementationsMissingMethod() {
353     $this->cacheBackend->expects($this->exactly(1))
354       ->method('get')
355       ->will($this->onConsecutiveCalls(
356         (object) ['data' => ['hook' => [
357           'module_handler_test' => [],
358           'module_handler_test_missing' => [],
359         ]]]
360       ));
361
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([
365         $this->root, [
366           'module_handler_test' => [
367             'type' => 'module',
368             'pathname' => 'core/tests/Drupal/Tests/Core/Extension/modules/module_handler_test/module_handler_test.info.yml',
369             'filename' => 'module_handler_test.module',
370           ]
371         ], $this->cacheBackend
372       ])
373       ->setMethods(['buildImplementationInfo'])
374       ->getMock();
375
376     $module_handler->expects($this->never())->method('buildImplementationInfo');
377     $this->assertEquals(['module_handler_test'], $module_handler->getImplementations('hook'));
378   }
379
380   /**
381    * Test invoke all.
382    *
383    * @covers ::invokeAll
384    */
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]));
389   }
390
391   /**
392    * Test that write cache calls through to cache library correctly.
393    *
394    * @covers ::writeCache
395    */
396   public function testWriteCache() {
397     $this->cacheBackend
398       ->expects($this->exactly(2))
399       ->method('get')
400       ->will($this->returnValue(NULL));
401     $this->cacheBackend
402       ->expects($this->exactly(2))
403       ->method('set')
404       ->with($this->logicalOr('module_implements', 'hook_info'));
405     $this->moduleHandler->getImplementations('hook');
406     $this->moduleHandler->writeCache();
407   }
408
409   /**
410    * Test hook_hook_info() fetching through getHookInfo().
411    *
412    * @covers ::getHookInfo
413    * @covers ::buildHookInfo
414    */
415   public function testGetHookInfo() {
416
417     // Set up some synthetic results.
418     $this->cacheBackend
419       ->expects($this->exactly(2))
420       ->method('get')
421       ->will($this->onConsecutiveCalls(
422         NULL,
423         (object) ['data' =>
424           ['hook_foo' => ['group' => 'hook']]])
425       );
426
427     // Results from building from mocked environment.
428     $this->assertEquals([
429       'hook' => ['group' => 'hook'],
430     ], $this->moduleHandler->getHookInfo());
431
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());
437   }
438
439   /**
440    * Test internal implementation cache reset.
441    *
442    * @covers ::resetImplementations
443    */
444   public function testResetImplementations() {
445
446     // Prime caches
447     $this->moduleHandler->getImplementations('hook');
448     $this->moduleHandler->getHookInfo();
449
450     // Reset all caches internal and external.
451     $this->cacheBackend
452       ->expects($this->once())
453       ->method('delete')
454       ->with('hook_info');
455     $this->cacheBackend
456       ->expects($this->exactly(2))
457       ->method('set')
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();
462
463     // Request implementation and ensure hook_info and module_implements skip
464     // local caches.
465     $this->cacheBackend
466       ->expects($this->exactly(2))
467       ->method('get')
468       ->with($this->logicalOr('module_implements', 'hook_info'));
469     $this->moduleHandler->getImplementations('hook');
470   }
471
472   /**
473    * @dataProvider dependencyProvider
474    * @covers ::parseDependency
475    */
476   public function testDependencyParsing($dependency, $expected) {
477     $version = ModuleHandler::parseDependency($dependency);
478     $this->assertEquals($expected, $version);
479   }
480
481   /**
482    * Provider for testing dependency parsing.
483    */
484   public function dependencyProvider() {
485     return [
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']]]],
490       // Not supported?.
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']]]],
502     ];
503   }
504
505   /**
506    * @covers ::getModuleDirectories
507    */
508   public function testGetModuleDirectories() {
509     $this->moduleHandler->setModuleList([]);
510     $this->moduleHandler->addModule('module', 'place');
511     $this->assertEquals(['module' => $this->root . '/place'], $this->moduleHandler->getModuleDirectories());
512   }
513
514 }