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