Merge "Enable configuration to supply options for Special:Search form"
[lhc/web/wiklou.git] / tests / phpunit / includes / registration / ExtensionProcessorTest.php
1 <?php
2
3 use Wikimedia\TestingAccessWrapper;
4
5 /**
6 * @covers ExtensionProcessor
7 */
8 class ExtensionProcessorTest extends MediaWikiTestCase {
9
10 private $dir, $dirname;
11
12 public function setUp() {
13 parent::setUp();
14 $this->dir = __DIR__ . '/FooBar/extension.json';
15 $this->dirname = dirname( $this->dir );
16 }
17
18 /**
19 * 'name' is absolutely required
20 *
21 * @var array
22 */
23 public static $default = [
24 'name' => 'FooBar',
25 ];
26
27 public function testExtractInfo() {
28 // Test that attributes that begin with @ are ignored
29 $processor = new ExtensionProcessor();
30 $processor->extractInfo( $this->dir, self::$default + [
31 '@metadata' => [ 'foobarbaz' ],
32 'AnAttribute' => [ 'omg' ],
33 'AutoloadClasses' => [ 'FooBar' => 'includes/FooBar.php' ],
34 'SpecialPages' => [ 'Foo' => 'SpecialFoo' ],
35 'callback' => 'FooBar::onRegistration',
36 ], 1 );
37
38 $extracted = $processor->getExtractedInfo();
39 $attributes = $extracted['attributes'];
40 $this->assertArrayHasKey( 'AnAttribute', $attributes );
41 $this->assertArrayNotHasKey( '@metadata', $attributes );
42 $this->assertArrayNotHasKey( 'AutoloadClasses', $attributes );
43 $this->assertSame(
44 [ 'FooBar' => 'FooBar::onRegistration' ],
45 $extracted['callbacks']
46 );
47 $this->assertSame(
48 [ 'Foo' => 'SpecialFoo' ],
49 $extracted['globals']['wgSpecialPages']
50 );
51 }
52
53 public function testExtractNamespaces() {
54 // Test that namespace IDs can be overwritten
55 if ( !defined( 'MW_EXTENSION_PROCESSOR_TEST_EXTRACT_INFO_X' ) ) {
56 define( 'MW_EXTENSION_PROCESSOR_TEST_EXTRACT_INFO_X', 123456 );
57 }
58
59 $processor = new ExtensionProcessor();
60 $processor->extractInfo( $this->dir, self::$default + [
61 'namespaces' => [
62 [
63 'id' => 332200,
64 'constant' => 'MW_EXTENSION_PROCESSOR_TEST_EXTRACT_INFO_A',
65 'name' => 'Test_A',
66 'defaultcontentmodel' => 'TestModel',
67 'gender' => [
68 'male' => 'Male test',
69 'female' => 'Female test',
70 ],
71 'subpages' => true,
72 'content' => true,
73 'protection' => 'userright',
74 ],
75 [ // Test_X will use ID 123456 not 334400
76 'id' => 334400,
77 'constant' => 'MW_EXTENSION_PROCESSOR_TEST_EXTRACT_INFO_X',
78 'name' => 'Test_X',
79 'defaultcontentmodel' => 'TestModel'
80 ],
81 ]
82 ], 1 );
83
84 $extracted = $processor->getExtractedInfo();
85
86 $this->assertArrayHasKey(
87 'MW_EXTENSION_PROCESSOR_TEST_EXTRACT_INFO_A',
88 $extracted['defines']
89 );
90 $this->assertArrayNotHasKey(
91 'MW_EXTENSION_PROCESSOR_TEST_EXTRACT_INFO_X',
92 $extracted['defines']
93 );
94
95 $this->assertSame(
96 $extracted['defines']['MW_EXTENSION_PROCESSOR_TEST_EXTRACT_INFO_A'],
97 332200
98 );
99
100 $this->assertArrayHasKey( 'ExtensionNamespaces', $extracted['attributes'] );
101 $this->assertArrayHasKey( 123456, $extracted['attributes']['ExtensionNamespaces'] );
102 $this->assertArrayHasKey( 332200, $extracted['attributes']['ExtensionNamespaces'] );
103 $this->assertArrayNotHasKey( 334400, $extracted['attributes']['ExtensionNamespaces'] );
104
105 $this->assertSame( 'Test_X', $extracted['attributes']['ExtensionNamespaces'][123456] );
106 $this->assertSame( 'Test_A', $extracted['attributes']['ExtensionNamespaces'][332200] );
107 $this->assertSame(
108 [ 'male' => 'Male test', 'female' => 'Female test' ],
109 $extracted['globals']['wgExtraGenderNamespaces'][332200]
110 );
111 // A has subpages, X does not
112 $this->assertTrue( $extracted['globals']['wgNamespacesWithSubpages'][332200] );
113 $this->assertArrayNotHasKey( 123456, $extracted['globals']['wgNamespacesWithSubpages'] );
114 }
115
116 public static function provideRegisterHooks() {
117 $merge = [ ExtensionRegistry::MERGE_STRATEGY => 'array_merge_recursive' ];
118 // Format:
119 // Current $wgHooks
120 // Content in extension.json
121 // Expected value of $wgHooks
122 return [
123 // No hooks
124 [
125 [],
126 self::$default,
127 $merge,
128 ],
129 // No current hooks, adding one for "FooBaz" in string format
130 [
131 [],
132 [ 'Hooks' => [ 'FooBaz' => 'FooBazCallback' ] ] + self::$default,
133 [ 'FooBaz' => [ 'FooBazCallback' ] ] + $merge,
134 ],
135 // Hook for "FooBaz", adding another one
136 [
137 [ 'FooBaz' => [ 'PriorCallback' ] ],
138 [ 'Hooks' => [ 'FooBaz' => 'FooBazCallback' ] ] + self::$default,
139 [ 'FooBaz' => [ 'PriorCallback', 'FooBazCallback' ] ] + $merge,
140 ],
141 // No current hooks, adding one for "FooBaz" in verbose array format
142 [
143 [],
144 [ 'Hooks' => [ 'FooBaz' => [ 'FooBazCallback' ] ] ] + self::$default,
145 [ 'FooBaz' => [ 'FooBazCallback' ] ] + $merge,
146 ],
147 // Hook for "BarBaz", adding one for "FooBaz"
148 [
149 [ 'BarBaz' => [ 'BarBazCallback' ] ],
150 [ 'Hooks' => [ 'FooBaz' => 'FooBazCallback' ] ] + self::$default,
151 [
152 'BarBaz' => [ 'BarBazCallback' ],
153 'FooBaz' => [ 'FooBazCallback' ],
154 ] + $merge,
155 ],
156 // Callbacks for FooBaz wrapped in an array
157 [
158 [],
159 [ 'Hooks' => [ 'FooBaz' => [ 'Callback1' ] ] ] + self::$default,
160 [
161 'FooBaz' => [ 'Callback1' ],
162 ] + $merge,
163 ],
164 // Multiple callbacks for FooBaz hook
165 [
166 [],
167 [ 'Hooks' => [ 'FooBaz' => [ 'Callback1', 'Callback2' ] ] ] + self::$default,
168 [
169 'FooBaz' => [ 'Callback1', 'Callback2' ],
170 ] + $merge,
171 ],
172 ];
173 }
174
175 /**
176 * @dataProvider provideRegisterHooks
177 */
178 public function testRegisterHooks( $pre, $info, $expected ) {
179 $processor = new MockExtensionProcessor( [ 'wgHooks' => $pre ] );
180 $processor->extractInfo( $this->dir, $info, 1 );
181 $extracted = $processor->getExtractedInfo();
182 $this->assertEquals( $expected, $extracted['globals']['wgHooks'] );
183 }
184
185 public function testExtractConfig1() {
186 $processor = new ExtensionProcessor;
187 $info = [
188 'config' => [
189 'Bar' => 'somevalue',
190 'Foo' => 10,
191 '@IGNORED' => 'yes',
192 ],
193 ] + self::$default;
194 $info2 = [
195 'config' => [
196 '_prefix' => 'eg',
197 'Bar' => 'somevalue'
198 ],
199 'name' => 'FooBar2',
200 ];
201 $processor->extractInfo( $this->dir, $info, 1 );
202 $processor->extractInfo( $this->dir, $info2, 1 );
203 $extracted = $processor->getExtractedInfo();
204 $this->assertEquals( 'somevalue', $extracted['globals']['wgBar'] );
205 $this->assertEquals( 10, $extracted['globals']['wgFoo'] );
206 $this->assertArrayNotHasKey( 'wg@IGNORED', $extracted['globals'] );
207 // Custom prefix:
208 $this->assertEquals( 'somevalue', $extracted['globals']['egBar'] );
209 }
210
211 public function testExtractConfig2() {
212 $processor = new ExtensionProcessor;
213 $info = [
214 'config' => [
215 'Bar' => [ 'value' => 'somevalue' ],
216 'Foo' => [ 'value' => 10 ],
217 'Path' => [ 'value' => 'foo.txt', 'path' => true ],
218 'Namespaces' => [
219 'value' => [
220 '10' => true,
221 '12' => false,
222 ],
223 'merge_strategy' => 'array_plus',
224 ],
225 ],
226 ] + self::$default;
227 $info2 = [
228 'config' => [
229 'Bar' => [ 'value' => 'somevalue' ],
230 ],
231 'config_prefix' => 'eg',
232 'name' => 'FooBar2',
233 ];
234 $processor->extractInfo( $this->dir, $info, 2 );
235 $processor->extractInfo( $this->dir, $info2, 2 );
236 $extracted = $processor->getExtractedInfo();
237 $this->assertEquals( 'somevalue', $extracted['globals']['wgBar'] );
238 $this->assertEquals( 10, $extracted['globals']['wgFoo'] );
239 $this->assertEquals( "{$this->dirname}/foo.txt", $extracted['globals']['wgPath'] );
240 // Custom prefix:
241 $this->assertEquals( 'somevalue', $extracted['globals']['egBar'] );
242 $this->assertSame(
243 [ 10 => true, 12 => false, ExtensionRegistry::MERGE_STRATEGY => 'array_plus' ],
244 $extracted['globals']['wgNamespaces']
245 );
246 }
247
248 /**
249 * @expectedException RuntimeException
250 */
251 public function testDuplicateConfigKey1() {
252 $processor = new ExtensionProcessor;
253 $info = [
254 'config' => [
255 'Bar' => '',
256 ]
257 ] + self::$default;
258 $info2 = [
259 'config' => [
260 'Bar' => 'g',
261 ],
262 'name' => 'FooBar2',
263 ];
264 $processor->extractInfo( $this->dir, $info, 1 );
265 $processor->extractInfo( $this->dir, $info2, 1 );
266 }
267
268 /**
269 * @expectedException RuntimeException
270 */
271 public function testDuplicateConfigKey2() {
272 $processor = new ExtensionProcessor;
273 $info = [
274 'config' => [
275 'Bar' => [ 'value' => 'somevalue' ],
276 ]
277 ] + self::$default;
278 $info2 = [
279 'config' => [
280 'Bar' => [ 'value' => 'somevalue' ],
281 ],
282 'name' => 'FooBar2',
283 ];
284 $processor->extractInfo( $this->dir, $info, 2 );
285 $processor->extractInfo( $this->dir, $info2, 2 );
286 }
287
288 public static function provideExtractExtensionMessagesFiles() {
289 $dir = __DIR__ . '/FooBar/';
290 return [
291 [
292 [ 'ExtensionMessagesFiles' => [ 'FooBarAlias' => 'FooBar.alias.php' ] ],
293 [ 'wgExtensionMessagesFiles' => [ 'FooBarAlias' => $dir . 'FooBar.alias.php' ] ]
294 ],
295 [
296 [
297 'ExtensionMessagesFiles' => [
298 'FooBarAlias' => 'FooBar.alias.php',
299 'FooBarMagic' => 'FooBar.magic.i18n.php',
300 ],
301 ],
302 [
303 'wgExtensionMessagesFiles' => [
304 'FooBarAlias' => $dir . 'FooBar.alias.php',
305 'FooBarMagic' => $dir . 'FooBar.magic.i18n.php',
306 ],
307 ],
308 ],
309 ];
310 }
311
312 /**
313 * @dataProvider provideExtractExtensionMessagesFiles
314 */
315 public function testExtractExtensionMessagesFiles( $input, $expected ) {
316 $processor = new ExtensionProcessor();
317 $processor->extractInfo( $this->dir, $input + self::$default, 1 );
318 $out = $processor->getExtractedInfo();
319 foreach ( $expected as $key => $value ) {
320 $this->assertEquals( $value, $out['globals'][$key] );
321 }
322 }
323
324 public static function provideExtractMessagesDirs() {
325 $dir = __DIR__ . '/FooBar/';
326 return [
327 [
328 [ 'MessagesDirs' => [ 'VisualEditor' => 'i18n' ] ],
329 [ 'wgMessagesDirs' => [ 'VisualEditor' => [ $dir . 'i18n' ] ] ]
330 ],
331 [
332 [ 'MessagesDirs' => [ 'VisualEditor' => [ 'i18n', 'foobar' ] ] ],
333 [ 'wgMessagesDirs' => [ 'VisualEditor' => [ $dir . 'i18n', $dir . 'foobar' ] ] ]
334 ],
335 ];
336 }
337
338 /**
339 * @dataProvider provideExtractMessagesDirs
340 */
341 public function testExtractMessagesDirs( $input, $expected ) {
342 $processor = new ExtensionProcessor();
343 $processor->extractInfo( $this->dir, $input + self::$default, 1 );
344 $out = $processor->getExtractedInfo();
345 foreach ( $expected as $key => $value ) {
346 $this->assertEquals( $value, $out['globals'][$key] );
347 }
348 }
349
350 public function testExtractCredits() {
351 $processor = new ExtensionProcessor();
352 $processor->extractInfo( $this->dir, self::$default, 1 );
353 $this->setExpectedException( Exception::class );
354 $processor->extractInfo( $this->dir, self::$default, 1 );
355 }
356
357 /**
358 * @dataProvider provideExtractResourceLoaderModules
359 */
360 public function testExtractResourceLoaderModules(
361 $input,
362 array $expectedGlobals,
363 array $expectedAttribs = []
364 ) {
365 $processor = new ExtensionProcessor();
366 $processor->extractInfo( $this->dir, $input + self::$default, 1 );
367 $out = $processor->getExtractedInfo();
368 foreach ( $expectedGlobals as $key => $value ) {
369 $this->assertEquals( $value, $out['globals'][$key] );
370 }
371 foreach ( $expectedAttribs as $key => $value ) {
372 $this->assertEquals( $value, $out['attributes'][$key] );
373 }
374 }
375
376 public static function provideExtractResourceLoaderModules() {
377 $dir = __DIR__ . '/FooBar';
378 return [
379 // Generic module with localBasePath/remoteExtPath specified
380 [
381 // Input
382 [
383 'ResourceModules' => [
384 'test.foo' => [
385 'styles' => 'foobar.js',
386 'localBasePath' => '',
387 'remoteExtPath' => 'FooBar',
388 ],
389 ],
390 ],
391 // Expected
392 [
393 'wgResourceModules' => [
394 'test.foo' => [
395 'styles' => 'foobar.js',
396 'localBasePath' => $dir,
397 'remoteExtPath' => 'FooBar',
398 ],
399 ],
400 ],
401 ],
402 // ResourceFileModulePaths specified:
403 [
404 // Input
405 [
406 'ResourceFileModulePaths' => [
407 'localBasePath' => 'modules',
408 'remoteExtPath' => 'FooBar/modules',
409 ],
410 'ResourceModules' => [
411 // No paths
412 'test.foo' => [
413 'styles' => 'foo.js',
414 ],
415 // Different paths set
416 'test.bar' => [
417 'styles' => 'bar.js',
418 'localBasePath' => 'subdir',
419 'remoteExtPath' => 'FooBar/subdir',
420 ],
421 // Custom class with no paths set
422 'test.class' => [
423 'class' => 'FooBarModule',
424 'extra' => 'argument',
425 ],
426 // Custom class with a localBasePath
427 'test.class.with.path' => [
428 'class' => 'FooBarPathModule',
429 'extra' => 'argument',
430 'localBasePath' => '',
431 ]
432 ],
433 ],
434 // Expected
435 [
436 'wgResourceModules' => [
437 'test.foo' => [
438 'styles' => 'foo.js',
439 'localBasePath' => "$dir/modules",
440 'remoteExtPath' => 'FooBar/modules',
441 ],
442 'test.bar' => [
443 'styles' => 'bar.js',
444 'localBasePath' => "$dir/subdir",
445 'remoteExtPath' => 'FooBar/subdir',
446 ],
447 'test.class' => [
448 'class' => 'FooBarModule',
449 'extra' => 'argument',
450 'localBasePath' => "$dir/modules",
451 'remoteExtPath' => 'FooBar/modules',
452 ],
453 'test.class.with.path' => [
454 'class' => 'FooBarPathModule',
455 'extra' => 'argument',
456 'localBasePath' => $dir,
457 'remoteExtPath' => 'FooBar/modules',
458 ]
459 ],
460 ],
461 ],
462 // ResourceModuleSkinStyles with file module paths
463 [
464 // Input
465 [
466 'ResourceFileModulePaths' => [
467 'localBasePath' => '',
468 'remoteSkinPath' => 'FooBar',
469 ],
470 'ResourceModuleSkinStyles' => [
471 'foobar' => [
472 'test.foo' => 'foo.css',
473 ]
474 ],
475 ],
476 // Expected
477 [
478 'wgResourceModuleSkinStyles' => [
479 'foobar' => [
480 'test.foo' => 'foo.css',
481 'localBasePath' => $dir,
482 'remoteSkinPath' => 'FooBar',
483 ],
484 ],
485 ],
486 ],
487 // ResourceModuleSkinStyles with file module paths and an override
488 [
489 // Input
490 [
491 'ResourceFileModulePaths' => [
492 'localBasePath' => '',
493 'remoteSkinPath' => 'FooBar',
494 ],
495 'ResourceModuleSkinStyles' => [
496 'foobar' => [
497 'test.foo' => 'foo.css',
498 'remoteSkinPath' => 'BarFoo'
499 ],
500 ],
501 ],
502 // Expected
503 [
504 'wgResourceModuleSkinStyles' => [
505 'foobar' => [
506 'test.foo' => 'foo.css',
507 'localBasePath' => $dir,
508 'remoteSkinPath' => 'BarFoo',
509 ],
510 ],
511 ],
512 ],
513 'QUnit test module' => [
514 // Input
515 [
516 'QUnitTestModule' => [
517 'localBasePath' => '',
518 'remoteExtPath' => 'Foo',
519 'scripts' => 'bar.js',
520 ],
521 ],
522 // Expected
523 [],
524 [
525 'QUnitTestModules' => [
526 'test.FooBar' => [
527 'localBasePath' => $dir,
528 'remoteExtPath' => 'Foo',
529 'scripts' => 'bar.js',
530 ],
531 ],
532 ],
533 ],
534 ];
535 }
536
537 public static function provideSetToGlobal() {
538 return [
539 [
540 [ 'wgAPIModules', 'wgAvailableRights' ],
541 [],
542 [
543 'APIModules' => [ 'foobar' => 'ApiFooBar' ],
544 'AvailableRights' => [ 'foobar', 'unfoobar' ],
545 ],
546 [
547 'wgAPIModules' => [ 'foobar' => 'ApiFooBar' ],
548 'wgAvailableRights' => [ 'foobar', 'unfoobar' ],
549 ],
550 ],
551 [
552 [ 'wgAPIModules', 'wgAvailableRights' ],
553 [
554 'wgAPIModules' => [ 'barbaz' => 'ApiBarBaz' ],
555 'wgAvailableRights' => [ 'barbaz' ]
556 ],
557 [
558 'APIModules' => [ 'foobar' => 'ApiFooBar' ],
559 'AvailableRights' => [ 'foobar', 'unfoobar' ],
560 ],
561 [
562 'wgAPIModules' => [ 'barbaz' => 'ApiBarBaz', 'foobar' => 'ApiFooBar' ],
563 'wgAvailableRights' => [ 'barbaz', 'foobar', 'unfoobar' ],
564 ],
565 ],
566 [
567 [ 'wgGroupPermissions' ],
568 [
569 'wgGroupPermissions' => [
570 'sysop' => [ 'delete' ]
571 ],
572 ],
573 [
574 'GroupPermissions' => [
575 'sysop' => [ 'undelete' ],
576 'user' => [ 'edit' ]
577 ],
578 ],
579 [
580 'wgGroupPermissions' => [
581 'sysop' => [ 'delete', 'undelete' ],
582 'user' => [ 'edit' ]
583 ],
584 ]
585 ]
586 ];
587 }
588
589 /**
590 * Attributes under manifest_version 2
591 */
592 public function testExtractAttributes() {
593 $processor = new ExtensionProcessor();
594 // Load FooBar extension
595 $processor->extractInfo( $this->dir, [ 'name' => 'FooBar' ], 2 );
596 $processor->extractInfo(
597 $this->dir,
598 [
599 'name' => 'Baz',
600 'attributes' => [
601 // Loaded
602 'FooBar' => [
603 'Plugins' => [
604 'ext.baz.foobar',
605 ],
606 ],
607 // Not loaded
608 'FizzBuzz' => [
609 'MorePlugins' => [
610 'ext.baz.fizzbuzz',
611 ],
612 ],
613 ],
614 ],
615 2
616 );
617
618 $info = $processor->getExtractedInfo();
619 $this->assertArrayHasKey( 'FooBarPlugins', $info['attributes'] );
620 $this->assertSame( [ 'ext.baz.foobar' ], $info['attributes']['FooBarPlugins'] );
621 $this->assertArrayNotHasKey( 'FizzBuzzMorePlugins', $info['attributes'] );
622 }
623
624 /**
625 * Attributes under manifest_version 1
626 */
627 public function testAttributes1() {
628 $processor = new ExtensionProcessor();
629 $processor->extractInfo(
630 $this->dir,
631 [
632 'name' => 'FooBar',
633 'FooBarPlugins' => [
634 'ext.baz.foobar',
635 ],
636 'FizzBuzzMorePlugins' => [
637 'ext.baz.fizzbuzz',
638 ],
639 ],
640 1
641 );
642 $processor->extractInfo(
643 $this->dir,
644 [
645 'name' => 'FooBar2',
646 'FizzBuzzMorePlugins' => [
647 'ext.bar.fizzbuzz',
648 ]
649 ],
650 1
651 );
652
653 $info = $processor->getExtractedInfo();
654 $this->assertArrayHasKey( 'FooBarPlugins', $info['attributes'] );
655 $this->assertSame( [ 'ext.baz.foobar' ], $info['attributes']['FooBarPlugins'] );
656 $this->assertArrayHasKey( 'FizzBuzzMorePlugins', $info['attributes'] );
657 $this->assertSame(
658 [ 'ext.baz.fizzbuzz', 'ext.bar.fizzbuzz' ],
659 $info['attributes']['FizzBuzzMorePlugins']
660 );
661 }
662
663 public function testAttributes1_notarray() {
664 $processor = new ExtensionProcessor();
665 $this->setExpectedException(
666 InvalidArgumentException::class,
667 "The value for 'FooBarPlugins' should be an array (from {$this->dir})"
668 );
669 $processor->extractInfo(
670 $this->dir,
671 [
672 'FooBarPlugins' => 'ext.baz.foobar',
673 ] + self::$default,
674 1
675 );
676 }
677
678 public function testExtractPathBasedGlobal() {
679 $processor = new ExtensionProcessor();
680 $processor->extractInfo(
681 $this->dir,
682 [
683 'ParserTestFiles' => [
684 'tests/parserTests.txt',
685 'tests/extraParserTests.txt',
686 ],
687 'ServiceWiringFiles' => [
688 'includes/ServiceWiring.php'
689 ],
690 ] + self::$default,
691 1
692 );
693 $globals = $processor->getExtractedInfo()['globals'];
694 $this->assertArrayHasKey( 'wgParserTestFiles', $globals );
695 $this->assertSame( [
696 "{$this->dirname}/tests/parserTests.txt",
697 "{$this->dirname}/tests/extraParserTests.txt"
698 ], $globals['wgParserTestFiles'] );
699 $this->assertArrayHasKey( 'wgServiceWiringFiles', $globals );
700 $this->assertSame( [
701 "{$this->dirname}/includes/ServiceWiring.php"
702 ], $globals['wgServiceWiringFiles'] );
703 }
704
705 public function testGetRequirements() {
706 $info = self::$default + [
707 'requires' => [
708 'MediaWiki' => '>= 1.25.0',
709 'platform' => [
710 'php' => '>= 5.5.9'
711 ],
712 'extensions' => [
713 'Bar' => '*'
714 ]
715 ]
716 ];
717 $processor = new ExtensionProcessor();
718 $this->assertSame(
719 $info['requires'],
720 $processor->getRequirements( $info, false )
721 );
722 $this->assertSame(
723 [],
724 $processor->getRequirements( [], false )
725 );
726 }
727
728 public function testGetDevRequirements() {
729 $info = self::$default + [
730 'dev-requires' => [
731 'MediaWiki' => '>= 1.31.0',
732 'platform' => [
733 'ext-foo' => '*',
734 ],
735 'skins' => [
736 'Baz' => '*',
737 ],
738 'extensions' => [
739 'Biz' => '*',
740 ],
741 ],
742 ];
743 $processor = new ExtensionProcessor();
744 $this->assertSame(
745 $info['dev-requires'],
746 $processor->getRequirements( $info, true )
747 );
748 // Set some standard requirements, so we can test merging
749 $info['requires'] = [
750 'MediaWiki' => '>= 1.25.0',
751 'platform' => [
752 'php' => '>= 5.5.9'
753 ],
754 'extensions' => [
755 'Bar' => '*'
756 ]
757 ];
758 $this->assertSame(
759 [
760 'MediaWiki' => '>= 1.25.0 >= 1.31.0',
761 'platform' => [
762 'php' => '>= 5.5.9',
763 'ext-foo' => '*',
764 ],
765 'extensions' => [
766 'Bar' => '*',
767 'Biz' => '*',
768 ],
769 'skins' => [
770 'Baz' => '*',
771 ],
772 ],
773 $processor->getRequirements( $info, true )
774 );
775
776 // If there's no dev-requires, it just returns requires
777 unset( $info['dev-requires'] );
778 $this->assertSame(
779 $info['requires'],
780 $processor->getRequirements( $info, true )
781 );
782 }
783
784 public function testGetExtraAutoloaderPaths() {
785 $processor = new ExtensionProcessor();
786 $this->assertSame(
787 [ "{$this->dirname}/vendor/autoload.php" ],
788 $processor->getExtraAutoloaderPaths( $this->dirname, [
789 'load_composer_autoloader' => true,
790 ] )
791 );
792 }
793
794 /**
795 * Verify that extension.schema.json is in sync with ExtensionProcessor
796 *
797 * @coversNothing
798 */
799 public function testGlobalSettingsDocumentedInSchema() {
800 global $IP;
801 $globalSettings = TestingAccessWrapper::newFromClass(
802 ExtensionProcessor::class )->globalSettings;
803
804 $version = ExtensionRegistry::MANIFEST_VERSION;
805 $schema = FormatJson::decode(
806 file_get_contents( "$IP/docs/extension.schema.v$version.json" ),
807 true
808 );
809 $missing = [];
810 foreach ( $globalSettings as $global ) {
811 if ( !isset( $schema['properties'][$global] ) ) {
812 $missing[] = $global;
813 }
814 }
815
816 $this->assertEquals( [], $missing,
817 "The following global settings are not documented in docs/extension.schema.json" );
818 }
819 }
820
821 /**
822 * Allow overriding the default value of $this->globals
823 * so we can test merging
824 */
825 class MockExtensionProcessor extends ExtensionProcessor {
826 public function __construct( $globals = [] ) {
827 $this->globals = $globals + $this->globals;
828 }
829 }