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