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\Services\DestructibleService
;
9 use MediaWiki\Services\SalvageableService
;
10 use MediaWiki\Services\ServiceDisabledException
;
11 use MediaWiki\Shell\CommandFactory
;
12 use MediaWiki\Storage\BlobStore
;
13 use MediaWiki\Storage\BlobStoreFactory
;
14 use MediaWiki\Storage\RevisionLookup
;
15 use MediaWiki\Storage\RevisionStore
;
16 use MediaWiki\Storage\SqlBlobStore
;
19 * @covers MediaWiki\MediaWikiServices
23 class MediaWikiServicesTest
extends MediaWikiTestCase
{
28 private function newTestConfig() {
29 $globalConfig = new GlobalVarConfig();
31 $testConfig = new HashConfig();
32 $testConfig->set( 'ServiceWiringFiles', $globalConfig->get( 'ServiceWiringFiles' ) );
33 $testConfig->set( 'ConfigRegistry', $globalConfig->get( 'ConfigRegistry' ) );
39 * @return MediaWikiServices
41 private function newMediaWikiServices( Config
$config = null ) {
42 if ( $config === null ) {
43 $config = $this->newTestConfig();
46 $instance = new MediaWikiServices( $config );
48 // Load the default wiring from the specified files.
49 $wiringFiles = $config->get( 'ServiceWiringFiles' );
50 $instance->loadWiringFiles( $wiringFiles );
55 public function testGetInstance() {
56 $services = MediaWikiServices
::getInstance();
57 $this->assertInstanceOf( 'MediaWiki\\MediaWikiServices', $services );
60 public function testForceGlobalInstance() {
61 $newServices = $this->newMediaWikiServices();
62 $oldServices = MediaWikiServices
::forceGlobalInstance( $newServices );
64 $this->assertInstanceOf( 'MediaWiki\\MediaWikiServices', $oldServices );
65 $this->assertNotSame( $oldServices, $newServices );
67 $theServices = MediaWikiServices
::getInstance();
68 $this->assertSame( $theServices, $newServices );
70 MediaWikiServices
::forceGlobalInstance( $oldServices );
72 $theServices = MediaWikiServices
::getInstance();
73 $this->assertSame( $theServices, $oldServices );
76 public function testResetGlobalInstance() {
77 $newServices = $this->newMediaWikiServices();
78 $oldServices = MediaWikiServices
::forceGlobalInstance( $newServices );
80 $service1 = $this->createMock( SalvageableService
::class );
81 $service1->expects( $this->never() )
82 ->method( 'salvage' );
84 $newServices->defineService(
86 function () use ( $service1 ) {
91 // force instantiation
92 $newServices->getService( 'Test' );
94 MediaWikiServices
::resetGlobalInstance( $this->newTestConfig() );
95 $theServices = MediaWikiServices
::getInstance();
99 $theServices->getService( 'Test' ),
100 'service definition should survive reset'
103 $this->assertNotSame( $theServices, $newServices );
104 $this->assertNotSame( $theServices, $oldServices );
106 MediaWikiServices
::forceGlobalInstance( $oldServices );
109 public function testResetGlobalInstance_quick() {
110 $newServices = $this->newMediaWikiServices();
111 $oldServices = MediaWikiServices
::forceGlobalInstance( $newServices );
113 $service1 = $this->createMock( SalvageableService
::class );
114 $service1->expects( $this->never() )
115 ->method( 'salvage' );
117 $service2 = $this->createMock( SalvageableService
::class );
118 $service2->expects( $this->once() )
119 ->method( 'salvage' )
122 // sequence of values the instantiator will return
123 $instantiatorReturnValues = [
128 $newServices->defineService(
130 function () use ( &$instantiatorReturnValues ) {
131 return array_shift( $instantiatorReturnValues );
135 // force instantiation
136 $newServices->getService( 'Test' );
138 MediaWikiServices
::resetGlobalInstance( $this->newTestConfig(), 'quick' );
139 $theServices = MediaWikiServices
::getInstance();
141 $this->assertSame( $service2, $theServices->getService( 'Test' ) );
143 $this->assertNotSame( $theServices, $newServices );
144 $this->assertNotSame( $theServices, $oldServices );
146 MediaWikiServices
::forceGlobalInstance( $oldServices );
149 public function testDisableStorageBackend() {
150 $newServices = $this->newMediaWikiServices();
151 $oldServices = MediaWikiServices
::forceGlobalInstance( $newServices );
153 $lbFactory = $this->getMockBuilder( 'LBFactorySimple' )
154 ->disableOriginalConstructor()
157 $newServices->redefineService(
158 'DBLoadBalancerFactory',
159 function () use ( $lbFactory ) {
164 // force the service to become active, so we can check that it does get destroyed
165 $newServices->getService( 'DBLoadBalancerFactory' );
167 MediaWikiServices
::disableStorageBackend(); // should destroy DBLoadBalancerFactory
170 MediaWikiServices
::getInstance()->getService( 'DBLoadBalancerFactory' );
171 $this->fail( 'DBLoadBalancerFactory should have been disabled' );
173 catch ( ServiceDisabledException
$ex ) {
175 } catch ( Throwable
$ex ) {
176 $this->fail( 'ServiceDisabledException expected, caught ' . get_class( $ex ) );
179 MediaWikiServices
::forceGlobalInstance( $oldServices );
180 $newServices->destroy();
183 public function testResetChildProcessServices() {
184 $newServices = $this->newMediaWikiServices();
185 $oldServices = MediaWikiServices
::forceGlobalInstance( $newServices );
187 $service1 = $this->createMock( DestructibleService
::class );
188 $service1->expects( $this->once() )
189 ->method( 'destroy' );
191 $service2 = $this->createMock( DestructibleService
::class );
192 $service2->expects( $this->never() )
193 ->method( 'destroy' );
195 // sequence of values the instantiator will return
196 $instantiatorReturnValues = [
201 $newServices->defineService(
203 function () use ( &$instantiatorReturnValues ) {
204 return array_shift( $instantiatorReturnValues );
208 // force the service to become active, so we can check that it does get destroyed
209 $oldTestService = $newServices->getService( 'Test' );
211 MediaWikiServices
::resetChildProcessServices();
212 $finalServices = MediaWikiServices
::getInstance();
214 $newTestService = $finalServices->getService( 'Test' );
215 $this->assertNotSame( $oldTestService, $newTestService );
217 MediaWikiServices
::forceGlobalInstance( $oldServices );
220 public function testResetServiceForTesting() {
221 $services = $this->newMediaWikiServices();
224 $services->defineService(
226 function () use ( &$serviceCounter ) {
228 $service = $this->createMock( 'MediaWiki\Services\DestructibleService' );
229 $service->expects( $this->once() )->method( 'destroy' );
234 // This should do nothing. In particular, it should not create a service instance.
235 $services->resetServiceForTesting( 'Test' );
236 $this->assertEquals( 0, $serviceCounter, 'No service instance should be created yet.' );
238 $oldInstance = $services->getService( 'Test' );
239 $this->assertEquals( 1, $serviceCounter, 'A service instance should exit now.' );
241 // The old instance should be detached, and destroy() called.
242 $services->resetServiceForTesting( 'Test' );
243 $newInstance = $services->getService( 'Test' );
245 $this->assertNotSame( $oldInstance, $newInstance );
247 // Satisfy the expectation that destroy() is called also for the second service instance.
248 $newInstance->destroy();
251 public function testResetServiceForTesting_noDestroy() {
252 $services = $this->newMediaWikiServices();
254 $services->defineService(
257 $service = $this->createMock( 'MediaWiki\Services\DestructibleService' );
258 $service->expects( $this->never() )->method( 'destroy' );
263 $oldInstance = $services->getService( 'Test' );
265 // The old instance should be detached, but destroy() not called.
266 $services->resetServiceForTesting( 'Test', false );
267 $newInstance = $services->getService( 'Test' );
269 $this->assertNotSame( $oldInstance, $newInstance );
272 public function provideGetters() {
273 $getServiceCases = $this->provideGetService();
276 // All getters should be named just like the service, with "get" added.
277 foreach ( $getServiceCases as $name => $case ) {
278 if ( $name[0] === '_' ) {
279 // Internal service, no getter
282 list( $service, $class ) = $case;
283 $getterCases[$name] = [
293 * @dataProvider provideGetters
295 public function testGetters( $getter, $type ) {
296 // Test against the default instance, since the dummy will not know the default services.
297 $services = MediaWikiServices
::getInstance();
298 $service = $services->$getter();
299 $this->assertInstanceOf( $type, $service );
302 public function provideGetService() {
303 // NOTE: This should list all service getters defined in ServiceWiring.php.
304 // NOTE: For every test case defined here there should be a corresponding
305 // test case defined in provideGetters().
307 'BootstrapConfig' => [ 'BootstrapConfig', Config
::class ],
308 'ConfigFactory' => [ 'ConfigFactory', ConfigFactory
::class ],
309 'MainConfig' => [ 'MainConfig', Config
::class ],
310 'SiteStore' => [ 'SiteStore', SiteStore
::class ],
311 'SiteLookup' => [ 'SiteLookup', SiteLookup
::class ],
312 'StatsdDataFactory' => [ 'StatsdDataFactory', IBufferingStatsdDataFactory
::class ],
313 'InterwikiLookup' => [ 'InterwikiLookup', InterwikiLookup
::class ],
314 'EventRelayerGroup' => [ 'EventRelayerGroup', EventRelayerGroup
::class ],
315 'SearchEngineFactory' => [ 'SearchEngineFactory', SearchEngineFactory
::class ],
316 'SearchEngineConfig' => [ 'SearchEngineConfig', SearchEngineConfig
::class ],
317 'SkinFactory' => [ 'SkinFactory', SkinFactory
::class ],
318 'DBLoadBalancerFactory' => [ 'DBLoadBalancerFactory', Wikimedia\Rdbms\LBFactory
::class ],
319 'DBLoadBalancer' => [ 'DBLoadBalancer', 'LoadBalancer' ],
320 'WatchedItemStore' => [ 'WatchedItemStore', WatchedItemStore
::class ],
321 'WatchedItemQueryService' => [ 'WatchedItemQueryService', WatchedItemQueryService
::class ],
322 'CryptRand' => [ 'CryptRand', CryptRand
::class ],
323 'CryptHKDF' => [ 'CryptHKDF', CryptHKDF
::class ],
324 'MediaHandlerFactory' => [ 'MediaHandlerFactory', MediaHandlerFactory
::class ],
325 'Parser' => [ 'Parser', Parser
::class ],
326 'ParserCache' => [ 'ParserCache', ParserCache
::class ],
327 'GenderCache' => [ 'GenderCache', GenderCache
::class ],
328 'LinkCache' => [ 'LinkCache', LinkCache
::class ],
329 'LinkRenderer' => [ 'LinkRenderer', LinkRenderer
::class ],
330 'LinkRendererFactory' => [ 'LinkRendererFactory', LinkRendererFactory
::class ],
331 '_MediaWikiTitleCodec' => [ '_MediaWikiTitleCodec', MediaWikiTitleCodec
::class ],
332 'MimeAnalyzer' => [ 'MimeAnalyzer', MimeAnalyzer
::class ],
333 'TitleFormatter' => [ 'TitleFormatter', TitleFormatter
::class ],
334 'TitleParser' => [ 'TitleParser', TitleParser
::class ],
335 'ProxyLookup' => [ 'ProxyLookup', ProxyLookup
::class ],
336 'MainObjectStash' => [ 'MainObjectStash', BagOStuff
::class ],
337 'MainWANObjectCache' => [ 'MainWANObjectCache', WANObjectCache
::class ],
338 'LocalServerObjectCache' => [ 'LocalServerObjectCache', BagOStuff
::class ],
339 'VirtualRESTServiceClient' => [ 'VirtualRESTServiceClient', VirtualRESTServiceClient
::class ],
340 'ShellCommandFactory' => [ 'ShellCommandFactory', CommandFactory
::class ],
341 'BlobStoreFactory' => [ 'BlobStoreFactory', BlobStoreFactory
::class ],
342 'BlobStore' => [ 'BlobStore', BlobStore
::class ],
343 '_SqlBlobStore' => [ '_SqlBlobStore', SqlBlobStore
::class ],
344 'RevisionStore' => [ 'RevisionStore', RevisionStore
::class ],
345 'RevisionLookup' => [ 'RevisionLookup', RevisionLookup
::class ],
346 'HttpRequestFactory' => [ 'HttpRequestFactory', HttpRequestFactory
::class ],
351 * @dataProvider provideGetService
353 public function testGetService( $name, $type ) {
354 // Test against the default instance, since the dummy will not know the default services.
355 $services = MediaWikiServices
::getInstance();
357 $service = $services->getService( $name );
358 $this->assertInstanceOf( $type, $service );
361 public function testDefaultServiceInstantiation() {
362 // Check all services in the default instance, not a dummy instance!
363 // Note that we instantiate all services here, including any that
364 // were registered by extensions.
365 $services = MediaWikiServices
::getInstance();
366 $names = $services->getServiceNames();
368 foreach ( $names as $name ) {
369 $this->assertTrue( $services->hasService( $name ) );
370 $service = $services->getService( $name );
371 $this->assertInternalType( 'object', $service );