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