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