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