Merge "Selenium: replace UserLoginPage with BlankPage where possible"
[lhc/web/wiklou.git] / tests / phpunit / includes / libs / services / ServiceContainerTest.php
1 <?php
2
3 use Wikimedia\Services\ServiceContainer;
4
5 /**
6 * @covers Wikimedia\Services\ServiceContainer
7 */
8 class ServiceContainerTest extends PHPUnit\Framework\TestCase {
9
10 use MediaWikiCoversValidator; // TODO this library is supposed to be independent of MediaWiki
11 use PHPUnit4And6Compat;
12
13 private function newServiceContainer( $extraArgs = [] ) {
14 return new ServiceContainer( $extraArgs );
15 }
16
17 public function testGetServiceNames() {
18 $services = $this->newServiceContainer();
19 $names = $services->getServiceNames();
20
21 $this->assertInternalType( 'array', $names );
22 $this->assertEmpty( $names );
23
24 $name = 'TestService92834576';
25 $services->defineService( $name, function () {
26 return null;
27 } );
28
29 $names = $services->getServiceNames();
30 $this->assertContains( $name, $names );
31 }
32
33 public function testHasService() {
34 $services = $this->newServiceContainer();
35
36 $name = 'TestService92834576';
37 $this->assertFalse( $services->hasService( $name ) );
38
39 $services->defineService( $name, function () {
40 return null;
41 } );
42
43 $this->assertTrue( $services->hasService( $name ) );
44 }
45
46 public function testGetService() {
47 $services = $this->newServiceContainer( [ 'Foo' ] );
48
49 $theService = new stdClass();
50 $name = 'TestService92834576';
51 $count = 0;
52
53 $services->defineService(
54 $name,
55 function ( $actualLocator, $extra ) use ( $services, $theService, &$count ) {
56 $count++;
57 PHPUnit_Framework_Assert::assertSame( $services, $actualLocator );
58 PHPUnit_Framework_Assert::assertSame( $extra, 'Foo' );
59 return $theService;
60 }
61 );
62
63 $this->assertSame( $theService, $services->getService( $name ) );
64
65 $services->getService( $name );
66 $this->assertSame( 1, $count, 'instantiator should be called exactly once!' );
67 }
68
69 public function testGetService_fail_unknown() {
70 $services = $this->newServiceContainer();
71
72 $name = 'TestService92834576';
73
74 $this->setExpectedException( Wikimedia\Services\NoSuchServiceException::class );
75
76 $services->getService( $name );
77 }
78
79 public function testPeekService() {
80 $services = $this->newServiceContainer();
81
82 $services->defineService(
83 'Foo',
84 function () {
85 return new stdClass();
86 }
87 );
88
89 $services->defineService(
90 'Bar',
91 function () {
92 return new stdClass();
93 }
94 );
95
96 // trigger instantiation of Foo
97 $services->getService( 'Foo' );
98
99 $this->assertInternalType(
100 'object',
101 $services->peekService( 'Foo' ),
102 'Peek should return the service object if it had been accessed before.'
103 );
104
105 $this->assertNull(
106 $services->peekService( 'Bar' ),
107 'Peek should return null if the service was never accessed.'
108 );
109 }
110
111 public function testPeekService_fail_unknown() {
112 $services = $this->newServiceContainer();
113
114 $name = 'TestService92834576';
115
116 $this->setExpectedException( Wikimedia\Services\NoSuchServiceException::class );
117
118 $services->peekService( $name );
119 }
120
121 public function testDefineService() {
122 $services = $this->newServiceContainer();
123
124 $theService = new stdClass();
125 $name = 'TestService92834576';
126
127 $services->defineService( $name, function ( $actualLocator ) use ( $services, $theService ) {
128 PHPUnit_Framework_Assert::assertSame( $services, $actualLocator );
129 return $theService;
130 } );
131
132 $this->assertTrue( $services->hasService( $name ) );
133 $this->assertSame( $theService, $services->getService( $name ) );
134 }
135
136 public function testDefineService_fail_duplicate() {
137 $services = $this->newServiceContainer();
138
139 $theService = new stdClass();
140 $name = 'TestService92834576';
141
142 $services->defineService( $name, function () use ( $theService ) {
143 return $theService;
144 } );
145
146 $this->setExpectedException( Wikimedia\Services\ServiceAlreadyDefinedException::class );
147
148 $services->defineService( $name, function () use ( $theService ) {
149 return $theService;
150 } );
151 }
152
153 public function testApplyWiring() {
154 $services = $this->newServiceContainer();
155
156 $wiring = [
157 'Foo' => function () {
158 return 'Foo!';
159 },
160 'Bar' => function () {
161 return 'Bar!';
162 },
163 ];
164
165 $services->applyWiring( $wiring );
166
167 $this->assertSame( 'Foo!', $services->getService( 'Foo' ) );
168 $this->assertSame( 'Bar!', $services->getService( 'Bar' ) );
169 }
170
171 public function testImportWiring() {
172 $services = $this->newServiceContainer();
173
174 $wiring = [
175 'Foo' => function () {
176 return 'Foo!';
177 },
178 'Bar' => function () {
179 return 'Bar!';
180 },
181 'Car' => function () {
182 return 'FUBAR!';
183 },
184 ];
185
186 $services->applyWiring( $wiring );
187
188 $services->addServiceManipulator( 'Foo', function ( $service ) {
189 return $service . '+X';
190 } );
191
192 $services->addServiceManipulator( 'Car', function ( $service ) {
193 return $service . '+X';
194 } );
195
196 $newServices = $this->newServiceContainer();
197
198 // create a service with manipulator
199 $newServices->defineService( 'Foo', function () {
200 return 'Foo!';
201 } );
202
203 $newServices->addServiceManipulator( 'Foo', function ( $service ) {
204 return $service . '+Y';
205 } );
206
207 // create a service before importing, so we can later check that
208 // existing service instances survive importWiring()
209 $newServices->defineService( 'Car', function () {
210 return 'Car!';
211 } );
212
213 // force instantiation
214 $newServices->getService( 'Car' );
215
216 // Define another service, so we can later check that extra wiring
217 // is not lost.
218 $newServices->defineService( 'Xar', function () {
219 return 'Xar!';
220 } );
221
222 // import wiring, but skip `Bar`
223 $newServices->importWiring( $services, [ 'Bar' ] );
224
225 $this->assertNotContains( 'Bar', $newServices->getServiceNames(), 'Skip `Bar` service' );
226 $this->assertSame( 'Foo!+Y+X', $newServices->getService( 'Foo' ) );
227
228 // import all wiring, but preserve existing service instance
229 $newServices->importWiring( $services );
230
231 $this->assertContains( 'Bar', $newServices->getServiceNames(), 'Import all services' );
232 $this->assertSame( 'Bar!', $newServices->getService( 'Bar' ) );
233 $this->assertSame( 'Car!', $newServices->getService( 'Car' ), 'Use existing service instance' );
234 $this->assertSame( 'Xar!', $newServices->getService( 'Xar' ), 'Predefined services are kept' );
235 }
236
237 public function testLoadWiringFiles() {
238 $services = $this->newServiceContainer();
239
240 $wiringFiles = [
241 __DIR__ . '/TestWiring1.php',
242 __DIR__ . '/TestWiring2.php',
243 ];
244
245 $services->loadWiringFiles( $wiringFiles );
246
247 $this->assertSame( 'Foo!', $services->getService( 'Foo' ) );
248 $this->assertSame( 'Bar!', $services->getService( 'Bar' ) );
249 }
250
251 public function testLoadWiringFiles_fail_duplicate() {
252 $services = $this->newServiceContainer();
253
254 $wiringFiles = [
255 __DIR__ . '/TestWiring1.php',
256 __DIR__ . '/./TestWiring1.php',
257 ];
258
259 // loading the same file twice should fail, because
260 $this->setExpectedException( Wikimedia\Services\ServiceAlreadyDefinedException::class );
261
262 $services->loadWiringFiles( $wiringFiles );
263 }
264
265 public function testRedefineService() {
266 $services = $this->newServiceContainer( [ 'Foo' ] );
267
268 $theService1 = new stdClass();
269 $name = 'TestService92834576';
270
271 $services->defineService( $name, function () {
272 PHPUnit_Framework_Assert::fail(
273 'The original instantiator function should not get called'
274 );
275 } );
276
277 // redefine before instantiation
278 $services->redefineService(
279 $name,
280 function ( $actualLocator, $extra ) use ( $services, $theService1 ) {
281 PHPUnit_Framework_Assert::assertSame( $services, $actualLocator );
282 PHPUnit_Framework_Assert::assertSame( 'Foo', $extra );
283 return $theService1;
284 }
285 );
286
287 // force instantiation, check result
288 $this->assertSame( $theService1, $services->getService( $name ) );
289 }
290
291 public function testRedefineService_disabled() {
292 $services = $this->newServiceContainer( [ 'Foo' ] );
293
294 $theService1 = new stdClass();
295 $name = 'TestService92834576';
296
297 $services->defineService( $name, function () {
298 return 'Foo';
299 } );
300
301 // disable the service. we should be able to redefine it anyway.
302 $services->disableService( $name );
303
304 $services->redefineService( $name, function () use ( $theService1 ) {
305 return $theService1;
306 } );
307
308 // force instantiation, check result
309 $this->assertSame( $theService1, $services->getService( $name ) );
310 }
311
312 public function testRedefineService_fail_undefined() {
313 $services = $this->newServiceContainer();
314
315 $theService = new stdClass();
316 $name = 'TestService92834576';
317
318 $this->setExpectedException( Wikimedia\Services\NoSuchServiceException::class );
319
320 $services->redefineService( $name, function () use ( $theService ) {
321 return $theService;
322 } );
323 }
324
325 public function testRedefineService_fail_in_use() {
326 $services = $this->newServiceContainer( [ 'Foo' ] );
327
328 $theService = new stdClass();
329 $name = 'TestService92834576';
330
331 $services->defineService( $name, function () {
332 return 'Foo';
333 } );
334
335 // create the service, so it can no longer be redefined
336 $services->getService( $name );
337
338 $this->setExpectedException( Wikimedia\Services\CannotReplaceActiveServiceException::class );
339
340 $services->redefineService( $name, function () use ( $theService ) {
341 return $theService;
342 } );
343 }
344
345 public function testAddServiceManipulator() {
346 $services = $this->newServiceContainer( [ 'Foo' ] );
347
348 $theService1 = new stdClass();
349 $theService2 = new stdClass();
350 $name = 'TestService92834576';
351
352 $services->defineService(
353 $name,
354 function ( $actualLocator, $extra ) use ( $services, $theService1 ) {
355 PHPUnit_Framework_Assert::assertSame( $services, $actualLocator );
356 PHPUnit_Framework_Assert::assertSame( 'Foo', $extra );
357 return $theService1;
358 }
359 );
360
361 $services->addServiceManipulator(
362 $name,
363 function (
364 $theService, $actualLocator, $extra
365 ) use (
366 $services, $theService1, $theService2
367 ) {
368 PHPUnit_Framework_Assert::assertSame( $theService1, $theService );
369 PHPUnit_Framework_Assert::assertSame( $services, $actualLocator );
370 PHPUnit_Framework_Assert::assertSame( 'Foo', $extra );
371 return $theService2;
372 }
373 );
374
375 // force instantiation, check result
376 $this->assertSame( $theService2, $services->getService( $name ) );
377 }
378
379 public function testAddServiceManipulator_fail_undefined() {
380 $services = $this->newServiceContainer();
381
382 $theService = new stdClass();
383 $name = 'TestService92834576';
384
385 $this->setExpectedException( Wikimedia\Services\NoSuchServiceException::class );
386
387 $services->addServiceManipulator( $name, function () use ( $theService ) {
388 return $theService;
389 } );
390 }
391
392 public function testAddServiceManipulator_fail_in_use() {
393 $services = $this->newServiceContainer( [ 'Foo' ] );
394
395 $theService = new stdClass();
396 $name = 'TestService92834576';
397
398 $services->defineService( $name, function () use ( $theService ) {
399 return $theService;
400 } );
401
402 // create the service, so it can no longer be redefined
403 $services->getService( $name );
404
405 $this->setExpectedException( Wikimedia\Services\CannotReplaceActiveServiceException::class );
406
407 $services->addServiceManipulator( $name, function () {
408 return 'Foo';
409 } );
410 }
411
412 public function testDisableService() {
413 $services = $this->newServiceContainer( [ 'Foo' ] );
414
415 $destructible = $this->getMockBuilder( Wikimedia\Services\DestructibleService::class )
416 ->getMock();
417 $destructible->expects( $this->once() )
418 ->method( 'destroy' );
419
420 $services->defineService( 'Foo', function () use ( $destructible ) {
421 return $destructible;
422 } );
423 $services->defineService( 'Bar', function () {
424 return new stdClass();
425 } );
426 $services->defineService( 'Qux', function () {
427 return new stdClass();
428 } );
429
430 // instantiate Foo and Bar services
431 $services->getService( 'Foo' );
432 $services->getService( 'Bar' );
433
434 // disable service, should call destroy() once.
435 $services->disableService( 'Foo' );
436
437 // disabled service should still be listed
438 $this->assertContains( 'Foo', $services->getServiceNames() );
439
440 // getting other services should still work
441 $services->getService( 'Bar' );
442
443 // disable non-destructible service, and not-yet-instantiated service
444 $services->disableService( 'Bar' );
445 $services->disableService( 'Qux' );
446
447 $this->assertNull( $services->peekService( 'Bar' ) );
448 $this->assertNull( $services->peekService( 'Qux' ) );
449
450 // disabled service should still be listed
451 $this->assertContains( 'Bar', $services->getServiceNames() );
452 $this->assertContains( 'Qux', $services->getServiceNames() );
453
454 $this->setExpectedException( Wikimedia\Services\ServiceDisabledException::class );
455 $services->getService( 'Qux' );
456 }
457
458 public function testDisableService_fail_undefined() {
459 $services = $this->newServiceContainer();
460
461 $theService = new stdClass();
462 $name = 'TestService92834576';
463
464 $this->setExpectedException( Wikimedia\Services\NoSuchServiceException::class );
465
466 $services->redefineService( $name, function () use ( $theService ) {
467 return $theService;
468 } );
469 }
470
471 public function testDestroy() {
472 $services = $this->newServiceContainer();
473
474 $destructible = $this->getMockBuilder( Wikimedia\Services\DestructibleService::class )
475 ->getMock();
476 $destructible->expects( $this->once() )
477 ->method( 'destroy' );
478
479 $services->defineService( 'Foo', function () use ( $destructible ) {
480 return $destructible;
481 } );
482
483 $services->defineService( 'Bar', function () {
484 return new stdClass();
485 } );
486
487 // create the service
488 $services->getService( 'Foo' );
489
490 // destroy the container
491 $services->destroy();
492
493 $this->setExpectedException( Wikimedia\Services\ContainerDisabledException::class );
494 $services->getService( 'Bar' );
495 }
496
497 }