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