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