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