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