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