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