Merge "Make DBAccessBase use DBConnRef, rename $wiki, and hide getLoadBalancer()"
[lhc/web/wiklou.git] / tests / phpunit / includes / Storage / SqlBlobStoreTest.php
1 <?php
2
3 namespace MediaWiki\Tests\Storage;
4
5 use InvalidArgumentException;
6 use Language;
7 use MediaWiki\MediaWikiServices;
8 use MediaWiki\Storage\BlobAccessException;
9 use MediaWiki\Storage\SqlBlobStore;
10 use MediaWikiTestCase;
11 use stdClass;
12 use TitleValue;
13
14 /**
15 * @covers \MediaWiki\Storage\SqlBlobStore
16 * @group Database
17 */
18 class SqlBlobStoreTest extends MediaWikiTestCase {
19
20 /**
21 * @return SqlBlobStore
22 */
23 public function getBlobStore( $legacyEncoding = false, $compressRevisions = false ) {
24 $services = MediaWikiServices::getInstance();
25
26 $store = new SqlBlobStore(
27 $services->getDBLoadBalancer(),
28 $services->getExternalStoreAccess(),
29 $services->getMainWANObjectCache()
30 );
31
32 if ( $compressRevisions ) {
33 $store->setCompressBlobs( $compressRevisions );
34 }
35 if ( $legacyEncoding ) {
36 $store->setLegacyEncoding( $legacyEncoding, Language::factory( 'en' ) );
37 }
38
39 return $store;
40 }
41
42 /**
43 * @covers \MediaWiki\Storage\SqlBlobStore::getCompressBlobs()
44 * @covers \MediaWiki\Storage\SqlBlobStore::setCompressBlobs()
45 */
46 public function testGetSetCompressRevisions() {
47 $store = $this->getBlobStore();
48 $this->assertFalse( $store->getCompressBlobs() );
49 $store->setCompressBlobs( true );
50 $this->assertTrue( $store->getCompressBlobs() );
51 }
52
53 /**
54 * @covers \MediaWiki\Storage\SqlBlobStore::getLegacyEncoding()
55 * @covers \MediaWiki\Storage\SqlBlobStore::getLegacyEncodingConversionLang()
56 * @covers \MediaWiki\Storage\SqlBlobStore::setLegacyEncoding()
57 */
58 public function testGetSetLegacyEncoding() {
59 $store = $this->getBlobStore();
60 $this->assertFalse( $store->getLegacyEncoding() );
61 $this->assertNull( $store->getLegacyEncodingConversionLang() );
62 $en = Language::factory( 'en' );
63 $store->setLegacyEncoding( 'foo', $en );
64 $this->assertSame( 'foo', $store->getLegacyEncoding() );
65 $this->assertSame( $en, $store->getLegacyEncodingConversionLang() );
66 }
67
68 /**
69 * @covers \MediaWiki\Storage\SqlBlobStore::getCacheExpiry()
70 * @covers \MediaWiki\Storage\SqlBlobStore::setCacheExpiry()
71 */
72 public function testGetSetCacheExpiry() {
73 $store = $this->getBlobStore();
74 $this->assertSame( 604800, $store->getCacheExpiry() );
75 $store->setCacheExpiry( 12 );
76 $this->assertSame( 12, $store->getCacheExpiry() );
77 }
78
79 /**
80 * @covers \MediaWiki\Storage\SqlBlobStore::getUseExternalStore()
81 * @covers \MediaWiki\Storage\SqlBlobStore::setUseExternalStore()
82 */
83 public function testGetSetUseExternalStore() {
84 $store = $this->getBlobStore();
85 $this->assertFalse( $store->getUseExternalStore() );
86 $store->setUseExternalStore( true );
87 $this->assertTrue( $store->getUseExternalStore() );
88 }
89
90 public function provideDecompress() {
91 yield '(no legacy encoding), empty in empty out' => [ false, '', [], '' ];
92 yield '(no legacy encoding), empty in empty out' => [ false, 'A', [], 'A' ];
93 yield '(no legacy encoding), error flag -> false' => [ false, 'X', [ 'error' ], false ];
94 yield '(no legacy encoding), string in with gzip flag returns string' => [
95 // gzip string below generated with gzdeflate( 'AAAABBAAA' )
96 false, "sttttr\002\022\000", [ 'gzip' ], 'AAAABBAAA',
97 ];
98 yield '(no legacy encoding), string in with object flag returns false' => [
99 // gzip string below generated with serialize( 'JOJO' )
100 false, "s:4:\"JOJO\";", [ 'object' ], false,
101 ];
102 yield '(no legacy encoding), serialized object in with object flag returns string' => [
103 false,
104 // Using a TitleValue object as it has a getText method (which is needed)
105 serialize( new TitleValue( 0, 'HHJJDDFF' ) ),
106 [ 'object' ],
107 'HHJJDDFF',
108 ];
109 yield '(no legacy encoding), serialized object in with object & gzip flag returns string' => [
110 false,
111 // Using a TitleValue object as it has a getText method (which is needed)
112 gzdeflate( serialize( new TitleValue( 0, '8219JJJ840' ) ) ),
113 [ 'object', 'gzip' ],
114 '8219JJJ840',
115 ];
116 yield '(ISO-8859-1 encoding), string in string out' => [
117 'ISO-8859-1',
118 iconv( 'utf-8', 'ISO-8859-1', "1®Àþ1" ),
119 [],
120 '1®Àþ1',
121 ];
122 yield '(ISO-8859-1 encoding), serialized object in with gzip flags returns string' => [
123 'ISO-8859-1',
124 gzdeflate( iconv( 'utf-8', 'ISO-8859-1', "4®Àþ4" ) ),
125 [ 'gzip' ],
126 '4®Àþ4',
127 ];
128 yield '(ISO-8859-1 encoding), serialized object in with object flags returns string' => [
129 'ISO-8859-1',
130 serialize( new TitleValue( 0, iconv( 'utf-8', 'ISO-8859-1', "3®Àþ3" ) ) ),
131 [ 'object' ],
132 '3®Àþ3',
133 ];
134 yield '(ISO-8859-1 encoding), serialized object in with object & gzip flags returns string' => [
135 'ISO-8859-1',
136 gzdeflate( serialize( new TitleValue( 0, iconv( 'utf-8', 'ISO-8859-1', "2®Àþ2" ) ) ) ),
137 [ 'gzip', 'object' ],
138 '2®Àþ2',
139 ];
140 yield 'T184749 (windows-1252 encoding), string in string out' => [
141 'windows-1252',
142 iconv( 'utf-8', 'windows-1252', "sammansättningar" ),
143 [],
144 'sammansättningar',
145 ];
146 yield 'T184749 (windows-1252 encoding), string in string out with gzip' => [
147 'windows-1252',
148 gzdeflate( iconv( 'utf-8', 'windows-1252', "sammansättningar" ) ),
149 [ 'gzip' ],
150 'sammansättningar',
151 ];
152 }
153
154 /**
155 * @dataProvider provideDecompress
156 * @covers \MediaWiki\Storage\SqlBlobStore::decompressData
157 *
158 * @param string|bool $legacyEncoding
159 * @param mixed $data
160 * @param array $flags
161 * @param mixed $expected
162 */
163 public function testDecompressData( $legacyEncoding, $data, $flags, $expected ) {
164 $store = $this->getBlobStore( $legacyEncoding );
165 $this->assertSame(
166 $expected,
167 $store->decompressData( $data, $flags )
168 );
169 }
170
171 /**
172 * @covers \MediaWiki\Storage\SqlBlobStore::decompressData
173 */
174 public function testDecompressData_InvalidArgumentException() {
175 $store = $this->getBlobStore();
176
177 $this->setExpectedException( InvalidArgumentException::class );
178 $store->decompressData( false, [] );
179 }
180
181 /**
182 * @covers \MediaWiki\Storage\SqlBlobStore::compressData
183 */
184 public function testCompressRevisionTextUtf8() {
185 $store = $this->getBlobStore();
186 $row = new stdClass;
187 $row->old_text = "Wiki est l'\xc3\xa9cole superieur !";
188 $row->old_flags = $store->compressData( $row->old_text );
189 $this->assertTrue( strpos( $row->old_flags, 'utf-8' ) !== false,
190 "Flags should contain 'utf-8'" );
191 $this->assertFalse( strpos( $row->old_flags, 'gzip' ) !== false,
192 "Flags should not contain 'gzip'" );
193 $this->assertEquals( "Wiki est l'\xc3\xa9cole superieur !",
194 $row->old_text, "Direct check" );
195 }
196
197 /**
198 * @covers \MediaWiki\Storage\SqlBlobStore::compressData
199 */
200 public function testCompressRevisionTextUtf8Gzip() {
201 $store = $this->getBlobStore( false, true );
202 $this->checkPHPExtension( 'zlib' );
203
204 $row = new stdClass;
205 $row->old_text = "Wiki est l'\xc3\xa9cole superieur !";
206 $row->old_flags = $store->compressData( $row->old_text );
207 $this->assertTrue( strpos( $row->old_flags, 'utf-8' ) !== false,
208 "Flags should contain 'utf-8'" );
209 $this->assertTrue( strpos( $row->old_flags, 'gzip' ) !== false,
210 "Flags should contain 'gzip'" );
211 $this->assertEquals( "Wiki est l'\xc3\xa9cole superieur !",
212 gzinflate( $row->old_text ), "Direct check" );
213 }
214
215 public function provideBlobs() {
216 yield [ '' ];
217 yield [ 'someText' ];
218 yield [ "sammansättningar" ];
219 }
220
221 /**
222 * @param string $blob
223 * @dataProvider provideBlobs
224 * @covers \MediaWiki\Storage\SqlBlobStore::storeBlob
225 * @covers \MediaWiki\Storage\SqlBlobStore::getBlob
226 */
227 public function testSimpleStoreGetBlobSimpleRoundtrip( $blob ) {
228 $store = $this->getBlobStore();
229 $address = $store->storeBlob( $blob );
230 $this->assertSame( $blob, $store->getBlob( $address ) );
231 }
232
233 /**
234 * @covers \MediaWiki\Storage\SqlBlobStore::storeBlob
235 * @covers \MediaWiki\Storage\SqlBlobStore::getBlobBatch
236 */
237 public function testSimpleStorageGetBlobBatchSimpleEmpty() {
238 $store = $this->getBlobStore();
239 $this->assertArrayEquals(
240 [],
241 $store->getBlobBatch( [] )->getValue()
242 );
243 }
244
245 /**
246 * @param string $blob
247 * @dataProvider provideBlobs
248 * @covers \MediaWiki\Storage\SqlBlobStore::storeBlob
249 * @covers \MediaWiki\Storage\SqlBlobStore::getBlobBatch
250 */
251 public function testSimpleStorageGetBlobBatchSimpleRoundtrip( $blob ) {
252 $store = $this->getBlobStore();
253 $addresses = [
254 $store->storeBlob( $blob ),
255 $store->storeBlob( $blob . '1' )
256 ];
257 $this->assertArrayEquals(
258 array_combine( $addresses, [ $blob, $blob . '1' ] ),
259 $store->getBlobBatch( $addresses )->getValue()
260 );
261 }
262
263 /**
264 * @covers \MediaWiki\Storage\SqlBlobStore::getBlob
265 */
266 public function testSimpleStorageNonExistentBlob() {
267 $this->setExpectedException( BlobAccessException::class );
268 $store = $this->getBlobStore();
269 $store->getBlob( 'tt:this_will_not_exist' );
270 }
271
272 /**
273 * @covers \MediaWiki\Storage\SqlBlobStore::getBlobBatch
274 */
275 public function testSimpleStorageNonExistentBlobBatch() {
276 $store = $this->getBlobStore();
277 $result = $store->getBlobBatch( [ 'tt:this_will_not_exist', 'tt:1000', 'bla:1001' ] );
278 $this->assertSame(
279 [
280 'tt:this_will_not_exist' => null,
281 'tt:1000' => null,
282 'bla:1001' => null
283 ],
284 $result->getValue()
285 );
286 $this->assertSame( [
287 [
288 'type' => 'warning',
289 'message' => 'internalerror',
290 'params' => [
291 'Bad blob address: tt:this_will_not_exist'
292 ]
293 ],
294 [
295 'type' => 'warning',
296 'message' => 'internalerror',
297 'params' => [
298 'Unknown blob address schema: bla'
299 ]
300 ],
301 [
302 'type' => 'warning',
303 'message' => 'internalerror',
304 'params' => [
305 'Unable to fetch blob at tt:1000'
306 ]
307 ]
308 ], $result->getErrors() );
309 }
310
311 /**
312 * @covers \MediaWiki\Storage\SqlBlobStore::getBlobBatch
313 */
314 public function testSimpleStoragePartialNonExistentBlobBatch() {
315 $store = $this->getBlobStore();
316 $address = $store->storeBlob( 'test_data' );
317 $result = $store->getBlobBatch( [ $address, 'tt:this_will_not_exist_too' ] );
318 $this->assertSame(
319 [
320 $address => 'test_data',
321 'tt:this_will_not_exist_too' => null
322 ],
323 $result->getValue()
324 );
325 $this->assertSame( [
326 [
327 'type' => 'warning',
328 'message' => 'internalerror',
329 'params' => [
330 'Bad blob address: tt:this_will_not_exist_too'
331 ]
332 ],
333 ], $result->getErrors() );
334 }
335
336 /**
337 * @dataProvider provideBlobs
338 * @covers \MediaWiki\Storage\SqlBlobStore::storeBlob
339 * @covers \MediaWiki\Storage\SqlBlobStore::getBlob
340 */
341 public function testSimpleStoreGetBlobSimpleRoundtripWindowsLegacyEncoding( $blob ) {
342 $store = $this->getBlobStore( 'windows-1252' );
343 $address = $store->storeBlob( $blob );
344 $this->assertSame( $blob, $store->getBlob( $address ) );
345 }
346
347 /**
348 * @dataProvider provideBlobs
349 * @covers \MediaWiki\Storage\SqlBlobStore::storeBlob
350 * @covers \MediaWiki\Storage\SqlBlobStore::getBlob
351 */
352 public function testSimpleStoreGetBlobSimpleRoundtripWindowsLegacyEncodingGzip( $blob ) {
353 // FIXME: fails under postgres
354 $this->markTestSkippedIfDbType( 'postgres' );
355 $store = $this->getBlobStore( 'windows-1252', true );
356 $address = $store->storeBlob( $blob );
357 $this->assertSame( $blob, $store->getBlob( $address ) );
358 }
359
360 public function provideGetTextIdFromAddress() {
361 yield [ 'tt:17', 17 ];
362 yield [ 'xy:17', null ];
363 yield [ 'xy:xyzzy', null ];
364 }
365
366 /**
367 * @dataProvider provideGetTextIdFromAddress
368 */
369 public function testGetTextIdFromAddress( $address, $textId ) {
370 $store = $this->getBlobStore();
371 $this->assertSame( $textId, $store->getTextIdFromAddress( $address ) );
372 }
373
374 public function provideGetTextIdFromAddressInvalidArgumentException() {
375 yield [ 'tt:-17' ];
376 yield [ 'tt:xy' ];
377 yield [ 'tt:0' ];
378 yield [ 'tt:' ];
379 yield [ 'xy' ];
380 yield [ '' ];
381 }
382
383 /**
384 * @dataProvider provideGetTextIdFromAddressInvalidArgumentException
385 */
386 public function testGetTextIdFromAddressInvalidArgumentException( $address ) {
387 $this->setExpectedException( InvalidArgumentException::class );
388 $store = $this->getBlobStore();
389 $store->getTextIdFromAddress( $address );
390 }
391
392 public function testMakeAddressFromTextId() {
393 $this->assertSame( 'tt:17', SqlBlobStore::makeAddressFromTextId( 17 ) );
394 }
395
396 }