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