Merge "Remove parameter 'options' from hook 'SkinEditSectionLinks'"
[lhc/web/wiklou.git] / tests / phpunit / includes / filebackend / FileBackendTest.php
1 <?php
2
3 use MediaWiki\MediaWikiServices;
4 use Wikimedia\TestingAccessWrapper;
5
6 /**
7 * @group FileRepo
8 * @group FileBackend
9 * @group medium
10 *
11 * @covers FileBackend
12 *
13 * @covers CopyFileOp
14 * @covers CreateFileOp
15 * @covers DeleteFileOp
16 * @covers DescribeFileOp
17 * @covers FSFile
18 * @covers FSFileBackend
19 * @covers FSFileBackendDirList
20 * @covers FSFileBackendFileList
21 * @covers FSFileBackendList
22 * @covers FSFileOpHandle
23 * @covers FileBackendDBRepoWrapper
24 * @covers FileBackendError
25 * @covers FileBackendGroup
26 * @covers FileBackendMultiWrite
27 * @covers FileBackendStore
28 * @covers FileBackendStoreOpHandle
29 * @covers FileBackendStoreShardDirIterator
30 * @covers FileBackendStoreShardFileIterator
31 * @covers FileBackendStoreShardListIterator
32 * @covers FileJournal
33 * @covers FileOp
34 * @covers FileOpBatch
35 * @covers HTTPFileStreamer
36 * @covers LockManagerGroup
37 * @covers MemoryFileBackend
38 * @covers MoveFileOp
39 * @covers MySqlLockManager
40 * @covers NullFileJournal
41 * @covers NullFileOp
42 * @covers StoreFileOp
43 * @covers TempFSFile
44 *
45 * @covers FSLockManager
46 * @covers LockManager
47 * @covers NullLockManager
48 */
49 class FileBackendTest extends MediaWikiTestCase {
50
51 /** @var FileBackend */
52 private $backend;
53 /** @var FileBackendMultiWrite */
54 private $multiBackend;
55 /** @var FSFileBackend */
56 public $singleBackend;
57 private static $backendToUse;
58
59 protected function setUp() {
60 global $wgFileBackends;
61 parent::setUp();
62 $tmpDir = $this->getNewTempDirectory();
63 if ( $this->getCliArg( 'use-filebackend' ) ) {
64 if ( self::$backendToUse ) {
65 $this->singleBackend = self::$backendToUse;
66 } else {
67 $name = $this->getCliArg( 'use-filebackend' );
68 $useConfig = [];
69 foreach ( $wgFileBackends as $conf ) {
70 if ( $conf['name'] == $name ) {
71 $useConfig = $conf;
72 break;
73 }
74 }
75 $useConfig['name'] = 'localtesting'; // swap name
76 $useConfig['shardViaHashLevels'] = [ // test sharding
77 'unittest-cont1' => [ 'levels' => 1, 'base' => 16, 'repeat' => 1 ]
78 ];
79 if ( isset( $useConfig['fileJournal'] ) ) {
80 $useConfig['fileJournal'] = FileJournal::factory( $useConfig['fileJournal'], $name );
81 }
82 $useConfig['lockManager'] = LockManagerGroup::singleton()->get( $useConfig['lockManager'] );
83 $class = $useConfig['class'];
84 self::$backendToUse = new $class( $useConfig );
85 $this->singleBackend = self::$backendToUse;
86 }
87 } else {
88 $this->singleBackend = new FSFileBackend( [
89 'name' => 'localtesting',
90 'lockManager' => LockManagerGroup::singleton()->get( 'fsLockManager' ),
91 'wikiId' => wfWikiID(),
92 'containerPaths' => [
93 'unittest-cont1' => "{$tmpDir}/localtesting-cont1",
94 'unittest-cont2' => "{$tmpDir}/localtesting-cont2" ]
95 ] );
96 }
97 $this->multiBackend = new FileBackendMultiWrite( [
98 'name' => 'localtesting',
99 'lockManager' => LockManagerGroup::singleton()->get( 'fsLockManager' ),
100 'parallelize' => 'implicit',
101 'wikiId' => 'testdb',
102 'backends' => [
103 [
104 'name' => 'localmultitesting1',
105 'class' => FSFileBackend::class,
106 'containerPaths' => [
107 'unittest-cont1' => "{$tmpDir}/localtestingmulti1-cont1",
108 'unittest-cont2' => "{$tmpDir}/localtestingmulti1-cont2" ],
109 'isMultiMaster' => false
110 ],
111 [
112 'name' => 'localmultitesting2',
113 'class' => FSFileBackend::class,
114 'containerPaths' => [
115 'unittest-cont1' => "{$tmpDir}/localtestingmulti2-cont1",
116 'unittest-cont2' => "{$tmpDir}/localtestingmulti2-cont2" ],
117 'isMultiMaster' => true
118 ]
119 ]
120 ] );
121 }
122
123 private static function baseStorePath() {
124 return 'mwstore://localtesting';
125 }
126
127 private function backendClass() {
128 return get_class( $this->backend );
129 }
130
131 /**
132 * @dataProvider provider_testIsStoragePath
133 */
134 public function testIsStoragePath( $path, $isStorePath ) {
135 $this->assertEquals( $isStorePath, FileBackend::isStoragePath( $path ),
136 "FileBackend::isStoragePath on path '$path'" );
137 }
138
139 public static function provider_testIsStoragePath() {
140 return [
141 [ 'mwstore://', true ],
142 [ 'mwstore://backend', true ],
143 [ 'mwstore://backend/container', true ],
144 [ 'mwstore://backend/container/', true ],
145 [ 'mwstore://backend/container/path', true ],
146 [ 'mwstore://backend//container/', true ],
147 [ 'mwstore://backend//container//', true ],
148 [ 'mwstore://backend//container//path', true ],
149 [ 'mwstore:///', true ],
150 [ 'mwstore:/', false ],
151 [ 'mwstore:', false ],
152 ];
153 }
154
155 /**
156 * @dataProvider provider_testSplitStoragePath
157 */
158 public function testSplitStoragePath( $path, $res ) {
159 $this->assertEquals( $res, FileBackend::splitStoragePath( $path ),
160 "FileBackend::splitStoragePath on path '$path'" );
161 }
162
163 public static function provider_testSplitStoragePath() {
164 return [
165 [ 'mwstore://backend/container', [ 'backend', 'container', '' ] ],
166 [ 'mwstore://backend/container/', [ 'backend', 'container', '' ] ],
167 [ 'mwstore://backend/container/path', [ 'backend', 'container', 'path' ] ],
168 [ 'mwstore://backend/container//path', [ 'backend', 'container', '/path' ] ],
169 [ 'mwstore://backend//container/path', [ null, null, null ] ],
170 [ 'mwstore://backend//container//path', [ null, null, null ] ],
171 [ 'mwstore://', [ null, null, null ] ],
172 [ 'mwstore://backend', [ null, null, null ] ],
173 [ 'mwstore:///', [ null, null, null ] ],
174 [ 'mwstore:/', [ null, null, null ] ],
175 [ 'mwstore:', [ null, null, null ] ]
176 ];
177 }
178
179 /**
180 * @dataProvider provider_normalizeStoragePath
181 */
182 public function testNormalizeStoragePath( $path, $res ) {
183 $this->assertEquals( $res, FileBackend::normalizeStoragePath( $path ),
184 "FileBackend::normalizeStoragePath on path '$path'" );
185 }
186
187 public static function provider_normalizeStoragePath() {
188 return [
189 [ 'mwstore://backend/container', 'mwstore://backend/container' ],
190 [ 'mwstore://backend/container/', 'mwstore://backend/container' ],
191 [ 'mwstore://backend/container/path', 'mwstore://backend/container/path' ],
192 [ 'mwstore://backend/container//path', 'mwstore://backend/container/path' ],
193 [ 'mwstore://backend/container///path', 'mwstore://backend/container/path' ],
194 [
195 'mwstore://backend/container///path//to///obj',
196 'mwstore://backend/container/path/to/obj'
197 ],
198 [ 'mwstore://', null ],
199 [ 'mwstore://backend', null ],
200 [ 'mwstore://backend//container/path', null ],
201 [ 'mwstore://backend//container//path', null ],
202 [ 'mwstore:///', null ],
203 [ 'mwstore:/', null ],
204 [ 'mwstore:', null ],
205 ];
206 }
207
208 /**
209 * @dataProvider provider_testParentStoragePath
210 */
211 public function testParentStoragePath( $path, $res ) {
212 $this->assertEquals( $res, FileBackend::parentStoragePath( $path ),
213 "FileBackend::parentStoragePath on path '$path'" );
214 }
215
216 public static function provider_testParentStoragePath() {
217 return [
218 [ 'mwstore://backend/container/path/to/obj', 'mwstore://backend/container/path/to' ],
219 [ 'mwstore://backend/container/path/to', 'mwstore://backend/container/path' ],
220 [ 'mwstore://backend/container/path', 'mwstore://backend/container' ],
221 [ 'mwstore://backend/container', null ],
222 [ 'mwstore://backend/container/path/to/obj/', 'mwstore://backend/container/path/to' ],
223 [ 'mwstore://backend/container/path/to/', 'mwstore://backend/container/path' ],
224 [ 'mwstore://backend/container/path/', 'mwstore://backend/container' ],
225 [ 'mwstore://backend/container/', null ],
226 ];
227 }
228
229 /**
230 * @dataProvider provider_testExtensionFromPath
231 */
232 public function testExtensionFromPath( $path, $res ) {
233 $this->assertEquals( $res, FileBackend::extensionFromPath( $path ),
234 "FileBackend::extensionFromPath on path '$path'" );
235 }
236
237 public static function provider_testExtensionFromPath() {
238 return [
239 [ 'mwstore://backend/container/path.txt', 'txt' ],
240 [ 'mwstore://backend/container/path.svg.png', 'png' ],
241 [ 'mwstore://backend/container/path', '' ],
242 [ 'mwstore://backend/container/path.', '' ],
243 ];
244 }
245
246 /**
247 * @dataProvider provider_testStore
248 */
249 public function testStore( $op ) {
250 $this->addTmpFiles( $op['src'] );
251
252 $this->backend = $this->singleBackend;
253 $this->tearDownFiles();
254 $this->doTestStore( $op );
255 $this->tearDownFiles();
256
257 $this->backend = $this->multiBackend;
258 $this->tearDownFiles();
259 $this->doTestStore( $op );
260 $this->tearDownFiles();
261 }
262
263 private function doTestStore( $op ) {
264 $backendName = $this->backendClass();
265
266 $source = $op['src'];
267 $dest = $op['dst'];
268 $this->prepare( [ 'dir' => dirname( $dest ) ] );
269
270 file_put_contents( $source, "Unit test file" );
271
272 if ( isset( $op['overwrite'] ) || isset( $op['overwriteSame'] ) ) {
273 $this->backend->store( $op );
274 }
275
276 $status = $this->backend->doOperation( $op );
277
278 $this->assertGoodStatus( $status,
279 "Store from $source to $dest succeeded without warnings ($backendName)." );
280 $this->assertEquals( true, $status->isOK(),
281 "Store from $source to $dest succeeded ($backendName)." );
282 $this->assertEquals( [ 0 => true ], $status->success,
283 "Store from $source to $dest has proper 'success' field in Status ($backendName)." );
284 $this->assertEquals( true, file_exists( $source ),
285 "Source file $source still exists ($backendName)." );
286 $this->assertEquals( true, $this->backend->fileExists( [ 'src' => $dest ] ),
287 "Destination file $dest exists ($backendName)." );
288
289 $this->assertEquals( filesize( $source ),
290 $this->backend->getFileSize( [ 'src' => $dest ] ),
291 "Destination file $dest has correct size ($backendName)." );
292
293 $props1 = FSFile::getPropsFromPath( $source );
294 $props2 = $this->backend->getFileProps( [ 'src' => $dest ] );
295 $this->assertEquals( $props1, $props2,
296 "Source and destination have the same props ($backendName)." );
297
298 $this->assertBackendPathsConsistent( [ $dest ] );
299 }
300
301 public static function provider_testStore() {
302 $cases = [];
303
304 $tmpName = TempFSFile::factory( "unittests_", 'txt', wfTempDir() )->getPath();
305 $toPath = self::baseStorePath() . '/unittest-cont1/e/fun/obj1.txt';
306 $op = [ 'op' => 'store', 'src' => $tmpName, 'dst' => $toPath ];
307 $cases[] = [ $op ];
308
309 $op2 = $op;
310 $op2['overwrite'] = true;
311 $cases[] = [ $op2 ];
312
313 $op3 = $op;
314 $op3['overwriteSame'] = true;
315 $cases[] = [ $op3 ];
316
317 return $cases;
318 }
319
320 /**
321 * @dataProvider provider_testCopy
322 */
323 public function testCopy( $op ) {
324 $this->backend = $this->singleBackend;
325 $this->tearDownFiles();
326 $this->doTestCopy( $op );
327 $this->tearDownFiles();
328
329 $this->backend = $this->multiBackend;
330 $this->tearDownFiles();
331 $this->doTestCopy( $op );
332 $this->tearDownFiles();
333 }
334
335 private function doTestCopy( $op ) {
336 $backendName = $this->backendClass();
337
338 $source = $op['src'];
339 $dest = $op['dst'];
340 $this->prepare( [ 'dir' => dirname( $source ) ] );
341 $this->prepare( [ 'dir' => dirname( $dest ) ] );
342
343 if ( isset( $op['ignoreMissingSource'] ) ) {
344 $status = $this->backend->doOperation( $op );
345 $this->assertGoodStatus( $status,
346 "Move from $source to $dest succeeded without warnings ($backendName)." );
347 $this->assertEquals( [ 0 => true ], $status->success,
348 "Move from $source to $dest has proper 'success' field in Status ($backendName)." );
349 $this->assertEquals( false, $this->backend->fileExists( [ 'src' => $source ] ),
350 "Source file $source does not exist ($backendName)." );
351 $this->assertEquals( false, $this->backend->fileExists( [ 'src' => $dest ] ),
352 "Destination file $dest does not exist ($backendName)." );
353
354 return;
355 }
356
357 $status = $this->backend->doOperation(
358 [ 'op' => 'create', 'content' => 'blahblah', 'dst' => $source ] );
359 $this->assertGoodStatus( $status,
360 "Creation of file at $source succeeded ($backendName)." );
361
362 if ( isset( $op['overwrite'] ) || isset( $op['overwriteSame'] ) ) {
363 $this->backend->copy( $op );
364 }
365
366 $status = $this->backend->doOperation( $op );
367
368 $this->assertGoodStatus( $status,
369 "Copy from $source to $dest succeeded without warnings ($backendName)." );
370 $this->assertEquals( true, $status->isOK(),
371 "Copy from $source to $dest succeeded ($backendName)." );
372 $this->assertEquals( [ 0 => true ], $status->success,
373 "Copy from $source to $dest has proper 'success' field in Status ($backendName)." );
374 $this->assertEquals( true, $this->backend->fileExists( [ 'src' => $source ] ),
375 "Source file $source still exists ($backendName)." );
376 $this->assertEquals( true, $this->backend->fileExists( [ 'src' => $dest ] ),
377 "Destination file $dest exists after copy ($backendName)." );
378
379 $this->assertEquals(
380 $this->backend->getFileSize( [ 'src' => $source ] ),
381 $this->backend->getFileSize( [ 'src' => $dest ] ),
382 "Destination file $dest has correct size ($backendName)." );
383
384 $props1 = $this->backend->getFileProps( [ 'src' => $source ] );
385 $props2 = $this->backend->getFileProps( [ 'src' => $dest ] );
386 $this->assertEquals( $props1, $props2,
387 "Source and destination have the same props ($backendName)." );
388
389 $this->assertBackendPathsConsistent( [ $source, $dest ] );
390 }
391
392 public static function provider_testCopy() {
393 $cases = [];
394
395 $source = self::baseStorePath() . '/unittest-cont1/e/file.txt';
396 $dest = self::baseStorePath() . '/unittest-cont2/a/fileMoved.txt';
397
398 $op = [ 'op' => 'copy', 'src' => $source, 'dst' => $dest ];
399 $cases[] = [
400 $op, // operation
401 $source, // source
402 $dest, // dest
403 ];
404
405 $op2 = $op;
406 $op2['overwrite'] = true;
407 $cases[] = [
408 $op2, // operation
409 $source, // source
410 $dest, // dest
411 ];
412
413 $op2 = $op;
414 $op2['overwriteSame'] = true;
415 $cases[] = [
416 $op2, // operation
417 $source, // source
418 $dest, // dest
419 ];
420
421 $op2 = $op;
422 $op2['ignoreMissingSource'] = true;
423 $cases[] = [
424 $op2, // operation
425 $source, // source
426 $dest, // dest
427 ];
428
429 $op2 = $op;
430 $op2['ignoreMissingSource'] = true;
431 $cases[] = [
432 $op2, // operation
433 self::baseStorePath() . '/unittest-cont-bad/e/file.txt', // source
434 $dest, // dest
435 ];
436
437 return $cases;
438 }
439
440 /**
441 * @dataProvider provider_testMove
442 */
443 public function testMove( $op ) {
444 $this->backend = $this->singleBackend;
445 $this->tearDownFiles();
446 $this->doTestMove( $op );
447 $this->tearDownFiles();
448
449 $this->backend = $this->multiBackend;
450 $this->tearDownFiles();
451 $this->doTestMove( $op );
452 $this->tearDownFiles();
453 }
454
455 private function doTestMove( $op ) {
456 $backendName = $this->backendClass();
457
458 $source = $op['src'];
459 $dest = $op['dst'];
460 $this->prepare( [ 'dir' => dirname( $source ) ] );
461 $this->prepare( [ 'dir' => dirname( $dest ) ] );
462
463 if ( isset( $op['ignoreMissingSource'] ) ) {
464 $status = $this->backend->doOperation( $op );
465 $this->assertGoodStatus( $status,
466 "Move from $source to $dest succeeded without warnings ($backendName)." );
467 $this->assertEquals( [ 0 => true ], $status->success,
468 "Move from $source to $dest has proper 'success' field in Status ($backendName)." );
469 $this->assertEquals( false, $this->backend->fileExists( [ 'src' => $source ] ),
470 "Source file $source does not exist ($backendName)." );
471 $this->assertEquals( false, $this->backend->fileExists( [ 'src' => $dest ] ),
472 "Destination file $dest does not exist ($backendName)." );
473
474 return;
475 }
476
477 $status = $this->backend->doOperation(
478 [ 'op' => 'create', 'content' => 'blahblah', 'dst' => $source ] );
479 $this->assertGoodStatus( $status,
480 "Creation of file at $source succeeded ($backendName)." );
481
482 if ( isset( $op['overwrite'] ) || isset( $op['overwriteSame'] ) ) {
483 $this->backend->copy( $op );
484 }
485
486 $status = $this->backend->doOperation( $op );
487 $this->assertGoodStatus( $status,
488 "Move from $source to $dest succeeded without warnings ($backendName)." );
489 $this->assertEquals( true, $status->isOK(),
490 "Move from $source to $dest succeeded ($backendName)." );
491 $this->assertEquals( [ 0 => true ], $status->success,
492 "Move from $source to $dest has proper 'success' field in Status ($backendName)." );
493 $this->assertEquals( false, $this->backend->fileExists( [ 'src' => $source ] ),
494 "Source file $source does not still exists ($backendName)." );
495 $this->assertEquals( true, $this->backend->fileExists( [ 'src' => $dest ] ),
496 "Destination file $dest exists after move ($backendName)." );
497
498 $this->assertNotEquals(
499 $this->backend->getFileSize( [ 'src' => $source ] ),
500 $this->backend->getFileSize( [ 'src' => $dest ] ),
501 "Destination file $dest has correct size ($backendName)." );
502
503 $props1 = $this->backend->getFileProps( [ 'src' => $source ] );
504 $props2 = $this->backend->getFileProps( [ 'src' => $dest ] );
505 $this->assertEquals( false, $props1['fileExists'],
506 "Source file does not exist accourding to props ($backendName)." );
507 $this->assertEquals( true, $props2['fileExists'],
508 "Destination file exists accourding to props ($backendName)." );
509
510 $this->assertBackendPathsConsistent( [ $source, $dest ] );
511 }
512
513 public static function provider_testMove() {
514 $cases = [];
515
516 $source = self::baseStorePath() . '/unittest-cont1/e/file.txt';
517 $dest = self::baseStorePath() . '/unittest-cont2/a/fileMoved.txt';
518
519 $op = [ 'op' => 'move', 'src' => $source, 'dst' => $dest ];
520 $cases[] = [
521 $op, // operation
522 $source, // source
523 $dest, // dest
524 ];
525
526 $op2 = $op;
527 $op2['overwrite'] = true;
528 $cases[] = [
529 $op2, // operation
530 $source, // source
531 $dest, // dest
532 ];
533
534 $op2 = $op;
535 $op2['overwriteSame'] = true;
536 $cases[] = [
537 $op2, // operation
538 $source, // source
539 $dest, // dest
540 ];
541
542 $op2 = $op;
543 $op2['ignoreMissingSource'] = true;
544 $cases[] = [
545 $op2, // operation
546 $source, // source
547 $dest, // dest
548 ];
549
550 $op2 = $op;
551 $op2['ignoreMissingSource'] = true;
552 $cases[] = [
553 $op2, // operation
554 self::baseStorePath() . '/unittest-cont-bad/e/file.txt', // source
555 $dest, // dest
556 ];
557
558 return $cases;
559 }
560
561 /**
562 * @dataProvider provider_testDelete
563 */
564 public function testDelete( $op, $withSource, $okStatus ) {
565 $this->backend = $this->singleBackend;
566 $this->tearDownFiles();
567 $this->doTestDelete( $op, $withSource, $okStatus );
568 $this->tearDownFiles();
569
570 $this->backend = $this->multiBackend;
571 $this->tearDownFiles();
572 $this->doTestDelete( $op, $withSource, $okStatus );
573 $this->tearDownFiles();
574 }
575
576 private function doTestDelete( $op, $withSource, $okStatus ) {
577 $backendName = $this->backendClass();
578
579 $source = $op['src'];
580 $this->prepare( [ 'dir' => dirname( $source ) ] );
581
582 if ( $withSource ) {
583 $status = $this->backend->doOperation(
584 [ 'op' => 'create', 'content' => 'blahblah', 'dst' => $source ] );
585 $this->assertGoodStatus( $status,
586 "Creation of file at $source succeeded ($backendName)." );
587 }
588
589 $status = $this->backend->doOperation( $op );
590 if ( $okStatus ) {
591 $this->assertGoodStatus( $status,
592 "Deletion of file at $source succeeded without warnings ($backendName)." );
593 $this->assertEquals( true, $status->isOK(),
594 "Deletion of file at $source succeeded ($backendName)." );
595 $this->assertEquals( [ 0 => true ], $status->success,
596 "Deletion of file at $source has proper 'success' field in Status ($backendName)." );
597 } else {
598 $this->assertEquals( false, $status->isOK(),
599 "Deletion of file at $source failed ($backendName)." );
600 }
601
602 $this->assertEquals( false, $this->backend->fileExists( [ 'src' => $source ] ),
603 "Source file $source does not exist after move ($backendName)." );
604
605 $this->assertFalse(
606 $this->backend->getFileSize( [ 'src' => $source ] ),
607 "Source file $source has correct size (false) ($backendName)." );
608
609 $props1 = $this->backend->getFileProps( [ 'src' => $source ] );
610 $this->assertFalse( $props1['fileExists'],
611 "Source file $source does not exist according to props ($backendName)." );
612
613 $this->assertBackendPathsConsistent( [ $source ] );
614 }
615
616 public static function provider_testDelete() {
617 $cases = [];
618
619 $source = self::baseStorePath() . '/unittest-cont1/e/myfacefile.txt';
620
621 $op = [ 'op' => 'delete', 'src' => $source ];
622 $cases[] = [
623 $op, // operation
624 true, // with source
625 true // succeeds
626 ];
627
628 $cases[] = [
629 $op, // operation
630 false, // without source
631 false // fails
632 ];
633
634 $op['ignoreMissingSource'] = true;
635 $cases[] = [
636 $op, // operation
637 false, // without source
638 true // succeeds
639 ];
640
641 $op['ignoreMissingSource'] = true;
642 $op['src'] = self::baseStorePath() . '/unittest-cont-bad/e/file.txt';
643 $cases[] = [
644 $op, // operation
645 false, // without source
646 true // succeeds
647 ];
648
649 return $cases;
650 }
651
652 /**
653 * @dataProvider provider_testDescribe
654 */
655 public function testDescribe( $op, $withSource, $okStatus ) {
656 $this->backend = $this->singleBackend;
657 $this->tearDownFiles();
658 $this->doTestDescribe( $op, $withSource, $okStatus );
659 $this->tearDownFiles();
660
661 $this->backend = $this->multiBackend;
662 $this->tearDownFiles();
663 $this->doTestDescribe( $op, $withSource, $okStatus );
664 $this->tearDownFiles();
665 }
666
667 private function doTestDescribe( $op, $withSource, $okStatus ) {
668 $backendName = $this->backendClass();
669
670 $source = $op['src'];
671 $this->prepare( [ 'dir' => dirname( $source ) ] );
672
673 if ( $withSource ) {
674 $status = $this->backend->doOperation(
675 [ 'op' => 'create', 'content' => 'blahblah', 'dst' => $source,
676 'headers' => [ 'Content-Disposition' => 'xxx' ] ] );
677 $this->assertGoodStatus( $status,
678 "Creation of file at $source succeeded ($backendName)." );
679 if ( $this->backend->hasFeatures( FileBackend::ATTR_HEADERS ) ) {
680 $attr = $this->backend->getFileXAttributes( [ 'src' => $source ] );
681 $this->assertHasHeaders( [ 'Content-Disposition' => 'xxx' ], $attr );
682 }
683
684 $status = $this->backend->describe( [ 'src' => $source,
685 'headers' => [ 'Content-Disposition' => '' ] ] ); // remove
686 $this->assertGoodStatus( $status,
687 "Removal of header for $source succeeded ($backendName)." );
688
689 if ( $this->backend->hasFeatures( FileBackend::ATTR_HEADERS ) ) {
690 $attr = $this->backend->getFileXAttributes( [ 'src' => $source ] );
691 $this->assertFalse( isset( $attr['headers']['content-disposition'] ),
692 "File 'Content-Disposition' header removed." );
693 }
694 }
695
696 $status = $this->backend->doOperation( $op );
697 if ( $okStatus ) {
698 $this->assertGoodStatus( $status,
699 "Describe of file at $source succeeded without warnings ($backendName)." );
700 $this->assertEquals( true, $status->isOK(),
701 "Describe of file at $source succeeded ($backendName)." );
702 $this->assertEquals( [ 0 => true ], $status->success,
703 "Describe of file at $source has proper 'success' field in Status ($backendName)." );
704 if ( $this->backend->hasFeatures( FileBackend::ATTR_HEADERS ) ) {
705 $attr = $this->backend->getFileXAttributes( [ 'src' => $source ] );
706 $this->assertHasHeaders( $op['headers'], $attr );
707 }
708 } else {
709 $this->assertEquals( false, $status->isOK(),
710 "Describe of file at $source failed ($backendName)." );
711 }
712
713 $this->assertBackendPathsConsistent( [ $source ] );
714 }
715
716 private function assertHasHeaders( array $headers, array $attr ) {
717 foreach ( $headers as $n => $v ) {
718 if ( $n !== '' ) {
719 $this->assertTrue( isset( $attr['headers'][strtolower( $n )] ),
720 "File has '$n' header." );
721 $this->assertEquals( $v, $attr['headers'][strtolower( $n )],
722 "File has '$n' header value." );
723 } else {
724 $this->assertFalse( isset( $attr['headers'][strtolower( $n )] ),
725 "File does not have '$n' header." );
726 }
727 }
728 }
729
730 public static function provider_testDescribe() {
731 $cases = [];
732
733 $source = self::baseStorePath() . '/unittest-cont1/e/myfacefile.txt';
734
735 $op = [ 'op' => 'describe', 'src' => $source,
736 'headers' => [ 'Content-Disposition' => 'inline' ], ];
737 $cases[] = [
738 $op, // operation
739 true, // with source
740 true // succeeds
741 ];
742
743 $cases[] = [
744 $op, // operation
745 false, // without source
746 false // fails
747 ];
748
749 return $cases;
750 }
751
752 /**
753 * @dataProvider provider_testCreate
754 */
755 public function testCreate( $op, $alreadyExists, $okStatus, $newSize ) {
756 $this->backend = $this->singleBackend;
757 $this->tearDownFiles();
758 $this->doTestCreate( $op, $alreadyExists, $okStatus, $newSize );
759 $this->tearDownFiles();
760
761 $this->backend = $this->multiBackend;
762 $this->tearDownFiles();
763 $this->doTestCreate( $op, $alreadyExists, $okStatus, $newSize );
764 $this->tearDownFiles();
765 }
766
767 private function doTestCreate( $op, $alreadyExists, $okStatus, $newSize ) {
768 $backendName = $this->backendClass();
769
770 $dest = $op['dst'];
771 $this->prepare( [ 'dir' => dirname( $dest ) ] );
772
773 $oldText = 'blah...blah...waahwaah';
774 if ( $alreadyExists ) {
775 $status = $this->backend->doOperation(
776 [ 'op' => 'create', 'content' => $oldText, 'dst' => $dest ] );
777 $this->assertGoodStatus( $status,
778 "Creation of file at $dest succeeded ($backendName)." );
779 }
780
781 $status = $this->backend->doOperation( $op );
782 if ( $okStatus ) {
783 $this->assertGoodStatus( $status,
784 "Creation of file at $dest succeeded without warnings ($backendName)." );
785 $this->assertEquals( true, $status->isOK(),
786 "Creation of file at $dest succeeded ($backendName)." );
787 $this->assertEquals( [ 0 => true ], $status->success,
788 "Creation of file at $dest has proper 'success' field in Status ($backendName)." );
789 } else {
790 $this->assertEquals( false, $status->isOK(),
791 "Creation of file at $dest failed ($backendName)." );
792 }
793
794 $this->assertEquals( true, $this->backend->fileExists( [ 'src' => $dest ] ),
795 "Destination file $dest exists after creation ($backendName)." );
796
797 $props1 = $this->backend->getFileProps( [ 'src' => $dest ] );
798 $this->assertEquals( true, $props1['fileExists'],
799 "Destination file $dest exists according to props ($backendName)." );
800 if ( $okStatus ) { // file content is what we saved
801 $this->assertEquals( $newSize, $props1['size'],
802 "Destination file $dest has expected size according to props ($backendName)." );
803 $this->assertEquals( $newSize,
804 $this->backend->getFileSize( [ 'src' => $dest ] ),
805 "Destination file $dest has correct size ($backendName)." );
806 } else { // file content is some other previous text
807 $this->assertEquals( strlen( $oldText ), $props1['size'],
808 "Destination file $dest has original size according to props ($backendName)." );
809 $this->assertEquals( strlen( $oldText ),
810 $this->backend->getFileSize( [ 'src' => $dest ] ),
811 "Destination file $dest has original size according to props ($backendName)." );
812 }
813
814 $this->assertBackendPathsConsistent( [ $dest ] );
815 }
816
817 /**
818 * @dataProvider provider_testCreate
819 */
820 public static function provider_testCreate() {
821 $cases = [];
822
823 $dest = self::baseStorePath() . '/unittest-cont2/a/myspacefile.txt';
824
825 $op = [ 'op' => 'create', 'content' => 'test test testing', 'dst' => $dest ];
826 $cases[] = [
827 $op, // operation
828 false, // no dest already exists
829 true, // succeeds
830 strlen( $op['content'] )
831 ];
832
833 $op2 = $op;
834 $op2['content'] = "\n";
835 $cases[] = [
836 $op2, // operation
837 false, // no dest already exists
838 true, // succeeds
839 strlen( $op2['content'] )
840 ];
841
842 $op2 = $op;
843 $op2['content'] = "fsf\n waf 3kt";
844 $cases[] = [
845 $op2, // operation
846 true, // dest already exists
847 false, // fails
848 strlen( $op2['content'] )
849 ];
850
851 $op2 = $op;
852 $op2['content'] = "egm'g gkpe gpqg eqwgwqg";
853 $op2['overwrite'] = true;
854 $cases[] = [
855 $op2, // operation
856 true, // dest already exists
857 true, // succeeds
858 strlen( $op2['content'] )
859 ];
860
861 $op2 = $op;
862 $op2['content'] = "39qjmg3-qg";
863 $op2['overwriteSame'] = true;
864 $cases[] = [
865 $op2, // operation
866 true, // dest already exists
867 false, // succeeds
868 strlen( $op2['content'] )
869 ];
870
871 return $cases;
872 }
873
874 public function testDoQuickOperations() {
875 $this->backend = $this->singleBackend;
876 $this->doTestDoQuickOperations();
877 $this->tearDownFiles();
878
879 $this->backend = $this->multiBackend;
880 $this->doTestDoQuickOperations();
881 $this->tearDownFiles();
882 }
883
884 private function doTestDoQuickOperations() {
885 $backendName = $this->backendClass();
886
887 $base = self::baseStorePath();
888 $files = [
889 "$base/unittest-cont1/e/fileA.a",
890 "$base/unittest-cont1/e/fileB.a",
891 "$base/unittest-cont1/e/fileC.a"
892 ];
893 $createOps = [];
894 $purgeOps = [];
895 foreach ( $files as $path ) {
896 $status = $this->prepare( [ 'dir' => dirname( $path ) ] );
897 $this->assertGoodStatus( $status,
898 "Preparing $path succeeded without warnings ($backendName)." );
899 $createOps[] = [ 'op' => 'create', 'dst' => $path, 'content' => mt_rand( 0, 50000 ) ];
900 $copyOps[] = [ 'op' => 'copy', 'src' => $path, 'dst' => "$path-2" ];
901 $moveOps[] = [ 'op' => 'move', 'src' => "$path-2", 'dst' => "$path-3" ];
902 $purgeOps[] = [ 'op' => 'delete', 'src' => $path ];
903 $purgeOps[] = [ 'op' => 'delete', 'src' => "$path-3" ];
904 }
905 $purgeOps[] = [ 'op' => 'null' ];
906
907 $this->assertGoodStatus(
908 $this->backend->doQuickOperations( $createOps ),
909 "Creation of source files succeeded ($backendName)." );
910 foreach ( $files as $file ) {
911 $this->assertTrue( $this->backend->fileExists( [ 'src' => $file ] ),
912 "File $file exists." );
913 }
914
915 $this->assertGoodStatus(
916 $this->backend->doQuickOperations( $copyOps ),
917 "Quick copy of source files succeeded ($backendName)." );
918 foreach ( $files as $file ) {
919 $this->assertTrue( $this->backend->fileExists( [ 'src' => "$file-2" ] ),
920 "File $file-2 exists." );
921 }
922
923 $this->assertGoodStatus(
924 $this->backend->doQuickOperations( $moveOps ),
925 "Quick move of source files succeeded ($backendName)." );
926 foreach ( $files as $file ) {
927 $this->assertTrue( $this->backend->fileExists( [ 'src' => "$file-3" ] ),
928 "File $file-3 move in." );
929 $this->assertFalse( $this->backend->fileExists( [ 'src' => "$file-2" ] ),
930 "File $file-2 moved away." );
931 }
932
933 $this->assertGoodStatus(
934 $this->backend->quickCopy( [ 'src' => $files[0], 'dst' => $files[0] ] ),
935 "Copy of file {$files[0]} over itself succeeded ($backendName)." );
936 $this->assertTrue( $this->backend->fileExists( [ 'src' => $files[0] ] ),
937 "File {$files[0]} still exists." );
938
939 $this->assertGoodStatus(
940 $this->backend->quickMove( [ 'src' => $files[0], 'dst' => $files[0] ] ),
941 "Move of file {$files[0]} over itself succeeded ($backendName)." );
942 $this->assertTrue( $this->backend->fileExists( [ 'src' => $files[0] ] ),
943 "File {$files[0]} still exists." );
944
945 $this->assertGoodStatus(
946 $this->backend->doQuickOperations( $purgeOps ),
947 "Quick deletion of source files succeeded ($backendName)." );
948 foreach ( $files as $file ) {
949 $this->assertFalse( $this->backend->fileExists( [ 'src' => $file ] ),
950 "File $file purged." );
951 $this->assertFalse( $this->backend->fileExists( [ 'src' => "$file-3" ] ),
952 "File $file-3 purged." );
953 }
954 }
955
956 /**
957 * @dataProvider provider_testConcatenate
958 */
959 public function testConcatenate( $op, $srcs, $srcsContent, $alreadyExists, $okStatus ) {
960 $this->backend = $this->singleBackend;
961 $this->tearDownFiles();
962 $this->doTestConcatenate( $op, $srcs, $srcsContent, $alreadyExists, $okStatus );
963 $this->tearDownFiles();
964
965 $this->backend = $this->multiBackend;
966 $this->tearDownFiles();
967 $this->doTestConcatenate( $op, $srcs, $srcsContent, $alreadyExists, $okStatus );
968 $this->tearDownFiles();
969 }
970
971 private function doTestConcatenate( $params, $srcs, $srcsContent, $alreadyExists, $okStatus ) {
972 $backendName = $this->backendClass();
973
974 $expContent = '';
975 // Create sources
976 $ops = [];
977 foreach ( $srcs as $i => $source ) {
978 $this->prepare( [ 'dir' => dirname( $source ) ] );
979 $ops[] = [
980 'op' => 'create', // operation
981 'dst' => $source, // source
982 'content' => $srcsContent[$i]
983 ];
984 $expContent .= $srcsContent[$i];
985 }
986 $status = $this->backend->doOperations( $ops );
987
988 $this->assertGoodStatus( $status,
989 "Creation of source files succeeded ($backendName)." );
990
991 $dest = $params['dst'] = $this->getNewTempFile();
992 if ( $alreadyExists ) {
993 $ok = file_put_contents( $dest, 'blah...blah...waahwaah' ) !== false;
994 $this->assertEquals( true, $ok,
995 "Creation of file at $dest succeeded ($backendName)." );
996 } else {
997 $ok = file_put_contents( $dest, '' ) !== false;
998 $this->assertEquals( true, $ok,
999 "Creation of 0-byte file at $dest succeeded ($backendName)." );
1000 }
1001
1002 // Combine the files into one
1003 $status = $this->backend->concatenate( $params );
1004 if ( $okStatus ) {
1005 $this->assertGoodStatus( $status,
1006 "Creation of concat file at $dest succeeded without warnings ($backendName)." );
1007 $this->assertEquals( true, $status->isOK(),
1008 "Creation of concat file at $dest succeeded ($backendName)." );
1009 } else {
1010 $this->assertEquals( false, $status->isOK(),
1011 "Creation of concat file at $dest failed ($backendName)." );
1012 }
1013
1014 if ( $okStatus ) {
1015 $this->assertEquals( true, is_file( $dest ),
1016 "Dest concat file $dest exists after creation ($backendName)." );
1017 } else {
1018 $this->assertEquals( true, is_file( $dest ),
1019 "Dest concat file $dest exists after failed creation ($backendName)." );
1020 }
1021
1022 $contents = file_get_contents( $dest );
1023 $this->assertNotEquals( false, $contents, "File at $dest exists ($backendName)." );
1024
1025 if ( $okStatus ) {
1026 $this->assertEquals( $expContent, $contents,
1027 "Concat file at $dest has correct contents ($backendName)." );
1028 } else {
1029 $this->assertNotEquals( $expContent, $contents,
1030 "Concat file at $dest has correct contents ($backendName)." );
1031 }
1032 }
1033
1034 public static function provider_testConcatenate() {
1035 $cases = [];
1036
1037 $srcs = [
1038 self::baseStorePath() . '/unittest-cont1/e/file1.txt',
1039 self::baseStorePath() . '/unittest-cont1/e/file2.txt',
1040 self::baseStorePath() . '/unittest-cont1/e/file3.txt',
1041 self::baseStorePath() . '/unittest-cont1/e/file4.txt',
1042 self::baseStorePath() . '/unittest-cont1/e/file5.txt',
1043 self::baseStorePath() . '/unittest-cont1/e/file6.txt',
1044 self::baseStorePath() . '/unittest-cont1/e/file7.txt',
1045 self::baseStorePath() . '/unittest-cont1/e/file8.txt',
1046 self::baseStorePath() . '/unittest-cont1/e/file9.txt',
1047 self::baseStorePath() . '/unittest-cont1/e/file10.txt'
1048 ];
1049 $content = [
1050 'egfage',
1051 'ageageag',
1052 'rhokohlr',
1053 'shgmslkg',
1054 'kenga',
1055 'owagmal',
1056 'kgmae',
1057 'g eak;g',
1058 'lkaem;a',
1059 'legma'
1060 ];
1061 $params = [ 'srcs' => $srcs ];
1062
1063 $cases[] = [
1064 $params, // operation
1065 $srcs, // sources
1066 $content, // content for each source
1067 false, // no dest already exists
1068 true, // succeeds
1069 ];
1070
1071 $cases[] = [
1072 $params, // operation
1073 $srcs, // sources
1074 $content, // content for each source
1075 true, // dest already exists
1076 false, // succeeds
1077 ];
1078
1079 return $cases;
1080 }
1081
1082 /**
1083 * @dataProvider provider_testGetFileStat
1084 */
1085 public function testGetFileStat( $path, $content, $alreadyExists ) {
1086 $this->backend = $this->singleBackend;
1087 $this->tearDownFiles();
1088 $this->doTestGetFileStat( $path, $content, $alreadyExists );
1089 $this->tearDownFiles();
1090
1091 $this->backend = $this->multiBackend;
1092 $this->tearDownFiles();
1093 $this->doTestGetFileStat( $path, $content, $alreadyExists );
1094 $this->tearDownFiles();
1095 }
1096
1097 private function doTestGetFileStat( $path, $content, $alreadyExists ) {
1098 $backendName = $this->backendClass();
1099
1100 if ( $alreadyExists ) {
1101 $this->prepare( [ 'dir' => dirname( $path ) ] );
1102 $status = $this->create( [ 'dst' => $path, 'content' => $content ] );
1103 $this->assertGoodStatus( $status,
1104 "Creation of file at $path succeeded ($backendName)." );
1105
1106 $size = $this->backend->getFileSize( [ 'src' => $path ] );
1107 $time = $this->backend->getFileTimestamp( [ 'src' => $path ] );
1108 $stat = $this->backend->getFileStat( [ 'src' => $path ] );
1109
1110 $this->assertEquals( strlen( $content ), $size,
1111 "Correct file size of '$path'" );
1112 $this->assertTrue( abs( time() - wfTimestamp( TS_UNIX, $time ) ) < 10,
1113 "Correct file timestamp of '$path'" );
1114
1115 $size = $stat['size'];
1116 $time = $stat['mtime'];
1117 $this->assertEquals( strlen( $content ), $size,
1118 "Correct file size of '$path'" );
1119 $this->assertTrue( abs( time() - wfTimestamp( TS_UNIX, $time ) ) < 10,
1120 "Correct file timestamp of '$path'" );
1121
1122 $this->backend->clearCache( [ $path ] );
1123
1124 $size = $this->backend->getFileSize( [ 'src' => $path ] );
1125
1126 $this->assertEquals( strlen( $content ), $size,
1127 "Correct file size of '$path'" );
1128
1129 $this->backend->preloadCache( [ $path ] );
1130
1131 $size = $this->backend->getFileSize( [ 'src' => $path ] );
1132
1133 $this->assertEquals( strlen( $content ), $size,
1134 "Correct file size of '$path'" );
1135 } else {
1136 $size = $this->backend->getFileSize( [ 'src' => $path ] );
1137 $time = $this->backend->getFileTimestamp( [ 'src' => $path ] );
1138 $stat = $this->backend->getFileStat( [ 'src' => $path ] );
1139
1140 $this->assertFalse( $size, "Correct file size of '$path'" );
1141 $this->assertFalse( $time, "Correct file timestamp of '$path'" );
1142 $this->assertFalse( $stat, "Correct file stat of '$path'" );
1143 }
1144 }
1145
1146 public static function provider_testGetFileStat() {
1147 $cases = [];
1148
1149 $base = self::baseStorePath();
1150 $cases[] = [ "$base/unittest-cont1/e/b/z/some_file.txt", "some file contents", true ];
1151 $cases[] = [ "$base/unittest-cont1/e/b/some-other_file.txt", "", true ];
1152 $cases[] = [ "$base/unittest-cont1/e/b/some-diff_file.txt", null, false ];
1153
1154 return $cases;
1155 }
1156
1157 /**
1158 * @dataProvider provider_testGetFileStat
1159 */
1160 public function testStreamFile( $path, $content, $alreadyExists ) {
1161 $this->backend = $this->singleBackend;
1162 $this->tearDownFiles();
1163 $this->doTestStreamFile( $path, $content, $alreadyExists );
1164 $this->tearDownFiles();
1165
1166 $this->backend = $this->multiBackend;
1167 $this->tearDownFiles();
1168 $this->doTestStreamFile( $path, $content, $alreadyExists );
1169 $this->tearDownFiles();
1170 }
1171
1172 private function doTestStreamFile( $path, $content ) {
1173 $backendName = $this->backendClass();
1174
1175 if ( $content !== null ) {
1176 $this->prepare( [ 'dir' => dirname( $path ) ] );
1177 $status = $this->create( [ 'dst' => $path, 'content' => $content ] );
1178 $this->assertGoodStatus( $status,
1179 "Creation of file at $path succeeded ($backendName)." );
1180
1181 ob_start();
1182 $this->backend->streamFile( [ 'src' => $path, 'headless' => 1, 'allowOB' => 1 ] );
1183 $data = ob_get_contents();
1184 ob_end_clean();
1185
1186 $this->assertEquals( $content, $data, "Correct content streamed from '$path'" );
1187 } else { // 404 case
1188 ob_start();
1189 $this->backend->streamFile( [ 'src' => $path, 'headless' => 1, 'allowOB' => 1 ] );
1190 $data = ob_get_contents();
1191 ob_end_clean();
1192
1193 $this->assertRegExp( '#<h1>File not found</h1>#', $data,
1194 "Correct content streamed from '$path' ($backendName)" );
1195 }
1196 }
1197
1198 public static function provider_testStreamFile() {
1199 $cases = [];
1200
1201 $base = self::baseStorePath();
1202 $cases[] = [ "$base/unittest-cont1/e/b/z/some_file.txt", "some file contents" ];
1203 $cases[] = [ "$base/unittest-cont1/e/b/some-other_file.txt", null ];
1204
1205 return $cases;
1206 }
1207
1208 public function testStreamFileRange() {
1209 $this->backend = $this->singleBackend;
1210 $this->tearDownFiles();
1211 $this->doTestStreamFileRange();
1212 $this->tearDownFiles();
1213
1214 $this->backend = $this->multiBackend;
1215 $this->tearDownFiles();
1216 $this->doTestStreamFileRange();
1217 $this->tearDownFiles();
1218 }
1219
1220 private function doTestStreamFileRange() {
1221 $backendName = $this->backendClass();
1222
1223 $base = self::baseStorePath();
1224 $path = "$base/unittest-cont1/e/b/z/range_file.txt";
1225 $content = "0123456789ABCDEF";
1226
1227 $this->prepare( [ 'dir' => dirname( $path ) ] );
1228 $status = $this->create( [ 'dst' => $path, 'content' => $content ] );
1229 $this->assertGoodStatus( $status,
1230 "Creation of file at $path succeeded ($backendName)." );
1231
1232 static $ranges = [
1233 'bytes=0-0' => '0',
1234 'bytes=0-3' => '0123',
1235 'bytes=4-8' => '45678',
1236 'bytes=15-15' => 'F',
1237 'bytes=14-15' => 'EF',
1238 'bytes=-5' => 'BCDEF',
1239 'bytes=-1' => 'F',
1240 'bytes=10-16' => 'ABCDEF',
1241 'bytes=10-99' => 'ABCDEF',
1242 ];
1243
1244 foreach ( $ranges as $range => $chunk ) {
1245 ob_start();
1246 $this->backend->streamFile( [ 'src' => $path, 'headless' => 1, 'allowOB' => 1,
1247 'options' => [ 'range' => $range ] ] );
1248 $data = ob_get_contents();
1249 ob_end_clean();
1250
1251 $this->assertEquals( $chunk, $data, "Correct chunk streamed from '$path' for '$range'" );
1252 }
1253 }
1254
1255 /**
1256 * @dataProvider provider_testGetFileContents
1257 */
1258 public function testGetFileContents( $source, $content ) {
1259 $this->backend = $this->singleBackend;
1260 $this->tearDownFiles();
1261 $this->doTestGetFileContents( $source, $content );
1262 $this->tearDownFiles();
1263
1264 $this->backend = $this->multiBackend;
1265 $this->tearDownFiles();
1266 $this->doTestGetFileContents( $source, $content );
1267 $this->tearDownFiles();
1268 }
1269
1270 private function doTestGetFileContents( $source, $content ) {
1271 $backendName = $this->backendClass();
1272
1273 $srcs = (array)$source;
1274 $content = (array)$content;
1275 foreach ( $srcs as $i => $src ) {
1276 $this->prepare( [ 'dir' => dirname( $src ) ] );
1277 $status = $this->backend->doOperation(
1278 [ 'op' => 'create', 'content' => $content[$i], 'dst' => $src ] );
1279 $this->assertGoodStatus( $status,
1280 "Creation of file at $src succeeded ($backendName)." );
1281 }
1282
1283 if ( is_array( $source ) ) {
1284 $contents = $this->backend->getFileContentsMulti( [ 'srcs' => $source ] );
1285 foreach ( $contents as $path => $data ) {
1286 $this->assertNotEquals( false, $data, "Contents of $path exists ($backendName)." );
1287 $this->assertEquals(
1288 current( $content ),
1289 $data,
1290 "Contents of $path is correct ($backendName)."
1291 );
1292 next( $content );
1293 }
1294 $this->assertEquals(
1295 $source,
1296 array_keys( $contents ),
1297 "Contents in right order ($backendName)."
1298 );
1299 $this->assertEquals(
1300 count( $source ),
1301 count( $contents ),
1302 "Contents array size correct ($backendName)."
1303 );
1304 } else {
1305 $data = $this->backend->getFileContents( [ 'src' => $source ] );
1306 $this->assertNotEquals( false, $data, "Contents of $source exists ($backendName)." );
1307 $this->assertEquals( $content[0], $data, "Contents of $source is correct ($backendName)." );
1308 }
1309 }
1310
1311 public static function provider_testGetFileContents() {
1312 $cases = [];
1313
1314 $base = self::baseStorePath();
1315 $cases[] = [ "$base/unittest-cont1/e/b/z/some_file.txt", "some file contents" ];
1316 $cases[] = [ "$base/unittest-cont1/e/b/some-other_file.txt", "more file contents" ];
1317 $cases[] = [
1318 [ "$base/unittest-cont1/e/a/x.txt", "$base/unittest-cont1/e/a/y.txt",
1319 "$base/unittest-cont1/e/a/z.txt" ],
1320 [ "contents xx", "contents xy", "contents xz" ]
1321 ];
1322
1323 return $cases;
1324 }
1325
1326 /**
1327 * @dataProvider provider_testGetLocalCopy
1328 */
1329 public function testGetLocalCopy( $source, $content ) {
1330 $this->backend = $this->singleBackend;
1331 $this->tearDownFiles();
1332 $this->doTestGetLocalCopy( $source, $content );
1333 $this->tearDownFiles();
1334
1335 $this->backend = $this->multiBackend;
1336 $this->tearDownFiles();
1337 $this->doTestGetLocalCopy( $source, $content );
1338 $this->tearDownFiles();
1339 }
1340
1341 private function doTestGetLocalCopy( $source, $content ) {
1342 $backendName = $this->backendClass();
1343
1344 $srcs = (array)$source;
1345 $content = (array)$content;
1346 foreach ( $srcs as $i => $src ) {
1347 $this->prepare( [ 'dir' => dirname( $src ) ] );
1348 $status = $this->backend->doOperation(
1349 [ 'op' => 'create', 'content' => $content[$i], 'dst' => $src ] );
1350 $this->assertGoodStatus( $status,
1351 "Creation of file at $src succeeded ($backendName)." );
1352 }
1353
1354 if ( is_array( $source ) ) {
1355 $tmpFiles = $this->backend->getLocalCopyMulti( [ 'srcs' => $source ] );
1356 foreach ( $tmpFiles as $path => $tmpFile ) {
1357 $this->assertNotNull( $tmpFile,
1358 "Creation of local copy of $path succeeded ($backendName)." );
1359 $contents = file_get_contents( $tmpFile->getPath() );
1360 $this->assertNotEquals( false, $contents, "Local copy of $path exists ($backendName)." );
1361 $this->assertEquals(
1362 current( $content ),
1363 $contents,
1364 "Local copy of $path is correct ($backendName)."
1365 );
1366 next( $content );
1367 }
1368 $this->assertEquals(
1369 $source,
1370 array_keys( $tmpFiles ),
1371 "Local copies in right order ($backendName)."
1372 );
1373 $this->assertEquals(
1374 count( $source ),
1375 count( $tmpFiles ),
1376 "Local copies array size correct ($backendName)."
1377 );
1378 } else {
1379 $tmpFile = $this->backend->getLocalCopy( [ 'src' => $source ] );
1380 $this->assertNotNull( $tmpFile,
1381 "Creation of local copy of $source succeeded ($backendName)." );
1382 $contents = file_get_contents( $tmpFile->getPath() );
1383 $this->assertNotEquals( false, $contents, "Local copy of $source exists ($backendName)." );
1384 $this->assertEquals(
1385 $content[0],
1386 $contents,
1387 "Local copy of $source is correct ($backendName)."
1388 );
1389 }
1390
1391 $obj = new stdClass();
1392 $tmpFile->bind( $obj );
1393 }
1394
1395 public static function provider_testGetLocalCopy() {
1396 $cases = [];
1397
1398 $base = self::baseStorePath();
1399 $cases[] = [ "$base/unittest-cont1/e/a/z/some_file.txt", "some file contents" ];
1400 $cases[] = [ "$base/unittest-cont1/e/a/some-other_file.txt", "more file contents" ];
1401 $cases[] = [ "$base/unittest-cont1/e/a/\$odd&.txt", "test file contents" ];
1402 $cases[] = [
1403 [ "$base/unittest-cont1/e/a/x.txt", "$base/unittest-cont1/e/a/y.txt",
1404 "$base/unittest-cont1/e/a/z.txt" ],
1405 [ "contents xx $", "contents xy 111", "contents xz" ]
1406 ];
1407
1408 return $cases;
1409 }
1410
1411 /**
1412 * @dataProvider provider_testGetLocalReference
1413 */
1414 public function testGetLocalReference( $source, $content ) {
1415 $this->backend = $this->singleBackend;
1416 $this->tearDownFiles();
1417 $this->doTestGetLocalReference( $source, $content );
1418 $this->tearDownFiles();
1419
1420 $this->backend = $this->multiBackend;
1421 $this->tearDownFiles();
1422 $this->doTestGetLocalReference( $source, $content );
1423 $this->tearDownFiles();
1424 }
1425
1426 private function doTestGetLocalReference( $source, $content ) {
1427 $backendName = $this->backendClass();
1428
1429 $srcs = (array)$source;
1430 $content = (array)$content;
1431 foreach ( $srcs as $i => $src ) {
1432 $this->prepare( [ 'dir' => dirname( $src ) ] );
1433 $status = $this->backend->doOperation(
1434 [ 'op' => 'create', 'content' => $content[$i], 'dst' => $src ] );
1435 $this->assertGoodStatus( $status,
1436 "Creation of file at $src succeeded ($backendName)." );
1437 }
1438
1439 if ( is_array( $source ) ) {
1440 $tmpFiles = $this->backend->getLocalReferenceMulti( [ 'srcs' => $source ] );
1441 foreach ( $tmpFiles as $path => $tmpFile ) {
1442 $this->assertNotNull( $tmpFile,
1443 "Creation of local copy of $path succeeded ($backendName)." );
1444 $contents = file_get_contents( $tmpFile->getPath() );
1445 $this->assertNotEquals( false, $contents, "Local ref of $path exists ($backendName)." );
1446 $this->assertEquals(
1447 current( $content ),
1448 $contents,
1449 "Local ref of $path is correct ($backendName)."
1450 );
1451 next( $content );
1452 }
1453 $this->assertEquals(
1454 $source,
1455 array_keys( $tmpFiles ),
1456 "Local refs in right order ($backendName)."
1457 );
1458 $this->assertEquals(
1459 count( $source ),
1460 count( $tmpFiles ),
1461 "Local refs array size correct ($backendName)."
1462 );
1463 } else {
1464 $tmpFile = $this->backend->getLocalReference( [ 'src' => $source ] );
1465 $this->assertNotNull( $tmpFile,
1466 "Creation of local copy of $source succeeded ($backendName)." );
1467 $contents = file_get_contents( $tmpFile->getPath() );
1468 $this->assertNotEquals( false, $contents, "Local ref of $source exists ($backendName)." );
1469 $this->assertEquals( $content[0], $contents, "Local ref of $source is correct ($backendName)." );
1470 }
1471 }
1472
1473 public static function provider_testGetLocalReference() {
1474 $cases = [];
1475
1476 $base = self::baseStorePath();
1477 $cases[] = [ "$base/unittest-cont1/e/a/z/some_file.txt", "some file contents" ];
1478 $cases[] = [ "$base/unittest-cont1/e/a/some-other_file.txt", "more file contents" ];
1479 $cases[] = [ "$base/unittest-cont1/e/a/\$odd&.txt", "test file contents" ];
1480 $cases[] = [
1481 [ "$base/unittest-cont1/e/a/x.txt", "$base/unittest-cont1/e/a/y.txt",
1482 "$base/unittest-cont1/e/a/z.txt" ],
1483 [ "contents xx 1111", "contents xy %", "contents xz $" ]
1484 ];
1485
1486 return $cases;
1487 }
1488
1489 public function testGetLocalCopyAndReference404() {
1490 $this->backend = $this->singleBackend;
1491 $this->tearDownFiles();
1492 $this->doTestGetLocalCopyAndReference404();
1493 $this->tearDownFiles();
1494
1495 $this->backend = $this->multiBackend;
1496 $this->tearDownFiles();
1497 $this->doTestGetLocalCopyAndReference404();
1498 $this->tearDownFiles();
1499 }
1500
1501 public function doTestGetLocalCopyAndReference404() {
1502 $backendName = $this->backendClass();
1503
1504 $base = self::baseStorePath();
1505
1506 $tmpFile = $this->backend->getLocalCopy( [
1507 'src' => "$base/unittest-cont1/not-there" ] );
1508 $this->assertEquals( null, $tmpFile, "Local copy of not existing file is null ($backendName)." );
1509
1510 $tmpFile = $this->backend->getLocalReference( [
1511 'src' => "$base/unittest-cont1/not-there" ] );
1512 $this->assertEquals( null, $tmpFile, "Local ref of not existing file is null ($backendName)." );
1513 }
1514
1515 /**
1516 * @dataProvider provider_testGetFileHttpUrl
1517 */
1518 public function testGetFileHttpUrl( $source, $content ) {
1519 $this->backend = $this->singleBackend;
1520 $this->tearDownFiles();
1521 $this->doTestGetFileHttpUrl( $source, $content );
1522 $this->tearDownFiles();
1523
1524 $this->backend = $this->multiBackend;
1525 $this->tearDownFiles();
1526 $this->doTestGetFileHttpUrl( $source, $content );
1527 $this->tearDownFiles();
1528 }
1529
1530 private function doTestGetFileHttpUrl( $source, $content ) {
1531 $backendName = $this->backendClass();
1532
1533 $this->prepare( [ 'dir' => dirname( $source ) ] );
1534 $status = $this->backend->doOperation(
1535 [ 'op' => 'create', 'content' => $content, 'dst' => $source ] );
1536 $this->assertGoodStatus( $status,
1537 "Creation of file at $source succeeded ($backendName)." );
1538
1539 $url = $this->backend->getFileHttpUrl( [ 'src' => $source ] );
1540
1541 if ( $url !== null ) { // supported
1542 $data = MediaWikiServices::getInstance()->getHttpRequestFactory()->
1543 get( $url, [], __METHOD__ );
1544 $this->assertEquals( $content, $data,
1545 "HTTP GET of URL has right contents ($backendName)." );
1546 }
1547 }
1548
1549 public static function provider_testGetFileHttpUrl() {
1550 $cases = [];
1551
1552 $base = self::baseStorePath();
1553 $cases[] = [ "$base/unittest-cont1/e/a/z/some_file.txt", "some file contents" ];
1554 $cases[] = [ "$base/unittest-cont1/e/a/some-other_file.txt", "more file contents" ];
1555 $cases[] = [ "$base/unittest-cont1/e/a/\$odd&.txt", "test file contents" ];
1556
1557 return $cases;
1558 }
1559
1560 /**
1561 * @dataProvider provider_testPrepareAndClean
1562 */
1563 public function testPrepareAndClean( $path, $isOK ) {
1564 $this->backend = $this->singleBackend;
1565 $this->doTestPrepareAndClean( $path, $isOK );
1566 $this->tearDownFiles();
1567
1568 $this->backend = $this->multiBackend;
1569 $this->doTestPrepareAndClean( $path, $isOK );
1570 $this->tearDownFiles();
1571 }
1572
1573 public static function provider_testPrepareAndClean() {
1574 $base = self::baseStorePath();
1575
1576 return [
1577 [ "$base/unittest-cont1/e/a/z/some_file1.txt", true ],
1578 [ "$base/unittest-cont2/a/z/some_file2.txt", true ],
1579 # Specific to FS backend with no basePath field set
1580 # [ "$base/unittest-cont3/a/z/some_file3.txt", false ],
1581 ];
1582 }
1583
1584 private function doTestPrepareAndClean( $path, $isOK ) {
1585 $backendName = $this->backendClass();
1586
1587 $status = $this->prepare( [ 'dir' => dirname( $path ) ] );
1588 if ( $isOK ) {
1589 $this->assertGoodStatus( $status,
1590 "Preparing dir $path succeeded without warnings ($backendName)." );
1591 $this->assertEquals( true, $status->isOK(),
1592 "Preparing dir $path succeeded ($backendName)." );
1593 } else {
1594 $this->assertEquals( false, $status->isOK(),
1595 "Preparing dir $path failed ($backendName)." );
1596 }
1597
1598 $status = $this->backend->secure( [ 'dir' => dirname( $path ) ] );
1599 if ( $isOK ) {
1600 $this->assertGoodStatus( $status,
1601 "Securing dir $path succeeded without warnings ($backendName)." );
1602 $this->assertEquals( true, $status->isOK(),
1603 "Securing dir $path succeeded ($backendName)." );
1604 } else {
1605 $this->assertEquals( false, $status->isOK(),
1606 "Securing dir $path failed ($backendName)." );
1607 }
1608
1609 $status = $this->backend->publish( [ 'dir' => dirname( $path ) ] );
1610 if ( $isOK ) {
1611 $this->assertGoodStatus( $status,
1612 "Publishing dir $path succeeded without warnings ($backendName)." );
1613 $this->assertEquals( true, $status->isOK(),
1614 "Publishing dir $path succeeded ($backendName)." );
1615 } else {
1616 $this->assertEquals( false, $status->isOK(),
1617 "Publishing dir $path failed ($backendName)." );
1618 }
1619
1620 $status = $this->backend->clean( [ 'dir' => dirname( $path ) ] );
1621 if ( $isOK ) {
1622 $this->assertGoodStatus( $status,
1623 "Cleaning dir $path succeeded without warnings ($backendName)." );
1624 $this->assertEquals( true, $status->isOK(),
1625 "Cleaning dir $path succeeded ($backendName)." );
1626 } else {
1627 $this->assertEquals( false, $status->isOK(),
1628 "Cleaning dir $path failed ($backendName)." );
1629 }
1630 }
1631
1632 public function testRecursiveClean() {
1633 $this->backend = $this->singleBackend;
1634 $this->doTestRecursiveClean();
1635 $this->tearDownFiles();
1636
1637 $this->backend = $this->multiBackend;
1638 $this->doTestRecursiveClean();
1639 $this->tearDownFiles();
1640 }
1641
1642 private function doTestRecursiveClean() {
1643 $backendName = $this->backendClass();
1644
1645 $base = self::baseStorePath();
1646 $dirs = [
1647 "$base/unittest-cont1",
1648 "$base/unittest-cont1/e",
1649 "$base/unittest-cont1/e/a",
1650 "$base/unittest-cont1/e/a/b",
1651 "$base/unittest-cont1/e/a/b/c",
1652 "$base/unittest-cont1/e/a/b/c/d0",
1653 "$base/unittest-cont1/e/a/b/c/d1",
1654 "$base/unittest-cont1/e/a/b/c/d2",
1655 "$base/unittest-cont1/e/a/b/c/d0/1",
1656 "$base/unittest-cont1/e/a/b/c/d0/2",
1657 "$base/unittest-cont1/e/a/b/c/d1/3",
1658 "$base/unittest-cont1/e/a/b/c/d1/4",
1659 "$base/unittest-cont1/e/a/b/c/d2/5",
1660 "$base/unittest-cont1/e/a/b/c/d2/6"
1661 ];
1662 foreach ( $dirs as $dir ) {
1663 $status = $this->prepare( [ 'dir' => $dir ] );
1664 $this->assertGoodStatus( $status,
1665 "Preparing dir $dir succeeded without warnings ($backendName)." );
1666 }
1667
1668 if ( $this->backend instanceof FSFileBackend ) {
1669 foreach ( $dirs as $dir ) {
1670 $this->assertEquals( true, $this->backend->directoryExists( [ 'dir' => $dir ] ),
1671 "Dir $dir exists ($backendName)." );
1672 }
1673 }
1674
1675 $status = $this->backend->clean(
1676 [ 'dir' => "$base/unittest-cont1", 'recursive' => 1 ] );
1677 $this->assertGoodStatus( $status,
1678 "Recursive cleaning of dir $dir succeeded without warnings ($backendName)." );
1679
1680 foreach ( $dirs as $dir ) {
1681 $this->assertEquals( false, $this->backend->directoryExists( [ 'dir' => $dir ] ),
1682 "Dir $dir no longer exists ($backendName)." );
1683 }
1684 }
1685
1686 public function testDoOperations() {
1687 $this->backend = $this->singleBackend;
1688 $this->tearDownFiles();
1689 $this->doTestDoOperations();
1690 $this->tearDownFiles();
1691
1692 $this->backend = $this->multiBackend;
1693 $this->tearDownFiles();
1694 $this->doTestDoOperations();
1695 $this->tearDownFiles();
1696 }
1697
1698 private function doTestDoOperations() {
1699 $base = self::baseStorePath();
1700
1701 $fileA = "$base/unittest-cont1/e/a/b/fileA.txt";
1702 $fileAContents = '3tqtmoeatmn4wg4qe-mg3qt3 tq';
1703 $fileB = "$base/unittest-cont1/e/a/b/fileB.txt";
1704 $fileBContents = 'g-jmq3gpqgt3qtg q3GT ';
1705 $fileC = "$base/unittest-cont1/e/a/b/fileC.txt";
1706 $fileCContents = 'eigna[ogmewt 3qt g3qg flew[ag';
1707 $fileD = "$base/unittest-cont1/e/a/b/fileD.txt";
1708
1709 $this->prepare( [ 'dir' => dirname( $fileA ) ] );
1710 $this->create( [ 'dst' => $fileA, 'content' => $fileAContents ] );
1711 $this->prepare( [ 'dir' => dirname( $fileB ) ] );
1712 $this->create( [ 'dst' => $fileB, 'content' => $fileBContents ] );
1713 $this->prepare( [ 'dir' => dirname( $fileC ) ] );
1714 $this->create( [ 'dst' => $fileC, 'content' => $fileCContents ] );
1715 $this->prepare( [ 'dir' => dirname( $fileD ) ] );
1716
1717 $status = $this->backend->doOperations( [
1718 [ 'op' => 'describe', 'src' => $fileA,
1719 'headers' => [ 'X-Content-Length' => '91.3' ], 'disposition' => 'inline' ],
1720 [ 'op' => 'copy', 'src' => $fileA, 'dst' => $fileC, 'overwrite' => 1 ],
1721 // Now: A:<A>, B:<B>, C:<A>, D:<empty> (file:<orginal contents>)
1722 [ 'op' => 'copy', 'src' => $fileC, 'dst' => $fileA, 'overwriteSame' => 1 ],
1723 // Now: A:<A>, B:<B>, C:<A>, D:<empty>
1724 [ 'op' => 'move', 'src' => $fileC, 'dst' => $fileD, 'overwrite' => 1 ],
1725 // Now: A:<A>, B:<B>, C:<empty>, D:<A>
1726 [ 'op' => 'move', 'src' => $fileB, 'dst' => $fileC ],
1727 // Now: A:<A>, B:<empty>, C:<B>, D:<A>
1728 [ 'op' => 'move', 'src' => $fileD, 'dst' => $fileA, 'overwriteSame' => 1 ],
1729 // Now: A:<A>, B:<empty>, C:<B>, D:<empty>
1730 [ 'op' => 'move', 'src' => $fileC, 'dst' => $fileA, 'overwrite' => 1 ],
1731 // Now: A:<B>, B:<empty>, C:<empty>, D:<empty>
1732 [ 'op' => 'copy', 'src' => $fileA, 'dst' => $fileC ],
1733 // Now: A:<B>, B:<empty>, C:<B>, D:<empty>
1734 [ 'op' => 'move', 'src' => $fileA, 'dst' => $fileC, 'overwriteSame' => 1 ],
1735 // Now: A:<empty>, B:<empty>, C:<B>, D:<empty>
1736 [ 'op' => 'copy', 'src' => $fileC, 'dst' => $fileC, 'overwrite' => 1 ],
1737 // Does nothing
1738 [ 'op' => 'copy', 'src' => $fileC, 'dst' => $fileC, 'overwriteSame' => 1 ],
1739 // Does nothing
1740 [ 'op' => 'move', 'src' => $fileC, 'dst' => $fileC, 'overwrite' => 1 ],
1741 // Does nothing
1742 [ 'op' => 'move', 'src' => $fileC, 'dst' => $fileC, 'overwriteSame' => 1 ],
1743 // Does nothing
1744 [ 'op' => 'null' ],
1745 // Does nothing
1746 ] );
1747
1748 $this->assertGoodStatus( $status, "Operation batch succeeded" );
1749 $this->assertEquals( true, $status->isOK(), "Operation batch succeeded" );
1750 $this->assertEquals( 14, count( $status->success ),
1751 "Operation batch has correct success array" );
1752
1753 $this->assertEquals( false, $this->backend->fileExists( [ 'src' => $fileA ] ),
1754 "File does not exist at $fileA" );
1755 $this->assertEquals( false, $this->backend->fileExists( [ 'src' => $fileB ] ),
1756 "File does not exist at $fileB" );
1757 $this->assertEquals( false, $this->backend->fileExists( [ 'src' => $fileD ] ),
1758 "File does not exist at $fileD" );
1759
1760 $this->assertEquals( true, $this->backend->fileExists( [ 'src' => $fileC ] ),
1761 "File exists at $fileC" );
1762 $this->assertEquals( $fileBContents,
1763 $this->backend->getFileContents( [ 'src' => $fileC ] ),
1764 "Correct file contents of $fileC" );
1765 $this->assertEquals( strlen( $fileBContents ),
1766 $this->backend->getFileSize( [ 'src' => $fileC ] ),
1767 "Correct file size of $fileC" );
1768 $this->assertEquals( Wikimedia\base_convert( sha1( $fileBContents ), 16, 36, 31 ),
1769 $this->backend->getFileSha1Base36( [ 'src' => $fileC ] ),
1770 "Correct file SHA-1 of $fileC" );
1771 }
1772
1773 public function testDoOperationsPipeline() {
1774 $this->backend = $this->singleBackend;
1775 $this->tearDownFiles();
1776 $this->doTestDoOperationsPipeline();
1777 $this->tearDownFiles();
1778
1779 $this->backend = $this->multiBackend;
1780 $this->tearDownFiles();
1781 $this->doTestDoOperationsPipeline();
1782 $this->tearDownFiles();
1783 }
1784
1785 // concurrency orientated
1786 private function doTestDoOperationsPipeline() {
1787 $base = self::baseStorePath();
1788
1789 $fileAContents = '3tqtmoeatmn4wg4qe-mg3qt3 tq';
1790 $fileBContents = 'g-jmq3gpqgt3qtg q3GT ';
1791 $fileCContents = 'eigna[ogmewt 3qt g3qg flew[ag';
1792
1793 $tmpNameA = TempFSFile::factory( "unittests_", 'txt', wfTempDir() )->getPath();
1794 $tmpNameB = TempFSFile::factory( "unittests_", 'txt', wfTempDir() )->getPath();
1795 $tmpNameC = TempFSFile::factory( "unittests_", 'txt', wfTempDir() )->getPath();
1796 $this->addTmpFiles( [ $tmpNameA, $tmpNameB, $tmpNameC ] );
1797 file_put_contents( $tmpNameA, $fileAContents );
1798 file_put_contents( $tmpNameB, $fileBContents );
1799 file_put_contents( $tmpNameC, $fileCContents );
1800
1801 $fileA = "$base/unittest-cont1/e/a/b/fileA.txt";
1802 $fileB = "$base/unittest-cont1/e/a/b/fileB.txt";
1803 $fileC = "$base/unittest-cont1/e/a/b/fileC.txt";
1804 $fileD = "$base/unittest-cont1/e/a/b/fileD.txt";
1805
1806 $this->prepare( [ 'dir' => dirname( $fileA ) ] );
1807 $this->create( [ 'dst' => $fileA, 'content' => $fileAContents ] );
1808 $this->prepare( [ 'dir' => dirname( $fileB ) ] );
1809 $this->prepare( [ 'dir' => dirname( $fileC ) ] );
1810 $this->prepare( [ 'dir' => dirname( $fileD ) ] );
1811
1812 $status = $this->backend->doOperations( [
1813 [ 'op' => 'store', 'src' => $tmpNameA, 'dst' => $fileA, 'overwriteSame' => 1 ],
1814 [ 'op' => 'store', 'src' => $tmpNameB, 'dst' => $fileB, 'overwrite' => 1 ],
1815 [ 'op' => 'store', 'src' => $tmpNameC, 'dst' => $fileC, 'overwrite' => 1 ],
1816 [ 'op' => 'copy', 'src' => $fileA, 'dst' => $fileC, 'overwrite' => 1 ],
1817 // Now: A:<A>, B:<B>, C:<A>, D:<empty> (file:<orginal contents>)
1818 [ 'op' => 'copy', 'src' => $fileC, 'dst' => $fileA, 'overwriteSame' => 1 ],
1819 // Now: A:<A>, B:<B>, C:<A>, D:<empty>
1820 [ 'op' => 'move', 'src' => $fileC, 'dst' => $fileD, 'overwrite' => 1 ],
1821 // Now: A:<A>, B:<B>, C:<empty>, D:<A>
1822 [ 'op' => 'move', 'src' => $fileB, 'dst' => $fileC ],
1823 // Now: A:<A>, B:<empty>, C:<B>, D:<A>
1824 [ 'op' => 'move', 'src' => $fileD, 'dst' => $fileA, 'overwriteSame' => 1 ],
1825 // Now: A:<A>, B:<empty>, C:<B>, D:<empty>
1826 [ 'op' => 'move', 'src' => $fileC, 'dst' => $fileA, 'overwrite' => 1 ],
1827 // Now: A:<B>, B:<empty>, C:<empty>, D:<empty>
1828 [ 'op' => 'copy', 'src' => $fileA, 'dst' => $fileC ],
1829 // Now: A:<B>, B:<empty>, C:<B>, D:<empty>
1830 [ 'op' => 'move', 'src' => $fileA, 'dst' => $fileC, 'overwriteSame' => 1 ],
1831 // Now: A:<empty>, B:<empty>, C:<B>, D:<empty>
1832 [ 'op' => 'copy', 'src' => $fileC, 'dst' => $fileC, 'overwrite' => 1 ],
1833 // Does nothing
1834 [ 'op' => 'copy', 'src' => $fileC, 'dst' => $fileC, 'overwriteSame' => 1 ],
1835 // Does nothing
1836 [ 'op' => 'move', 'src' => $fileC, 'dst' => $fileC, 'overwrite' => 1 ],
1837 // Does nothing
1838 [ 'op' => 'move', 'src' => $fileC, 'dst' => $fileC, 'overwriteSame' => 1 ],
1839 // Does nothing
1840 [ 'op' => 'null' ],
1841 // Does nothing
1842 ] );
1843
1844 $this->assertGoodStatus( $status, "Operation batch succeeded" );
1845 $this->assertEquals( true, $status->isOK(), "Operation batch succeeded" );
1846 $this->assertEquals( 16, count( $status->success ),
1847 "Operation batch has correct success array" );
1848
1849 $this->assertEquals( false, $this->backend->fileExists( [ 'src' => $fileA ] ),
1850 "File does not exist at $fileA" );
1851 $this->assertEquals( false, $this->backend->fileExists( [ 'src' => $fileB ] ),
1852 "File does not exist at $fileB" );
1853 $this->assertEquals( false, $this->backend->fileExists( [ 'src' => $fileD ] ),
1854 "File does not exist at $fileD" );
1855
1856 $this->assertEquals( true, $this->backend->fileExists( [ 'src' => $fileC ] ),
1857 "File exists at $fileC" );
1858 $this->assertEquals( $fileBContents,
1859 $this->backend->getFileContents( [ 'src' => $fileC ] ),
1860 "Correct file contents of $fileC" );
1861 $this->assertEquals( strlen( $fileBContents ),
1862 $this->backend->getFileSize( [ 'src' => $fileC ] ),
1863 "Correct file size of $fileC" );
1864 $this->assertEquals( Wikimedia\base_convert( sha1( $fileBContents ), 16, 36, 31 ),
1865 $this->backend->getFileSha1Base36( [ 'src' => $fileC ] ),
1866 "Correct file SHA-1 of $fileC" );
1867 }
1868
1869 public function testDoOperationsFailing() {
1870 $this->backend = $this->singleBackend;
1871 $this->tearDownFiles();
1872 $this->doTestDoOperationsFailing();
1873 $this->tearDownFiles();
1874
1875 $this->backend = $this->multiBackend;
1876 $this->tearDownFiles();
1877 $this->doTestDoOperationsFailing();
1878 $this->tearDownFiles();
1879 }
1880
1881 private function doTestDoOperationsFailing() {
1882 $base = self::baseStorePath();
1883
1884 $fileA = "$base/unittest-cont2/a/b/fileA.txt";
1885 $fileAContents = '3tqtmoeatmn4wg4qe-mg3qt3 tq';
1886 $fileB = "$base/unittest-cont2/a/b/fileB.txt";
1887 $fileBContents = 'g-jmq3gpqgt3qtg q3GT ';
1888 $fileC = "$base/unittest-cont2/a/b/fileC.txt";
1889 $fileCContents = 'eigna[ogmewt 3qt g3qg flew[ag';
1890 $fileD = "$base/unittest-cont2/a/b/fileD.txt";
1891
1892 $this->prepare( [ 'dir' => dirname( $fileA ) ] );
1893 $this->create( [ 'dst' => $fileA, 'content' => $fileAContents ] );
1894 $this->prepare( [ 'dir' => dirname( $fileB ) ] );
1895 $this->create( [ 'dst' => $fileB, 'content' => $fileBContents ] );
1896 $this->prepare( [ 'dir' => dirname( $fileC ) ] );
1897 $this->create( [ 'dst' => $fileC, 'content' => $fileCContents ] );
1898
1899 $status = $this->backend->doOperations( [
1900 [ 'op' => 'copy', 'src' => $fileA, 'dst' => $fileC, 'overwrite' => 1 ],
1901 // Now: A:<A>, B:<B>, C:<A>, D:<empty> (file:<orginal contents>)
1902 [ 'op' => 'copy', 'src' => $fileC, 'dst' => $fileA, 'overwriteSame' => 1 ],
1903 // Now: A:<A>, B:<B>, C:<A>, D:<empty>
1904 [ 'op' => 'copy', 'src' => $fileB, 'dst' => $fileD, 'overwrite' => 1 ],
1905 // Now: A:<A>, B:<B>, C:<A>, D:<B>
1906 [ 'op' => 'move', 'src' => $fileC, 'dst' => $fileD ],
1907 // Now: A:<A>, B:<B>, C:<A>, D:<empty> (failed)
1908 [ 'op' => 'move', 'src' => $fileB, 'dst' => $fileC, 'overwriteSame' => 1 ],
1909 // Now: A:<A>, B:<B>, C:<A>, D:<empty> (failed)
1910 [ 'op' => 'move', 'src' => $fileB, 'dst' => $fileA, 'overwrite' => 1 ],
1911 // Now: A:<B>, B:<empty>, C:<A>, D:<empty>
1912 [ 'op' => 'delete', 'src' => $fileD ],
1913 // Now: A:<B>, B:<empty>, C:<A>, D:<empty>
1914 [ 'op' => 'null' ],
1915 // Does nothing
1916 ], [ 'force' => 1 ] );
1917
1918 $this->assertNotEquals( [], $status->getErrors(), "Operation had warnings" );
1919 $this->assertEquals( true, $status->isOK(), "Operation batch succeeded" );
1920 $this->assertEquals( 8, count( $status->success ),
1921 "Operation batch has correct success array" );
1922
1923 $this->assertEquals( false, $this->backend->fileExists( [ 'src' => $fileB ] ),
1924 "File does not exist at $fileB" );
1925 $this->assertEquals( false, $this->backend->fileExists( [ 'src' => $fileD ] ),
1926 "File does not exist at $fileD" );
1927
1928 $this->assertEquals( true, $this->backend->fileExists( [ 'src' => $fileA ] ),
1929 "File does not exist at $fileA" );
1930 $this->assertEquals( true, $this->backend->fileExists( [ 'src' => $fileC ] ),
1931 "File exists at $fileC" );
1932 $this->assertEquals( $fileBContents,
1933 $this->backend->getFileContents( [ 'src' => $fileA ] ),
1934 "Correct file contents of $fileA" );
1935 $this->assertEquals( strlen( $fileBContents ),
1936 $this->backend->getFileSize( [ 'src' => $fileA ] ),
1937 "Correct file size of $fileA" );
1938 $this->assertEquals( Wikimedia\base_convert( sha1( $fileBContents ), 16, 36, 31 ),
1939 $this->backend->getFileSha1Base36( [ 'src' => $fileA ] ),
1940 "Correct file SHA-1 of $fileA" );
1941 }
1942
1943 public function testGetFileList() {
1944 $this->backend = $this->singleBackend;
1945 $this->tearDownFiles();
1946 $this->doTestGetFileList();
1947 $this->tearDownFiles();
1948
1949 $this->backend = $this->multiBackend;
1950 $this->tearDownFiles();
1951 $this->doTestGetFileList();
1952 $this->tearDownFiles();
1953 }
1954
1955 private function doTestGetFileList() {
1956 $backendName = $this->backendClass();
1957 $base = self::baseStorePath();
1958
1959 // Should have no errors
1960 $iter = $this->backend->getFileList( [ 'dir' => "$base/unittest-cont-notexists" ] );
1961
1962 $files = [
1963 "$base/unittest-cont1/e/test1.txt",
1964 "$base/unittest-cont1/e/test2.txt",
1965 "$base/unittest-cont1/e/test3.txt",
1966 "$base/unittest-cont1/e/subdir1/test1.txt",
1967 "$base/unittest-cont1/e/subdir1/test2.txt",
1968 "$base/unittest-cont1/e/subdir2/test3.txt",
1969 "$base/unittest-cont1/e/subdir2/test4.txt",
1970 "$base/unittest-cont1/e/subdir2/subdir/test1.txt",
1971 "$base/unittest-cont1/e/subdir2/subdir/test2.txt",
1972 "$base/unittest-cont1/e/subdir2/subdir/test3.txt",
1973 "$base/unittest-cont1/e/subdir2/subdir/test4.txt",
1974 "$base/unittest-cont1/e/subdir2/subdir/test5.txt",
1975 "$base/unittest-cont1/e/subdir2/subdir/sub/test0.txt",
1976 "$base/unittest-cont1/e/subdir2/subdir/sub/120-px-file.txt",
1977 ];
1978
1979 // Add the files
1980 $ops = [];
1981 foreach ( $files as $file ) {
1982 $this->prepare( [ 'dir' => dirname( $file ) ] );
1983 $ops[] = [ 'op' => 'create', 'content' => 'xxy', 'dst' => $file ];
1984 }
1985 $status = $this->backend->doQuickOperations( $ops );
1986 $this->assertGoodStatus( $status,
1987 "Creation of files succeeded ($backendName)." );
1988 $this->assertEquals( true, $status->isOK(),
1989 "Creation of files succeeded with OK status ($backendName)." );
1990
1991 // Expected listing at root
1992 $expected = [
1993 "e/test1.txt",
1994 "e/test2.txt",
1995 "e/test3.txt",
1996 "e/subdir1/test1.txt",
1997 "e/subdir1/test2.txt",
1998 "e/subdir2/test3.txt",
1999 "e/subdir2/test4.txt",
2000 "e/subdir2/subdir/test1.txt",
2001 "e/subdir2/subdir/test2.txt",
2002 "e/subdir2/subdir/test3.txt",
2003 "e/subdir2/subdir/test4.txt",
2004 "e/subdir2/subdir/test5.txt",
2005 "e/subdir2/subdir/sub/test0.txt",
2006 "e/subdir2/subdir/sub/120-px-file.txt",
2007 ];
2008 sort( $expected );
2009
2010 // Actual listing (no trailing slash) at root
2011 $iter = $this->backend->getFileList( [ 'dir' => "$base/unittest-cont1" ] );
2012 $list = $this->listToArray( $iter );
2013 sort( $list );
2014 $this->assertEquals( $expected, $list, "Correct file listing ($backendName)." );
2015
2016 // Actual listing (no trailing slash) at root with advise
2017 $iter = $this->backend->getFileList( [
2018 'dir' => "$base/unittest-cont1",
2019 'adviseStat' => 1
2020 ] );
2021 $list = $this->listToArray( $iter );
2022 sort( $list );
2023 $this->assertEquals( $expected, $list, "Correct file listing ($backendName)." );
2024
2025 // Actual listing (with trailing slash) at root
2026 $list = [];
2027 $iter = $this->backend->getFileList( [ 'dir' => "$base/unittest-cont1/" ] );
2028 foreach ( $iter as $file ) {
2029 $list[] = $file;
2030 }
2031 sort( $list );
2032 $this->assertEquals( $expected, $list, "Correct file listing ($backendName)." );
2033
2034 // Expected listing at subdir
2035 $expected = [
2036 "test1.txt",
2037 "test2.txt",
2038 "test3.txt",
2039 "test4.txt",
2040 "test5.txt",
2041 "sub/test0.txt",
2042 "sub/120-px-file.txt",
2043 ];
2044 sort( $expected );
2045
2046 // Actual listing (no trailing slash) at subdir
2047 $iter = $this->backend->getFileList( [ 'dir' => "$base/unittest-cont1/e/subdir2/subdir" ] );
2048 $list = $this->listToArray( $iter );
2049 sort( $list );
2050 $this->assertEquals( $expected, $list, "Correct file listing ($backendName)." );
2051
2052 // Actual listing (no trailing slash) at subdir with advise
2053 $iter = $this->backend->getFileList( [
2054 'dir' => "$base/unittest-cont1/e/subdir2/subdir",
2055 'adviseStat' => 1
2056 ] );
2057 $list = $this->listToArray( $iter );
2058 sort( $list );
2059 $this->assertEquals( $expected, $list, "Correct file listing ($backendName)." );
2060
2061 // Actual listing (with trailing slash) at subdir
2062 $list = [];
2063 $iter = $this->backend->getFileList( [ 'dir' => "$base/unittest-cont1/e/subdir2/subdir/" ] );
2064 foreach ( $iter as $file ) {
2065 $list[] = $file;
2066 }
2067 sort( $list );
2068 $this->assertEquals( $expected, $list, "Correct file listing ($backendName)." );
2069
2070 // Actual listing (using iterator second time)
2071 $list = $this->listToArray( $iter );
2072 sort( $list );
2073 $this->assertEquals( $expected, $list, "Correct file listing ($backendName), second iteration." );
2074
2075 // Actual listing (top files only) at root
2076 $iter = $this->backend->getTopFileList( [ 'dir' => "$base/unittest-cont1" ] );
2077 $list = $this->listToArray( $iter );
2078 sort( $list );
2079 $this->assertEquals( [], $list, "Correct top file listing ($backendName)." );
2080
2081 // Expected listing (top files only) at subdir
2082 $expected = [
2083 "test1.txt",
2084 "test2.txt",
2085 "test3.txt",
2086 "test4.txt",
2087 "test5.txt"
2088 ];
2089 sort( $expected );
2090
2091 // Actual listing (top files only) at subdir
2092 $iter = $this->backend->getTopFileList(
2093 [ 'dir' => "$base/unittest-cont1/e/subdir2/subdir" ]
2094 );
2095 $list = $this->listToArray( $iter );
2096 sort( $list );
2097 $this->assertEquals( $expected, $list, "Correct top file listing ($backendName)." );
2098
2099 // Actual listing (top files only) at subdir with advise
2100 $iter = $this->backend->getTopFileList( [
2101 'dir' => "$base/unittest-cont1/e/subdir2/subdir",
2102 'adviseStat' => 1
2103 ] );
2104 $list = $this->listToArray( $iter );
2105 sort( $list );
2106 $this->assertEquals( $expected, $list, "Correct top file listing ($backendName)." );
2107
2108 foreach ( $files as $file ) { // clean up
2109 $this->backend->doOperation( [ 'op' => 'delete', 'src' => $file ] );
2110 }
2111
2112 $iter = $this->backend->getFileList( [ 'dir' => "$base/unittest-cont1/not/exists" ] );
2113 foreach ( $iter as $iter ) {
2114 // no errors
2115 }
2116 }
2117
2118 public function testGetDirectoryList() {
2119 $this->backend = $this->singleBackend;
2120 $this->tearDownFiles();
2121 $this->doTestGetDirectoryList();
2122 $this->tearDownFiles();
2123
2124 $this->backend = $this->multiBackend;
2125 $this->tearDownFiles();
2126 $this->doTestGetDirectoryList();
2127 $this->tearDownFiles();
2128 }
2129
2130 private function doTestGetDirectoryList() {
2131 $backendName = $this->backendClass();
2132
2133 $base = self::baseStorePath();
2134 $files = [
2135 "$base/unittest-cont1/e/test1.txt",
2136 "$base/unittest-cont1/e/test2.txt",
2137 "$base/unittest-cont1/e/test3.txt",
2138 "$base/unittest-cont1/e/subdir1/test1.txt",
2139 "$base/unittest-cont1/e/subdir1/test2.txt",
2140 "$base/unittest-cont1/e/subdir2/test3.txt",
2141 "$base/unittest-cont1/e/subdir2/test4.txt",
2142 "$base/unittest-cont1/e/subdir2/subdir/test1.txt",
2143 "$base/unittest-cont1/e/subdir3/subdir/test2.txt",
2144 "$base/unittest-cont1/e/subdir4/subdir/test3.txt",
2145 "$base/unittest-cont1/e/subdir4/subdir/test4.txt",
2146 "$base/unittest-cont1/e/subdir4/subdir/test5.txt",
2147 "$base/unittest-cont1/e/subdir4/subdir/sub/test0.txt",
2148 "$base/unittest-cont1/e/subdir4/subdir/sub/120-px-file.txt",
2149 ];
2150
2151 // Add the files
2152 $ops = [];
2153 foreach ( $files as $file ) {
2154 $this->prepare( [ 'dir' => dirname( $file ) ] );
2155 $ops[] = [ 'op' => 'create', 'content' => 'xxy', 'dst' => $file ];
2156 }
2157 $status = $this->backend->doQuickOperations( $ops );
2158 $this->assertGoodStatus( $status,
2159 "Creation of files succeeded ($backendName)." );
2160 $this->assertEquals( true, $status->isOK(),
2161 "Creation of files succeeded with OK status ($backendName)." );
2162
2163 $this->assertEquals( true,
2164 $this->backend->directoryExists( [ 'dir' => "$base/unittest-cont1/e/subdir1" ] ),
2165 "Directory exists in ($backendName)." );
2166 $this->assertEquals( true,
2167 $this->backend->directoryExists( [ 'dir' => "$base/unittest-cont1/e/subdir2/subdir" ] ),
2168 "Directory exists in ($backendName)." );
2169 $this->assertEquals( false,
2170 $this->backend->directoryExists( [ 'dir' => "$base/unittest-cont1/e/subdir2/test1.txt" ] ),
2171 "Directory does not exists in ($backendName)." );
2172
2173 // Expected listing
2174 $expected = [
2175 "e",
2176 ];
2177 sort( $expected );
2178
2179 // Actual listing (no trailing slash)
2180 $list = [];
2181 $iter = $this->backend->getTopDirectoryList( [ 'dir' => "$base/unittest-cont1" ] );
2182 foreach ( $iter as $file ) {
2183 $list[] = $file;
2184 }
2185 sort( $list );
2186
2187 $this->assertEquals( $expected, $list, "Correct top dir listing ($backendName)." );
2188
2189 // Expected listing
2190 $expected = [
2191 "subdir1",
2192 "subdir2",
2193 "subdir3",
2194 "subdir4",
2195 ];
2196 sort( $expected );
2197
2198 // Actual listing (no trailing slash)
2199 $list = [];
2200 $iter = $this->backend->getTopDirectoryList( [ 'dir' => "$base/unittest-cont1/e" ] );
2201 foreach ( $iter as $file ) {
2202 $list[] = $file;
2203 }
2204 sort( $list );
2205
2206 $this->assertEquals( $expected, $list, "Correct top dir listing ($backendName)." );
2207
2208 // Actual listing (with trailing slash)
2209 $list = [];
2210 $iter = $this->backend->getTopDirectoryList( [ 'dir' => "$base/unittest-cont1/e/" ] );
2211 foreach ( $iter as $file ) {
2212 $list[] = $file;
2213 }
2214 sort( $list );
2215
2216 $this->assertEquals( $expected, $list, "Correct top dir listing ($backendName)." );
2217
2218 // Expected listing
2219 $expected = [
2220 "subdir",
2221 ];
2222 sort( $expected );
2223
2224 // Actual listing (no trailing slash)
2225 $list = [];
2226 $iter = $this->backend->getTopDirectoryList( [ 'dir' => "$base/unittest-cont1/e/subdir2" ] );
2227 foreach ( $iter as $file ) {
2228 $list[] = $file;
2229 }
2230 sort( $list );
2231
2232 $this->assertEquals( $expected, $list, "Correct top dir listing ($backendName)." );
2233
2234 // Actual listing (with trailing slash)
2235 $list = [];
2236 $iter = $this->backend->getTopDirectoryList(
2237 [ 'dir' => "$base/unittest-cont1/e/subdir2/" ]
2238 );
2239
2240 foreach ( $iter as $file ) {
2241 $list[] = $file;
2242 }
2243 sort( $list );
2244
2245 $this->assertEquals( $expected, $list, "Correct top dir listing ($backendName)." );
2246
2247 // Actual listing (using iterator second time)
2248 $list = [];
2249 foreach ( $iter as $file ) {
2250 $list[] = $file;
2251 }
2252 sort( $list );
2253
2254 $this->assertEquals(
2255 $expected,
2256 $list,
2257 "Correct top dir listing ($backendName), second iteration."
2258 );
2259
2260 // Expected listing (recursive)
2261 $expected = [
2262 "e",
2263 "e/subdir1",
2264 "e/subdir2",
2265 "e/subdir3",
2266 "e/subdir4",
2267 "e/subdir2/subdir",
2268 "e/subdir3/subdir",
2269 "e/subdir4/subdir",
2270 "e/subdir4/subdir/sub",
2271 ];
2272 sort( $expected );
2273
2274 // Actual listing (recursive)
2275 $list = [];
2276 $iter = $this->backend->getDirectoryList( [ 'dir' => "$base/unittest-cont1/" ] );
2277 foreach ( $iter as $file ) {
2278 $list[] = $file;
2279 }
2280 sort( $list );
2281
2282 $this->assertEquals( $expected, $list, "Correct dir listing ($backendName)." );
2283
2284 // Expected listing (recursive)
2285 $expected = [
2286 "subdir",
2287 "subdir/sub",
2288 ];
2289 sort( $expected );
2290
2291 // Actual listing (recursive)
2292 $list = [];
2293 $iter = $this->backend->getDirectoryList( [ 'dir' => "$base/unittest-cont1/e/subdir4" ] );
2294 foreach ( $iter as $file ) {
2295 $list[] = $file;
2296 }
2297 sort( $list );
2298
2299 $this->assertEquals( $expected, $list, "Correct dir listing ($backendName)." );
2300
2301 // Actual listing (recursive, second time)
2302 $list = [];
2303 foreach ( $iter as $file ) {
2304 $list[] = $file;
2305 }
2306 sort( $list );
2307
2308 $this->assertEquals( $expected, $list, "Correct dir listing ($backendName)." );
2309
2310 $iter = $this->backend->getDirectoryList( [ 'dir' => "$base/unittest-cont1/e/subdir1" ] );
2311 $items = $this->listToArray( $iter );
2312 $this->assertEquals( [], $items, "Directory listing is empty." );
2313
2314 foreach ( $files as $file ) { // clean up
2315 $this->backend->doOperation( [ 'op' => 'delete', 'src' => $file ] );
2316 }
2317
2318 $iter = $this->backend->getDirectoryList( [ 'dir' => "$base/unittest-cont1/not/exists" ] );
2319 foreach ( $iter as $file ) {
2320 // no errors
2321 }
2322
2323 $items = $this->listToArray( $iter );
2324 $this->assertEquals( [], $items, "Directory listing is empty." );
2325
2326 $iter = $this->backend->getDirectoryList( [ 'dir' => "$base/unittest-cont1/e/not/exists" ] );
2327 $items = $this->listToArray( $iter );
2328 $this->assertEquals( [], $items, "Directory listing is empty." );
2329 }
2330
2331 public function testLockCalls() {
2332 $this->backend = $this->singleBackend;
2333 $this->doTestLockCalls();
2334 }
2335
2336 private function doTestLockCalls() {
2337 $backendName = $this->backendClass();
2338 $base = $this->backend->getContainerStoragePath( 'test' );
2339
2340 $paths = [
2341 "$base/test1.txt",
2342 "$base/test2.txt",
2343 "$base/test3.txt",
2344 "$base/subdir1",
2345 "$base/subdir1", // duplicate
2346 "$base/subdir1/test1.txt",
2347 "$base/subdir1/test2.txt",
2348 "$base/subdir2",
2349 "$base/subdir2", // duplicate
2350 "$base/subdir2/test3.txt",
2351 "$base/subdir2/test4.txt",
2352 "$base/subdir2/subdir",
2353 "$base/subdir2/subdir/test1.txt",
2354 "$base/subdir2/subdir/test2.txt",
2355 "$base/subdir2/subdir/test3.txt",
2356 "$base/subdir2/subdir/test4.txt",
2357 "$base/subdir2/subdir/test5.txt",
2358 "$base/subdir2/subdir/sub",
2359 "$base/subdir2/subdir/sub/test0.txt",
2360 "$base/subdir2/subdir/sub/120-px-file.txt",
2361 ];
2362
2363 for ( $i = 0; $i < 25; $i++ ) {
2364 $status = $this->backend->lockFiles( $paths, LockManager::LOCK_EX );
2365 $this->assertEquals( print_r( [], true ), print_r( $status->getErrors(), true ),
2366 "Locking of files succeeded ($backendName) ($i)." );
2367 $this->assertEquals( true, $status->isOK(),
2368 "Locking of files succeeded with OK status ($backendName) ($i)." );
2369
2370 $status = $this->backend->lockFiles( $paths, LockManager::LOCK_SH );
2371 $this->assertEquals( print_r( [], true ), print_r( $status->getErrors(), true ),
2372 "Locking of files succeeded ($backendName) ($i)." );
2373 $this->assertEquals( true, $status->isOK(),
2374 "Locking of files succeeded with OK status ($backendName) ($i)." );
2375
2376 $status = $this->backend->unlockFiles( $paths, LockManager::LOCK_SH );
2377 $this->assertEquals( print_r( [], true ), print_r( $status->getErrors(), true ),
2378 "Locking of files succeeded ($backendName) ($i)." );
2379 $this->assertEquals( true, $status->isOK(),
2380 "Locking of files succeeded with OK status ($backendName) ($i)." );
2381
2382 $status = $this->backend->unlockFiles( $paths, LockManager::LOCK_EX );
2383 $this->assertEquals( print_r( [], true ), print_r( $status->getErrors(), true ),
2384 "Locking of files succeeded ($backendName). ($i)" );
2385 $this->assertEquals( true, $status->isOK(),
2386 "Locking of files succeeded with OK status ($backendName) ($i)." );
2387
2388 # # Flip the acquire/release ordering around ##
2389
2390 $status = $this->backend->lockFiles( $paths, LockManager::LOCK_SH );
2391 $this->assertEquals( print_r( [], true ), print_r( $status->getErrors(), true ),
2392 "Locking of files succeeded ($backendName) ($i)." );
2393 $this->assertEquals( true, $status->isOK(),
2394 "Locking of files succeeded with OK status ($backendName) ($i)." );
2395
2396 $status = $this->backend->lockFiles( $paths, LockManager::LOCK_EX );
2397 $this->assertEquals( print_r( [], true ), print_r( $status->getErrors(), true ),
2398 "Locking of files succeeded ($backendName) ($i)." );
2399 $this->assertEquals( true, $status->isOK(),
2400 "Locking of files succeeded with OK status ($backendName) ($i)." );
2401
2402 $status = $this->backend->unlockFiles( $paths, LockManager::LOCK_EX );
2403 $this->assertEquals( print_r( [], true ), print_r( $status->getErrors(), true ),
2404 "Locking of files succeeded ($backendName). ($i)" );
2405 $this->assertEquals( true, $status->isOK(),
2406 "Locking of files succeeded with OK status ($backendName) ($i)." );
2407
2408 $status = $this->backend->unlockFiles( $paths, LockManager::LOCK_SH );
2409 $this->assertEquals( print_r( [], true ), print_r( $status->getErrors(), true ),
2410 "Locking of files succeeded ($backendName) ($i)." );
2411 $this->assertEquals( true, $status->isOK(),
2412 "Locking of files succeeded with OK status ($backendName) ($i)." );
2413 }
2414
2415 $status = Status::newGood();
2416 $sl = $this->backend->getScopedFileLocks( $paths, LockManager::LOCK_EX, $status );
2417 $this->assertInstanceOf( ScopedLock::class, $sl,
2418 "Scoped locking of files succeeded ($backendName)." );
2419 $this->assertEquals( [], $status->getErrors(),
2420 "Scoped locking of files succeeded ($backendName)." );
2421 $this->assertEquals( true, $status->isOK(),
2422 "Scoped locking of files succeeded with OK status ($backendName)." );
2423
2424 ScopedLock::release( $sl );
2425 $this->assertEquals( null, $sl,
2426 "Scoped unlocking of files succeeded ($backendName)." );
2427 $this->assertEquals( [], $status->getErrors(),
2428 "Scoped unlocking of files succeeded ($backendName)." );
2429 $this->assertEquals( true, $status->isOK(),
2430 "Scoped unlocking of files succeeded with OK status ($backendName)." );
2431 }
2432
2433 /**
2434 * @dataProvider provider_testGetContentType
2435 */
2436 public function testGetContentType( $mimeCallback, $mimeFromString ) {
2437 global $IP;
2438
2439 $be = TestingAccessWrapper::newFromObject( new MemoryFileBackend(
2440 [
2441 'name' => 'testing',
2442 'class' => MemoryFileBackend::class,
2443 'wikiId' => 'meow',
2444 'mimeCallback' => $mimeCallback
2445 ]
2446 ) );
2447
2448 $dst = 'mwstore://testing/container/path/to/file_no_ext';
2449 $src = "$IP/tests/phpunit/data/media/srgb.jpg";
2450 $this->assertEquals( 'image/jpeg', $be->getContentType( $dst, null, $src ) );
2451 $this->assertEquals(
2452 $mimeFromString ? 'image/jpeg' : 'unknown/unknown',
2453 $be->getContentType( $dst, file_get_contents( $src ), null ) );
2454
2455 $src = "$IP/tests/phpunit/data/media/Png-native-test.png";
2456 $this->assertEquals( 'image/png', $be->getContentType( $dst, null, $src ) );
2457 $this->assertEquals(
2458 $mimeFromString ? 'image/png' : 'unknown/unknown',
2459 $be->getContentType( $dst, file_get_contents( $src ), null ) );
2460 }
2461
2462 public static function provider_testGetContentType() {
2463 return [
2464 [ null, false ],
2465 [ [ FileBackendGroup::singleton(), 'guessMimeInternal' ], true ]
2466 ];
2467 }
2468
2469 public function testReadAffinity() {
2470 $be = TestingAccessWrapper::newFromObject(
2471 new FileBackendMultiWrite( [
2472 'name' => 'localtesting',
2473 'wikiId' => wfWikiID() . mt_rand(),
2474 'backends' => [
2475 [ // backend 0
2476 'name' => 'multitesting0',
2477 'class' => MemoryFileBackend::class,
2478 'isMultiMaster' => false,
2479 'readAffinity' => true
2480 ],
2481 [ // backend 1
2482 'name' => 'multitesting1',
2483 'class' => MemoryFileBackend::class,
2484 'isMultiMaster' => true
2485 ]
2486 ]
2487 ] )
2488 );
2489
2490 $this->assertEquals(
2491 1,
2492 $be->getReadIndexFromParams( [ 'latest' => 1 ] ),
2493 'Reads with "latest" flag use backend 1'
2494 );
2495 $this->assertEquals(
2496 0,
2497 $be->getReadIndexFromParams( [ 'latest' => 0 ] ),
2498 'Reads without "latest" flag use backend 0'
2499 );
2500
2501 $p = 'container/test-cont/file.txt';
2502 $be->backends[0]->quickCreate( [
2503 'dst' => "mwstore://multitesting0/$p", 'content' => 'cattitude' ] );
2504 $be->backends[1]->quickCreate( [
2505 'dst' => "mwstore://multitesting1/$p", 'content' => 'princess of power' ] );
2506
2507 $this->assertEquals(
2508 'cattitude',
2509 $be->getFileContents( [ 'src' => "mwstore://localtesting/$p" ] ),
2510 "Non-latest read came from backend 0"
2511 );
2512 $this->assertEquals(
2513 'princess of power',
2514 $be->getFileContents( [ 'src' => "mwstore://localtesting/$p", 'latest' => 1 ] ),
2515 "Latest read came from backend1"
2516 );
2517 }
2518
2519 public function testAsyncWrites() {
2520 $be = TestingAccessWrapper::newFromObject(
2521 new FileBackendMultiWrite( [
2522 'name' => 'localtesting',
2523 'wikiId' => wfWikiID() . mt_rand(),
2524 'backends' => [
2525 [ // backend 0
2526 'name' => 'multitesting0',
2527 'class' => MemoryFileBackend::class,
2528 'isMultiMaster' => false
2529 ],
2530 [ // backend 1
2531 'name' => 'multitesting1',
2532 'class' => MemoryFileBackend::class,
2533 'isMultiMaster' => true
2534 ]
2535 ],
2536 'replication' => 'async'
2537 ] )
2538 );
2539
2540 $this->setMwGlobals( 'wgCommandLineMode', false );
2541
2542 $p = 'container/test-cont/file.txt';
2543 $be->quickCreate( [
2544 'dst' => "mwstore://localtesting/$p", 'content' => 'cattitude' ] );
2545
2546 $this->assertEquals(
2547 false,
2548 $be->backends[0]->getFileContents( [ 'src' => "mwstore://multitesting0/$p" ] ),
2549 "File not yet written to backend 0"
2550 );
2551 $this->assertEquals(
2552 'cattitude',
2553 $be->backends[1]->getFileContents( [ 'src' => "mwstore://multitesting1/$p" ] ),
2554 "File already written to backend 1"
2555 );
2556
2557 DeferredUpdates::doUpdates();
2558
2559 $this->assertEquals(
2560 'cattitude',
2561 $be->backends[0]->getFileContents( [ 'src' => "mwstore://multitesting0/$p" ] ),
2562 "File now written to backend 0"
2563 );
2564 }
2565
2566 public function testSanitizeOpHeaders() {
2567 $be = TestingAccessWrapper::newFromObject( new MemoryFileBackend( [
2568 'name' => 'localtesting',
2569 'wikiId' => wfWikiID()
2570 ] ) );
2571
2572 $input = [
2573 'headers' => [
2574 'content-Disposition' => FileBackend::makeContentDisposition( 'inline', 'name' ),
2575 'Content-dUration' => 25.6,
2576 'X-LONG-VALUE' => str_pad( '0', 300 ),
2577 'CONTENT-LENGTH' => 855055,
2578 ]
2579 ];
2580 $expected = [
2581 'headers' => [
2582 'content-disposition' => FileBackend::makeContentDisposition( 'inline', 'name' ),
2583 'content-duration' => 25.6,
2584 'content-length' => 855055
2585 ]
2586 ];
2587
2588 Wikimedia\suppressWarnings();
2589 $actual = $be->sanitizeOpHeaders( $input );
2590 Wikimedia\restoreWarnings();
2591
2592 $this->assertEquals( $expected, $actual, "Header sanitized properly" );
2593 }
2594
2595 // helper function
2596 private function listToArray( $iter ) {
2597 return is_array( $iter ) ? $iter : iterator_to_array( $iter );
2598 }
2599
2600 // test helper wrapper for backend prepare() function
2601 private function prepare( array $params ) {
2602 return $this->backend->prepare( $params );
2603 }
2604
2605 // test helper wrapper for backend prepare() function
2606 private function create( array $params ) {
2607 $params['op'] = 'create';
2608
2609 return $this->backend->doQuickOperations( [ $params ] );
2610 }
2611
2612 function tearDownFiles() {
2613 $containers = [ 'unittest-cont1', 'unittest-cont2', 'unittest-cont-bad' ];
2614 foreach ( $containers as $container ) {
2615 $this->deleteFiles( $container );
2616 }
2617 }
2618
2619 private function deleteFiles( $container ) {
2620 $base = self::baseStorePath();
2621 $iter = $this->backend->getFileList( [ 'dir' => "$base/$container" ] );
2622 if ( $iter ) {
2623 foreach ( $iter as $file ) {
2624 $this->backend->quickDelete( [ 'src' => "$base/$container/$file" ] );
2625 }
2626 // free the directory, to avoid Permission denied under windows on rmdir
2627 unset( $iter );
2628 }
2629 $this->backend->clean( [ 'dir' => "$base/$container", 'recursive' => 1 ] );
2630 }
2631
2632 function assertBackendPathsConsistent( array $paths ) {
2633 if ( $this->backend instanceof FileBackendMultiWrite ) {
2634 $status = $this->backend->consistencyCheck( $paths );
2635 $this->assertGoodStatus( $status, "Files synced: " . implode( ',', $paths ) );
2636 }
2637 }
2638
2639 function assertGoodStatus( StatusValue $status, $msg ) {
2640 $this->assertEquals( print_r( [], 1 ), print_r( $status->getErrors(), 1 ), $msg );
2641 }
2642 }