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