Merge "Add semantic tags to license info text"
[lhc/web/wiklou.git] / tests / phpunit / includes / MediaWikiServicesTest.php
1 <?php
2 use MediaWiki\Interwiki\InterwikiLookup;
3 use MediaWiki\Linker\LinkRenderer;
4 use MediaWiki\Linker\LinkRendererFactory;
5 use MediaWiki\MediaWikiServices;
6 use MediaWiki\Services\DestructibleService;
7 use MediaWiki\Services\SalvageableService;
8 use MediaWiki\Services\ServiceDisabledException;
9 use MediaWiki\Shell\CommandFactory;
10 use MediaWiki\Storage\BlobStore;
11 use MediaWiki\Storage\BlobStoreFactory;
12 use MediaWiki\Storage\RevisionStore;
13 use MediaWiki\Storage\SqlBlobStore;
14
15 /**
16 * @covers MediaWiki\MediaWikiServices
17 *
18 * @group MediaWiki
19 */
20 class MediaWikiServicesTest extends MediaWikiTestCase {
21
22 /**
23 * @return Config
24 */
25 private function newTestConfig() {
26 $globalConfig = new GlobalVarConfig();
27
28 $testConfig = new HashConfig();
29 $testConfig->set( 'ServiceWiringFiles', $globalConfig->get( 'ServiceWiringFiles' ) );
30 $testConfig->set( 'ConfigRegistry', $globalConfig->get( 'ConfigRegistry' ) );
31
32 return $testConfig;
33 }
34
35 /**
36 * @return MediaWikiServices
37 */
38 private function newMediaWikiServices( Config $config = null ) {
39 if ( $config === null ) {
40 $config = $this->newTestConfig();
41 }
42
43 $instance = new MediaWikiServices( $config );
44
45 // Load the default wiring from the specified files.
46 $wiringFiles = $config->get( 'ServiceWiringFiles' );
47 $instance->loadWiringFiles( $wiringFiles );
48
49 return $instance;
50 }
51
52 public function testGetInstance() {
53 $services = MediaWikiServices::getInstance();
54 $this->assertInstanceOf( 'MediaWiki\\MediaWikiServices', $services );
55 }
56
57 public function testForceGlobalInstance() {
58 $newServices = $this->newMediaWikiServices();
59 $oldServices = MediaWikiServices::forceGlobalInstance( $newServices );
60
61 $this->assertInstanceOf( 'MediaWiki\\MediaWikiServices', $oldServices );
62 $this->assertNotSame( $oldServices, $newServices );
63
64 $theServices = MediaWikiServices::getInstance();
65 $this->assertSame( $theServices, $newServices );
66
67 MediaWikiServices::forceGlobalInstance( $oldServices );
68
69 $theServices = MediaWikiServices::getInstance();
70 $this->assertSame( $theServices, $oldServices );
71 }
72
73 public function testResetGlobalInstance() {
74 $newServices = $this->newMediaWikiServices();
75 $oldServices = MediaWikiServices::forceGlobalInstance( $newServices );
76
77 $service1 = $this->createMock( SalvageableService::class );
78 $service1->expects( $this->never() )
79 ->method( 'salvage' );
80
81 $newServices->defineService(
82 'Test',
83 function () use ( $service1 ) {
84 return $service1;
85 }
86 );
87
88 // force instantiation
89 $newServices->getService( 'Test' );
90
91 MediaWikiServices::resetGlobalInstance( $this->newTestConfig() );
92 $theServices = MediaWikiServices::getInstance();
93
94 $this->assertSame(
95 $service1,
96 $theServices->getService( 'Test' ),
97 'service definition should survive reset'
98 );
99
100 $this->assertNotSame( $theServices, $newServices );
101 $this->assertNotSame( $theServices, $oldServices );
102
103 MediaWikiServices::forceGlobalInstance( $oldServices );
104 }
105
106 public function testResetGlobalInstance_quick() {
107 $newServices = $this->newMediaWikiServices();
108 $oldServices = MediaWikiServices::forceGlobalInstance( $newServices );
109
110 $service1 = $this->createMock( SalvageableService::class );
111 $service1->expects( $this->never() )
112 ->method( 'salvage' );
113
114 $service2 = $this->createMock( SalvageableService::class );
115 $service2->expects( $this->once() )
116 ->method( 'salvage' )
117 ->with( $service1 );
118
119 // sequence of values the instantiator will return
120 $instantiatorReturnValues = [
121 $service1,
122 $service2,
123 ];
124
125 $newServices->defineService(
126 'Test',
127 function () use ( &$instantiatorReturnValues ) {
128 return array_shift( $instantiatorReturnValues );
129 }
130 );
131
132 // force instantiation
133 $newServices->getService( 'Test' );
134
135 MediaWikiServices::resetGlobalInstance( $this->newTestConfig(), 'quick' );
136 $theServices = MediaWikiServices::getInstance();
137
138 $this->assertSame( $service2, $theServices->getService( 'Test' ) );
139
140 $this->assertNotSame( $theServices, $newServices );
141 $this->assertNotSame( $theServices, $oldServices );
142
143 MediaWikiServices::forceGlobalInstance( $oldServices );
144 }
145
146 public function testDisableStorageBackend() {
147 $newServices = $this->newMediaWikiServices();
148 $oldServices = MediaWikiServices::forceGlobalInstance( $newServices );
149
150 $lbFactory = $this->getMockBuilder( 'LBFactorySimple' )
151 ->disableOriginalConstructor()
152 ->getMock();
153
154 $newServices->redefineService(
155 'DBLoadBalancerFactory',
156 function () use ( $lbFactory ) {
157 return $lbFactory;
158 }
159 );
160
161 // force the service to become active, so we can check that it does get destroyed
162 $newServices->getService( 'DBLoadBalancerFactory' );
163
164 MediaWikiServices::disableStorageBackend(); // should destroy DBLoadBalancerFactory
165
166 try {
167 MediaWikiServices::getInstance()->getService( 'DBLoadBalancerFactory' );
168 $this->fail( 'DBLoadBalancerFactory should have been disabled' );
169 }
170 catch ( ServiceDisabledException $ex ) {
171 // ok, as expected
172 } catch ( Throwable $ex ) {
173 $this->fail( 'ServiceDisabledException expected, caught ' . get_class( $ex ) );
174 }
175
176 MediaWikiServices::forceGlobalInstance( $oldServices );
177 $newServices->destroy();
178 }
179
180 public function testResetChildProcessServices() {
181 $newServices = $this->newMediaWikiServices();
182 $oldServices = MediaWikiServices::forceGlobalInstance( $newServices );
183
184 $service1 = $this->createMock( DestructibleService::class );
185 $service1->expects( $this->once() )
186 ->method( 'destroy' );
187
188 $service2 = $this->createMock( DestructibleService::class );
189 $service2->expects( $this->never() )
190 ->method( 'destroy' );
191
192 // sequence of values the instantiator will return
193 $instantiatorReturnValues = [
194 $service1,
195 $service2,
196 ];
197
198 $newServices->defineService(
199 'Test',
200 function () use ( &$instantiatorReturnValues ) {
201 return array_shift( $instantiatorReturnValues );
202 }
203 );
204
205 // force the service to become active, so we can check that it does get destroyed
206 $oldTestService = $newServices->getService( 'Test' );
207
208 MediaWikiServices::resetChildProcessServices();
209 $finalServices = MediaWikiServices::getInstance();
210
211 $newTestService = $finalServices->getService( 'Test' );
212 $this->assertNotSame( $oldTestService, $newTestService );
213
214 MediaWikiServices::forceGlobalInstance( $oldServices );
215 }
216
217 public function testResetServiceForTesting() {
218 $services = $this->newMediaWikiServices();
219 $serviceCounter = 0;
220
221 $services->defineService(
222 'Test',
223 function () use ( &$serviceCounter ) {
224 $serviceCounter++;
225 $service = $this->createMock( 'MediaWiki\Services\DestructibleService' );
226 $service->expects( $this->once() )->method( 'destroy' );
227 return $service;
228 }
229 );
230
231 // This should do nothing. In particular, it should not create a service instance.
232 $services->resetServiceForTesting( 'Test' );
233 $this->assertEquals( 0, $serviceCounter, 'No service instance should be created yet.' );
234
235 $oldInstance = $services->getService( 'Test' );
236 $this->assertEquals( 1, $serviceCounter, 'A service instance should exit now.' );
237
238 // The old instance should be detached, and destroy() called.
239 $services->resetServiceForTesting( 'Test' );
240 $newInstance = $services->getService( 'Test' );
241
242 $this->assertNotSame( $oldInstance, $newInstance );
243
244 // Satisfy the expectation that destroy() is called also for the second service instance.
245 $newInstance->destroy();
246 }
247
248 public function testResetServiceForTesting_noDestroy() {
249 $services = $this->newMediaWikiServices();
250
251 $services->defineService(
252 'Test',
253 function () {
254 $service = $this->createMock( 'MediaWiki\Services\DestructibleService' );
255 $service->expects( $this->never() )->method( 'destroy' );
256 return $service;
257 }
258 );
259
260 $oldInstance = $services->getService( 'Test' );
261
262 // The old instance should be detached, but destroy() not called.
263 $services->resetServiceForTesting( 'Test', false );
264 $newInstance = $services->getService( 'Test' );
265
266 $this->assertNotSame( $oldInstance, $newInstance );
267 }
268
269 public function provideGetters() {
270 $getServiceCases = $this->provideGetService();
271 $getterCases = [];
272
273 // All getters should be named just like the service, with "get" added.
274 foreach ( $getServiceCases as $name => $case ) {
275 if ( $name[0] === '_' ) {
276 // Internal service, no getter
277 continue;
278 }
279 list( $service, $class ) = $case;
280 $getterCases[$name] = [
281 'get' . $service,
282 $class,
283 ];
284 }
285
286 return $getterCases;
287 }
288
289 /**
290 * @dataProvider provideGetters
291 */
292 public function testGetters( $getter, $type ) {
293 // Test against the default instance, since the dummy will not know the default services.
294 $services = MediaWikiServices::getInstance();
295 $service = $services->$getter();
296 $this->assertInstanceOf( $type, $service );
297 }
298
299 public function provideGetService() {
300 // NOTE: This should list all service getters defined in ServiceWiring.php.
301 // NOTE: For every test case defined here there should be a corresponding
302 // test case defined in provideGetters().
303 return [
304 'BootstrapConfig' => [ 'BootstrapConfig', Config::class ],
305 'ConfigFactory' => [ 'ConfigFactory', ConfigFactory::class ],
306 'MainConfig' => [ 'MainConfig', Config::class ],
307 'SiteStore' => [ 'SiteStore', SiteStore::class ],
308 'SiteLookup' => [ 'SiteLookup', SiteLookup::class ],
309 'StatsdDataFactory' => [ 'StatsdDataFactory', IBufferingStatsdDataFactory::class ],
310 'InterwikiLookup' => [ 'InterwikiLookup', InterwikiLookup::class ],
311 'EventRelayerGroup' => [ 'EventRelayerGroup', EventRelayerGroup::class ],
312 'SearchEngineFactory' => [ 'SearchEngineFactory', SearchEngineFactory::class ],
313 'SearchEngineConfig' => [ 'SearchEngineConfig', SearchEngineConfig::class ],
314 'SkinFactory' => [ 'SkinFactory', SkinFactory::class ],
315 'DBLoadBalancerFactory' => [ 'DBLoadBalancerFactory', Wikimedia\Rdbms\LBFactory::class ],
316 'DBLoadBalancer' => [ 'DBLoadBalancer', 'LoadBalancer' ],
317 'WatchedItemStore' => [ 'WatchedItemStore', WatchedItemStore::class ],
318 'WatchedItemQueryService' => [ 'WatchedItemQueryService', WatchedItemQueryService::class ],
319 'CryptRand' => [ 'CryptRand', CryptRand::class ],
320 'CryptHKDF' => [ 'CryptHKDF', CryptHKDF::class ],
321 'MediaHandlerFactory' => [ 'MediaHandlerFactory', MediaHandlerFactory::class ],
322 'Parser' => [ 'Parser', Parser::class ],
323 'ParserCache' => [ 'ParserCache', ParserCache::class ],
324 'GenderCache' => [ 'GenderCache', GenderCache::class ],
325 'LinkCache' => [ 'LinkCache', LinkCache::class ],
326 'LinkRenderer' => [ 'LinkRenderer', LinkRenderer::class ],
327 'LinkRendererFactory' => [ 'LinkRendererFactory', LinkRendererFactory::class ],
328 '_MediaWikiTitleCodec' => [ '_MediaWikiTitleCodec', MediaWikiTitleCodec::class ],
329 'MimeAnalyzer' => [ 'MimeAnalyzer', MimeAnalyzer::class ],
330 'TitleFormatter' => [ 'TitleFormatter', TitleFormatter::class ],
331 'TitleParser' => [ 'TitleParser', TitleParser::class ],
332 'ProxyLookup' => [ 'ProxyLookup', ProxyLookup::class ],
333 'MainObjectStash' => [ 'MainObjectStash', BagOStuff::class ],
334 'MainWANObjectCache' => [ 'MainWANObjectCache', WANObjectCache::class ],
335 'LocalServerObjectCache' => [ 'LocalServerObjectCache', BagOStuff::class ],
336 'VirtualRESTServiceClient' => [ 'VirtualRESTServiceClient', VirtualRESTServiceClient::class ],
337 'ShellCommandFactory' => [ 'ShellCommandFactory', CommandFactory::class ],
338 'BlobStoreFactory' => [ 'BlobStoreFactory', BlobStoreFactory::class ],
339 'BlobStore' => [ 'BlobStore', BlobStore::class ],
340 '_SqlBlobStore' => [ '_SqlBlobStore', SqlBlobStore::class ],
341 'RevisionStore' => [ 'RevisionStore', RevisionStore::class ],
342 ];
343 }
344
345 /**
346 * @dataProvider provideGetService
347 */
348 public function testGetService( $name, $type ) {
349 // Test against the default instance, since the dummy will not know the default services.
350 $services = MediaWikiServices::getInstance();
351
352 $service = $services->getService( $name );
353 $this->assertInstanceOf( $type, $service );
354 }
355
356 public function testDefaultServiceInstantiation() {
357 // Check all services in the default instance, not a dummy instance!
358 // Note that we instantiate all services here, including any that
359 // were registered by extensions.
360 $services = MediaWikiServices::getInstance();
361 $names = $services->getServiceNames();
362
363 foreach ( $names as $name ) {
364 $this->assertTrue( $services->hasService( $name ) );
365 $service = $services->getService( $name );
366 $this->assertInternalType( 'object', $service );
367 }
368 }
369
370 }