Merge "Add semantic tags to license info text"
[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
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( 'MediaWiki\Services\NoSuchServiceException' );
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( 'MediaWiki\Services\NoSuchServiceException' );
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( 'MediaWiki\Services\ServiceAlreadyDefinedException' );
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 $newServices = $this->newServiceContainer();
189
190 // define a service before importing, so we can later check that
191 // existing service instances survive importWiring()
192 $newServices->defineService( 'Car', function () {
193 return 'Car!';
194 } );
195
196 // force instantiation
197 $newServices->getService( 'Car' );
198
199 // Define another service, so we can later check that extra wiring
200 // is not lost.
201 $newServices->defineService( 'Xar', function () {
202 return 'Xar!';
203 } );
204
205 // import wiring, but skip `Bar`
206 $newServices->importWiring( $services, [ 'Bar' ] );
207
208 $this->assertNotContains( 'Bar', $newServices->getServiceNames(), 'Skip `Bar` service' );
209 $this->assertSame( 'Foo!', $newServices->getService( 'Foo' ) );
210
211 // import all wiring, but preserve existing service instance
212 $newServices->importWiring( $services );
213
214 $this->assertContains( 'Bar', $newServices->getServiceNames(), 'Import all services' );
215 $this->assertSame( 'Bar!', $newServices->getService( 'Bar' ) );
216 $this->assertSame( 'Car!', $newServices->getService( 'Car' ), 'Use existing service instance' );
217 $this->assertSame( 'Xar!', $newServices->getService( 'Xar' ), 'Predefined services are kept' );
218 }
219
220 public function testLoadWiringFiles() {
221 $services = $this->newServiceContainer();
222
223 $wiringFiles = [
224 __DIR__ . '/TestWiring1.php',
225 __DIR__ . '/TestWiring2.php',
226 ];
227
228 $services->loadWiringFiles( $wiringFiles );
229
230 $this->assertSame( 'Foo!', $services->getService( 'Foo' ) );
231 $this->assertSame( 'Bar!', $services->getService( 'Bar' ) );
232 }
233
234 public function testLoadWiringFiles_fail_duplicate() {
235 $services = $this->newServiceContainer();
236
237 $wiringFiles = [
238 __DIR__ . '/TestWiring1.php',
239 __DIR__ . '/./TestWiring1.php',
240 ];
241
242 // loading the same file twice should fail, because
243 $this->setExpectedException( 'MediaWiki\Services\ServiceAlreadyDefinedException' );
244
245 $services->loadWiringFiles( $wiringFiles );
246 }
247
248 public function testRedefineService() {
249 $services = $this->newServiceContainer( [ 'Foo' ] );
250
251 $theService1 = new stdClass();
252 $name = 'TestService92834576';
253
254 $services->defineService( $name, function () {
255 PHPUnit_Framework_Assert::fail(
256 'The original instantiator function should not get called'
257 );
258 } );
259
260 // redefine before instantiation
261 $services->redefineService(
262 $name,
263 function ( $actualLocator, $extra ) use ( $services, $theService1 ) {
264 PHPUnit_Framework_Assert::assertSame( $services, $actualLocator );
265 PHPUnit_Framework_Assert::assertSame( 'Foo', $extra );
266 return $theService1;
267 }
268 );
269
270 // force instantiation, check result
271 $this->assertSame( $theService1, $services->getService( $name ) );
272 }
273
274 public function testRedefineService_disabled() {
275 $services = $this->newServiceContainer( [ 'Foo' ] );
276
277 $theService1 = new stdClass();
278 $name = 'TestService92834576';
279
280 $services->defineService( $name, function () {
281 return 'Foo';
282 } );
283
284 // disable the service. we should be able to redefine it anyway.
285 $services->disableService( $name );
286
287 $services->redefineService( $name, function () use ( $theService1 ) {
288 return $theService1;
289 } );
290
291 // force instantiation, check result
292 $this->assertSame( $theService1, $services->getService( $name ) );
293 }
294
295 public function testRedefineService_fail_undefined() {
296 $services = $this->newServiceContainer();
297
298 $theService = new stdClass();
299 $name = 'TestService92834576';
300
301 $this->setExpectedException( 'MediaWiki\Services\NoSuchServiceException' );
302
303 $services->redefineService( $name, function () use ( $theService ) {
304 return $theService;
305 } );
306 }
307
308 public function testRedefineService_fail_in_use() {
309 $services = $this->newServiceContainer( [ 'Foo' ] );
310
311 $theService = new stdClass();
312 $name = 'TestService92834576';
313
314 $services->defineService( $name, function () {
315 return 'Foo';
316 } );
317
318 // create the service, so it can no longer be redefined
319 $services->getService( $name );
320
321 $this->setExpectedException( 'MediaWiki\Services\CannotReplaceActiveServiceException' );
322
323 $services->redefineService( $name, function () use ( $theService ) {
324 return $theService;
325 } );
326 }
327
328 public function testDisableService() {
329 $services = $this->newServiceContainer( [ 'Foo' ] );
330
331 $destructible = $this->getMockBuilder( 'MediaWiki\Services\DestructibleService' )
332 ->getMock();
333 $destructible->expects( $this->once() )
334 ->method( 'destroy' );
335
336 $services->defineService( 'Foo', function () use ( $destructible ) {
337 return $destructible;
338 } );
339 $services->defineService( 'Bar', function () {
340 return new stdClass();
341 } );
342 $services->defineService( 'Qux', function () {
343 return new stdClass();
344 } );
345
346 // instantiate Foo and Bar services
347 $services->getService( 'Foo' );
348 $services->getService( 'Bar' );
349
350 // disable service, should call destroy() once.
351 $services->disableService( 'Foo' );
352
353 // disabled service should still be listed
354 $this->assertContains( 'Foo', $services->getServiceNames() );
355
356 // getting other services should still work
357 $services->getService( 'Bar' );
358
359 // disable non-destructible service, and not-yet-instantiated service
360 $services->disableService( 'Bar' );
361 $services->disableService( 'Qux' );
362
363 $this->assertNull( $services->peekService( 'Bar' ) );
364 $this->assertNull( $services->peekService( 'Qux' ) );
365
366 // disabled service should still be listed
367 $this->assertContains( 'Bar', $services->getServiceNames() );
368 $this->assertContains( 'Qux', $services->getServiceNames() );
369
370 $this->setExpectedException( 'MediaWiki\Services\ServiceDisabledException' );
371 $services->getService( 'Qux' );
372 }
373
374 public function testDisableService_fail_undefined() {
375 $services = $this->newServiceContainer();
376
377 $theService = new stdClass();
378 $name = 'TestService92834576';
379
380 $this->setExpectedException( 'MediaWiki\Services\NoSuchServiceException' );
381
382 $services->redefineService( $name, function () use ( $theService ) {
383 return $theService;
384 } );
385 }
386
387 public function testDestroy() {
388 $services = $this->newServiceContainer();
389
390 $destructible = $this->getMockBuilder( 'MediaWiki\Services\DestructibleService' )
391 ->getMock();
392 $destructible->expects( $this->once() )
393 ->method( 'destroy' );
394
395 $services->defineService( 'Foo', function () use ( $destructible ) {
396 return $destructible;
397 } );
398
399 $services->defineService( 'Bar', function () {
400 return new stdClass();
401 } );
402
403 // create the service
404 $services->getService( 'Foo' );
405
406 // destroy the container
407 $services->destroy();
408
409 $this->setExpectedException( 'MediaWiki\Services\ContainerDisabledException' );
410 $services->getService( 'Bar' );
411 }
412
413 }