rdbms: make connection counting logic in LoadBalancer less stateful
[lhc/web/wiklou.git] / tests / phpunit / includes / resourceloader / ResourceLoaderStartUpModuleTest.php
1 <?php
2
3 class ResourceLoaderStartUpModuleTest extends ResourceLoaderTestCase {
4
5 protected static function expandPlaceholders( $text ) {
6 return strtr( $text, [
7 '{blankVer}' => self::BLANK_VERSION
8 ] );
9 }
10
11 public function provideGetModuleRegistrations() {
12 return [
13 [ [
14 'msg' => 'Empty registry',
15 'modules' => [],
16 'out' => '
17 mw.loader.addSource( {
18 "local": "/w/load.php"
19 } );
20 mw.loader.register( [] );'
21 ] ],
22 [ [
23 'msg' => 'Basic registry',
24 'modules' => [
25 'test.blank' => new ResourceLoaderTestModule(),
26 ],
27 'out' => '
28 mw.loader.addSource( {
29 "local": "/w/load.php"
30 } );
31 mw.loader.register( [
32 [
33 "test.blank",
34 "{blankVer}"
35 ]
36 ] );',
37 ] ],
38 [ [
39 'msg' => 'Optimise the dependency tree (basic case)',
40 'modules' => [
41 'a' => new ResourceLoaderTestModule( [ 'dependencies' => [ 'b', 'c', 'd' ] ] ),
42 'b' => new ResourceLoaderTestModule( [ 'dependencies' => [ 'c' ] ] ),
43 'c' => new ResourceLoaderTestModule( [ 'dependencies' => [] ] ),
44 'd' => new ResourceLoaderTestModule( [ 'dependencies' => [] ] ),
45 ],
46 'out' => '
47 mw.loader.addSource( {
48 "local": "/w/load.php"
49 } );
50 mw.loader.register( [
51 [
52 "a",
53 "{blankVer}",
54 [
55 1,
56 3
57 ]
58 ],
59 [
60 "b",
61 "{blankVer}",
62 [
63 2
64 ]
65 ],
66 [
67 "c",
68 "{blankVer}"
69 ],
70 [
71 "d",
72 "{blankVer}"
73 ]
74 ] );',
75 ] ],
76 [ [
77 'msg' => 'Optimise the dependency tree (tolerate unknown deps)',
78 'modules' => [
79 'a' => new ResourceLoaderTestModule( [ 'dependencies' => [ 'b', 'c', 'x' ] ] ),
80 'b' => new ResourceLoaderTestModule( [ 'dependencies' => [ 'c', 'x' ] ] ),
81 'c' => new ResourceLoaderTestModule( [ 'dependencies' => [] ] ),
82 ],
83 'out' => '
84 mw.loader.addSource( {
85 "local": "/w/load.php"
86 } );
87 mw.loader.register( [
88 [
89 "a",
90 "{blankVer}",
91 [
92 1,
93 "x"
94 ]
95 ],
96 [
97 "b",
98 "{blankVer}",
99 [
100 2,
101 "x"
102 ]
103 ],
104 [
105 "c",
106 "{blankVer}"
107 ]
108 ] );',
109 ] ],
110 [ [
111 // Regression test for T223402.
112 'msg' => 'Optimise the dependency tree (indirect circular dependency)',
113 'modules' => [
114 'top' => new ResourceLoaderTestModule( [ 'dependencies' => [ 'middle1', 'util' ] ] ),
115 'middle1' => new ResourceLoaderTestModule( [ 'dependencies' => [ 'middle2', 'util' ] ] ),
116 'middle2' => new ResourceLoaderTestModule( [ 'dependencies' => [ 'bottom' ] ] ),
117 'bottom' => new ResourceLoaderTestModule( [ 'dependencies' => [ 'top' ] ] ),
118 'util' => new ResourceLoaderTestModule( [ 'dependencies' => [] ] ),
119 ],
120 'out' => '
121 mw.loader.addSource( {
122 "local": "/w/load.php"
123 } );
124 mw.loader.register( [
125 [
126 "top",
127 "{blankVer}",
128 [
129 1,
130 4
131 ]
132 ],
133 [
134 "middle1",
135 "{blankVer}",
136 [
137 2,
138 4
139 ]
140 ],
141 [
142 "middle2",
143 "{blankVer}",
144 [
145 3
146 ]
147 ],
148 [
149 "bottom",
150 "{blankVer}",
151 [
152 0
153 ]
154 ],
155 [
156 "util",
157 "{blankVer}"
158 ]
159 ] );',
160 ] ],
161 [ [
162 // Regression test for T223402.
163 'msg' => 'Optimise the dependency tree (direct circular dependency)',
164 'modules' => [
165 'top' => new ResourceLoaderTestModule( [ 'dependencies' => [ 'util', 'top' ] ] ),
166 'util' => new ResourceLoaderTestModule( [ 'dependencies' => [] ] ),
167 ],
168 'out' => '
169 mw.loader.addSource( {
170 "local": "/w/load.php"
171 } );
172 mw.loader.register( [
173 [
174 "top",
175 "{blankVer}",
176 [
177 1,
178 0
179 ]
180 ],
181 [
182 "util",
183 "{blankVer}"
184 ]
185 ] );',
186 ] ],
187 [ [
188 'msg' => 'Omit raw modules from registry',
189 'modules' => [
190 'test.raw' => new ResourceLoaderTestModule( [ 'isRaw' => true ] ),
191 'test.blank' => new ResourceLoaderTestModule(),
192 ],
193 'out' => '
194 mw.loader.addSource( {
195 "local": "/w/load.php"
196 } );
197 mw.loader.register( [
198 [
199 "test.blank",
200 "{blankVer}"
201 ]
202 ] );',
203 ] ],
204 [ [
205 'msg' => 'Version falls back gracefully if getVersionHash throws',
206 'modules' => [
207 'test.fail' => (
208 ( $mock = $this->getMockBuilder( ResourceLoaderTestModule::class )
209 ->setMethods( [ 'getVersionHash' ] )->getMock() )
210 && $mock->method( 'getVersionHash' )->will(
211 $this->throwException( new Exception )
212 )
213 ) ? $mock : $mock
214 ],
215 'out' => '
216 mw.loader.addSource( {
217 "local": "/w/load.php"
218 } );
219 mw.loader.register( [
220 [
221 "test.fail",
222 ""
223 ]
224 ] );
225 mw.loader.state( {
226 "test.fail": "error"
227 } );',
228 ] ],
229 [ [
230 'msg' => 'Use version from getVersionHash',
231 'modules' => [
232 'test.version' => (
233 ( $mock = $this->getMockBuilder( ResourceLoaderTestModule::class )
234 ->setMethods( [ 'getVersionHash' ] )->getMock() )
235 && $mock->method( 'getVersionHash' )->willReturn( '1234567' )
236 ) ? $mock : $mock
237 ],
238 'out' => '
239 mw.loader.addSource( {
240 "local": "/w/load.php"
241 } );
242 mw.loader.register( [
243 [
244 "test.version",
245 "1234567"
246 ]
247 ] );',
248 ] ],
249 [ [
250 'msg' => 'Re-hash version from getVersionHash if too long',
251 'modules' => [
252 'test.version' => (
253 ( $mock = $this->getMockBuilder( ResourceLoaderTestModule::class )
254 ->setMethods( [ 'getVersionHash' ] )->getMock() )
255 && $mock->method( 'getVersionHash' )->willReturn( '12345678' )
256 ) ? $mock : $mock
257 ],
258 'out' => '
259 mw.loader.addSource( {
260 "local": "/w/load.php"
261 } );
262 mw.loader.register( [
263 [
264 "test.version",
265 "016es8l"
266 ]
267 ] );',
268 ] ],
269 [ [
270 'msg' => 'Group signature',
271 'modules' => [
272 'test.blank' => new ResourceLoaderTestModule(),
273 'test.group.foo' => new ResourceLoaderTestModule( [ 'group' => 'x-foo' ] ),
274 'test.group.bar' => new ResourceLoaderTestModule( [ 'group' => 'x-bar' ] ),
275 ],
276 'out' => '
277 mw.loader.addSource( {
278 "local": "/w/load.php"
279 } );
280 mw.loader.register( [
281 [
282 "test.blank",
283 "{blankVer}"
284 ],
285 [
286 "test.group.foo",
287 "{blankVer}",
288 [],
289 "x-foo"
290 ],
291 [
292 "test.group.bar",
293 "{blankVer}",
294 [],
295 "x-bar"
296 ]
297 ] );'
298 ] ],
299 [ [
300 'msg' => 'Different target (non-test should not be registered)',
301 'modules' => [
302 'test.blank' => new ResourceLoaderTestModule(),
303 'test.target.foo' => new ResourceLoaderTestModule( [ 'targets' => [ 'x-foo' ] ] ),
304 ],
305 'out' => '
306 mw.loader.addSource( {
307 "local": "/w/load.php"
308 } );
309 mw.loader.register( [
310 [
311 "test.blank",
312 "{blankVer}"
313 ]
314 ] );'
315 ] ],
316 [ [
317 'msg' => 'Safemode disabled (default; register all modules)',
318 'modules' => [
319 // Default origin: ORIGIN_CORE_SITEWIDE
320 'test.blank' => new ResourceLoaderTestModule(),
321 'test.core-generated' => new ResourceLoaderTestModule( [
322 'origin' => ResourceLoaderModule::ORIGIN_CORE_INDIVIDUAL
323 ] ),
324 'test.sitewide' => new ResourceLoaderTestModule( [
325 'origin' => ResourceLoaderModule::ORIGIN_USER_SITEWIDE
326 ] ),
327 'test.user' => new ResourceLoaderTestModule( [
328 'origin' => ResourceLoaderModule::ORIGIN_USER_INDIVIDUAL
329 ] ),
330 ],
331 'out' => '
332 mw.loader.addSource( {
333 "local": "/w/load.php"
334 } );
335 mw.loader.register( [
336 [
337 "test.blank",
338 "{blankVer}"
339 ],
340 [
341 "test.core-generated",
342 "{blankVer}"
343 ],
344 [
345 "test.sitewide",
346 "{blankVer}"
347 ],
348 [
349 "test.user",
350 "{blankVer}"
351 ]
352 ] );'
353 ] ],
354 [ [
355 'msg' => 'Safemode enabled (filter modules with user/site origin)',
356 'extraQuery' => [ 'safemode' => '1' ],
357 'modules' => [
358 // Default origin: ORIGIN_CORE_SITEWIDE
359 'test.blank' => new ResourceLoaderTestModule(),
360 'test.core-generated' => new ResourceLoaderTestModule( [
361 'origin' => ResourceLoaderModule::ORIGIN_CORE_INDIVIDUAL
362 ] ),
363 'test.sitewide' => new ResourceLoaderTestModule( [
364 'origin' => ResourceLoaderModule::ORIGIN_USER_SITEWIDE
365 ] ),
366 'test.user' => new ResourceLoaderTestModule( [
367 'origin' => ResourceLoaderModule::ORIGIN_USER_INDIVIDUAL
368 ] ),
369 ],
370 'out' => '
371 mw.loader.addSource( {
372 "local": "/w/load.php"
373 } );
374 mw.loader.register( [
375 [
376 "test.blank",
377 "{blankVer}"
378 ],
379 [
380 "test.core-generated",
381 "{blankVer}"
382 ]
383 ] );'
384 ] ],
385 [ [
386 'msg' => 'Foreign source',
387 'sources' => [
388 'example' => [
389 'loadScript' => 'http://example.org/w/load.php',
390 'apiScript' => 'http://example.org/w/api.php',
391 ],
392 ],
393 'modules' => [
394 'test.blank' => new ResourceLoaderTestModule( [ 'source' => 'example' ] ),
395 ],
396 'out' => '
397 mw.loader.addSource( {
398 "local": "/w/load.php",
399 "example": "http://example.org/w/load.php"
400 } );
401 mw.loader.register( [
402 [
403 "test.blank",
404 "{blankVer}",
405 [],
406 null,
407 "example"
408 ]
409 ] );'
410 ] ],
411 [ [
412 'msg' => 'Conditional dependency function',
413 'modules' => [
414 'test.x.core' => new ResourceLoaderTestModule(),
415 'test.x.polyfill' => new ResourceLoaderTestModule( [
416 'skipFunction' => 'return true;'
417 ] ),
418 'test.y.polyfill' => new ResourceLoaderTestModule( [
419 'skipFunction' =>
420 'return !!(' .
421 ' window.JSON &&' .
422 ' JSON.parse &&' .
423 ' JSON.stringify' .
424 ');'
425 ] ),
426 'test.z.foo' => new ResourceLoaderTestModule( [
427 'dependencies' => [
428 'test.x.core',
429 'test.x.polyfill',
430 'test.y.polyfill',
431 ],
432 ] ),
433 ],
434 'out' => '
435 mw.loader.addSource( {
436 "local": "/w/load.php"
437 } );
438 mw.loader.register( [
439 [
440 "test.x.core",
441 "{blankVer}"
442 ],
443 [
444 "test.x.polyfill",
445 "{blankVer}",
446 [],
447 null,
448 null,
449 "return true;"
450 ],
451 [
452 "test.y.polyfill",
453 "{blankVer}",
454 [],
455 null,
456 null,
457 "return !!( window.JSON \u0026\u0026 JSON.parse \u0026\u0026 JSON.stringify);"
458 ],
459 [
460 "test.z.foo",
461 "{blankVer}",
462 [
463 0,
464 1,
465 2
466 ]
467 ]
468 ] );',
469 ] ],
470 [ [
471 // This may seem like an edge case, but a plain MediaWiki core install
472 // with a few extensions installed is likely far more complex than this
473 // even, not to mention an install like Wikipedia.
474 // TODO: Make this even more realistic.
475 'msg' => 'Advanced (everything combined)',
476 'sources' => [
477 'example' => [
478 'loadScript' => 'http://example.org/w/load.php',
479 'apiScript' => 'http://example.org/w/api.php',
480 ],
481 ],
482 'modules' => [
483 'test.blank' => new ResourceLoaderTestModule(),
484 'test.x.core' => new ResourceLoaderTestModule(),
485 'test.x.util' => new ResourceLoaderTestModule( [
486 'dependencies' => [
487 'test.x.core',
488 ],
489 ] ),
490 'test.x.foo' => new ResourceLoaderTestModule( [
491 'dependencies' => [
492 'test.x.core',
493 ],
494 ] ),
495 'test.x.bar' => new ResourceLoaderTestModule( [
496 'dependencies' => [
497 'test.x.core',
498 'test.x.util',
499 ],
500 ] ),
501 'test.x.quux' => new ResourceLoaderTestModule( [
502 'dependencies' => [
503 'test.x.foo',
504 'test.x.bar',
505 'test.x.util',
506 'test.x.unknown',
507 ],
508 ] ),
509 'test.group.foo.1' => new ResourceLoaderTestModule( [
510 'group' => 'x-foo',
511 ] ),
512 'test.group.foo.2' => new ResourceLoaderTestModule( [
513 'group' => 'x-foo',
514 ] ),
515 'test.group.bar.1' => new ResourceLoaderTestModule( [
516 'group' => 'x-bar',
517 ] ),
518 'test.group.bar.2' => new ResourceLoaderTestModule( [
519 'group' => 'x-bar',
520 'source' => 'example',
521 ] ),
522 'test.target.foo' => new ResourceLoaderTestModule( [
523 'targets' => [ 'x-foo' ],
524 ] ),
525 'test.target.bar' => new ResourceLoaderTestModule( [
526 'source' => 'example',
527 'targets' => [ 'x-foo' ],
528 ] ),
529 ],
530 'out' => '
531 mw.loader.addSource( {
532 "local": "/w/load.php",
533 "example": "http://example.org/w/load.php"
534 } );
535 mw.loader.register( [
536 [
537 "test.blank",
538 "{blankVer}"
539 ],
540 [
541 "test.x.core",
542 "{blankVer}"
543 ],
544 [
545 "test.x.util",
546 "{blankVer}",
547 [
548 1
549 ]
550 ],
551 [
552 "test.x.foo",
553 "{blankVer}",
554 [
555 1
556 ]
557 ],
558 [
559 "test.x.bar",
560 "{blankVer}",
561 [
562 2
563 ]
564 ],
565 [
566 "test.x.quux",
567 "{blankVer}",
568 [
569 3,
570 4,
571 "test.x.unknown"
572 ]
573 ],
574 [
575 "test.group.foo.1",
576 "{blankVer}",
577 [],
578 "x-foo"
579 ],
580 [
581 "test.group.foo.2",
582 "{blankVer}",
583 [],
584 "x-foo"
585 ],
586 [
587 "test.group.bar.1",
588 "{blankVer}",
589 [],
590 "x-bar"
591 ],
592 [
593 "test.group.bar.2",
594 "{blankVer}",
595 [],
596 "x-bar",
597 "example"
598 ]
599 ] );'
600 ] ],
601 ];
602 }
603
604 /**
605 * @dataProvider provideGetModuleRegistrations
606 * @covers ResourceLoaderStartUpModule
607 * @covers ResourceLoader::makeLoaderRegisterScript
608 */
609 public function testGetModuleRegistrations( $case ) {
610 $extraQuery = $case['extraQuery'] ?? [];
611 $context = $this->getResourceLoaderContext( $extraQuery );
612 $rl = $context->getResourceLoader();
613 if ( isset( $case['sources'] ) ) {
614 $rl->addSource( $case['sources'] );
615 }
616 $rl->register( $case['modules'] );
617 $module = new ResourceLoaderStartUpModule();
618 $out = ltrim( $case['out'], "\n" );
619
620 // Disable log from getModuleRegistrations via MWExceptionHandler
621 // for case where getVersionHash() is expected to throw.
622 $this->setLogger( 'exception', new Psr\Log\NullLogger() );
623
624 $this->assertEquals(
625 self::expandPlaceholders( $out ),
626 $module->getModuleRegistrations( $context ),
627 $case['msg']
628 );
629 }
630
631 public static function provideRegistrations() {
632 return [
633 [ [
634 'test.blank' => new ResourceLoaderTestModule(),
635 'test.min' => new ResourceLoaderTestModule( [
636 'skipFunction' =>
637 'return !!(' .
638 ' window.JSON &&' .
639 ' JSON.parse &&' .
640 ' JSON.stringify' .
641 ');',
642 'dependencies' => [
643 'test.blank',
644 ],
645 ] ),
646 ] ]
647 ];
648 }
649
650 /**
651 * @covers ResourceLoaderStartUpModule::getModuleRegistrations
652 * @dataProvider provideRegistrations
653 */
654 public function testRegistrationsMinified( $modules ) {
655 $this->setMwGlobals( 'wgResourceLoaderDebug', false );
656
657 $context = $this->getResourceLoaderContext();
658 $rl = $context->getResourceLoader();
659 $rl->register( $modules );
660 $module = new ResourceLoaderStartUpModule();
661 $out = 'mw.loader.addSource({"local":"/w/load.php"});' . "\n"
662 . 'mw.loader.register(['
663 . '["test.blank","{blankVer}"],'
664 . '["test.min","{blankVer}",[0],null,null,'
665 . '"return!!(window.JSON\u0026\u0026JSON.parse\u0026\u0026JSON.stringify);"'
666 . ']]);';
667
668 $this->assertEquals(
669 self::expandPlaceholders( $out ),
670 $module->getModuleRegistrations( $context ),
671 'Minified output'
672 );
673 }
674
675 /**
676 * @covers ResourceLoaderStartUpModule::getModuleRegistrations
677 * @dataProvider provideRegistrations
678 */
679 public function testRegistrationsUnminified( $modules ) {
680 $context = $this->getResourceLoaderContext();
681 $rl = $context->getResourceLoader();
682 $rl->register( $modules );
683 $module = new ResourceLoaderStartUpModule();
684 $out =
685 'mw.loader.addSource( {
686 "local": "/w/load.php"
687 } );
688 mw.loader.register( [
689 [
690 "test.blank",
691 "{blankVer}"
692 ],
693 [
694 "test.min",
695 "{blankVer}",
696 [
697 0
698 ],
699 null,
700 null,
701 "return !!( window.JSON \u0026\u0026 JSON.parse \u0026\u0026 JSON.stringify);"
702 ]
703 ] );';
704
705 $this->assertEquals(
706 self::expandPlaceholders( $out ),
707 $module->getModuleRegistrations( $context ),
708 'Unminified output'
709 );
710 }
711
712 /**
713 * @covers ResourceLoaderStartupModule::getDefinitionSummary
714 */
715 public function testGetVersionHash_varyConfig() {
716 $context = $this->getResourceLoaderContext();
717
718 $this->setMwGlobals( 'wgArticlePath', '/w1' );
719 $module = new ResourceLoaderStartupModule();
720 $version1 = $module->getVersionHash( $context );
721 $module = new ResourceLoaderStartupModule();
722 $version2 = $module->getVersionHash( $context );
723
724 $this->setMwGlobals( 'wgArticlePath', '/w3' );
725 $module = new ResourceLoaderStartupModule();
726 $version3 = $module->getVersionHash( $context );
727
728 $this->assertEquals(
729 $version1,
730 $version2,
731 'Deterministic version hash'
732 );
733
734 $this->assertNotEquals(
735 $version1,
736 $version3,
737 'Config change impacts version hash'
738 );
739 }
740
741 /**
742 * @covers ResourceLoaderStartupModule
743 */
744 public function testGetVersionHash_varyModule() {
745 $context1 = $this->getResourceLoaderContext();
746 $rl1 = $context1->getResourceLoader();
747 $rl1->register( [
748 'test.a' => new ResourceLoaderTestModule(),
749 'test.b' => new ResourceLoaderTestModule(),
750 ] );
751 $module = new ResourceLoaderStartupModule();
752 $version1 = $module->getVersionHash( $context1 );
753
754 $context2 = $this->getResourceLoaderContext();
755 $rl2 = $context2->getResourceLoader();
756 $rl2->register( [
757 'test.b' => new ResourceLoaderTestModule(),
758 'test.c' => new ResourceLoaderTestModule(),
759 ] );
760 $module = new ResourceLoaderStartupModule();
761 $version2 = $module->getVersionHash( $context2 );
762
763 $context3 = $this->getResourceLoaderContext();
764 $rl3 = $context3->getResourceLoader();
765 $rl3->register( [
766 'test.a' => new ResourceLoaderTestModule(),
767 'test.b' => new ResourceLoaderTestModule( [ 'script' => 'different' ] ),
768 ] );
769 $module = new ResourceLoaderStartupModule();
770 $version3 = $module->getVersionHash( $context3 );
771
772 // Module name *is* significant (T201686)
773 $this->assertNotEquals(
774 $version1,
775 $version2,
776 'Module name is significant'
777 );
778
779 $this->assertNotEquals(
780 $version1,
781 $version3,
782 'Hash change of any module impacts startup hash'
783 );
784 }
785
786 /**
787 * @covers ResourceLoaderStartupModule
788 */
789 public function testGetVersionHash_varyDeps() {
790 $context = $this->getResourceLoaderContext();
791 $rl = $context->getResourceLoader();
792 $rl->register( [
793 'test.a' => new ResourceLoaderTestModule( [ 'dependencies' => [ 'x', 'y' ] ] ),
794 ] );
795 $module = new ResourceLoaderStartupModule();
796 $version1 = $module->getVersionHash( $context );
797
798 $context = $this->getResourceLoaderContext();
799 $rl = $context->getResourceLoader();
800 $rl->register( [
801 'test.a' => new ResourceLoaderTestModule( [ 'dependencies' => [ 'x', 'z' ] ] ),
802 ] );
803 $module = new ResourceLoaderStartupModule();
804 $version2 = $module->getVersionHash( $context );
805
806 // Dependencies *are* significant (T201686)
807 $this->assertNotEquals(
808 $version1,
809 $version2,
810 'Dependencies are significant'
811 );
812 }
813
814 }