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