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