2 // This file is part of Moodle - http://moodle.org/
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.
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.
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/>.
18 * core_component related tests.
22 * @copyright 2013 Petr Skoda {@link http://skodak.org}
23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
26 defined('MOODLE_INTERNAL') || die();
30 * Class core_component_testcase.
32 class core_component_testcase extends advanced_testcase {
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!!!
39 const SUBSYSTEMCOUNT = 68;
41 public function setUp() {
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);
50 public function tearDown() {
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);
60 public function test_get_core_subsystems() {
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...
75 $this->assertFileExists("$CFG->dirroot/lang/en/$subsystem.php", 'Core subsystems without fulldir are usually used for lang strings.');
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, '\\'));
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()) {
92 $file = $item->getFilename();
93 if ($file === 'moodle.php') {
94 // Do not add new lang strings unless really necessary!!!
98 if (substr($file, -4) !== '.php') {
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!');
109 public function test_deprecated_get_core_subsystems() {
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]);
129 $this->assertSame($fulldir, $CFG->dirroot.'/'.$realsubsystems[$subsystem]);
133 public function test_get_plugin_types() {
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);
145 public function test_deprecated_get_plugin_types() {
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]);
161 public function test_get_plugin_list() {
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);
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));
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));
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));
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));
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));
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'));
249 public function test_normalize_componentname() {
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'));
287 public function test_normalize_component() {
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'));
325 public function test_deprecated_normalize_component() {
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'));
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)));
372 $subsystems = core_component::get_core_subsystems();
373 foreach ($subsystems as $subsystem => $fulldir) {
374 $this->assertSame($fulldir, core_component::get_component_directory(('core_'.$subsystem)));
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)));
387 $subsystems = core_component::get_core_subsystems();
388 foreach ($subsystems as $subsystem => $fulldir) {
389 $this->assertSame($fulldir, get_component_directory(('core_'.$subsystem)));
393 public function test_get_subtype_parent() {
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.php");
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'));
405 public function test_get_subplugins() {
408 // Any plugin with more subtypes is ok here.
409 $this->assertFileExists("$CFG->dirroot/mod/assign/db/subplugins.php");
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.php");
424 $this->assertNull(core_component::get_subplugins('mod_choice'));
426 $this->assertNull(core_component::get_subplugins('xxxx_yyyy'));
429 public function test_get_plugin_types_with_subplugins() {
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.
436 'mod' => "$CFG->dirroot/mod",
437 'editor' => "$CFG->dirroot/lib/editor",
438 'tool' => "$CFG->dirroot/$CFG->admin/tool",
439 'local' => "$CFG->dirroot/local",
442 $this->assertSame($expected, $types);
446 public function test_get_plugin_list_with_file() {
447 $this->resetAfterTest(true);
449 // No extra reset here because core_component reset automatically.
452 $reports = core_component::get_plugin_list('report');
453 foreach ($reports as $name => $fulldir) {
454 if (file_exists("$fulldir/lib.php")) {
460 $list = core_component::get_plugin_list_with_file('report', 'lib.php', false);
461 $this->assertEquals($expected, array_keys($list));
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));
472 $list = core_component::get_plugin_list_with_file('report', 'idontexist.php', true);
473 $this->assertEquals(array(), array_keys($list));
476 public function test_get_component_classes_int_namespace() {
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 funcion 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'));
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(2, core_component::get_component_classes_in_namespace('tool_mobile', ''));
509 $this->assertCount(1, core_component::get_component_classes_in_namespace('tool_filetypes'));
513 * Data provider for classloader test
515 public function classloader_provider() {
518 // As part of these tests, we Check that there are no unexpected problems with overlapping PSR namespaces.
519 // This is not in the spec, but may come up in some libraries using both namespaces and PEAR-style class names.
520 // If problems arise we can remove this test, but will need to add a warning.
521 // Normalise to forward slash for testing purposes.
522 $directory = str_replace('\\', '/', $CFG->dirroot) . "/lib/tests/fixtures/component/";
525 'psr0' => 'lib/tests/fixtures/component/psr0',
526 'overlap' => 'lib/tests/fixtures/component/overlap'
529 'psr4' => 'lib/tests/fixtures/component/psr4',
530 'overlap' => 'lib/tests/fixtures/component/overlap'
533 'PSR-0 Classloading - Root' => [
536 'classname' => 'psr0_main',
537 'includedfiles' => "{$directory}psr0/main.php",
539 'PSR-0 Classloading - Sub namespace - underscores' => [
542 'classname' => 'psr0_subnamespace_example',
543 'includedfiles' => "{$directory}psr0/subnamespace/example.php",
545 'PSR-0 Classloading - Sub namespace - slashes' => [
548 'classname' => 'psr0\\subnamespace\\slashes',
549 'includedfiles' => "{$directory}psr0/subnamespace/slashes.php",
551 'PSR-4 Classloading - Root' => [
554 'classname' => 'psr4\\main',
555 'includedfiles' => "{$directory}psr4/main.php",
557 'PSR-4 Classloading - Sub namespace' => [
560 'classname' => 'psr4\\subnamespace\\example',
561 'includedfiles' => "{$directory}psr4/subnamespace/example.php",
563 'PSR-4 Classloading - Ensure underscores are not converted to paths' => [
566 'classname' => 'psr4\\subnamespace\\underscore_example',
567 'includedfiles' => "{$directory}psr4/subnamespace/underscore_example.php",
569 'Overlap - Ensure no unexpected problems with PSR-4 when overlapping namespaces.' => [
572 'classname' => 'overlap\\subnamespace\\example',
573 'includedfiles' => "{$directory}overlap/subnamespace/example.php",
575 'Overlap - Ensure no unexpected problems with PSR-0 overlapping namespaces.' => [
578 'classname' => 'overlap_subnamespace_example2',
579 'includedfiles' => "{$directory}overlap/subnamespace/example2.php",
585 * Test the classloader.
587 * @dataProvider classloader_provider
588 * @param array $psr0 The PSR-0 namespaces to be used in the test.
589 * @param array $psr4 The PSR-4 namespaces to be used in the test.
590 * @param string $classname The name of the class to attempt to load.
591 * @param string $includedfiles The file expected to be loaded.
593 public function test_classloader($psr0, $psr4, $classname, $includedfiles) {
594 $psr0namespaces = new ReflectionProperty('core_component', 'psr0namespaces');
595 $psr0namespaces->setAccessible(true);
596 $psr0namespaces->setValue(null, $psr0);
598 $psr4namespaces = new ReflectionProperty('core_component', 'psr4namespaces');
599 $psr4namespaces->setAccessible(true);
600 $psr4namespaces->setValue(null, $psr4);
602 core_component::classloader($classname);
603 if (DIRECTORY_SEPARATOR != '/') {
604 // Denormalise the expected path so that we can quickly compare with get_included_files.
605 $includedfiles = str_replace('/', DIRECTORY_SEPARATOR, $includedfiles);
607 $this->assertContains($includedfiles, get_included_files());
608 $this->assertTrue(class_exists($classname, false));
612 * Data provider for psr_classloader test
614 public function psr_classloader_provider() {
617 // As part of these tests, we Check that there are no unexpected problems with overlapping PSR namespaces.
618 // This is not in the spec, but may come up in some libraries using both namespaces and PEAR-style class names.
619 // If problems arise we can remove this test, but will need to add a warning.
620 // Normalise to forward slash for testing purposes.
621 $directory = str_replace('\\', '/', $CFG->dirroot) . "/lib/tests/fixtures/component/";
624 'psr0' => 'lib/tests/fixtures/component/psr0',
625 'overlap' => 'lib/tests/fixtures/component/overlap'
628 'psr4' => 'lib/tests/fixtures/component/psr4',
629 'overlap' => 'lib/tests/fixtures/component/overlap'
632 'PSR-0 Classloading - Root' => [
635 'classname' => 'psr0_main',
636 'file' => "{$directory}psr0/main.php",
638 'PSR-0 Classloading - Sub namespace - underscores' => [
641 'classname' => 'psr0_subnamespace_example',
642 'file' => "{$directory}psr0/subnamespace/example.php",
644 'PSR-0 Classloading - Sub namespace - slashes' => [
647 'classname' => 'psr0\\subnamespace\\slashes',
648 'file' => "{$directory}psr0/subnamespace/slashes.php",
650 'PSR-0 Classloading - non-existant file' => [
653 'classname' => 'psr0_subnamespace_nonexistant_file',
656 'PSR-4 Classloading - Root' => [
659 'classname' => 'psr4\\main',
660 'file' => "{$directory}psr4/main.php",
662 'PSR-4 Classloading - Sub namespace' => [
665 'classname' => 'psr4\\subnamespace\\example',
666 'file' => "{$directory}psr4/subnamespace/example.php",
668 'PSR-4 Classloading - Ensure underscores are not converted to paths' => [
671 'classname' => 'psr4\\subnamespace\\underscore_example',
672 'file' => "{$directory}psr4/subnamespace/underscore_example.php",
674 'PSR-4 Classloading - non-existant file' => [
677 'classname' => 'psr4\\subnamespace\\nonexistant',
680 'Overlap - Ensure no unexpected problems with PSR-4 when overlapping namespaces.' => [
683 'classname' => 'overlap\\subnamespace\\example',
684 'file' => "{$directory}overlap/subnamespace/example.php",
686 'Overlap - Ensure no unexpected problems with PSR-0 overlapping namespaces.' => [
689 'classname' => 'overlap_subnamespace_example2',
690 'file' => "{$directory}overlap/subnamespace/example2.php",
696 * Test the PSR classloader.
698 * @dataProvider psr_classloader_provider
699 * @param array $psr0 The PSR-0 namespaces to be used in the test.
700 * @param array $psr4 The PSR-4 namespaces to be used in the test.
701 * @param string $classname The name of the class to attempt to load.
702 * @param string|bool $file The expected file corresponding to the class or false for nonexistant.
704 public function test_psr_classloader($psr0, $psr4, $classname, $file) {
705 $psr0namespaces = new ReflectionProperty('core_component', 'psr0namespaces');
706 $psr0namespaces->setAccessible(true);
707 $psr0namespaces->setValue(null, $psr0);
709 $psr4namespaces = new ReflectionProperty('core_component', 'psr4namespaces');
710 $psr4namespaces->setAccessible(true);
711 $oldpsr4namespaces = $psr4namespaces->getValue(null);
712 $psr4namespaces->setValue(null, $psr4);
714 $component = new ReflectionClass('core_component');
715 $psrclassloader = $component->getMethod('psr_classloader');
716 $psrclassloader->setAccessible(true);
718 $returnvalue = $psrclassloader->invokeArgs(null, array($classname));
719 // Normalise to forward slashes for testing comparison.
721 $returnvalue = str_replace('\\', '/', $returnvalue);
723 $this->assertEquals($file, $returnvalue);
727 * Data provider for get_class_file test
729 public function get_class_file_provider() {
733 'Getting a file with underscores' => [
734 'classname' => 'Test_With_Underscores',
736 'path' => 'test/src',
737 'separators' => ['_'],
738 'result' => $CFG->dirroot . "/test/src/With/Underscores.php",
740 'Getting a file with slashes' => [
741 'classname' => 'Test\\With\\Slashes',
743 'path' => 'test/src',
744 'separators' => ['\\'],
745 'result' => $CFG->dirroot . "/test/src/With/Slashes.php",
747 'Getting a file with multiple namespaces' => [
748 'classname' => 'Test\\With\\Multiple\\Namespaces',
749 'prefix' => "Test\\With",
750 'path' => 'test/src',
751 'separators' => ['\\'],
752 'result' => $CFG->dirroot . "/test/src/Multiple/Namespaces.php",
754 'Getting a file with multiple namespaces' => [
755 'classname' => 'Nonexistant\\Namespace\\Test',
757 'path' => 'test/src',
758 'separators' => ['\\'],
765 * Test the PSR classloader.
767 * @dataProvider get_class_file_provider
768 * @param string $classname the name of the class.
769 * @param string $prefix The namespace prefix used to identify the base directory of the source files.
770 * @param string $path The relative path to the base directory of the source files.
771 * @param string[] $separators The characters that should be used for separating.
772 * @param string|bool $result The expected result to be returned from get_class_file.
774 public function test_get_class_file($classname, $prefix, $path, $separators, $result) {
775 $component = new ReflectionClass('core_component');
776 $psrclassloader = $component->getMethod('get_class_file');
777 $psrclassloader->setAccessible(true);
779 $file = $psrclassloader->invokeArgs(null, array($classname, $prefix, $path, $separators));
780 $this->assertEquals($result, $file);