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