MDL-69166 core: define payment as a subsystem and pg as a plugin type
[moodle.git] / lib / tests / component_test.php
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
17 /**
18  * core_component related tests.
19  *
20  * @package    core
21  * @category   phpunit
22  * @copyright  2013 Petr Skoda {@link http://skodak.org}
23  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24  */
26 defined('MOODLE_INTERNAL') || die();
29 /**
30  * Class core_component_testcase.
31  */
32 class core_component_testcase extends advanced_testcase {
34     /**
35      * To be changed if number of subsystems increases/decreases,
36      * this is defined here to annoy devs that try to add more without any thinking,
37      * always verify that it does not collide with any existing add-on modules and subplugins!!!
38      */
39     const SUBSYSTEMCOUNT = 72;
41     public function setUp(): void {
42         $psr0namespaces = new ReflectionProperty('core_component', 'psr0namespaces');
43         $psr0namespaces->setAccessible(true);
44         $this->oldpsr0namespaces = $psr0namespaces->getValue(null);
46         $psr4namespaces = new ReflectionProperty('core_component', 'psr4namespaces');
47         $psr4namespaces->setAccessible(true);
48         $this->oldpsr4namespaces = $psr4namespaces->getValue(null);
49     }
50     public function tearDown(): void {
51         $psr0namespaces = new ReflectionProperty('core_component', 'psr0namespaces');
52         $psr0namespaces->setAccessible(true);
53         $psr0namespaces->setValue(null, $this->oldpsr0namespaces);
55         $psr4namespaces = new ReflectionProperty('core_component', 'psr4namespaces');
56         $psr4namespaces->setAccessible(true);
57         $psr4namespaces->setValue(null, $this->oldpsr4namespaces);
58     }
60     public function test_get_core_subsystems() {
61         global $CFG;
63         $subsystems = core_component::get_core_subsystems();
65         $this->assertCount(self::SUBSYSTEMCOUNT, $subsystems, 'Oh, somebody added or removed a core subsystem, think twice before doing that!');
67         // Make sure all paths are full/null, exist and are inside dirroot.
68         foreach ($subsystems as $subsystem => $fulldir) {
69             $this->assertFalse(strpos($subsystem, '_'), 'Core subsystems must be one work without underscores');
70             if ($fulldir === null) {
71                 if ($subsystem === 'filepicker' or $subsystem === 'help') {
72                     // Arrgghh, let's not introduce more subsystems for no real reason...
73                 } else {
74                     // Lang strings.
75                     $this->assertFileExists("$CFG->dirroot/lang/en/$subsystem.php", 'Core subsystems without fulldir are usually used for lang strings.');
76                 }
77                 continue;
78             }
79             $this->assertFileExists($fulldir);
80             // Check that base uses realpath() separators and "/" in the subdirs.
81             $this->assertStringStartsWith($CFG->dirroot.'/', $fulldir);
82             $reldir = substr($fulldir, strlen($CFG->dirroot)+1);
83             $this->assertFalse(strpos($reldir, '\\'));
84         }
86         // Make sure all core language files are also subsystems!
87         $items = new DirectoryIterator("$CFG->dirroot/lang/en");
88         foreach ($items as $item) {
89             if ($item->isDot() or $item->isDir()) {
90                 continue;
91             }
92             $file = $item->getFilename();
93             if ($file === 'moodle.php') {
94                 // Do not add new lang strings unless really necessary!!!
95                 continue;
96             }
98             if (substr($file, -4) !== '.php') {
99                 continue;
100             }
101             $file = substr($file, 0, strlen($file)-4);
102             $this->assertArrayHasKey($file, $subsystems, 'All core lang files should be subsystems, think twice before adding anything!');
103         }
104         unset($item);
105         unset($items);
107     }
109     public function test_deprecated_get_core_subsystems() {
110         global $CFG;
112         $subsystems = core_component::get_core_subsystems();
114         $this->assertSame($subsystems, get_core_subsystems(true));
116         $realsubsystems = get_core_subsystems();
117         $this->assertDebuggingCalled();
118         $this->assertSame($realsubsystems, get_core_subsystems(false));
119         $this->assertDebuggingCalled();
121         $this->assertEquals(count($subsystems), count($realsubsystems));
123         foreach ($subsystems as $subsystem => $fulldir) {
124             $this->assertArrayHasKey($subsystem, $realsubsystems);
125             if ($fulldir === null) {
126                 $this->assertNull($realsubsystems[$subsystem]);
127                 continue;
128             }
129             $this->assertSame($fulldir, $CFG->dirroot.'/'.$realsubsystems[$subsystem]);
130         }
131     }
133     public function test_get_plugin_types() {
134         global $CFG;
136         $this->assertTrue(empty($CFG->themedir), 'Non-empty $CFG->themedir is not covered by any tests yet, you need to disable it.');
138         $plugintypes = core_component::get_plugin_types();
140         foreach ($plugintypes as $plugintype => $fulldir) {
141             $this->assertStringStartsWith("$CFG->dirroot/", $fulldir);
142         }
143     }
145     public function test_deprecated_get_plugin_types() {
146         global $CFG;
148         $plugintypes = core_component::get_plugin_types();
150         $this->assertSame($plugintypes, get_plugin_types());
151         $this->assertSame($plugintypes, get_plugin_types(true));
153         $realplugintypes = get_plugin_types(false);
154         $this->assertDebuggingCalled();
156         foreach ($plugintypes as $plugintype => $fulldir) {
157             $this->assertSame($fulldir, $CFG->dirroot.'/'.$realplugintypes[$plugintype]);
158         }
159     }
161     public function test_get_plugin_list() {
162         global $CFG;
164         $plugintypes = core_component::get_plugin_types();
166         foreach ($plugintypes as $plugintype => $fulldir) {
167             $plugins = core_component::get_plugin_list($plugintype);
168             foreach ($plugins as $pluginname => $plugindir) {
169                 $this->assertStringStartsWith("$CFG->dirroot/", $plugindir);
170             }
171             if ($plugintype !== 'auth') {
172                 // Let's crosscheck it with independent implementation (auth/db is an exception).
173                 $reldir = substr($fulldir, strlen($CFG->dirroot)+1);
174                 $dirs = get_list_of_plugins($reldir);
175                 $dirs = array_values($dirs);
176                 $this->assertDebuggingCalled();
177                 $this->assertSame($dirs, array_keys($plugins));
178             }
179         }
180     }
182     public function test_deprecated_get_plugin_list() {
183         $plugintypes = core_component::get_plugin_types();
185         foreach ($plugintypes as $plugintype => $fulldir) {
186             $plugins = core_component::get_plugin_list($plugintype);
187             $this->assertSame($plugins, get_plugin_list($plugintype));
188         }
189     }
191     public function test_get_plugin_directory() {
192         $plugintypes = core_component::get_plugin_types();
194         foreach ($plugintypes as $plugintype => $fulldir) {
195             $plugins = core_component::get_plugin_list($plugintype);
196             foreach ($plugins as $pluginname => $plugindir) {
197                 $this->assertSame($plugindir, core_component::get_plugin_directory($plugintype, $pluginname));
198             }
199         }
200     }
202     public function test_deprecated_get_plugin_directory() {
203         $plugintypes = core_component::get_plugin_types();
205         foreach ($plugintypes as $plugintype => $fulldir) {
206             $plugins = core_component::get_plugin_list($plugintype);
207             foreach ($plugins as $pluginname => $plugindir) {
208                 $this->assertSame(core_component::get_plugin_directory($plugintype, $pluginname), get_plugin_directory($plugintype, $pluginname));
209             }
210         }
211     }
213     public function test_get_subsystem_directory() {
214         $subsystems = core_component::get_core_subsystems();
215         foreach ($subsystems as $subsystem => $fulldir) {
216             $this->assertSame($fulldir, core_component::get_subsystem_directory($subsystem));
217         }
218     }
220     public function test_is_valid_plugin_name() {
221         $this->assertTrue(core_component::is_valid_plugin_name('mod', 'example1'));
222         $this->assertTrue(core_component::is_valid_plugin_name('mod', 'feedback360'));
223         $this->assertFalse(core_component::is_valid_plugin_name('mod', 'feedback_360'));
224         $this->assertFalse(core_component::is_valid_plugin_name('mod', '2feedback'));
225         $this->assertFalse(core_component::is_valid_plugin_name('mod', '1example'));
226         $this->assertFalse(core_component::is_valid_plugin_name('mod', 'example.xx'));
227         $this->assertFalse(core_component::is_valid_plugin_name('mod', '.example'));
228         $this->assertFalse(core_component::is_valid_plugin_name('mod', '_example'));
229         $this->assertFalse(core_component::is_valid_plugin_name('mod', 'example_'));
230         $this->assertFalse(core_component::is_valid_plugin_name('mod', 'example_x1'));
231         $this->assertFalse(core_component::is_valid_plugin_name('mod', 'example-x1'));
232         $this->assertFalse(core_component::is_valid_plugin_name('mod', 'role'));
234         $this->assertTrue(core_component::is_valid_plugin_name('tool', 'example1'));
235         $this->assertTrue(core_component::is_valid_plugin_name('tool', 'example_x1'));
236         $this->assertTrue(core_component::is_valid_plugin_name('tool', 'example_x1_xxx'));
237         $this->assertTrue(core_component::is_valid_plugin_name('tool', 'feedback360'));
238         $this->assertTrue(core_component::is_valid_plugin_name('tool', 'feed_back360'));
239         $this->assertTrue(core_component::is_valid_plugin_name('tool', 'role'));
240         $this->assertFalse(core_component::is_valid_plugin_name('tool', '1example'));
241         $this->assertFalse(core_component::is_valid_plugin_name('tool', 'example.xx'));
242         $this->assertFalse(core_component::is_valid_plugin_name('tool', 'example-xx'));
243         $this->assertFalse(core_component::is_valid_plugin_name('tool', '.example'));
244         $this->assertFalse(core_component::is_valid_plugin_name('tool', '_example'));
245         $this->assertFalse(core_component::is_valid_plugin_name('tool', 'example_'));
246         $this->assertFalse(core_component::is_valid_plugin_name('tool', 'example__x1'));
247     }
249     public function test_normalize_componentname() {
250         // Moodle core.
251         $this->assertSame('core', core_component::normalize_componentname('core'));
252         $this->assertSame('core', core_component::normalize_componentname('moodle'));
253         $this->assertSame('core', core_component::normalize_componentname(''));
255         // Moodle core subsystems.
256         $this->assertSame('core_admin', core_component::normalize_componentname('admin'));
257         $this->assertSame('core_admin', core_component::normalize_componentname('core_admin'));
258         $this->assertSame('core_admin', core_component::normalize_componentname('moodle_admin'));
260         // Activity modules and their subplugins.
261         $this->assertSame('mod_workshop', core_component::normalize_componentname('workshop'));
262         $this->assertSame('mod_workshop', core_component::normalize_componentname('mod_workshop'));
263         $this->assertSame('workshopform_accumulative', core_component::normalize_componentname('workshopform_accumulative'));
264         $this->assertSame('mod_quiz', core_component::normalize_componentname('quiz'));
265         $this->assertSame('quiz_grading', core_component::normalize_componentname('quiz_grading'));
266         $this->assertSame('mod_data', core_component::normalize_componentname('data'));
267         $this->assertSame('datafield_checkbox', core_component::normalize_componentname('datafield_checkbox'));
269         // Other plugin types.
270         $this->assertSame('auth_mnet', core_component::normalize_componentname('auth_mnet'));
271         $this->assertSame('enrol_self', core_component::normalize_componentname('enrol_self'));
272         $this->assertSame('block_html', core_component::normalize_componentname('block_html'));
273         $this->assertSame('block_mnet_hosts', core_component::normalize_componentname('block_mnet_hosts'));
274         $this->assertSame('local_amos', core_component::normalize_componentname('local_amos'));
275         $this->assertSame('local_admin', core_component::normalize_componentname('local_admin'));
277         // Unknown words without underscore are supposed to be activity modules.
278         $this->assertSame('mod_whoonearthwouldcomewithsuchastupidnameofcomponent',
279             core_component::normalize_componentname('whoonearthwouldcomewithsuchastupidnameofcomponent'));
280         // Module names can not contain underscores, this must be a subplugin.
281         $this->assertSame('whoonearth_wouldcomewithsuchastupidnameofcomponent',
282             core_component::normalize_componentname('whoonearth_wouldcomewithsuchastupidnameofcomponent'));
283         $this->assertSame('whoonearth_would_come_withsuchastupidnameofcomponent',
284             core_component::normalize_componentname('whoonearth_would_come_withsuchastupidnameofcomponent'));
285     }
287     public function test_normalize_component() {
288         // Moodle core.
289         $this->assertSame(array('core', null), core_component::normalize_component('core'));
290         $this->assertSame(array('core', null), core_component::normalize_component('moodle'));
291         $this->assertSame(array('core', null), core_component::normalize_component(''));
293         // Moodle core subsystems.
294         $this->assertSame(array('core', 'admin'), core_component::normalize_component('admin'));
295         $this->assertSame(array('core', 'admin'), core_component::normalize_component('core_admin'));
296         $this->assertSame(array('core', 'admin'), core_component::normalize_component('moodle_admin'));
298         // Activity modules and their subplugins.
299         $this->assertSame(array('mod', 'workshop'), core_component::normalize_component('workshop'));
300         $this->assertSame(array('mod', 'workshop'), core_component::normalize_component('mod_workshop'));
301         $this->assertSame(array('workshopform', 'accumulative'), core_component::normalize_component('workshopform_accumulative'));
302         $this->assertSame(array('mod', 'quiz'), core_component::normalize_component('quiz'));
303         $this->assertSame(array('quiz', 'grading'), core_component::normalize_component('quiz_grading'));
304         $this->assertSame(array('mod', 'data'), core_component::normalize_component('data'));
305         $this->assertSame(array('datafield', 'checkbox'), core_component::normalize_component('datafield_checkbox'));
307         // Other plugin types.
308         $this->assertSame(array('auth', 'mnet'), core_component::normalize_component('auth_mnet'));
309         $this->assertSame(array('enrol', 'self'), core_component::normalize_component('enrol_self'));
310         $this->assertSame(array('block', 'html'), core_component::normalize_component('block_html'));
311         $this->assertSame(array('block', 'mnet_hosts'), core_component::normalize_component('block_mnet_hosts'));
312         $this->assertSame(array('local', 'amos'), core_component::normalize_component('local_amos'));
313         $this->assertSame(array('local', 'admin'), core_component::normalize_component('local_admin'));
315         // Unknown words without underscore are supposed to be activity modules.
316         $this->assertSame(array('mod', 'whoonearthwouldcomewithsuchastupidnameofcomponent'),
317             core_component::normalize_component('whoonearthwouldcomewithsuchastupidnameofcomponent'));
318         // Module names can not contain underscores, this must be a subplugin.
319         $this->assertSame(array('whoonearth', 'wouldcomewithsuchastupidnameofcomponent'),
320             core_component::normalize_component('whoonearth_wouldcomewithsuchastupidnameofcomponent'));
321         $this->assertSame(array('whoonearth', 'would_come_withsuchastupidnameofcomponent'),
322             core_component::normalize_component('whoonearth_would_come_withsuchastupidnameofcomponent'));
323     }
325     public function test_deprecated_normalize_component() {
326         // Moodle core.
327         $this->assertSame(array('core', null), normalize_component('core'));
328         $this->assertSame(array('core', null), normalize_component(''));
329         $this->assertSame(array('core', null), normalize_component('moodle'));
331         // Moodle core subsystems.
332         $this->assertSame(array('core', 'admin'), normalize_component('admin'));
333         $this->assertSame(array('core', 'admin'), normalize_component('core_admin'));
334         $this->assertSame(array('core', 'admin'), normalize_component('moodle_admin'));
336         // Activity modules and their subplugins.
337         $this->assertSame(array('mod', 'workshop'), normalize_component('workshop'));
338         $this->assertSame(array('mod', 'workshop'), normalize_component('mod_workshop'));
339         $this->assertSame(array('workshopform', 'accumulative'), normalize_component('workshopform_accumulative'));
340         $this->assertSame(array('mod', 'quiz'), normalize_component('quiz'));
341         $this->assertSame(array('quiz', 'grading'), normalize_component('quiz_grading'));
342         $this->assertSame(array('mod', 'data'), normalize_component('data'));
343         $this->assertSame(array('datafield', 'checkbox'), normalize_component('datafield_checkbox'));
345         // Other plugin types.
346         $this->assertSame(array('auth', 'mnet'), normalize_component('auth_mnet'));
347         $this->assertSame(array('enrol', 'self'), normalize_component('enrol_self'));
348         $this->assertSame(array('block', 'html'), normalize_component('block_html'));
349         $this->assertSame(array('block', 'mnet_hosts'), normalize_component('block_mnet_hosts'));
350         $this->assertSame(array('local', 'amos'), normalize_component('local_amos'));
351         $this->assertSame(array('local', 'admin'), normalize_component('local_admin'));
353         // Unknown words without underscore are supposed to be activity modules.
354         $this->assertSame(array('mod', 'whoonearthwouldcomewithsuchastupidnameofcomponent'),
355             normalize_component('whoonearthwouldcomewithsuchastupidnameofcomponent'));
356         // Module names can not contain underscores, this must be a subplugin.
357         $this->assertSame(array('whoonearth', 'wouldcomewithsuchastupidnameofcomponent'),
358             normalize_component('whoonearth_wouldcomewithsuchastupidnameofcomponent'));
359         $this->assertSame(array('whoonearth', 'would_come_withsuchastupidnameofcomponent'),
360             normalize_component('whoonearth_would_come_withsuchastupidnameofcomponent'));
361     }
363     public function test_get_component_directory() {
364         $plugintypes = core_component::get_plugin_types();
365         foreach ($plugintypes as $plugintype => $fulldir) {
366             $plugins = core_component::get_plugin_list($plugintype);
367             foreach ($plugins as $pluginname => $plugindir) {
368                 $this->assertSame($plugindir, core_component::get_component_directory(($plugintype.'_'.$pluginname)));
369             }
370         }
372         $subsystems = core_component::get_core_subsystems();
373         foreach ($subsystems as $subsystem => $fulldir) {
374             $this->assertSame($fulldir, core_component::get_component_directory(('core_'.$subsystem)));
375         }
376     }
378     public function test_deprecated_get_component_directory() {
379         $plugintypes = core_component::get_plugin_types();
380         foreach ($plugintypes as $plugintype => $fulldir) {
381             $plugins = core_component::get_plugin_list($plugintype);
382             foreach ($plugins as $pluginname => $plugindir) {
383                 $this->assertSame($plugindir, get_component_directory(($plugintype.'_'.$pluginname)));
384             }
385         }
387         $subsystems = core_component::get_core_subsystems();
388         foreach ($subsystems as $subsystem => $fulldir) {
389             $this->assertSame($fulldir, get_component_directory(('core_'.$subsystem)));
390         }
391     }
393     public function test_get_subtype_parent() {
394         global $CFG;
396         $this->assertNull(core_component::get_subtype_parent('mod'));
398         // Any plugin with more subtypes is ok here.
399         $this->assertFileExists("$CFG->dirroot/mod/assign/db/subplugins.json");
400         $this->assertSame('mod_assign', core_component::get_subtype_parent('assignsubmission'));
401         $this->assertSame('mod_assign', core_component::get_subtype_parent('assignfeedback'));
402         $this->assertNull(core_component::get_subtype_parent('assignxxxxx'));
403     }
405     public function test_get_subplugins() {
406         global $CFG;
408         // Any plugin with more subtypes is ok here.
409         $this->assertFileExists("$CFG->dirroot/mod/assign/db/subplugins.json");
411         $subplugins = core_component::get_subplugins('mod_assign');
412         $this->assertSame(array('assignsubmission', 'assignfeedback'), array_keys($subplugins));
414         $subs = core_component::get_plugin_list('assignsubmission');
415         $feeds = core_component::get_plugin_list('assignfeedback');
417         $this->assertSame(array_keys($subs), $subplugins['assignsubmission']);
418         $this->assertSame(array_keys($feeds), $subplugins['assignfeedback']);
420         // Any plugin without subtypes is ok here.
421         $this->assertFileExists("$CFG->dirroot/mod/choice");
422         $this->assertFileNotExists("$CFG->dirroot/mod/choice/db/subplugins.json");
424         $this->assertNull(core_component::get_subplugins('mod_choice'));
426         $this->assertNull(core_component::get_subplugins('xxxx_yyyy'));
427     }
429     public function test_get_plugin_types_with_subplugins() {
430         global $CFG;
432         $types = core_component::get_plugin_types_with_subplugins();
434         // Hardcode it here to detect if anybody hacks the code to include more subplugin types.
435         $expected = array(
436             'mod' => "$CFG->dirroot/mod",
437             'editor' => "$CFG->dirroot/lib/editor",
438             'tool' => "$CFG->dirroot/$CFG->admin/tool",
439             'local' => "$CFG->dirroot/local",
440         );
442         $this->assertSame($expected, $types);
444     }
446     public function test_get_plugin_list_with_file() {
447         $this->resetAfterTest(true);
449         // No extra reset here because core_component reset automatically.
451         $expected = array();
452         $reports = core_component::get_plugin_list('report');
453         foreach ($reports as $name => $fulldir) {
454             if (file_exists("$fulldir/lib.php")) {
455                 $expected[] = $name;
456             }
457         }
459         // Test cold.
460         $list = core_component::get_plugin_list_with_file('report', 'lib.php', false);
461         $this->assertEquals($expected, array_keys($list));
463         // Test hot.
464         $list = core_component::get_plugin_list_with_file('report', 'lib.php', false);
465         $this->assertEquals($expected, array_keys($list));
467         // Test with include.
468         $list = core_component::get_plugin_list_with_file('report', 'lib.php', true);
469         $this->assertEquals($expected, array_keys($list));
471         // Test missing.
472         $list = core_component::get_plugin_list_with_file('report', 'idontexist.php', true);
473         $this->assertEquals(array(), array_keys($list));
474     }
476     public function test_get_component_classes_in_namespace() {
478         // Unexisting.
479         $this->assertCount(0, core_component::get_component_classes_in_namespace('core_unexistingcomponent', 'something'));
480         $this->assertCount(0, core_component::get_component_classes_in_namespace('auth_cas', 'something'));
482         // Matches the last namespace level name not partials.
483         $this->assertCount(0, core_component::get_component_classes_in_namespace('auth_cas', 'tas'));
484         $this->assertCount(0, core_component::get_component_classes_in_namespace('core_user', 'course'));
485         $this->assertCount(0, core_component::get_component_classes_in_namespace('mod_forum', 'output\\emaildigest'));
486         $this->assertCount(0, core_component::get_component_classes_in_namespace('mod_forum', '\\output\\emaildigest'));
487         $this->assertCount(2, core_component::get_component_classes_in_namespace('mod_forum', 'output\\email'));
488         $this->assertCount(2, core_component::get_component_classes_in_namespace('mod_forum', '\\output\\email'));
489         $this->assertCount(2, core_component::get_component_classes_in_namespace('mod_forum', 'output\\email\\'));
490         $this->assertCount(2, core_component::get_component_classes_in_namespace('mod_forum', '\\output\\email\\'));
492         // Prefix with backslash if it doesn\'t come prefixed.
493         $this->assertCount(1, core_component::get_component_classes_in_namespace('auth_cas', 'task'));
494         $this->assertCount(1, core_component::get_component_classes_in_namespace('auth_cas', '\\task'));
496         // Core as a component works, the function can normalise the component name.
497         $this->assertCount(7, core_component::get_component_classes_in_namespace('core', 'update'));
498         $this->assertCount(7, core_component::get_component_classes_in_namespace('', 'update'));
499         $this->assertCount(7, core_component::get_component_classes_in_namespace('moodle', 'update'));
501         // Multiple levels.
502         $this->assertCount(5, core_component::get_component_classes_in_namespace('core_user', '\\output\\myprofile\\'));
503         $this->assertCount(5, core_component::get_component_classes_in_namespace('core_user', 'output\\myprofile\\'));
504         $this->assertCount(5, core_component::get_component_classes_in_namespace('core_user', '\\output\\myprofile'));
505         $this->assertCount(5, core_component::get_component_classes_in_namespace('core_user', 'output\\myprofile'));
507         // Without namespace it returns classes/ classes.
508         $this->assertCount(5, core_component::get_component_classes_in_namespace('tool_mobile', ''));
509         $this->assertCount(2, core_component::get_component_classes_in_namespace('tool_filetypes'));
511         // When no component is specified, classes are returned for the namespace in all components.
512         // (We don't assert exact amounts here as the count of `output` classes will change depending on plugins installed).
513         $this->assertGreaterThan(
514             count(\core_component::get_component_classes_in_namespace('core', 'output')),
515             count(\core_component::get_component_classes_in_namespace(null, 'output')));
517         // Without either a component or namespace it returns an empty array.
518         $this->assertEmpty(\core_component::get_component_classes_in_namespace());
519         $this->assertEmpty(\core_component::get_component_classes_in_namespace(null));
520         $this->assertEmpty(\core_component::get_component_classes_in_namespace(null, ''));
521     }
523     /**
524      * Data provider for classloader test
525      */
526     public function classloader_provider() {
527         global $CFG;
529         // As part of these tests, we Check that there are no unexpected problems with overlapping PSR namespaces.
530         // This is not in the spec, but may come up in some libraries using both namespaces and PEAR-style class names.
531         // If problems arise we can remove this test, but will need to add a warning.
532         // Normalise to forward slash for testing purposes.
533         $directory = str_replace('\\', '/', $CFG->dirroot) . "/lib/tests/fixtures/component/";
535         $psr0 = [
536           'psr0'      => 'lib/tests/fixtures/component/psr0',
537           'overlap'   => 'lib/tests/fixtures/component/overlap'
538         ];
539         $psr4 = [
540           'psr4'      => 'lib/tests/fixtures/component/psr4',
541           'overlap'   => 'lib/tests/fixtures/component/overlap'
542         ];
543         return [
544           'PSR-0 Classloading - Root' => [
545               'psr0' => $psr0,
546               'psr4' => $psr4,
547               'classname' => 'psr0_main',
548               'includedfiles' => "{$directory}psr0/main.php",
549           ],
550           'PSR-0 Classloading - Sub namespace - underscores' => [
551               'psr0' => $psr0,
552               'psr4' => $psr4,
553               'classname' => 'psr0_subnamespace_example',
554               'includedfiles' => "{$directory}psr0/subnamespace/example.php",
555           ],
556           'PSR-0 Classloading - Sub namespace - slashes' => [
557               'psr0' => $psr0,
558               'psr4' => $psr4,
559               'classname' => 'psr0\\subnamespace\\slashes',
560               'includedfiles' => "{$directory}psr0/subnamespace/slashes.php",
561           ],
562           'PSR-4 Classloading - Root' => [
563               'psr0' => $psr0,
564               'psr4' => $psr4,
565               'classname' => 'psr4\\main',
566               'includedfiles' => "{$directory}psr4/main.php",
567           ],
568           'PSR-4 Classloading - Sub namespace' => [
569               'psr0' => $psr0,
570               'psr4' => $psr4,
571               'classname' => 'psr4\\subnamespace\\example',
572               'includedfiles' => "{$directory}psr4/subnamespace/example.php",
573           ],
574           'PSR-4 Classloading - Ensure underscores are not converted to paths' => [
575               'psr0' => $psr0,
576               'psr4' => $psr4,
577               'classname' => 'psr4\\subnamespace\\underscore_example',
578               'includedfiles' => "{$directory}psr4/subnamespace/underscore_example.php",
579           ],
580           'Overlap - Ensure no unexpected problems with PSR-4 when overlapping namespaces.' => [
581               'psr0' => $psr0,
582               'psr4' => $psr4,
583               'classname' => 'overlap\\subnamespace\\example',
584               'includedfiles' => "{$directory}overlap/subnamespace/example.php",
585           ],
586           'Overlap - Ensure no unexpected problems with PSR-0 overlapping namespaces.' => [
587               'psr0' => $psr0,
588               'psr4' => $psr4,
589               'classname' => 'overlap_subnamespace_example2',
590               'includedfiles' => "{$directory}overlap/subnamespace/example2.php",
591           ],
592         ];
593     }
595     /**
596      * Test the classloader.
597      *
598      * @dataProvider classloader_provider
599      * @param array $psr0 The PSR-0 namespaces to be used in the test.
600      * @param array $psr4 The PSR-4 namespaces to be used in the test.
601      * @param string $classname The name of the class to attempt to load.
602      * @param string $includedfiles The file expected to be loaded.
603      */
604     public function test_classloader($psr0, $psr4, $classname, $includedfiles) {
605         $psr0namespaces = new ReflectionProperty('core_component', 'psr0namespaces');
606         $psr0namespaces->setAccessible(true);
607         $psr0namespaces->setValue(null, $psr0);
609         $psr4namespaces = new ReflectionProperty('core_component', 'psr4namespaces');
610         $psr4namespaces->setAccessible(true);
611         $psr4namespaces->setValue(null, $psr4);
613         core_component::classloader($classname);
614         if (DIRECTORY_SEPARATOR != '/') {
615             // Denormalise the expected path so that we can quickly compare with get_included_files.
616             $includedfiles = str_replace('/', DIRECTORY_SEPARATOR, $includedfiles);
617         }
618         $this->assertContains($includedfiles, get_included_files());
619         $this->assertTrue(class_exists($classname, false));
620     }
622     /**
623      * Data provider for psr_classloader test
624      */
625     public function psr_classloader_provider() {
626         global $CFG;
628         // As part of these tests, we Check that there are no unexpected problems with overlapping PSR namespaces.
629         // This is not in the spec, but may come up in some libraries using both namespaces and PEAR-style class names.
630         // If problems arise we can remove this test, but will need to add a warning.
631         // Normalise to forward slash for testing purposes.
632         $directory = str_replace('\\', '/', $CFG->dirroot) . "/lib/tests/fixtures/component/";
634         $psr0 = [
635           'psr0'      => 'lib/tests/fixtures/component/psr0',
636           'overlap'   => 'lib/tests/fixtures/component/overlap'
637         ];
638         $psr4 = [
639           'psr4'      => 'lib/tests/fixtures/component/psr4',
640           'overlap'   => 'lib/tests/fixtures/component/overlap'
641         ];
642         return [
643           'PSR-0 Classloading - Root' => [
644               'psr0' => $psr0,
645               'psr4' => $psr4,
646               'classname' => 'psr0_main',
647               'file' => "{$directory}psr0/main.php",
648           ],
649           'PSR-0 Classloading - Sub namespace - underscores' => [
650               'psr0' => $psr0,
651               'psr4' => $psr4,
652               'classname' => 'psr0_subnamespace_example',
653               'file' => "{$directory}psr0/subnamespace/example.php",
654           ],
655           'PSR-0 Classloading - Sub namespace - slashes' => [
656               'psr0' => $psr0,
657               'psr4' => $psr4,
658               'classname' => 'psr0\\subnamespace\\slashes',
659               'file' => "{$directory}psr0/subnamespace/slashes.php",
660           ],
661           'PSR-0 Classloading - non-existant file' => [
662               'psr0' => $psr0,
663               'psr4' => $psr4,
664               'classname' => 'psr0_subnamespace_nonexistant_file',
665               'file' => false,
666           ],
667           'PSR-4 Classloading - Root' => [
668               'psr0' => $psr0,
669               'psr4' => $psr4,
670               'classname' => 'psr4\\main',
671               'file' => "{$directory}psr4/main.php",
672           ],
673           'PSR-4 Classloading - Sub namespace' => [
674               'psr0' => $psr0,
675               'psr4' => $psr4,
676               'classname' => 'psr4\\subnamespace\\example',
677               'file' => "{$directory}psr4/subnamespace/example.php",
678           ],
679           'PSR-4 Classloading - Ensure underscores are not converted to paths' => [
680               'psr0' => $psr0,
681               'psr4' => $psr4,
682               'classname' => 'psr4\\subnamespace\\underscore_example',
683               'file' => "{$directory}psr4/subnamespace/underscore_example.php",
684           ],
685           'PSR-4 Classloading - non-existant file' => [
686               'psr0' => $psr0,
687               'psr4' => $psr4,
688               'classname' => 'psr4\\subnamespace\\nonexistant',
689               'file' => false,
690           ],
691           'Overlap - Ensure no unexpected problems with PSR-4 when overlapping namespaces.' => [
692               'psr0' => $psr0,
693               'psr4' => $psr4,
694               'classname' => 'overlap\\subnamespace\\example',
695               'file' => "{$directory}overlap/subnamespace/example.php",
696           ],
697           'Overlap - Ensure no unexpected problems with PSR-0 overlapping namespaces.' => [
698               'psr0' => $psr0,
699               'psr4' => $psr4,
700               'classname' => 'overlap_subnamespace_example2',
701               'file' => "{$directory}overlap/subnamespace/example2.php",
702           ],
703         ];
704     }
706     /**
707      * Test the PSR classloader.
708      *
709      * @dataProvider psr_classloader_provider
710      * @param array $psr0 The PSR-0 namespaces to be used in the test.
711      * @param array $psr4 The PSR-4 namespaces to be used in the test.
712      * @param string $classname The name of the class to attempt to load.
713      * @param string|bool $file The expected file corresponding to the class or false for nonexistant.
714      */
715     public function test_psr_classloader($psr0, $psr4, $classname, $file) {
716         $psr0namespaces = new ReflectionProperty('core_component', 'psr0namespaces');
717         $psr0namespaces->setAccessible(true);
718         $psr0namespaces->setValue(null, $psr0);
720         $psr4namespaces = new ReflectionProperty('core_component', 'psr4namespaces');
721         $psr4namespaces->setAccessible(true);
722         $oldpsr4namespaces = $psr4namespaces->getValue(null);
723         $psr4namespaces->setValue(null, $psr4);
725         $component = new ReflectionClass('core_component');
726         $psrclassloader = $component->getMethod('psr_classloader');
727         $psrclassloader->setAccessible(true);
729         $returnvalue = $psrclassloader->invokeArgs(null, array($classname));
730         // Normalise to forward slashes for testing comparison.
731         if ($returnvalue) {
732             $returnvalue = str_replace('\\', '/', $returnvalue);
733         }
734         $this->assertEquals($file, $returnvalue);
735     }
737     /**
738      * Data provider for get_class_file test
739      */
740     public function get_class_file_provider() {
741         global $CFG;
743         return [
744           'Getting a file with underscores' => [
745               'classname' => 'Test_With_Underscores',
746               'prefix' => "Test",
747               'path' => 'test/src',
748               'separators' => ['_'],
749               'result' => $CFG->dirroot . "/test/src/With/Underscores.php",
750           ],
751           'Getting a file with slashes' => [
752               'classname' => 'Test\\With\\Slashes',
753               'prefix' => "Test",
754               'path' => 'test/src',
755               'separators' => ['\\'],
756               'result' => $CFG->dirroot . "/test/src/With/Slashes.php",
757           ],
758           'Getting a file with multiple namespaces' => [
759               'classname' => 'Test\\With\\Multiple\\Namespaces',
760               'prefix' => "Test\\With",
761               'path' => 'test/src',
762               'separators' => ['\\'],
763               'result' => $CFG->dirroot . "/test/src/Multiple/Namespaces.php",
764           ],
765           'Getting a file with multiple namespaces' => [
766               'classname' => 'Nonexistant\\Namespace\\Test',
767               'prefix' => "Test",
768               'path' => 'test/src',
769               'separators' => ['\\'],
770               'result' => false,
771           ],
772         ];
773     }
775     /**
776      * Test the PSR classloader.
777      *
778      * @dataProvider get_class_file_provider
779      * @param string $classname the name of the class.
780      * @param string $prefix The namespace prefix used to identify the base directory of the source files.
781      * @param string $path The relative path to the base directory of the source files.
782      * @param string[] $separators The characters that should be used for separating.
783      * @param string|bool $result The expected result to be returned from get_class_file.
784      */
785     public function test_get_class_file($classname, $prefix, $path, $separators, $result) {
786         $component = new ReflectionClass('core_component');
787         $psrclassloader = $component->getMethod('get_class_file');
788         $psrclassloader->setAccessible(true);
790         $file = $psrclassloader->invokeArgs(null, array($classname, $prefix, $path, $separators));
791         $this->assertEquals($result, $file);
792     }
794     /**
795      * Confirm the get_component_list method contains an entry for every component.
796      */
797     public function test_get_component_list_contains_all_components() {
798         global $CFG;
799         $componentslist = \core_component::get_component_list();
801         // We should have an entry for each plugin type, and one additional for 'core'.
802         $plugintypes = \core_component::get_plugin_types();
803         $numelementsexpected = count($plugintypes) + 1;
804         $this->assertEquals($numelementsexpected, count($componentslist));
806         // And an entry for each of the plugin types.
807         foreach (array_keys($plugintypes) as $plugintype) {
808             $this->assertArrayHasKey($plugintype, $componentslist);
809         }
811         // And finally, one for 'core'.
812         $this->assertArrayHasKey('core', $componentslist);
814         // Check a few of the known plugin types to confirm their presence at their respective type index.
815         $this->assertEquals($componentslist['core']['core_comment'], $CFG->dirroot . '/comment');
816         $this->assertEquals($componentslist['mod']['mod_forum'], $CFG->dirroot . '/mod/forum');
817         $this->assertEquals($componentslist['tool']['tool_usertours'], $CFG->dirroot . '/' . $CFG->admin . '/tool/usertours');
818     }
820     /**
821      * Test the get_component_names() method.
822      */
823     public function test_get_component_names() {
824         global $CFG;
825         $componentnames = \core_component::get_component_names();
827         // We should have an entry for each plugin type.
828         $plugintypes = \core_component::get_plugin_types();
829         $numplugintypes = 0;
830         foreach ($plugintypes as $type => $typedir) {
831             foreach (\core_component::get_plugin_list($type) as $plugin) {
832                 $numplugintypes++;
833             }
834         }
835         // And an entry for each core subsystem.
836         $numcomponents = $numplugintypes + count(\core_component::get_core_subsystems());
838         $this->assertEquals($numcomponents, count($componentnames));
840         // Check a few of the known plugin types to confirm their presence at their respective type index.
841         $this->assertContains('core_comment', $componentnames);
842         $this->assertContains('mod_forum', $componentnames);
843         $this->assertContains('tool_usertours', $componentnames);
844         $this->assertContains('core_favourites', $componentnames);
845     }