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