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