Proyectos de Subversion Moodle

Rev

Ir a la última revisión | | Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 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/>.
16
 
17
// phpcs:disable moodle.PHPUnit.TestCaseNames.MissingNS
18
 
19
/**
20
 * core_component related tests.
21
 *
22
 * @package    core
23
 * @category   test
24
 * @copyright  2013 Petr Skoda {@link http://skodak.org}
25
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
26
 *
27
 * @covers \core_component
28
 */
29
final class component_test extends advanced_testcase {
30
    /**
31
     * To be changed if number of subsystems increases/decreases,
32
     * this is defined here to annoy devs that try to add more without any thinking,
33
     * always verify that it does not collide with any existing add-on modules and subplugins!!!
34
     */
35
    const SUBSYSTEMCOUNT = 77;
36
 
37
    public function test_get_core_subsystems(): void {
38
        global $CFG;
39
 
40
        $subsystems = core_component::get_core_subsystems();
41
 
42
        $this->assertCount(
43
            self::SUBSYSTEMCOUNT,
44
            $subsystems,
45
            'Oh, somebody added or removed a core subsystem, think twice before doing that!',
46
        );
47
 
48
        // Make sure all paths are full/null, exist and are inside dirroot.
49
        foreach ($subsystems as $subsystem => $fulldir) {
50
            $this->assertFalse(strpos($subsystem, '_'), 'Core subsystems must be one work without underscores');
51
            if ($fulldir === null) {
52
                if ($subsystem === 'filepicker' || $subsystem === 'help') { // phpcs:ignore
53
                    // Arrgghh, let's not introduce more subsystems for no real reason...
54
                } else {
55
                    // Lang strings.
56
                    $this->assertFileExists(
57
                        "$CFG->dirroot/lang/en/$subsystem.php",
58
                        'Core subsystems without fulldir are usually used for lang strings.',
59
                    );
60
                }
61
                continue;
62
            }
63
            $this->assertFileExists($fulldir);
64
            // Check that base uses realpath() separators and "/" in the subdirs.
65
            $this->assertStringStartsWith($CFG->dirroot . '/', $fulldir);
66
            $reldir = substr($fulldir, strlen($CFG->dirroot) + 1);
67
            $this->assertFalse(strpos($reldir, '\\'));
68
        }
69
 
70
        // Make sure all core language files are also subsystems!
71
        $items = new DirectoryIterator("$CFG->dirroot/lang/en");
72
        foreach ($items as $item) {
73
            if ($item->isDot() || $item->isDir()) {
74
                continue;
75
            }
76
            $file = $item->getFilename();
77
            if ($file === 'moodle.php') {
78
                // Do not add new lang strings unless really necessary!!!
79
                continue;
80
            }
81
 
82
            if (substr($file, -4) !== '.php') {
83
                continue;
84
            }
85
            $file = substr($file, 0, strlen($file) - 4);
86
            $this->assertArrayHasKey(
87
                $file,
88
                $subsystems,
89
                'All core lang files should be subsystems, think twice before adding anything!',
90
            );
91
        }
92
        unset($item);
93
        unset($items);
94
    }
95
 
96
    public function test_deprecated_get_core_subsystems(): void {
97
        global $CFG;
98
 
99
        $subsystems = core_component::get_core_subsystems();
100
 
101
        $this->assertSame($subsystems, get_core_subsystems(true));
102
 
103
        $realsubsystems = get_core_subsystems();
104
        $this->assertDebuggingCalled();
105
        $this->assertSame($realsubsystems, get_core_subsystems(false));
106
        $this->assertDebuggingCalled();
107
 
108
        $this->assertEquals(count($subsystems), count($realsubsystems));
109
 
110
        foreach ($subsystems as $subsystem => $fulldir) {
111
            $this->assertArrayHasKey($subsystem, $realsubsystems);
112
            if ($fulldir === null) {
113
                $this->assertNull($realsubsystems[$subsystem]);
114
                continue;
115
            }
116
            $this->assertSame($fulldir, $CFG->dirroot . '/' . $realsubsystems[$subsystem]);
117
        }
118
    }
119
 
120
    public function test_get_plugin_types(): void {
121
        global $CFG;
122
 
123
        $this->assertTrue(
124
            empty($CFG->themedir),
125
            'Non-empty $CFG->themedir is not covered by any tests yet, you need to disable it.',
126
        );
127
 
128
        $plugintypes = core_component::get_plugin_types();
129
 
130
        foreach ($plugintypes as $plugintype => $fulldir) {
131
            $this->assertStringStartsWith("$CFG->dirroot/", $fulldir);
132
        }
133
    }
134
 
135
    public function test_deprecated_get_plugin_types(): void {
136
        global $CFG;
137
 
138
        $plugintypes = core_component::get_plugin_types();
139
 
140
        $this->assertSame($plugintypes, get_plugin_types());
141
        $this->assertSame($plugintypes, get_plugin_types(true));
142
 
143
        $realplugintypes = get_plugin_types(false);
144
        $this->assertDebuggingCalled();
145
 
146
        foreach ($plugintypes as $plugintype => $fulldir) {
147
            $this->assertSame($fulldir, $CFG->dirroot . '/' . $realplugintypes[$plugintype]);
148
        }
149
    }
150
 
151
    public function test_get_plugin_list(): void {
152
        global $CFG;
153
 
154
        $plugintypes = core_component::get_plugin_types();
155
 
156
        foreach ($plugintypes as $plugintype => $fulldir) {
157
            $plugins = core_component::get_plugin_list($plugintype);
158
            foreach ($plugins as $pluginname => $plugindir) {
159
                $this->assertStringStartsWith("$CFG->dirroot/", $plugindir);
160
            }
161
            if ($plugintype !== 'auth') {
162
                // Let's crosscheck it with independent implementation (auth/db is an exception).
163
                $reldir = substr($fulldir, strlen($CFG->dirroot) + 1);
164
                $dirs = get_list_of_plugins($reldir);
165
                $dirs = array_values($dirs);
166
                $this->assertDebuggingCalled();
167
                $this->assertSame($dirs, array_keys($plugins));
168
            }
169
        }
170
    }
171
 
172
    public function test_deprecated_get_plugin_list(): void {
173
        $plugintypes = core_component::get_plugin_types();
174
 
175
        foreach ($plugintypes as $plugintype => $fulldir) {
176
            $plugins = core_component::get_plugin_list($plugintype);
177
            $this->assertSame($plugins, get_plugin_list($plugintype));
178
        }
179
    }
180
 
181
    public function test_get_plugin_directory(): void {
182
        $plugintypes = core_component::get_plugin_types();
183
 
184
        foreach ($plugintypes as $plugintype => $fulldir) {
185
            $plugins = core_component::get_plugin_list($plugintype);
186
            foreach ($plugins as $pluginname => $plugindir) {
187
                $this->assertSame($plugindir, core_component::get_plugin_directory($plugintype, $pluginname));
188
            }
189
        }
190
    }
191
 
192
    public function test_deprecated_get_plugin_directory(): void {
193
        $plugintypes = core_component::get_plugin_types();
194
 
195
        foreach ($plugintypes as $plugintype => $fulldir) {
196
            $plugins = core_component::get_plugin_list($plugintype);
197
            foreach ($plugins as $pluginname => $plugindir) {
198
                $this->assertSame(
199
                    core_component::get_plugin_directory($plugintype, $pluginname),
200
                    get_plugin_directory($plugintype, $pluginname),
201
                );
202
            }
203
        }
204
    }
205
 
206
    public function test_get_subsystem_directory(): void {
207
        $subsystems = core_component::get_core_subsystems();
208
        foreach ($subsystems as $subsystem => $fulldir) {
209
            $this->assertSame($fulldir, core_component::get_subsystem_directory($subsystem));
210
        }
211
    }
212
 
213
    /**
214
     * Test that the get_plugin_list_with_file() function returns the correct list of plugins.
215
     *
216
     * @covers \core_component::is_valid_plugin_name
217
     * @dataProvider is_valid_plugin_name_provider
218
     * @param array $arguments
219
     * @param bool $expected
220
     */
221
    public function test_is_valid_plugin_name(array $arguments, bool $expected): void {
222
        $this->assertEquals($expected, core_component::is_valid_plugin_name(...$arguments));
223
    }
224
 
225
    /**
226
     * Data provider for the is_valid_plugin_name function.
227
     *
228
     * @return array
229
     */
230
    public static function is_valid_plugin_name_provider(): array {
231
        return [
232
            [['mod', 'example1'], true],
233
            [['mod', 'feedback360'], true],
234
            [['mod', 'feedback_360'], false],
235
            [['mod', '2feedback'], false],
236
            [['mod', '1example'], false],
237
            [['mod', 'example.xx'], false],
238
            [['mod', '.example'], false],
239
            [['mod', '_example'], false],
240
            [['mod', 'example_'], false],
241
            [['mod', 'example_x1'], false],
242
            [['mod', 'example-x1'], false],
243
            [['mod', 'role'], false],
244
 
245
            [['tool', 'example1'], true],
246
            [['tool', 'example_x1'], true],
247
            [['tool', 'example_x1_xxx'], true],
248
            [['tool', 'feedback360'], true],
249
            [['tool', 'feed_back360'], true],
250
            [['tool', 'role'], true],
251
            [['tool', '1example'], false],
252
            [['tool', 'example.xx'], false],
253
            [['tool', 'example-xx'], false],
254
            [['tool', '.example'], false],
255
            [['tool', '_example'], false],
256
            [['tool', 'example_'], false],
257
            [['tool', 'example__x1'], false],
258
 
259
            // Some invalid cases.
260
            [['mod', null], false],
261
            [['mod', ''], false],
262
            [['tool', null], false],
263
            [['tool', ''], false],
264
        ];
265
    }
266
 
267
    public function test_normalize_componentname(): void {
268
        // Moodle core.
269
        $this->assertSame('core', core_component::normalize_componentname('core'));
270
        $this->assertSame('core', core_component::normalize_componentname('moodle'));
271
        $this->assertSame('core', core_component::normalize_componentname(''));
272
 
273
        // Moodle core subsystems.
274
        $this->assertSame('core_admin', core_component::normalize_componentname('admin'));
275
        $this->assertSame('core_admin', core_component::normalize_componentname('core_admin'));
276
        $this->assertSame('core_admin', core_component::normalize_componentname('moodle_admin'));
277
 
278
        // Activity modules and their subplugins.
279
        $this->assertSame('mod_workshop', core_component::normalize_componentname('workshop'));
280
        $this->assertSame('mod_workshop', core_component::normalize_componentname('mod_workshop'));
281
        $this->assertSame('workshopform_accumulative', core_component::normalize_componentname('workshopform_accumulative'));
282
        $this->assertSame('mod_quiz', core_component::normalize_componentname('quiz'));
283
        $this->assertSame('quiz_grading', core_component::normalize_componentname('quiz_grading'));
284
        $this->assertSame('mod_data', core_component::normalize_componentname('data'));
285
        $this->assertSame('datafield_checkbox', core_component::normalize_componentname('datafield_checkbox'));
286
 
287
        // Other plugin types.
288
        $this->assertSame('auth_mnet', core_component::normalize_componentname('auth_mnet'));
289
        $this->assertSame('enrol_self', core_component::normalize_componentname('enrol_self'));
290
        $this->assertSame('block_html', core_component::normalize_componentname('block_html'));
291
        $this->assertSame('block_mnet_hosts', core_component::normalize_componentname('block_mnet_hosts'));
292
        $this->assertSame('local_amos', core_component::normalize_componentname('local_amos'));
293
        $this->assertSame('local_admin', core_component::normalize_componentname('local_admin'));
294
 
295
        // Unknown words without underscore are supposed to be activity modules.
296
        $this->assertSame(
297
            'mod_whoonearthwouldcomewithsuchastupidnameofcomponent',
298
            core_component::normalize_componentname('whoonearthwouldcomewithsuchastupidnameofcomponent')
299
        );
300
        // Module names can not contain underscores, this must be a subplugin.
301
        $this->assertSame(
302
            'whoonearth_wouldcomewithsuchastupidnameofcomponent',
303
            core_component::normalize_componentname('whoonearth_wouldcomewithsuchastupidnameofcomponent')
304
        );
305
        $this->assertSame(
306
            'whoonearth_would_come_withsuchastupidnameofcomponent',
307
            core_component::normalize_componentname('whoonearth_would_come_withsuchastupidnameofcomponent')
308
        );
309
    }
310
 
311
    public function test_normalize_component(): void {
312
        // Moodle core.
313
        $this->assertSame(['core', null], core_component::normalize_component('core'));
314
        $this->assertSame(['core', null], core_component::normalize_component('moodle'));
315
        $this->assertSame(['core', null], core_component::normalize_component(''));
316
 
317
        // Moodle core subsystems.
318
        $this->assertSame(['core', 'admin'], core_component::normalize_component('admin'));
319
        $this->assertSame(['core', 'admin'], core_component::normalize_component('core_admin'));
320
        $this->assertSame(['core', 'admin'], core_component::normalize_component('moodle_admin'));
321
 
322
        // Activity modules and their subplugins.
323
        $this->assertSame(['mod', 'workshop'], core_component::normalize_component('workshop'));
324
        $this->assertSame(['mod', 'workshop'], core_component::normalize_component('mod_workshop'));
325
        $this->assertSame(['workshopform', 'accumulative'], core_component::normalize_component('workshopform_accumulative'));
326
        $this->assertSame(['mod', 'quiz'], core_component::normalize_component('quiz'));
327
        $this->assertSame(['quiz', 'grading'], core_component::normalize_component('quiz_grading'));
328
        $this->assertSame(['mod', 'data'], core_component::normalize_component('data'));
329
        $this->assertSame(['datafield', 'checkbox'], core_component::normalize_component('datafield_checkbox'));
330
 
331
        // Other plugin types.
332
        $this->assertSame(['auth', 'mnet'], core_component::normalize_component('auth_mnet'));
333
        $this->assertSame(['enrol', 'self'], core_component::normalize_component('enrol_self'));
334
        $this->assertSame(['block', 'html'], core_component::normalize_component('block_html'));
335
        $this->assertSame(['block', 'mnet_hosts'], core_component::normalize_component('block_mnet_hosts'));
336
        $this->assertSame(['local', 'amos'], core_component::normalize_component('local_amos'));
337
        $this->assertSame(['local', 'admin'], core_component::normalize_component('local_admin'));
338
 
339
        // Unknown words without underscore are supposed to be activity modules.
340
        $this->assertSame(
341
            ['mod', 'whoonearthwouldcomewithsuchastupidnameofcomponent'],
342
            core_component::normalize_component('whoonearthwouldcomewithsuchastupidnameofcomponent')
343
        );
344
        // Module names can not contain underscores, this must be a subplugin.
345
        $this->assertSame(
346
            ['whoonearth', 'wouldcomewithsuchastupidnameofcomponent'],
347
            core_component::normalize_component('whoonearth_wouldcomewithsuchastupidnameofcomponent')
348
        );
349
        $this->assertSame(
350
            ['whoonearth', 'would_come_withsuchastupidnameofcomponent'],
351
            core_component::normalize_component('whoonearth_would_come_withsuchastupidnameofcomponent')
352
        );
353
    }
354
 
355
    public function test_deprecated_normalize_component(): void {
356
        // Moodle core.
357
        $this->assertSame(['core', null], normalize_component('core'));
358
        $this->assertSame(['core', null], normalize_component(''));
359
        $this->assertSame(['core', null], normalize_component('moodle'));
360
 
361
        // Moodle core subsystems.
362
        $this->assertSame(['core', 'admin'], normalize_component('admin'));
363
        $this->assertSame(['core', 'admin'], normalize_component('core_admin'));
364
        $this->assertSame(['core', 'admin'], normalize_component('moodle_admin'));
365
 
366
        // Activity modules and their subplugins.
367
        $this->assertSame(['mod', 'workshop'], normalize_component('workshop'));
368
        $this->assertSame(['mod', 'workshop'], normalize_component('mod_workshop'));
369
        $this->assertSame(['workshopform', 'accumulative'], normalize_component('workshopform_accumulative'));
370
        $this->assertSame(['mod', 'quiz'], normalize_component('quiz'));
371
        $this->assertSame(['quiz', 'grading'], normalize_component('quiz_grading'));
372
        $this->assertSame(['mod', 'data'], normalize_component('data'));
373
        $this->assertSame(['datafield', 'checkbox'], normalize_component('datafield_checkbox'));
374
 
375
        // Other plugin types.
376
        $this->assertSame(['auth', 'mnet'], normalize_component('auth_mnet'));
377
        $this->assertSame(['enrol', 'self'], normalize_component('enrol_self'));
378
        $this->assertSame(['block', 'html'], normalize_component('block_html'));
379
        $this->assertSame(['block', 'mnet_hosts'], normalize_component('block_mnet_hosts'));
380
        $this->assertSame(['local', 'amos'], normalize_component('local_amos'));
381
        $this->assertSame(['local', 'admin'], normalize_component('local_admin'));
382
 
383
        // Unknown words without underscore are supposed to be activity modules.
384
        $this->assertSame(
385
            ['mod', 'whoonearthwouldcomewithsuchastupidnameofcomponent'],
386
            normalize_component('whoonearthwouldcomewithsuchastupidnameofcomponent')
387
        );
388
        // Module names can not contain underscores, this must be a subplugin.
389
        $this->assertSame(
390
            ['whoonearth', 'wouldcomewithsuchastupidnameofcomponent'],
391
            normalize_component('whoonearth_wouldcomewithsuchastupidnameofcomponent')
392
        );
393
        $this->assertSame(
394
            ['whoonearth', 'would_come_withsuchastupidnameofcomponent'],
395
            normalize_component('whoonearth_would_come_withsuchastupidnameofcomponent')
396
        );
397
    }
398
 
399
    public function test_get_component_directory(): void {
400
        $plugintypes = core_component::get_plugin_types();
401
        foreach ($plugintypes as $plugintype => $fulldir) {
402
            $plugins = core_component::get_plugin_list($plugintype);
403
            foreach ($plugins as $pluginname => $plugindir) {
404
                $this->assertSame($plugindir, core_component::get_component_directory(($plugintype . '_' . $pluginname)));
405
            }
406
        }
407
 
408
        $subsystems = core_component::get_core_subsystems();
409
        foreach ($subsystems as $subsystem => $fulldir) {
410
            $this->assertSame($fulldir, core_component::get_component_directory(('core_' . $subsystem)));
411
        }
412
    }
413
 
414
    /**
415
     * Unit tests for get_component_from_classname.
416
     *
417
     * @dataProvider get_component_from_classname_provider
418
     * @param string $classname The class name to test
419
     * @param string|null $expected The expected component
420
     * @covers \core_component::get_component_from_classname
421
     */
422
    public function test_get_component_from_classname(
423
        string $classname,
424
        string|null $expected,
425
    ): void {
426
        $this->assertEquals(
427
            $expected,
428
            \core_component::get_component_from_classname($classname),
429
        );
430
    }
431
 
432
    /**
433
     * Data provider for get_component_from_classname tests.
434
     *
435
     * @return array
436
     */
437
    public static function get_component_from_classname_provider(): array {
438
        // Start off with testcases which have the leading \.
439
        $testcases = [
440
            // Core.
441
            [\core\example::class, 'core'],
442
 
443
            // A core subsystem.
444
            [\core_message\example::class, 'core_message'],
445
 
446
            // A fake core subsystem.
447
            [\core_fake\example::class, null],
448
 
449
            // A plugin.
450
            [\mod_forum\example::class, 'mod_forum'],
451
 
452
            // A plugin in the old style is not supported.
453
            [\mod_forum_example::class, null],
454
 
455
            // A fake plugin.
456
            [\mod_fake\example::class, null],
457
 
458
            // A subplugin.
459
            [\tiny_link\example::class, 'tiny_link'],
460
        ];
461
 
462
        // Duplicate the testcases, adding a nested namespace.
463
        $testcases = array_merge(
464
            $testcases,
465
            array_map(
466
                fn ($testcase) => [$testcase[0] . '\\in\\sub\\directory', $testcase[1]],
467
                $testcases,
468
            ),
469
        );
470
 
471
        // Duplicate the testcases, removing the leading \.
472
        return array_merge(
473
            $testcases,
474
            array_map(
475
                fn ($testcase) => [ltrim($testcase[0], '\\'), $testcase[1]],
476
                $testcases,
477
            ),
478
        );
479
    }
480
 
481
    public function test_deprecated_get_component_directory(): void {
482
        $plugintypes = core_component::get_plugin_types();
483
        foreach ($plugintypes as $plugintype => $fulldir) {
484
            $plugins = core_component::get_plugin_list($plugintype);
485
            foreach ($plugins as $pluginname => $plugindir) {
486
                $this->assertSame($plugindir, get_component_directory(($plugintype . '_' . $pluginname)));
487
            }
488
        }
489
 
490
        $subsystems = core_component::get_core_subsystems();
491
        foreach ($subsystems as $subsystem => $fulldir) {
492
            $this->assertSame($fulldir, get_component_directory(('core_' . $subsystem)));
493
        }
494
    }
495
 
496
    public function test_get_subtype_parent(): void {
497
        global $CFG;
498
 
499
        $this->assertNull(core_component::get_subtype_parent('mod'));
500
 
501
        // Any plugin with more subtypes is ok here.
502
        $this->assertFileExists("$CFG->dirroot/mod/assign/db/subplugins.json");
503
        $this->assertSame('mod_assign', core_component::get_subtype_parent('assignsubmission'));
504
        $this->assertSame('mod_assign', core_component::get_subtype_parent('assignfeedback'));
505
        $this->assertNull(core_component::get_subtype_parent('assignxxxxx'));
506
    }
507
 
508
    public function test_get_subplugins(): void {
509
        global $CFG;
510
 
511
        // Any plugin with more subtypes is ok here.
512
        $this->assertFileExists("$CFG->dirroot/mod/assign/db/subplugins.json");
513
 
514
        $subplugins = core_component::get_subplugins('mod_assign');
515
        $this->assertSame(['assignsubmission', 'assignfeedback'], array_keys($subplugins));
516
 
517
        $subs = core_component::get_plugin_list('assignsubmission');
518
        $feeds = core_component::get_plugin_list('assignfeedback');
519
 
520
        $this->assertSame(array_keys($subs), $subplugins['assignsubmission']);
521
        $this->assertSame(array_keys($feeds), $subplugins['assignfeedback']);
522
 
523
        // Any plugin without subtypes is ok here.
524
        $this->assertFileExists("$CFG->dirroot/mod/choice");
525
        $this->assertFileDoesNotExist("$CFG->dirroot/mod/choice/db/subplugins.json");
526
 
527
        $this->assertNull(core_component::get_subplugins('mod_choice'));
528
 
529
        $this->assertNull(core_component::get_subplugins('xxxx_yyyy'));
530
    }
531
 
532
    public function test_get_plugin_types_with_subplugins(): void {
533
        global $CFG;
534
 
535
        $types = core_component::get_plugin_types_with_subplugins();
536
 
537
        // Hardcode it here to detect if anybody hacks the code to include more subplugin types.
538
        $expected = [
539
            'mod' => "$CFG->dirroot/mod",
540
            'editor' => "$CFG->dirroot/lib/editor",
541
            'tool' => "$CFG->dirroot/$CFG->admin/tool",
542
            'local' => "$CFG->dirroot/local",
543
        ];
544
 
545
        $this->assertSame($expected, $types);
546
    }
547
 
548
    public function test_get_plugin_list_with_file(): void {
549
        $this->resetAfterTest(true);
550
 
551
        // No extra reset here because core_component reset automatically.
552
 
553
        $expected = [];
554
        $reports = core_component::get_plugin_list('report');
555
        foreach ($reports as $name => $fulldir) {
556
            if (file_exists("$fulldir/lib.php")) {
557
                $expected[] = $name;
558
            }
559
        }
560
 
561
        // Test cold.
562
        $list = core_component::get_plugin_list_with_file('report', 'lib.php', false);
563
        $this->assertEquals($expected, array_keys($list));
564
 
565
        // Test hot.
566
        $list = core_component::get_plugin_list_with_file('report', 'lib.php', false);
567
        $this->assertEquals($expected, array_keys($list));
568
 
569
        // Test with include.
570
        $list = core_component::get_plugin_list_with_file('report', 'lib.php', true);
571
        $this->assertEquals($expected, array_keys($list));
572
 
573
        // Test missing.
574
        $list = core_component::get_plugin_list_with_file('report', 'idontexist.php', true);
575
        $this->assertEquals([], array_keys($list));
576
    }
577
 
578
    /**
579
     * Tests for get_component_classes_in_namespace.
580
     */
581
    public function test_get_component_classes_in_namespace(): void {
582
        // Unexisting.
583
        $this->assertCount(0, core_component::get_component_classes_in_namespace('core_unexistingcomponent', 'something'));
584
        $this->assertCount(0, core_component::get_component_classes_in_namespace('auth_cas', 'something'));
585
 
586
        // Matches the last namespace level name not partials.
587
        $this->assertCount(0, core_component::get_component_classes_in_namespace('auth_cas', 'tas'));
588
        $this->assertCount(0, core_component::get_component_classes_in_namespace('core_user', 'course'));
589
        $this->assertCount(0, core_component::get_component_classes_in_namespace('mod_forum', 'output\\emaildigest'));
590
        $this->assertCount(0, core_component::get_component_classes_in_namespace('mod_forum', '\\output\\emaildigest'));
591
 
592
        // Without either a component or namespace it returns an empty array.
593
        $this->assertEmpty(\core_component::get_component_classes_in_namespace());
594
        $this->assertEmpty(\core_component::get_component_classes_in_namespace(null));
595
        $this->assertEmpty(\core_component::get_component_classes_in_namespace(null, ''));
596
    }
597
 
598
    /**
599
     * Test that the get_component_classes_in_namespace() function returns classes in the correct namespace.
600
     *
601
     * @dataProvider get_component_classes_in_namespace_provider
602
     * @param array $methodargs
603
     * @param string $expectedclassnameformat
604
     */
605
    public function test_get_component_classes_in_namespace_provider(
606
        array $methodargs,
607
        string $expectedclassnameformat,
608
    ): void {
609
        $classlist = core_component::get_component_classes_in_namespace(...$methodargs);
610
        $this->assertGreaterThan(0, count($classlist));
611
 
612
        foreach (array_keys($classlist) as $classname) {
613
            $this->assertStringMatchesFormat($expectedclassnameformat, $classname);
614
        }
615
    }
616
 
617
    /**
618
     * Data provider for get_component_classes_in_namespace tests.
619
     *
620
     * @return array
621
     */
622
    public static function get_component_classes_in_namespace_provider(): array {
623
        return [
624
            // Matches the last namespace level name not partials.
625
            [
626
                ['mod_forum', 'output\\email'],
627
                'mod_forum\output\email\%s',
628
            ],
629
            [
630
                ['mod_forum', '\\output\\email'],
631
                'mod_forum\output\email\%s',
632
            ],
633
            [
634
                ['mod_forum', 'output\\email\\'],
635
                'mod_forum\output\email\%s',
636
            ],
637
            [
638
                ['mod_forum', '\\output\\email\\'],
639
                'mod_forum\output\email\%s',
640
            ],
641
            // Prefix with backslash if it doesn\'t come prefixed.
642
            [
643
                ['auth_cas', 'task'],
644
                'auth_cas\task\%s',
645
            ],
646
            [
647
                ['auth_cas', '\\task'],
648
                'auth_cas\task\%s',
649
            ],
650
 
651
            // Core as a component works, the function can normalise the component name.
652
            [
653
                ['core', 'update'],
654
                'core\update\%s',
655
            ],
656
            [
657
                ['', 'update'],
658
                'core\update\%s',
659
            ],
660
            [
661
                ['moodle', 'update'],
662
                'core\update\%s',
663
            ],
664
 
665
            // Multiple levels.
666
            [
667
                ['core_user', '\\output\\myprofile\\'],
668
                'core_user\output\myprofile\%s',
669
            ],
670
            [
671
                ['core_user', 'output\\myprofile\\'],
672
                'core_user\output\myprofile\%s',
673
            ],
674
            [
675
                ['core_user', '\\output\\myprofile'],
676
                'core_user\output\myprofile\%s',
677
            ],
678
            [
679
                ['core_user', 'output\\myprofile'],
680
                'core_user\output\myprofile\%s',
681
            ],
682
 
683
            // Without namespace it returns classes/ classes.
684
            [
685
                ['tool_mobile', ''],
686
                'tool_mobile\%s',
687
            ],
688
            [
689
                ['tool_filetypes'],
690
                'tool_filetypes\%s',
691
            ],
692
 
693
            // Multiple levels.
694
            [
695
                ['core_user', '\\output\\myprofile\\'],
696
                'core_user\output\myprofile\%s',
697
            ],
698
 
699
            // When no component is specified, classes are returned for the namespace in all components.
700
            // (We don't assert exact amounts here as the count of `output` classes will change depending on plugins installed).
701
            [
702
                ['core', 'output'],
703
                'core\%s',
704
            ],
705
            [
706
                [null, 'output'],
707
                '%s',
708
            ],
709
        ];
710
    }
711
 
712
    /**
713
     * Data provider for classloader test
714
     */
715
    public static function classloader_provider(): array {
716
        global $CFG;
717
 
718
        // As part of these tests, we Check that there are no unexpected problems with overlapping PSR namespaces.
719
        // This is not in the spec, but may come up in some libraries using both namespaces and PEAR-style class names.
720
        // If problems arise we can remove this test, but will need to add a warning.
721
        // Normalise to forward slash for testing purposes.
722
        $directory = str_replace('\\', '/', $CFG->dirroot) . "/lib/tests/fixtures/component/";
723
 
724
        $psr0 = [
725
          'psr0'      => 'lib/tests/fixtures/component/psr0',
726
          'overlap'   => 'lib/tests/fixtures/component/overlap',
727
        ];
728
        $psr4 = [
729
          'psr4'      => 'lib/tests/fixtures/component/psr4',
730
          'overlap'   => 'lib/tests/fixtures/component/overlap',
731
        ];
732
        return [
733
          'PSR-0 Classloading - Root' => [
734
              'psr0' => $psr0,
735
              'psr4' => $psr4,
736
              'classname' => 'psr0_main',
737
              'includedfiles' => "{$directory}psr0/main.php",
738
          ],
739
          'PSR-0 Classloading - Sub namespace - underscores' => [
740
              'psr0' => $psr0,
741
              'psr4' => $psr4,
742
              'classname' => 'psr0_subnamespace_example',
743
              'includedfiles' => "{$directory}psr0/subnamespace/example.php",
744
          ],
745
          'PSR-0 Classloading - Sub namespace - slashes' => [
746
              'psr0' => $psr0,
747
              'psr4' => $psr4,
748
              'classname' => 'psr0\\subnamespace\\slashes',
749
              'includedfiles' => "{$directory}psr0/subnamespace/slashes.php",
750
          ],
751
          'PSR-4 Classloading - Root' => [
752
              'psr0' => $psr0,
753
              'psr4' => $psr4,
754
              'classname' => 'psr4\\main',
755
              'includedfiles' => "{$directory}psr4/main.php",
756
          ],
757
          'PSR-4 Classloading - Sub namespace' => [
758
              'psr0' => $psr0,
759
              'psr4' => $psr4,
760
              'classname' => 'psr4\\subnamespace\\example',
761
              'includedfiles' => "{$directory}psr4/subnamespace/example.php",
762
          ],
763
          'PSR-4 Classloading - Ensure underscores are not converted to paths' => [
764
              'psr0' => $psr0,
765
              'psr4' => $psr4,
766
              'classname' => 'psr4\\subnamespace\\underscore_example',
767
              'includedfiles' => "{$directory}psr4/subnamespace/underscore_example.php",
768
          ],
769
          'Overlap - Ensure no unexpected problems with PSR-4 when overlapping namespaces.' => [
770
              'psr0' => $psr0,
771
              'psr4' => $psr4,
772
              'classname' => 'overlap\\subnamespace\\example',
773
              'includedfiles' => "{$directory}overlap/subnamespace/example.php",
774
          ],
775
          'Overlap - Ensure no unexpected problems with PSR-0 overlapping namespaces.' => [
776
              'psr0' => $psr0,
777
              'psr4' => $psr4,
778
              'classname' => 'overlap_subnamespace_example2',
779
              'includedfiles' => "{$directory}overlap/subnamespace/example2.php",
780
          ],
781
        ];
782
    }
783
 
784
    /**
785
     * Test the classloader.
786
     *
787
     * @dataProvider classloader_provider
788
     * @param array $psr0 The PSR-0 namespaces to be used in the test.
789
     * @param array $psr4 The PSR-4 namespaces to be used in the test.
790
     * @param string $classname The name of the class to attempt to load.
791
     * @param string $includedfiles The file expected to be loaded.
792
     * @runInSeparateProcess
793
     */
794
    public function test_classloader($psr0, $psr4, $classname, $includedfiles): void {
795
        $psr0namespaces = new ReflectionProperty('core_component', 'psr0namespaces');
796
        $psr0namespaces->setValue(null, $psr0);
797
 
798
        $psr4namespaces = new ReflectionProperty('core_component', 'psr4namespaces');
799
        $psr4namespaces->setValue(null, $psr4);
800
 
801
        core_component::classloader($classname);
802
        if (DIRECTORY_SEPARATOR != '/') {
803
            // Denormalise the expected path so that we can quickly compare with get_included_files.
804
            $includedfiles = str_replace('/', DIRECTORY_SEPARATOR, $includedfiles);
805
        }
806
        $this->assertContains($includedfiles, get_included_files());
807
        $this->assertTrue(class_exists($classname, false));
808
    }
809
 
810
    /**
811
     * Data provider for psr_classloader test
812
     */
813
    public static function psr_classloader_provider(): array {
814
        global $CFG;
815
 
816
        // As part of these tests, we Check that there are no unexpected problems with overlapping PSR namespaces.
817
        // This is not in the spec, but may come up in some libraries using both namespaces and PEAR-style class names.
818
        // If problems arise we can remove this test, but will need to add a warning.
819
        // Normalise to forward slash for testing purposes.
820
        $dirroot = str_replace('\\', '/', $CFG->dirroot);
821
        $directory = "{$dirroot}/lib/tests/fixtures/component/";
822
 
823
        $psr0 = [
824
          'psr0'      => 'lib/tests/fixtures/component/psr0',
825
          'overlap'   => 'lib/tests/fixtures/component/overlap',
826
        ];
827
        $psr4 = [
828
          'psr4'      => 'lib/tests/fixtures/component/psr4',
829
          'overlap'   => 'lib/tests/fixtures/component/overlap',
830
        ];
831
        return [
832
          'PSR-0 Classloading - Root' => [
833
              'psr0' => $psr0,
834
              'psr4' => $psr4,
835
              'classname' => 'psr0_main',
836
              'file' => "{$directory}psr0/main.php",
837
          ],
838
          'PSR-0 Classloading - Sub namespace - underscores' => [
839
              'psr0' => $psr0,
840
              'psr4' => $psr4,
841
              'classname' => 'psr0_subnamespace_example',
842
              'file' => "{$directory}psr0/subnamespace/example.php",
843
          ],
844
          'PSR-0 Classloading - Sub namespace - slashes' => [
845
              'psr0' => $psr0,
846
              'psr4' => $psr4,
847
              'classname' => 'psr0\\subnamespace\\slashes',
848
              'file' => "{$directory}psr0/subnamespace/slashes.php",
849
          ],
850
          'PSR-0 Classloading - non-existent file' => [
851
              'psr0' => $psr0,
852
              'psr4' => $psr4,
853
              'classname' => 'psr0_subnamespace_nonexistent_file',
854
              'file' => false,
855
          ],
856
          'PSR-4 Classloading - Root' => [
857
              'psr0' => $psr0,
858
              'psr4' => $psr4,
859
              'classname' => 'psr4\\main',
860
              'file' => "{$directory}psr4/main.php",
861
          ],
862
          'PSR-4 Classloading - Sub namespace' => [
863
              'psr0' => $psr0,
864
              'psr4' => $psr4,
865
              'classname' => 'psr4\\subnamespace\\example',
866
              'file' => "{$directory}psr4/subnamespace/example.php",
867
          ],
868
          'PSR-4 Classloading - Ensure underscores are not converted to paths' => [
869
              'psr0' => $psr0,
870
              'psr4' => $psr4,
871
              'classname' => 'psr4\\subnamespace\\underscore_example',
872
              'file' => "{$directory}psr4/subnamespace/underscore_example.php",
873
          ],
874
          'PSR-4 Classloading - non-existent file' => [
875
              'psr0' => $psr0,
876
              'psr4' => $psr4,
877
              'classname' => 'psr4\\subnamespace\\nonexistent',
878
              'file' => false,
879
          ],
880
          'Overlap - Ensure no unexpected problems with PSR-4 when overlapping namespaces.' => [
881
              'psr0' => $psr0,
882
              'psr4' => $psr4,
883
              'classname' => 'overlap\\subnamespace\\example',
884
              'file' => "{$directory}overlap/subnamespace/example.php",
885
          ],
886
          'Overlap - Ensure no unexpected problems with PSR-0 overlapping namespaces.' => [
887
              'psr0' => $psr0,
888
              'psr4' => $psr4,
889
              'classname' => 'overlap_subnamespace_example2',
890
              'file' => "{$directory}overlap/subnamespace/example2.php",
891
          ],
892
            'PSR-4 namespaces can come from multiple sources - first source' => [
893
                'psr0' => $psr0,
894
                'psr4' => [
895
                    'Psr\\Http\\Message' => [
896
                        'lib/psr/http-message/src',
897
                        'lib/psr/http-factory/src',
898
                    ],
899
                ],
900
                'classname' => 'Psr\Http\Message\ServerRequestInterface',
901
                'includedfiles' => "{$dirroot}/lib/psr/http-message/src/ServerRequestInterface.php",
902
            ],
903
            'PSR-4 namespaces can come from multiple sources - second source' => [
904
                'psr0' => [],
905
                'psr4' => [
906
                    'Psr\\Http\\Message' => [
907
                        'lib/psr/http-message/src',
908
                        'lib/psr/http-factory/src',
909
                    ],
910
                ],
911
                'classname' => 'Psr\Http\Message\ServerRequestFactoryInterface',
912
                'includedfiles' => "{$dirroot}/lib/psr/http-factory/src/ServerRequestFactoryInterface.php",
913
            ],
914
        ];
915
    }
916
 
917
    /**
918
     * Test the PSR classloader.
919
     *
920
     * @dataProvider psr_classloader_provider
921
     * @param array $psr0 The PSR-0 namespaces to be used in the test.
922
     * @param array $psr4 The PSR-4 namespaces to be used in the test.
923
     * @param string $classname The name of the class to attempt to load.
924
     * @param string|bool $file The expected file corresponding to the class or false for nonexistant.
925
     * @runInSeparateProcess
926
     */
927
    public function test_psr_classloader($psr0, $psr4, $classname, $file): void {
928
        $psr0namespaces = new ReflectionProperty('core_component', 'psr0namespaces');
929
        $psr0namespaces->setValue(null, $psr0);
930
 
931
        $psr4namespaces = new ReflectionProperty('core_component', 'psr4namespaces');
932
        $psr4namespaces->setValue(null, $psr4);
933
 
934
        $component = new ReflectionClass('core_component');
935
        $psrclassloader = $component->getMethod('psr_classloader');
936
 
937
        $returnvalue = $psrclassloader->invokeArgs(null, [$classname]);
938
        // Normalise to forward slashes for testing comparison.
939
        if ($returnvalue) {
940
            $returnvalue = str_replace('\\', '/', $returnvalue);
941
        }
942
        $this->assertEquals($file, $returnvalue);
943
    }
944
 
945
    /**
946
     * Data provider for get_class_file test
947
     */
948
    public static function get_class_file_provider(): array {
949
        global $CFG;
950
 
951
        return [
952
          'Getting a file with underscores' => [
953
              'classname' => 'Test_With_Underscores',
954
              'prefix' => "Test",
955
              'path' => 'test/src',
956
              'separators' => ['_'],
957
              'result' => $CFG->dirroot . "/test/src/With/Underscores.php",
958
          ],
959
          'Getting a file with slashes' => [
960
              'classname' => 'Test\\With\\Slashes',
961
              'prefix' => "Test",
962
              'path' => 'test/src',
963
              'separators' => ['\\'],
964
              'result' => $CFG->dirroot . "/test/src/With/Slashes.php",
965
          ],
966
          'Getting a file with multiple namespaces' => [
967
              'classname' => 'Test\\With\\Multiple\\Namespaces',
968
              'prefix' => "Test\\With",
969
              'path' => 'test/src',
970
              'separators' => ['\\'],
971
              'result' => $CFG->dirroot . "/test/src/Multiple/Namespaces.php",
972
          ],
973
          'Getting a file with multiple namespaces (non-existent)' => [
974
              'classname' => 'Nonexistent\\Namespace\\Test',
975
              'prefix' => "Test",
976
              'path' => 'test/src',
977
              'separators' => ['\\'],
978
              'result' => false,
979
          ],
980
        ];
981
    }
982
 
983
    /**
984
     * Test the PSR classloader.
985
     *
986
     * @dataProvider get_class_file_provider
987
     * @param string $classname the name of the class.
988
     * @param string $prefix The namespace prefix used to identify the base directory of the source files.
989
     * @param string $path The relative path to the base directory of the source files.
990
     * @param string[] $separators The characters that should be used for separating.
991
     * @param string|bool $result The expected result to be returned from get_class_file.
992
     */
993
    public function test_get_class_file($classname, $prefix, $path, $separators, $result): void {
994
        $component = new ReflectionClass('core_component');
995
        $psrclassloader = $component->getMethod('get_class_file');
996
 
997
        $file = $psrclassloader->invokeArgs(null, [$classname, $prefix, $path, $separators]);
998
        $this->assertEquals($result, $file);
999
    }
1000
 
1001
    /**
1002
     * Confirm the get_component_list method contains an entry for every component.
1003
     */
1004
    public function test_get_component_list_contains_all_components(): void {
1005
        global $CFG;
1006
        $componentslist = \core_component::get_component_list();
1007
 
1008
        // We should have an entry for each plugin type, and one additional for 'core'.
1009
        $plugintypes = \core_component::get_plugin_types();
1010
        $numelementsexpected = count($plugintypes) + 1;
1011
        $this->assertEquals($numelementsexpected, count($componentslist));
1012
 
1013
        // And an entry for each of the plugin types.
1014
        foreach (array_keys($plugintypes) as $plugintype) {
1015
            $this->assertArrayHasKey($plugintype, $componentslist);
1016
        }
1017
 
1018
        // And one for 'core'.
1019
        $this->assertArrayHasKey('core', $componentslist);
1020
 
1021
        // Check a few of the known plugin types to confirm their presence at their respective type index.
1022
        $this->assertEquals($componentslist['core']['core_comment'], $CFG->dirroot . '/comment');
1023
        $this->assertEquals($componentslist['mod']['mod_forum'], $CFG->dirroot . '/mod/forum');
1024
        $this->assertEquals($componentslist['tool']['tool_usertours'], $CFG->dirroot . '/' . $CFG->admin . '/tool/usertours');
1025
    }
1026
 
1027
    /**
1028
     * Test the get_component_names() method.
1029
     *
1030
     * @dataProvider get_component_names_provider
1031
     * @param bool $includecore Whether to include core in the list.
1032
     * @param bool $coreexpected Whether core is expected to be in the list.
1033
     */
1034
    public function test_get_component_names(
1035
        bool $includecore,
1036
        bool $coreexpected,
1037
    ): void {
1038
        global $CFG;
1039
        $componentnames = \core_component::get_component_names($includecore);
1040
 
1041
        // We should have an entry for each plugin type.
1042
        $plugintypes = \core_component::get_plugin_types();
1043
        $numplugintypes = 0;
1044
        foreach (array_keys($plugintypes) as $type) {
1045
            $numplugintypes += count(\core_component::get_plugin_list($type));
1046
        }
1047
        // And an entry for each core subsystem.
1048
        $numcomponents = $numplugintypes + count(\core_component::get_core_subsystems());
1049
 
1050
        if ($coreexpected) {
1051
            // Add one for core.
1052
            $numcomponents++;
1053
        }
1054
        $this->assertEquals($numcomponents, count($componentnames));
1055
 
1056
        // Check a few of the known plugin types to confirm their presence at their respective type index.
1057
        $this->assertContains('core_comment', $componentnames);
1058
        $this->assertContains('mod_forum', $componentnames);
1059
        $this->assertContains('tool_usertours', $componentnames);
1060
        $this->assertContains('core_favourites', $componentnames);
1061
        if ($coreexpected) {
1062
            $this->assertContains('core', $componentnames);
1063
        } else {
1064
            $this->assertNotContains('core', $componentnames);
1065
        }
1066
    }
1067
 
1068
    /**
1069
     * Data provider for get_component_names() test.
1070
     *
1071
     * @return array
1072
     */
1073
    public static function get_component_names_provider(): array {
1074
        return [
1075
            [false, false],
1076
            [true, true],
1077
        ];
1078
    }
1079
 
1080
    /**
1081
     * Basic tests for APIs related functions in the core_component class.
1082
     */
1083
    public function test_apis_methods(): void {
1084
        $apis = core_component::get_core_apis();
1085
        $this->assertIsArray($apis);
1086
 
1087
        $apinames = core_component::get_core_api_names();
1088
        $this->assertIsArray($apis);
1089
 
1090
        // Both should return the very same APIs.
1091
        $this->assertEquals($apinames, array_keys($apis));
1092
 
1093
        $this->assertFalse(core_component::is_core_api('lalala'));
1094
        $this->assertTrue(core_component::is_core_api('privacy'));
1095
    }
1096
 
1097
    /**
1098
     * Test that the apis.json structure matches expectations
1099
     *
1100
     * While we include an apis.schema.json file in core, there isn't any PHP built-in allowing us
1101
     * to validate it (3rd part libraries needed). Plus the schema doesn't allow to validate things
1102
     * like uniqueness or sorting. We are going to do all that here.
1103
     */
1104
    public function test_apis_json_validation(): void {
1105
        $apis = $sortedapis = core_component::get_core_apis();
1106
        ksort($sortedapis); // We'll need this later.
1107
 
1108
        $subsystems = core_component::get_core_subsystems(); // To verify all apis are pointing to valid subsystems.
1109
        $subsystems['core'] = 'anything'; // Let's add 'core' because it's a valid component for apis.
1110
 
1111
        // General structure validations.
1112
        $this->assertIsArray($apis);
1113
        $this->assertGreaterThan(25, count($apis));
1114
        $this->assertArrayHasKey('privacy', $apis); // Verify a few.
1115
        $this->assertArrayHasKey('external', $apis);
1116
        $this->assertArrayHasKey('search', $apis);
1117
        $this->assertEquals(array_keys($sortedapis), array_keys($apis)); // Verify json is sorted alphabetically.
1118
 
1119
        // Iterate over all apis and perform more validations.
1120
        foreach ($apis as $apiname => $attributes) {
1121
            // Message, to be used later and easier finding the problem.
1122
            $message = "Validation problem found with API: {$apiname}";
1123
 
1124
            $this->assertIsObject($attributes, $message);
1125
            $this->assertMatchesRegularExpression('/^[a-z][a-z0-9]+$/', $apiname, $message);
1126
            $this->assertEquals(['component', 'allowedlevel2', 'allowedspread'], array_keys((array)$attributes), $message);
1127
 
1128
            // Verify attributes.
1129
            if ($apiname !== 'core') { // Exception for core api, it doesn't have component.
1130
                // Check that component attribute looks correct.
1131
                $this->assertMatchesRegularExpression('/^(core|[a-z][a-z0-9_]+)$/', $attributes->component, $message);
1132
                // Ensure that the api component (without the core_ prefix) is a correct subsystem.
1133
                $this->assertArrayHasKey(str_replace('core_', '', $attributes->component), $subsystems, $message);
1134
            } else {
1135
                $this->assertNull($attributes->component, $message);
1136
            }
1137
 
1138
 
1139
            // Now check for the rest of attributes.
1140
            $this->assertIsBool($attributes->allowedlevel2, $message);
1141
            $this->assertIsBool($attributes->allowedspread, $message);
1142
 
1143
            // Cannot spread if level2 is not allowed.
1144
            $this->assertLessThanOrEqual($attributes->allowedlevel2, $attributes->allowedspread, $message);
1145
        }
1146
    }
1147
 
1148
    /**
1149
     * Test for monologo icons check in plugins.
1150
     */
1151
    public function test_has_monologo_icon(): void {
1152
        // The Forum activity plugin has monologo icons.
1153
        $this->assertTrue(core_component::has_monologo_icon('mod', 'forum'));
1154
        // The core H5P subsystem doesn't have monologo icons.
1155
        $this->assertFalse(core_component::has_monologo_icon('core', 'h5p'));
1156
        // The function will return false for a non-existent component.
1157
        $this->assertFalse(core_component::has_monologo_icon('randomcomponent', 'h5p'));
1158
    }
1159
 
1160
    /*
1161
     * Tests the getter for the db directory summary hash.
1162
     *
1163
     * @covers \core_component::get_all_directory_hashes
1164
     */
1165
    public function test_get_db_directories_hash(): void {
1166
        $initial = \core_component::get_all_component_hash();
1167
 
1168
        $dir = make_request_directory();
1169
        $hashes = \core_component::get_all_directory_hashes([$dir]);
1170
        $emptydirhash = \core_component::get_all_component_hash([$hashes]);
1171
 
1172
        // Confirm that a single empty directory is a different hash to the core hash.
1173
        $this->assertNotEquals($initial, $emptydirhash);
1174
 
1175
        // Now lets add something to the dir, and check the hash is different.
1176
        $file = fopen($dir . '/test.php', 'w');
1177
        fwrite($file, 'sometestdata');
1178
        fclose($file);
1179
 
1180
        $hashes = \core_component::get_all_directory_hashes([$dir]);
1181
        $onefiledirhash = \core_component::get_all_component_hash([$hashes]);
1182
        $this->assertNotEquals($emptydirhash, $onefiledirhash);
1183
 
1184
        // Now add a subdirectory inside the request dir. This should not affect the hash.
1185
        mkdir($dir . '/subdir');
1186
        $hashes = \core_component::get_all_directory_hashes([$dir]);
1187
        $finalhash = \core_component::get_all_component_hash([$hashes]);
1188
        $this->assertEquals($onefiledirhash, $finalhash);
1189
    }
1190
}