Merge "Reduced contention slam potential in User::invalidateCache()."
[lhc/web/wiklou.git] / tests / phpunit / includes / filerepo / FileBackendTest.php
1 <?php
2
3 /**
4 * @group FileRepo
5 * @group FileBackend
6 */
7 class FileBackendTest extends MediaWikiTestCase {
8 private $backend, $multiBackend;
9 private $filesToPrune = array();
10 private $dirsToPrune = array();
11 private static $backendToUse;
12
13 function setUp() {
14 global $wgFileBackends;
15 parent::setUp();
16 $tmpPrefix = wfTempDir() . '/filebackend-unittest-' . time() . '-' . mt_rand();
17 if ( $this->getCliArg( 'use-filebackend=' ) ) {
18 if ( self::$backendToUse ) {
19 $this->singleBackend = self::$backendToUse;
20 } else {
21 $name = $this->getCliArg( 'use-filebackend=' );
22 $useConfig = array();
23 foreach ( $wgFileBackends as $conf ) {
24 if ( $conf['name'] == $name ) {
25 $useConfig = $conf;
26 break;
27 }
28 }
29 $useConfig['name'] = 'localtesting'; // swap name
30 $class = $useConfig['class'];
31 self::$backendToUse = new $class( $useConfig );
32 $this->singleBackend = self::$backendToUse;
33 }
34 } else {
35 $this->singleBackend = new FSFileBackend( array(
36 'name' => 'localtesting',
37 'lockManager' => 'fsLockManager',
38 #'parallelize' => 'implicit',
39 'containerPaths' => array(
40 'unittest-cont1' => "{$tmpPrefix}-localtesting-cont1",
41 'unittest-cont2' => "{$tmpPrefix}-localtesting-cont2" )
42 ) );
43 }
44 $this->multiBackend = new FileBackendMultiWrite( array(
45 'name' => 'localtesting',
46 'lockManager' => 'fsLockManager',
47 'parallelize' => 'implicit',
48 'backends' => array(
49 array(
50 'name' => 'localmutlitesting1',
51 'class' => 'FSFileBackend',
52 'lockManager' => 'nullLockManager',
53 'containerPaths' => array(
54 'unittest-cont1' => "{$tmpPrefix}-localtestingmulti1-cont1",
55 'unittest-cont2' => "{$tmpPrefix}-localtestingmulti1-cont2" ),
56 'isMultiMaster' => false
57 ),
58 array(
59 'name' => 'localmutlitesting2',
60 'class' => 'FSFileBackend',
61 'lockManager' => 'nullLockManager',
62 'containerPaths' => array(
63 'unittest-cont1' => "{$tmpPrefix}-localtestingmulti2-cont1",
64 'unittest-cont2' => "{$tmpPrefix}-localtestingmulti2-cont2" ),
65 'isMultiMaster' => true
66 )
67 )
68 ) );
69 $this->filesToPrune = array();
70 }
71
72 private function baseStorePath() {
73 return 'mwstore://localtesting';
74 }
75
76 private function backendClass() {
77 return get_class( $this->backend );
78 }
79
80 /**
81 * @dataProvider provider_testIsStoragePath
82 */
83 public function testIsStoragePath( $path, $isStorePath ) {
84 $this->assertEquals( $isStorePath, FileBackend::isStoragePath( $path ),
85 "FileBackend::isStoragePath on path '$path'" );
86 }
87
88 function provider_testIsStoragePath() {
89 return array(
90 array( 'mwstore://', true ),
91 array( 'mwstore://backend', true ),
92 array( 'mwstore://backend/container', true ),
93 array( 'mwstore://backend/container/', true ),
94 array( 'mwstore://backend/container/path', true ),
95 array( 'mwstore://backend//container/', true ),
96 array( 'mwstore://backend//container//', true ),
97 array( 'mwstore://backend//container//path', true ),
98 array( 'mwstore:///', true ),
99 array( 'mwstore:/', false ),
100 array( 'mwstore:', false ),
101 );
102 }
103
104 /**
105 * @dataProvider provider_testSplitStoragePath
106 */
107 public function testSplitStoragePath( $path, $res ) {
108 $this->assertEquals( $res, FileBackend::splitStoragePath( $path ),
109 "FileBackend::splitStoragePath on path '$path'" );
110 }
111
112 function provider_testSplitStoragePath() {
113 return array(
114 array( 'mwstore://backend/container', array( 'backend', 'container', '' ) ),
115 array( 'mwstore://backend/container/', array( 'backend', 'container', '' ) ),
116 array( 'mwstore://backend/container/path', array( 'backend', 'container', 'path' ) ),
117 array( 'mwstore://backend/container//path', array( 'backend', 'container', '/path' ) ),
118 array( 'mwstore://backend//container/path', array( null, null, null ) ),
119 array( 'mwstore://backend//container//path', array( null, null, null ) ),
120 array( 'mwstore://', array( null, null, null ) ),
121 array( 'mwstore://backend', array( null, null, null ) ),
122 array( 'mwstore:///', array( null, null, null ) ),
123 array( 'mwstore:/', array( null, null, null ) ),
124 array( 'mwstore:', array( null, null, null ) )
125 );
126 }
127
128 /**
129 * @dataProvider provider_normalizeStoragePath
130 */
131 public function testNormalizeStoragePath( $path, $res ) {
132 $this->assertEquals( $res, FileBackend::normalizeStoragePath( $path ),
133 "FileBackend::normalizeStoragePath on path '$path'" );
134 }
135
136 function provider_normalizeStoragePath() {
137 return array(
138 array( 'mwstore://backend/container', 'mwstore://backend/container' ),
139 array( 'mwstore://backend/container/', 'mwstore://backend/container' ),
140 array( 'mwstore://backend/container/path', 'mwstore://backend/container/path' ),
141 array( 'mwstore://backend/container//path', 'mwstore://backend/container/path' ),
142 array( 'mwstore://backend/container///path', 'mwstore://backend/container/path' ),
143 array( 'mwstore://backend/container///path//to///obj', 'mwstore://backend/container/path/to/obj',
144 array( 'mwstore://', null ),
145 array( 'mwstore://backend', null ),
146 array( 'mwstore://backend//container/path', null ),
147 array( 'mwstore://backend//container//path', null ),
148 array( 'mwstore:///', null ),
149 array( 'mwstore:/', null ),
150 array( 'mwstore:', null ), )
151 );
152 }
153
154 /**
155 * @dataProvider provider_testParentStoragePath
156 */
157 public function testParentStoragePath( $path, $res ) {
158 $this->assertEquals( $res, FileBackend::parentStoragePath( $path ),
159 "FileBackend::parentStoragePath on path '$path'" );
160 }
161
162 function provider_testParentStoragePath() {
163 return array(
164 array( 'mwstore://backend/container/path/to/obj', 'mwstore://backend/container/path/to' ),
165 array( 'mwstore://backend/container/path/to', 'mwstore://backend/container/path' ),
166 array( 'mwstore://backend/container/path', 'mwstore://backend/container' ),
167 array( 'mwstore://backend/container', null ),
168 array( 'mwstore://backend/container/path/to/obj/', 'mwstore://backend/container/path/to' ),
169 array( 'mwstore://backend/container/path/to/', 'mwstore://backend/container/path' ),
170 array( 'mwstore://backend/container/path/', 'mwstore://backend/container' ),
171 array( 'mwstore://backend/container/', null ),
172 );
173 }
174
175 /**
176 * @dataProvider provider_testExtensionFromPath
177 */
178 public function testExtensionFromPath( $path, $res ) {
179 $this->assertEquals( $res, FileBackend::extensionFromPath( $path ),
180 "FileBackend::extensionFromPath on path '$path'" );
181 }
182
183 function provider_testExtensionFromPath() {
184 return array(
185 array( 'mwstore://backend/container/path.txt', 'txt' ),
186 array( 'mwstore://backend/container/path.svg.png', 'png' ),
187 array( 'mwstore://backend/container/path', '' ),
188 array( 'mwstore://backend/container/path.', '' ),
189 );
190 }
191
192 /**
193 * @dataProvider provider_testStore
194 */
195 public function testStore( $op ) {
196 $this->filesToPrune[] = $op['src'];
197
198 $this->backend = $this->singleBackend;
199 $this->tearDownFiles();
200 $this->doTestStore( $op );
201 $this->tearDownFiles();
202
203 $this->backend = $this->multiBackend;
204 $this->tearDownFiles();
205 $this->doTestStore( $op );
206 $this->filesToPrune[] = $op['src']; # avoid file leaking
207 $this->tearDownFiles();
208 }
209
210 private function doTestStore( $op ) {
211 $backendName = $this->backendClass();
212
213 $source = $op['src'];
214 $dest = $op['dst'];
215 $this->prepare( array( 'dir' => dirname( $dest ) ) );
216
217 file_put_contents( $source, "Unit test file" );
218
219 if ( isset( $op['overwrite'] ) || isset( $op['overwriteSame'] ) ) {
220 $this->backend->store( $op );
221 }
222
223 $status = $this->backend->doOperation( $op );
224
225 $this->assertGoodStatus( $status,
226 "Store from $source to $dest succeeded without warnings ($backendName)." );
227 $this->assertEquals( true, $status->isOK(),
228 "Store from $source to $dest succeeded ($backendName)." );
229 $this->assertEquals( array( 0 => true ), $status->success,
230 "Store from $source to $dest has proper 'success' field in Status ($backendName)." );
231 $this->assertEquals( true, file_exists( $source ),
232 "Source file $source still exists ($backendName)." );
233 $this->assertEquals( true, $this->backend->fileExists( array( 'src' => $dest ) ),
234 "Destination file $dest exists ($backendName)." );
235
236 $this->assertEquals( filesize( $source ),
237 $this->backend->getFileSize( array( 'src' => $dest ) ),
238 "Destination file $dest has correct size ($backendName)." );
239
240 $props1 = FSFile::getPropsFromPath( $source );
241 $props2 = $this->backend->getFileProps( array( 'src' => $dest ) );
242 $this->assertEquals( $props1, $props2,
243 "Source and destination have the same props ($backendName)." );
244 }
245
246 public function provider_testStore() {
247 $cases = array();
248
249 $tmpName = TempFSFile::factory( "unittests_", 'txt' )->getPath();
250 $toPath = $this->baseStorePath() . '/unittest-cont1/fun/obj1.txt';
251 $op = array( 'op' => 'store', 'src' => $tmpName, 'dst' => $toPath );
252 $cases[] = array(
253 $op, // operation
254 $tmpName, // source
255 $toPath, // dest
256 );
257
258 $op2 = $op;
259 $op2['overwrite'] = true;
260 $cases[] = array(
261 $op2, // operation
262 $tmpName, // source
263 $toPath, // dest
264 );
265
266 $op2 = $op;
267 $op2['overwriteSame'] = true;
268 $cases[] = array(
269 $op2, // operation
270 $tmpName, // source
271 $toPath, // dest
272 );
273
274 return $cases;
275 }
276
277 /**
278 * @dataProvider provider_testCopy
279 */
280 public function testCopy( $op ) {
281 $this->backend = $this->singleBackend;
282 $this->tearDownFiles();
283 $this->doTestCopy( $op );
284 $this->tearDownFiles();
285
286 $this->backend = $this->multiBackend;
287 $this->tearDownFiles();
288 $this->doTestCopy( $op );
289 $this->tearDownFiles();
290 }
291
292 private function doTestCopy( $op ) {
293 $backendName = $this->backendClass();
294
295 $source = $op['src'];
296 $dest = $op['dst'];
297 $this->prepare( array( 'dir' => dirname( $source ) ) );
298 $this->prepare( array( 'dir' => dirname( $dest ) ) );
299
300 $status = $this->backend->doOperation(
301 array( 'op' => 'create', 'content' => 'blahblah', 'dst' => $source ) );
302 $this->assertGoodStatus( $status,
303 "Creation of file at $source succeeded ($backendName)." );
304
305 if ( isset( $op['overwrite'] ) || isset( $op['overwriteSame'] ) ) {
306 $this->backend->copy( $op );
307 }
308
309 $status = $this->backend->doOperation( $op );
310
311 $this->assertGoodStatus( $status,
312 "Copy from $source to $dest succeeded without warnings ($backendName)." );
313 $this->assertEquals( true, $status->isOK(),
314 "Copy from $source to $dest succeeded ($backendName)." );
315 $this->assertEquals( array( 0 => true ), $status->success,
316 "Copy from $source to $dest has proper 'success' field in Status ($backendName)." );
317 $this->assertEquals( true, $this->backend->fileExists( array( 'src' => $source ) ),
318 "Source file $source still exists ($backendName)." );
319 $this->assertEquals( true, $this->backend->fileExists( array( 'src' => $dest ) ),
320 "Destination file $dest exists after copy ($backendName)." );
321
322 $this->assertEquals(
323 $this->backend->getFileSize( array( 'src' => $source ) ),
324 $this->backend->getFileSize( array( 'src' => $dest ) ),
325 "Destination file $dest has correct size ($backendName)." );
326
327 $props1 = $this->backend->getFileProps( array( 'src' => $source ) );
328 $props2 = $this->backend->getFileProps( array( 'src' => $dest ) );
329 $this->assertEquals( $props1, $props2,
330 "Source and destination have the same props ($backendName)." );
331 }
332
333 public function provider_testCopy() {
334 $cases = array();
335
336 $source = $this->baseStorePath() . '/unittest-cont1/file.txt';
337 $dest = $this->baseStorePath() . '/unittest-cont2/fileMoved.txt';
338
339 $op = array( 'op' => 'copy', 'src' => $source, 'dst' => $dest );
340 $cases[] = array(
341 $op, // operation
342 $source, // source
343 $dest, // dest
344 );
345
346 $op2 = $op;
347 $op2['overwrite'] = true;
348 $cases[] = array(
349 $op2, // operation
350 $source, // source
351 $dest, // dest
352 );
353
354 $op2 = $op;
355 $op2['overwriteSame'] = true;
356 $cases[] = array(
357 $op2, // operation
358 $source, // source
359 $dest, // dest
360 );
361
362 return $cases;
363 }
364
365 /**
366 * @dataProvider provider_testMove
367 */
368 public function testMove( $op ) {
369 $this->backend = $this->singleBackend;
370 $this->tearDownFiles();
371 $this->doTestMove( $op );
372 $this->tearDownFiles();
373
374 $this->backend = $this->multiBackend;
375 $this->tearDownFiles();
376 $this->doTestMove( $op );
377 $this->tearDownFiles();
378 }
379
380 private function doTestMove( $op ) {
381 $backendName = $this->backendClass();
382
383 $source = $op['src'];
384 $dest = $op['dst'];
385 $this->prepare( array( 'dir' => dirname( $source ) ) );
386 $this->prepare( array( 'dir' => dirname( $dest ) ) );
387
388 $status = $this->backend->doOperation(
389 array( 'op' => 'create', 'content' => 'blahblah', 'dst' => $source ) );
390 $this->assertGoodStatus( $status,
391 "Creation of file at $source succeeded ($backendName)." );
392
393 if ( isset( $op['overwrite'] ) || isset( $op['overwriteSame'] ) ) {
394 $this->backend->copy( $op );
395 }
396
397 $status = $this->backend->doOperation( $op );
398 $this->assertGoodStatus( $status,
399 "Move from $source to $dest succeeded without warnings ($backendName)." );
400 $this->assertEquals( true, $status->isOK(),
401 "Move from $source to $dest succeeded ($backendName)." );
402 $this->assertEquals( array( 0 => true ), $status->success,
403 "Move from $source to $dest has proper 'success' field in Status ($backendName)." );
404 $this->assertEquals( false, $this->backend->fileExists( array( 'src' => $source ) ),
405 "Source file $source does not still exists ($backendName)." );
406 $this->assertEquals( true, $this->backend->fileExists( array( 'src' => $dest ) ),
407 "Destination file $dest exists after move ($backendName)." );
408
409 $this->assertNotEquals(
410 $this->backend->getFileSize( array( 'src' => $source ) ),
411 $this->backend->getFileSize( array( 'src' => $dest ) ),
412 "Destination file $dest has correct size ($backendName)." );
413
414 $props1 = $this->backend->getFileProps( array( 'src' => $source ) );
415 $props2 = $this->backend->getFileProps( array( 'src' => $dest ) );
416 $this->assertEquals( false, $props1['fileExists'],
417 "Source file does not exist accourding to props ($backendName)." );
418 $this->assertEquals( true, $props2['fileExists'],
419 "Destination file exists accourding to props ($backendName)." );
420 }
421
422 public function provider_testMove() {
423 $cases = array();
424
425 $source = $this->baseStorePath() . '/unittest-cont1/file.txt';
426 $dest = $this->baseStorePath() . '/unittest-cont2/fileMoved.txt';
427
428 $op = array( 'op' => 'move', 'src' => $source, 'dst' => $dest );
429 $cases[] = array(
430 $op, // operation
431 $source, // source
432 $dest, // dest
433 );
434
435 $op2 = $op;
436 $op2['overwrite'] = true;
437 $cases[] = array(
438 $op2, // operation
439 $source, // source
440 $dest, // dest
441 );
442
443 $op2 = $op;
444 $op2['overwriteSame'] = true;
445 $cases[] = array(
446 $op2, // operation
447 $source, // source
448 $dest, // dest
449 );
450
451 return $cases;
452 }
453
454 /**
455 * @dataProvider provider_testDelete
456 */
457 public function testDelete( $op, $withSource, $okStatus ) {
458 $this->backend = $this->singleBackend;
459 $this->tearDownFiles();
460 $this->doTestDelete( $op, $withSource, $okStatus );
461 $this->tearDownFiles();
462
463 $this->backend = $this->multiBackend;
464 $this->tearDownFiles();
465 $this->doTestDelete( $op, $withSource, $okStatus );
466 $this->tearDownFiles();
467 }
468
469 private function doTestDelete( $op, $withSource, $okStatus ) {
470 $backendName = $this->backendClass();
471
472 $source = $op['src'];
473 $this->prepare( array( 'dir' => dirname( $source ) ) );
474
475 if ( $withSource ) {
476 $status = $this->backend->doOperation(
477 array( 'op' => 'create', 'content' => 'blahblah', 'dst' => $source ) );
478 $this->assertGoodStatus( $status,
479 "Creation of file at $source succeeded ($backendName)." );
480 }
481
482 $status = $this->backend->doOperation( $op );
483 if ( $okStatus ) {
484 $this->assertGoodStatus( $status,
485 "Deletion of file at $source succeeded without warnings ($backendName)." );
486 $this->assertEquals( true, $status->isOK(),
487 "Deletion of file at $source succeeded ($backendName)." );
488 $this->assertEquals( array( 0 => true ), $status->success,
489 "Deletion of file at $source has proper 'success' field in Status ($backendName)." );
490 } else {
491 $this->assertEquals( false, $status->isOK(),
492 "Deletion of file at $source failed ($backendName)." );
493 }
494
495 $this->assertEquals( false, $this->backend->fileExists( array( 'src' => $source ) ),
496 "Source file $source does not exist after move ($backendName)." );
497
498 $this->assertFalse(
499 $this->backend->getFileSize( array( 'src' => $source ) ),
500 "Source file $source has correct size (false) ($backendName)." );
501
502 $props1 = $this->backend->getFileProps( array( 'src' => $source ) );
503 $this->assertFalse( $props1['fileExists'],
504 "Source file $source does not exist according to props ($backendName)." );
505 }
506
507 public function provider_testDelete() {
508 $cases = array();
509
510 $source = $this->baseStorePath() . '/unittest-cont1/myfacefile.txt';
511
512 $op = array( 'op' => 'delete', 'src' => $source );
513 $cases[] = array(
514 $op, // operation
515 true, // with source
516 true // succeeds
517 );
518
519 $cases[] = array(
520 $op, // operation
521 false, // without source
522 false // fails
523 );
524
525 $op['ignoreMissingSource'] = true;
526 $cases[] = array(
527 $op, // operation
528 false, // without source
529 true // succeeds
530 );
531
532 return $cases;
533 }
534
535 /**
536 * @dataProvider provider_testCreate
537 */
538 public function testCreate( $op, $alreadyExists, $okStatus, $newSize ) {
539 $this->backend = $this->singleBackend;
540 $this->tearDownFiles();
541 $this->doTestCreate( $op, $alreadyExists, $okStatus, $newSize );
542 $this->tearDownFiles();
543
544 $this->backend = $this->multiBackend;
545 $this->tearDownFiles();
546 $this->doTestCreate( $op, $alreadyExists, $okStatus, $newSize );
547 $this->tearDownFiles();
548 }
549
550 private function doTestCreate( $op, $alreadyExists, $okStatus, $newSize ) {
551 $backendName = $this->backendClass();
552
553 $dest = $op['dst'];
554 $this->prepare( array( 'dir' => dirname( $dest ) ) );
555
556 $oldText = 'blah...blah...waahwaah';
557 if ( $alreadyExists ) {
558 $status = $this->backend->doOperation(
559 array( 'op' => 'create', 'content' => $oldText, 'dst' => $dest ) );
560 $this->assertGoodStatus( $status,
561 "Creation of file at $dest succeeded ($backendName)." );
562 }
563
564 $status = $this->backend->doOperation( $op );
565 if ( $okStatus ) {
566 $this->assertGoodStatus( $status,
567 "Creation of file at $dest succeeded without warnings ($backendName)." );
568 $this->assertEquals( true, $status->isOK(),
569 "Creation of file at $dest succeeded ($backendName)." );
570 $this->assertEquals( array( 0 => true ), $status->success,
571 "Creation of file at $dest has proper 'success' field in Status ($backendName)." );
572 } else {
573 $this->assertEquals( false, $status->isOK(),
574 "Creation of file at $dest failed ($backendName)." );
575 }
576
577 $this->assertEquals( true, $this->backend->fileExists( array( 'src' => $dest ) ),
578 "Destination file $dest exists after creation ($backendName)." );
579
580 $props1 = $this->backend->getFileProps( array( 'src' => $dest ) );
581 $this->assertEquals( true, $props1['fileExists'],
582 "Destination file $dest exists according to props ($backendName)." );
583 if ( $okStatus ) { // file content is what we saved
584 $this->assertEquals( $newSize, $props1['size'],
585 "Destination file $dest has expected size according to props ($backendName)." );
586 $this->assertEquals( $newSize,
587 $this->backend->getFileSize( array( 'src' => $dest ) ),
588 "Destination file $dest has correct size ($backendName)." );
589 } else { // file content is some other previous text
590 $this->assertEquals( strlen( $oldText ), $props1['size'],
591 "Destination file $dest has original size according to props ($backendName)." );
592 $this->assertEquals( strlen( $oldText ),
593 $this->backend->getFileSize( array( 'src' => $dest ) ),
594 "Destination file $dest has original size according to props ($backendName)." );
595 }
596 }
597
598 /**
599 * @dataProvider provider_testCreate
600 */
601 public function provider_testCreate() {
602 $cases = array();
603
604 $dest = $this->baseStorePath() . '/unittest-cont2/myspacefile.txt';
605
606 $op = array( 'op' => 'create', 'content' => 'test test testing', 'dst' => $dest );
607 $cases[] = array(
608 $op, // operation
609 false, // no dest already exists
610 true, // succeeds
611 strlen( $op['content'] )
612 );
613
614 $op2 = $op;
615 $op2['content'] = "\n";
616 $cases[] = array(
617 $op2, // operation
618 false, // no dest already exists
619 true, // succeeds
620 strlen( $op2['content'] )
621 );
622
623 $op2 = $op;
624 $op2['content'] = "fsf\n waf 3kt";
625 $cases[] = array(
626 $op2, // operation
627 true, // dest already exists
628 false, // fails
629 strlen( $op2['content'] )
630 );
631
632 $op2 = $op;
633 $op2['content'] = "egm'g gkpe gpqg eqwgwqg";
634 $op2['overwrite'] = true;
635 $cases[] = array(
636 $op2, // operation
637 true, // dest already exists
638 true, // succeeds
639 strlen( $op2['content'] )
640 );
641
642 $op2 = $op;
643 $op2['content'] = "39qjmg3-qg";
644 $op2['overwriteSame'] = true;
645 $cases[] = array(
646 $op2, // operation
647 true, // dest already exists
648 false, // succeeds
649 strlen( $op2['content'] )
650 );
651
652 return $cases;
653 }
654
655 /**
656 * @dataProvider provider_testConcatenate
657 */
658 public function testConcatenate( $op, $srcs, $srcsContent, $alreadyExists, $okStatus ) {
659 $this->filesToPrune[] = $op['dst'];
660
661 $this->backend = $this->singleBackend;
662 $this->tearDownFiles();
663 $this->doTestConcatenate( $op, $srcs, $srcsContent, $alreadyExists, $okStatus );
664 $this->tearDownFiles();
665
666 $this->backend = $this->multiBackend;
667 $this->tearDownFiles();
668 $this->doTestConcatenate( $op, $srcs, $srcsContent, $alreadyExists, $okStatus );
669 $this->filesToPrune[] = $op['dst']; # avoid file leaking
670 $this->tearDownFiles();
671 }
672
673 private function doTestConcatenate( $params, $srcs, $srcsContent, $alreadyExists, $okStatus ) {
674 $backendName = $this->backendClass();
675
676 $expContent = '';
677 // Create sources
678 $ops = array();
679 foreach ( $srcs as $i => $source ) {
680 $this->prepare( array( 'dir' => dirname( $source ) ) );
681 $ops[] = array(
682 'op' => 'create', // operation
683 'dst' => $source, // source
684 'content' => $srcsContent[$i]
685 );
686 $expContent .= $srcsContent[$i];
687 }
688 $status = $this->backend->doOperations( $ops );
689
690 $this->assertGoodStatus( $status,
691 "Creation of source files succeeded ($backendName)." );
692
693 $dest = $params['dst'];
694 if ( $alreadyExists ) {
695 $ok = file_put_contents( $dest, 'blah...blah...waahwaah' ) !== false;
696 $this->assertEquals( true, $ok,
697 "Creation of file at $dest succeeded ($backendName)." );
698 } else {
699 $ok = file_put_contents( $dest, '' ) !== false;
700 $this->assertEquals( true, $ok,
701 "Creation of 0-byte file at $dest succeeded ($backendName)." );
702 }
703
704 // Combine the files into one
705 $status = $this->backend->concatenate( $params );
706 if ( $okStatus ) {
707 $this->assertGoodStatus( $status,
708 "Creation of concat file at $dest succeeded without warnings ($backendName)." );
709 $this->assertEquals( true, $status->isOK(),
710 "Creation of concat file at $dest succeeded ($backendName)." );
711 } else {
712 $this->assertEquals( false, $status->isOK(),
713 "Creation of concat file at $dest failed ($backendName)." );
714 }
715
716 if ( $okStatus ) {
717 $this->assertEquals( true, is_file( $dest ),
718 "Dest concat file $dest exists after creation ($backendName)." );
719 } else {
720 $this->assertEquals( true, is_file( $dest ),
721 "Dest concat file $dest exists after failed creation ($backendName)." );
722 }
723
724 $contents = file_get_contents( $dest );
725 $this->assertNotEquals( false, $contents, "File at $dest exists ($backendName)." );
726
727 if ( $okStatus ) {
728 $this->assertEquals( $expContent, $contents,
729 "Concat file at $dest has correct contents ($backendName)." );
730 } else {
731 $this->assertNotEquals( $expContent, $contents,
732 "Concat file at $dest has correct contents ($backendName)." );
733 }
734 }
735
736 function provider_testConcatenate() {
737 $cases = array();
738
739 $rand = mt_rand( 0, 2000000000 ) . time();
740 $dest = wfTempDir() . "/randomfile!$rand.txt";
741 $srcs = array(
742 $this->baseStorePath() . '/unittest-cont1/file1.txt',
743 $this->baseStorePath() . '/unittest-cont1/file2.txt',
744 $this->baseStorePath() . '/unittest-cont1/file3.txt',
745 $this->baseStorePath() . '/unittest-cont1/file4.txt',
746 $this->baseStorePath() . '/unittest-cont1/file5.txt',
747 $this->baseStorePath() . '/unittest-cont1/file6.txt',
748 $this->baseStorePath() . '/unittest-cont1/file7.txt',
749 $this->baseStorePath() . '/unittest-cont1/file8.txt',
750 $this->baseStorePath() . '/unittest-cont1/file9.txt',
751 $this->baseStorePath() . '/unittest-cont1/file10.txt'
752 );
753 $content = array(
754 'egfage',
755 'ageageag',
756 'rhokohlr',
757 'shgmslkg',
758 'kenga',
759 'owagmal',
760 'kgmae',
761 'g eak;g',
762 'lkaem;a',
763 'legma'
764 );
765 $params = array( 'srcs' => $srcs, 'dst' => $dest );
766
767 $cases[] = array(
768 $params, // operation
769 $srcs, // sources
770 $content, // content for each source
771 false, // no dest already exists
772 true, // succeeds
773 );
774
775 $cases[] = array(
776 $params, // operation
777 $srcs, // sources
778 $content, // content for each source
779 true, // dest already exists
780 false, // succeeds
781 );
782
783 return $cases;
784 }
785
786 /**
787 * @dataProvider provider_testGetFileStat
788 */
789 public function testGetFileStat( $path, $content, $alreadyExists ) {
790 $this->backend = $this->singleBackend;
791 $this->tearDownFiles();
792 $this->doTestGetFileStat( $path, $content, $alreadyExists );
793 $this->tearDownFiles();
794
795 $this->backend = $this->multiBackend;
796 $this->tearDownFiles();
797 $this->doTestGetFileStat( $path, $content, $alreadyExists );
798 $this->tearDownFiles();
799 }
800
801 private function doTestGetFileStat( $path, $content, $alreadyExists ) {
802 $backendName = $this->backendClass();
803
804 if ( $alreadyExists ) {
805 $this->prepare( array( 'dir' => dirname( $path ) ) );
806 $status = $this->backend->create( array( 'dst' => $path, 'content' => $content ) );
807 $this->assertGoodStatus( $status,
808 "Creation of file at $path succeeded ($backendName)." );
809
810 $size = $this->backend->getFileSize( array( 'src' => $path ) );
811 $time = $this->backend->getFileTimestamp( array( 'src' => $path ) );
812 $stat = $this->backend->getFileStat( array( 'src' => $path ) );
813
814 $this->assertEquals( strlen( $content ), $size,
815 "Correct file size of '$path'" );
816 $this->assertTrue( abs( time() - wfTimestamp( TS_UNIX, $time ) ) < 5,
817 "Correct file timestamp of '$path'" );
818
819 $size = $stat['size'];
820 $time = $stat['mtime'];
821 $this->assertEquals( strlen( $content ), $size,
822 "Correct file size of '$path'" );
823 $this->assertTrue( abs( time() - wfTimestamp( TS_UNIX, $time ) ) < 5,
824 "Correct file timestamp of '$path'" );
825 } else {
826 $size = $this->backend->getFileSize( array( 'src' => $path ) );
827 $time = $this->backend->getFileTimestamp( array( 'src' => $path ) );
828 $stat = $this->backend->getFileStat( array( 'src' => $path ) );
829
830 $this->assertFalse( $size, "Correct file size of '$path'" );
831 $this->assertFalse( $time, "Correct file timestamp of '$path'" );
832 $this->assertFalse( $stat, "Correct file stat of '$path'" );
833 }
834 }
835
836 function provider_testGetFileStat() {
837 $cases = array();
838
839 $base = $this->baseStorePath();
840 $cases[] = array( "$base/unittest-cont1/b/z/some_file.txt", "some file contents", true );
841 $cases[] = array( "$base/unittest-cont1/b/some-other_file.txt", "", true );
842 $cases[] = array( "$base/unittest-cont1/b/some-diff_file.txt", null, false );
843
844 return $cases;
845 }
846
847 /**
848 * @dataProvider provider_testGetFileContents
849 */
850 public function testGetFileContents( $source, $content ) {
851 $this->backend = $this->singleBackend;
852 $this->tearDownFiles();
853 $this->doTestGetFileContents( $source, $content );
854 $this->tearDownFiles();
855
856 $this->backend = $this->multiBackend;
857 $this->tearDownFiles();
858 $this->doTestGetFileContents( $source, $content );
859 $this->tearDownFiles();
860 }
861
862 private function doTestGetFileContents( $source, $content ) {
863 $backendName = $this->backendClass();
864
865 $this->prepare( array( 'dir' => dirname( $source ) ) );
866
867 $status = $this->backend->doOperation(
868 array( 'op' => 'create', 'content' => $content, 'dst' => $source ) );
869 $this->assertGoodStatus( $status,
870 "Creation of file at $source succeeded ($backendName)." );
871 $this->assertEquals( true, $status->isOK(),
872 "Creation of file at $source succeeded with OK status ($backendName)." );
873
874 $newContents = $this->backend->getFileContents( array( 'src' => $source, 'latest' => 1 ) );
875 $this->assertNotEquals( false, $newContents,
876 "Read of file at $source succeeded ($backendName)." );
877
878 $this->assertEquals( $content, $newContents,
879 "Contents read match data at $source ($backendName)." );
880 }
881
882 function provider_testGetFileContents() {
883 $cases = array();
884
885 $base = $this->baseStorePath();
886 $cases[] = array( "$base/unittest-cont1/b/z/some_file.txt", "some file contents" );
887 $cases[] = array( "$base/unittest-cont1/b/some-other_file.txt", "more file contents" );
888
889 return $cases;
890 }
891
892 /**
893 * @dataProvider provider_testGetLocalCopy
894 */
895 public function testGetLocalCopy( $source, $content ) {
896 $this->backend = $this->singleBackend;
897 $this->tearDownFiles();
898 $this->doTestGetLocalCopy( $source, $content );
899 $this->tearDownFiles();
900
901 $this->backend = $this->multiBackend;
902 $this->tearDownFiles();
903 $this->doTestGetLocalCopy( $source, $content );
904 $this->tearDownFiles();
905 }
906
907 private function doTestGetLocalCopy( $source, $content ) {
908 $backendName = $this->backendClass();
909
910 $this->prepare( array( 'dir' => dirname( $source ) ) );
911
912 $status = $this->backend->doOperation(
913 array( 'op' => 'create', 'content' => $content, 'dst' => $source ) );
914 $this->assertGoodStatus( $status,
915 "Creation of file at $source succeeded ($backendName)." );
916
917 $tmpFile = $this->backend->getLocalCopy( array( 'src' => $source ) );
918 $this->assertNotNull( $tmpFile,
919 "Creation of local copy of $source succeeded ($backendName)." );
920
921 $contents = file_get_contents( $tmpFile->getPath() );
922 $this->assertNotEquals( false, $contents, "Local copy of $source exists ($backendName)." );
923 }
924
925 function provider_testGetLocalCopy() {
926 $cases = array();
927
928 $base = $this->baseStorePath();
929 $cases[] = array( "$base/unittest-cont1/a/z/some_file.txt", "some file contents" );
930 $cases[] = array( "$base/unittest-cont1/a/some-other_file.txt", "more file contents" );
931
932 return $cases;
933 }
934
935 /**
936 * @dataProvider provider_testGetLocalReference
937 */
938 public function testGetLocalReference( $source, $content ) {
939 $this->backend = $this->singleBackend;
940 $this->tearDownFiles();
941 $this->doTestGetLocalReference( $source, $content );
942 $this->tearDownFiles();
943
944 $this->backend = $this->multiBackend;
945 $this->tearDownFiles();
946 $this->doTestGetLocalReference( $source, $content );
947 $this->tearDownFiles();
948 }
949
950 private function doTestGetLocalReference( $source, $content ) {
951 $backendName = $this->backendClass();
952
953 $this->prepare( array( 'dir' => dirname( $source ) ) );
954
955 $status = $this->backend->doOperation(
956 array( 'op' => 'create', 'content' => $content, 'dst' => $source ) );
957 $this->assertGoodStatus( $status,
958 "Creation of file at $source succeeded ($backendName)." );
959
960 $tmpFile = $this->backend->getLocalReference( array( 'src' => $source ) );
961 $this->assertNotNull( $tmpFile,
962 "Creation of local copy of $source succeeded ($backendName)." );
963
964 $contents = file_get_contents( $tmpFile->getPath() );
965 $this->assertNotEquals( false, $contents, "Local copy of $source exists ($backendName)." );
966 }
967
968 function provider_testGetLocalReference() {
969 $cases = array();
970
971 $base = $this->baseStorePath();
972 $cases[] = array( "$base/unittest-cont1/a/z/some_file.txt", "some file contents" );
973 $cases[] = array( "$base/unittest-cont1/a/some-other_file.txt", "more file contents" );
974
975 return $cases;
976 }
977
978 /**
979 * @dataProvider provider_testPrepareAndClean
980 */
981 public function testPrepareAndClean( $path, $isOK ) {
982 $this->backend = $this->singleBackend;
983 $this->doTestPrepareAndClean( $path, $isOK );
984 $this->tearDownFiles();
985
986 $this->backend = $this->multiBackend;
987 $this->doTestPrepareAndClean( $path, $isOK );
988 $this->tearDownFiles();
989 }
990
991 function provider_testPrepareAndClean() {
992 $base = $this->baseStorePath();
993 return array(
994 array( "$base/unittest-cont1/a/z/some_file1.txt", true ),
995 array( "$base/unittest-cont2/a/z/some_file2.txt", true ),
996 # Specific to FS backend with no basePath field set
997 #array( "$base/unittest-cont3/a/z/some_file3.txt", false ),
998 );
999 }
1000
1001 private function doTestPrepareAndClean( $path, $isOK ) {
1002 $backendName = $this->backendClass();
1003
1004 $status = $this->prepare( array( 'dir' => dirname( $path ) ) );
1005 if ( $isOK ) {
1006 $this->assertGoodStatus( $status,
1007 "Preparing dir $path succeeded without warnings ($backendName)." );
1008 $this->assertEquals( true, $status->isOK(),
1009 "Preparing dir $path succeeded ($backendName)." );
1010 } else {
1011 $this->assertEquals( false, $status->isOK(),
1012 "Preparing dir $path failed ($backendName)." );
1013 }
1014
1015 $status = $this->backend->clean( array( 'dir' => dirname( $path ) ) );
1016 if ( $isOK ) {
1017 $this->assertGoodStatus( $status,
1018 "Cleaning dir $path succeeded without warnings ($backendName)." );
1019 $this->assertEquals( true, $status->isOK(),
1020 "Cleaning dir $path succeeded ($backendName)." );
1021 } else {
1022 $this->assertEquals( false, $status->isOK(),
1023 "Cleaning dir $path failed ($backendName)." );
1024 }
1025 }
1026
1027 public function testRecursiveClean() {
1028 $this->backend = $this->singleBackend;
1029 $this->doTestRecursiveClean();
1030 $this->tearDownFiles();
1031
1032 $this->backend = $this->multiBackend;
1033 $this->doTestRecursiveClean();
1034 $this->tearDownFiles();
1035 }
1036
1037 function doTestRecursiveClean() {
1038 $backendName = $this->backendClass();
1039
1040 $base = $this->baseStorePath();
1041 $dirs = array(
1042 "$base/unittest-cont1/a",
1043 "$base/unittest-cont1/a/b",
1044 "$base/unittest-cont1/a/b/c",
1045 "$base/unittest-cont1/a/b/c/d0",
1046 "$base/unittest-cont1/a/b/c/d1",
1047 "$base/unittest-cont1/a/b/c/d2",
1048 "$base/unittest-cont1/a/b/c/d0/1",
1049 "$base/unittest-cont1/a/b/c/d0/2",
1050 "$base/unittest-cont1/a/b/c/d1/3",
1051 "$base/unittest-cont1/a/b/c/d1/4",
1052 "$base/unittest-cont1/a/b/c/d2/5",
1053 "$base/unittest-cont1/a/b/c/d2/6"
1054 );
1055 foreach ( $dirs as $dir ) {
1056 $status = $this->prepare( array( 'dir' => $dir ) );
1057 $this->assertGoodStatus( $status,
1058 "Preparing dir $dir succeeded without warnings ($backendName)." );
1059 }
1060
1061 if ( $this->backend instanceof FSFileBackend ) {
1062 foreach ( $dirs as $dir ) {
1063 $this->assertEquals( true, $this->backend->directoryExists( array( 'dir' => $dir ) ),
1064 "Dir $dir exists ($backendName)." );
1065 }
1066 }
1067
1068 $status = $this->backend->clean(
1069 array( 'dir' => "$base/unittest-cont1", 'recursive' => 1 ) );
1070 $this->assertGoodStatus( $status,
1071 "Recursive cleaning of dir $dir succeeded without warnings ($backendName)." );
1072
1073 foreach ( $dirs as $dir ) {
1074 $this->assertEquals( false, $this->backend->directoryExists( array( 'dir' => $dir ) ),
1075 "Dir $dir no longer exists ($backendName)." );
1076 }
1077 }
1078
1079 // @TODO: testSecure
1080
1081 public function testDoOperations() {
1082 $this->backend = $this->singleBackend;
1083 $this->tearDownFiles();
1084 $this->doTestDoOperations();
1085 $this->tearDownFiles();
1086
1087 $this->backend = $this->multiBackend;
1088 $this->tearDownFiles();
1089 $this->doTestDoOperations();
1090 $this->tearDownFiles();
1091
1092 $this->backend = $this->singleBackend;
1093 $this->tearDownFiles();
1094 $this->doTestDoOperations2();
1095 $this->tearDownFiles();
1096
1097 $this->backend = $this->multiBackend;
1098 $this->tearDownFiles();
1099 $this->doTestDoOperations2();
1100 $this->tearDownFiles();
1101
1102 $this->backend = $this->singleBackend;
1103 $this->tearDownFiles();
1104 $this->doTestDoOperationsFailing();
1105 $this->tearDownFiles();
1106
1107 $this->backend = $this->multiBackend;
1108 $this->tearDownFiles();
1109 $this->doTestDoOperationsFailing();
1110 $this->tearDownFiles();
1111 }
1112
1113 private function doTestDoOperations() {
1114 $base = $this->baseStorePath();
1115
1116 $fileA = "$base/unittest-cont1/a/b/fileA.txt";
1117 $fileAContents = '3tqtmoeatmn4wg4qe-mg3qt3 tq';
1118 $fileB = "$base/unittest-cont1/a/b/fileB.txt";
1119 $fileBContents = 'g-jmq3gpqgt3qtg q3GT ';
1120 $fileC = "$base/unittest-cont1/a/b/fileC.txt";
1121 $fileCContents = 'eigna[ogmewt 3qt g3qg flew[ag';
1122 $fileD = "$base/unittest-cont1/a/b/fileD.txt";
1123
1124 $this->prepare( array( 'dir' => dirname( $fileA ) ) );
1125 $this->backend->create( array( 'dst' => $fileA, 'content' => $fileAContents ) );
1126 $this->prepare( array( 'dir' => dirname( $fileB ) ) );
1127 $this->backend->create( array( 'dst' => $fileB, 'content' => $fileBContents ) );
1128 $this->prepare( array( 'dir' => dirname( $fileC ) ) );
1129 $this->backend->create( array( 'dst' => $fileC, 'content' => $fileCContents ) );
1130 $this->prepare( array( 'dir' => dirname( $fileD ) ) );
1131
1132 $status = $this->backend->doOperations( array(
1133 array( 'op' => 'copy', 'src' => $fileA, 'dst' => $fileC, 'overwrite' => 1 ),
1134 // Now: A:<A>, B:<B>, C:<A>, D:<empty> (file:<orginal contents>)
1135 array( 'op' => 'copy', 'src' => $fileC, 'dst' => $fileA, 'overwriteSame' => 1 ),
1136 // Now: A:<A>, B:<B>, C:<A>, D:<empty>
1137 array( 'op' => 'move', 'src' => $fileC, 'dst' => $fileD, 'overwrite' => 1 ),
1138 // Now: A:<A>, B:<B>, C:<empty>, D:<A>
1139 array( 'op' => 'move', 'src' => $fileB, 'dst' => $fileC ),
1140 // Now: A:<A>, B:<empty>, C:<B>, D:<A>
1141 array( 'op' => 'move', 'src' => $fileD, 'dst' => $fileA, 'overwriteSame' => 1 ),
1142 // Now: A:<A>, B:<empty>, C:<B>, D:<empty>
1143 array( 'op' => 'move', 'src' => $fileC, 'dst' => $fileA, 'overwrite' => 1 ),
1144 // Now: A:<B>, B:<empty>, C:<empty>, D:<empty>
1145 array( 'op' => 'copy', 'src' => $fileA, 'dst' => $fileC ),
1146 // Now: A:<B>, B:<empty>, C:<B>, D:<empty>
1147 array( 'op' => 'move', 'src' => $fileA, 'dst' => $fileC, 'overwriteSame' => 1 ),
1148 // Now: A:<empty>, B:<empty>, C:<B>, D:<empty>
1149 array( 'op' => 'copy', 'src' => $fileC, 'dst' => $fileC, 'overwrite' => 1 ),
1150 // Does nothing
1151 array( 'op' => 'copy', 'src' => $fileC, 'dst' => $fileC, 'overwriteSame' => 1 ),
1152 // Does nothing
1153 array( 'op' => 'move', 'src' => $fileC, 'dst' => $fileC, 'overwrite' => 1 ),
1154 // Does nothing
1155 array( 'op' => 'move', 'src' => $fileC, 'dst' => $fileC, 'overwriteSame' => 1 ),
1156 // Does nothing
1157 array( 'op' => 'null' ),
1158 // Does nothing
1159 ) );
1160
1161 $this->assertGoodStatus( $status, "Operation batch succeeded" );
1162 $this->assertEquals( true, $status->isOK(), "Operation batch succeeded" );
1163 $this->assertEquals( 13, count( $status->success ),
1164 "Operation batch has correct success array" );
1165
1166 $this->assertEquals( false, $this->backend->fileExists( array( 'src' => $fileA ) ),
1167 "File does not exist at $fileA" );
1168 $this->assertEquals( false, $this->backend->fileExists( array( 'src' => $fileB ) ),
1169 "File does not exist at $fileB" );
1170 $this->assertEquals( false, $this->backend->fileExists( array( 'src' => $fileD ) ),
1171 "File does not exist at $fileD" );
1172
1173 $this->assertEquals( true, $this->backend->fileExists( array( 'src' => $fileC ) ),
1174 "File exists at $fileC" );
1175 $this->assertEquals( $fileBContents,
1176 $this->backend->getFileContents( array( 'src' => $fileC ) ),
1177 "Correct file contents of $fileC" );
1178 $this->assertEquals( strlen( $fileBContents ),
1179 $this->backend->getFileSize( array( 'src' => $fileC ) ),
1180 "Correct file size of $fileC" );
1181 $this->assertEquals( wfBaseConvert( sha1( $fileBContents ), 16, 36, 31 ),
1182 $this->backend->getFileSha1Base36( array( 'src' => $fileC ) ),
1183 "Correct file SHA-1 of $fileC" );
1184 }
1185
1186 // concurrency orientated
1187 function doTestDoOperations2() {
1188 $base = $this->baseStorePath();
1189
1190 $fileAContents = '3tqtmoeatmn4wg4qe-mg3qt3 tq';
1191 $fileBContents = 'g-jmq3gpqgt3qtg q3GT ';
1192 $fileCContents = 'eigna[ogmewt 3qt g3qg flew[ag';
1193
1194 $tmpNameA = TempFSFile::factory( "unittests_", 'txt' )->getPath();
1195 file_put_contents( $tmpNameA, $fileAContents );
1196 $tmpNameB = TempFSFile::factory( "unittests_", 'txt' )->getPath();
1197 file_put_contents( $tmpNameB, $fileBContents );
1198 $tmpNameC = TempFSFile::factory( "unittests_", 'txt' )->getPath();
1199 file_put_contents( $tmpNameC, $fileCContents );
1200
1201 $this->filesToPrune[] = $tmpNameA; # avoid file leaking
1202 $this->filesToPrune[] = $tmpNameB; # avoid file leaking
1203 $this->filesToPrune[] = $tmpNameC; # avoid file leaking
1204
1205 $fileA = "$base/unittest-cont1/a/b/fileA.txt";
1206 $fileB = "$base/unittest-cont1/a/b/fileB.txt";
1207 $fileC = "$base/unittest-cont1/a/b/fileC.txt";
1208 $fileD = "$base/unittest-cont1/a/b/fileD.txt";
1209
1210 $this->prepare( array( 'dir' => dirname( $fileA ) ) );
1211 $this->backend->create( array( 'dst' => $fileA, 'content' => $fileAContents ) );
1212 $this->prepare( array( 'dir' => dirname( $fileB ) ) );
1213 $this->prepare( array( 'dir' => dirname( $fileC ) ) );
1214 $this->prepare( array( 'dir' => dirname( $fileD ) ) );
1215
1216 $status = $this->backend->doOperations( array(
1217 array( 'op' => 'store', 'src' => $tmpNameA, 'dst' => $fileA, 'overwriteSame' => 1 ),
1218 array( 'op' => 'store', 'src' => $tmpNameB, 'dst' => $fileB, 'overwrite' => 1 ),
1219 array( 'op' => 'store', 'src' => $tmpNameC, 'dst' => $fileC, 'overwrite' => 1 ),
1220 array( 'op' => 'copy', 'src' => $fileA, 'dst' => $fileC, 'overwrite' => 1 ),
1221 // Now: A:<A>, B:<B>, C:<A>, D:<empty> (file:<orginal contents>)
1222 array( 'op' => 'copy', 'src' => $fileC, 'dst' => $fileA, 'overwriteSame' => 1 ),
1223 // Now: A:<A>, B:<B>, C:<A>, D:<empty>
1224 array( 'op' => 'move', 'src' => $fileC, 'dst' => $fileD, 'overwrite' => 1 ),
1225 // Now: A:<A>, B:<B>, C:<empty>, D:<A>
1226 array( 'op' => 'move', 'src' => $fileB, 'dst' => $fileC ),
1227 // Now: A:<A>, B:<empty>, C:<B>, D:<A>
1228 array( 'op' => 'move', 'src' => $fileD, 'dst' => $fileA, 'overwriteSame' => 1 ),
1229 // Now: A:<A>, B:<empty>, C:<B>, D:<empty>
1230 array( 'op' => 'move', 'src' => $fileC, 'dst' => $fileA, 'overwrite' => 1 ),
1231 // Now: A:<B>, B:<empty>, C:<empty>, D:<empty>
1232 array( 'op' => 'copy', 'src' => $fileA, 'dst' => $fileC ),
1233 // Now: A:<B>, B:<empty>, C:<B>, D:<empty>
1234 array( 'op' => 'move', 'src' => $fileA, 'dst' => $fileC, 'overwriteSame' => 1 ),
1235 // Now: A:<empty>, B:<empty>, C:<B>, D:<empty>
1236 array( 'op' => 'copy', 'src' => $fileC, 'dst' => $fileC, 'overwrite' => 1 ),
1237 // Does nothing
1238 array( 'op' => 'copy', 'src' => $fileC, 'dst' => $fileC, 'overwriteSame' => 1 ),
1239 // Does nothing
1240 array( 'op' => 'move', 'src' => $fileC, 'dst' => $fileC, 'overwrite' => 1 ),
1241 // Does nothing
1242 array( 'op' => 'move', 'src' => $fileC, 'dst' => $fileC, 'overwriteSame' => 1 ),
1243 // Does nothing
1244 array( 'op' => 'null' ),
1245 // Does nothing
1246 ) );
1247
1248 $this->assertGoodStatus( $status, "Operation batch succeeded" );
1249 $this->assertEquals( true, $status->isOK(), "Operation batch succeeded" );
1250 $this->assertEquals( 16, count( $status->success ),
1251 "Operation batch has correct success array" );
1252
1253 $this->assertEquals( false, $this->backend->fileExists( array( 'src' => $fileA ) ),
1254 "File does not exist at $fileA" );
1255 $this->assertEquals( false, $this->backend->fileExists( array( 'src' => $fileB ) ),
1256 "File does not exist at $fileB" );
1257 $this->assertEquals( false, $this->backend->fileExists( array( 'src' => $fileD ) ),
1258 "File does not exist at $fileD" );
1259
1260 $this->assertEquals( true, $this->backend->fileExists( array( 'src' => $fileC ) ),
1261 "File exists at $fileC" );
1262 $this->assertEquals( $fileBContents,
1263 $this->backend->getFileContents( array( 'src' => $fileC ) ),
1264 "Correct file contents of $fileC" );
1265 $this->assertEquals( strlen( $fileBContents ),
1266 $this->backend->getFileSize( array( 'src' => $fileC ) ),
1267 "Correct file size of $fileC" );
1268 $this->assertEquals( wfBaseConvert( sha1( $fileBContents ), 16, 36, 31 ),
1269 $this->backend->getFileSha1Base36( array( 'src' => $fileC ) ),
1270 "Correct file SHA-1 of $fileC" );
1271 }
1272
1273 function doTestDoOperationsFailing() {
1274 $base = $this->baseStorePath();
1275
1276 $fileA = "$base/unittest-cont2/a/b/fileA.txt";
1277 $fileAContents = '3tqtmoeatmn4wg4qe-mg3qt3 tq';
1278 $fileB = "$base/unittest-cont2/a/b/fileB.txt";
1279 $fileBContents = 'g-jmq3gpqgt3qtg q3GT ';
1280 $fileC = "$base/unittest-cont2/a/b/fileC.txt";
1281 $fileCContents = 'eigna[ogmewt 3qt g3qg flew[ag';
1282 $fileD = "$base/unittest-cont2/a/b/fileD.txt";
1283
1284 $this->prepare( array( 'dir' => dirname( $fileA ) ) );
1285 $this->backend->create( array( 'dst' => $fileA, 'content' => $fileAContents ) );
1286 $this->prepare( array( 'dir' => dirname( $fileB ) ) );
1287 $this->backend->create( array( 'dst' => $fileB, 'content' => $fileBContents ) );
1288 $this->prepare( array( 'dir' => dirname( $fileC ) ) );
1289 $this->backend->create( array( 'dst' => $fileC, 'content' => $fileCContents ) );
1290
1291 $status = $this->backend->doOperations( array(
1292 array( 'op' => 'copy', 'src' => $fileA, 'dst' => $fileC, 'overwrite' => 1 ),
1293 // Now: A:<A>, B:<B>, C:<A>, D:<empty> (file:<orginal contents>)
1294 array( 'op' => 'copy', 'src' => $fileC, 'dst' => $fileA, 'overwriteSame' => 1 ),
1295 // Now: A:<A>, B:<B>, C:<A>, D:<empty>
1296 array( 'op' => 'copy', 'src' => $fileB, 'dst' => $fileD, 'overwrite' => 1 ),
1297 // Now: A:<A>, B:<B>, C:<A>, D:<B>
1298 array( 'op' => 'move', 'src' => $fileC, 'dst' => $fileD ),
1299 // Now: A:<A>, B:<B>, C:<A>, D:<empty> (failed)
1300 array( 'op' => 'move', 'src' => $fileB, 'dst' => $fileC, 'overwriteSame' => 1 ),
1301 // Now: A:<A>, B:<B>, C:<A>, D:<empty> (failed)
1302 array( 'op' => 'move', 'src' => $fileB, 'dst' => $fileA, 'overwrite' => 1 ),
1303 // Now: A:<B>, B:<empty>, C:<A>, D:<empty>
1304 array( 'op' => 'delete', 'src' => $fileD ),
1305 // Now: A:<B>, B:<empty>, C:<A>, D:<empty>
1306 array( 'op' => 'null' ),
1307 // Does nothing
1308 ), array( 'force' => 1 ) );
1309
1310 $this->assertNotEquals( array(), $status->errors, "Operation had warnings" );
1311 $this->assertEquals( true, $status->isOK(), "Operation batch succeeded" );
1312 $this->assertEquals( 8, count( $status->success ),
1313 "Operation batch has correct success array" );
1314
1315 $this->assertEquals( false, $this->backend->fileExists( array( 'src' => $fileB ) ),
1316 "File does not exist at $fileB" );
1317 $this->assertEquals( false, $this->backend->fileExists( array( 'src' => $fileD ) ),
1318 "File does not exist at $fileD" );
1319
1320 $this->assertEquals( true, $this->backend->fileExists( array( 'src' => $fileA ) ),
1321 "File does not exist at $fileA" );
1322 $this->assertEquals( true, $this->backend->fileExists( array( 'src' => $fileC ) ),
1323 "File exists at $fileC" );
1324 $this->assertEquals( $fileBContents,
1325 $this->backend->getFileContents( array( 'src' => $fileA ) ),
1326 "Correct file contents of $fileA" );
1327 $this->assertEquals( strlen( $fileBContents ),
1328 $this->backend->getFileSize( array( 'src' => $fileA ) ),
1329 "Correct file size of $fileA" );
1330 $this->assertEquals( wfBaseConvert( sha1( $fileBContents ), 16, 36, 31 ),
1331 $this->backend->getFileSha1Base36( array( 'src' => $fileA ) ),
1332 "Correct file SHA-1 of $fileA" );
1333 }
1334
1335 public function testGetFileList() {
1336 $this->backend = $this->singleBackend;
1337 $this->tearDownFiles();
1338 $this->doTestGetFileList();
1339 $this->tearDownFiles();
1340
1341 $this->backend = $this->multiBackend;
1342 $this->tearDownFiles();
1343 $this->doTestGetFileList();
1344 $this->tearDownFiles();
1345 }
1346
1347 private function doTestGetFileList() {
1348 $backendName = $this->backendClass();
1349
1350 $base = $this->baseStorePath();
1351 $files = array(
1352 "$base/unittest-cont1/test1.txt",
1353 "$base/unittest-cont1/test2.txt",
1354 "$base/unittest-cont1/test3.txt",
1355 "$base/unittest-cont1/subdir1/test1.txt",
1356 "$base/unittest-cont1/subdir1/test2.txt",
1357 "$base/unittest-cont1/subdir2/test3.txt",
1358 "$base/unittest-cont1/subdir2/test4.txt",
1359 "$base/unittest-cont1/subdir2/subdir/test1.txt",
1360 "$base/unittest-cont1/subdir2/subdir/test2.txt",
1361 "$base/unittest-cont1/subdir2/subdir/test3.txt",
1362 "$base/unittest-cont1/subdir2/subdir/test4.txt",
1363 "$base/unittest-cont1/subdir2/subdir/test5.txt",
1364 "$base/unittest-cont1/subdir2/subdir/sub/test0.txt",
1365 "$base/unittest-cont1/subdir2/subdir/sub/120-px-file.txt",
1366 );
1367
1368 // Add the files
1369 $ops = array();
1370 foreach ( $files as $file ) {
1371 $this->prepare( array( 'dir' => dirname( $file ) ) );
1372 $ops[] = array( 'op' => 'create', 'content' => 'xxy', 'dst' => $file );
1373 }
1374 $status = $this->backend->doOperations( $ops );
1375 $this->assertGoodStatus( $status,
1376 "Creation of files succeeded ($backendName)." );
1377 $this->assertEquals( true, $status->isOK(),
1378 "Creation of files succeeded with OK status ($backendName)." );
1379
1380 // Expected listing
1381 $expected = array(
1382 "test1.txt",
1383 "test2.txt",
1384 "test3.txt",
1385 "subdir1/test1.txt",
1386 "subdir1/test2.txt",
1387 "subdir2/test3.txt",
1388 "subdir2/test4.txt",
1389 "subdir2/subdir/test1.txt",
1390 "subdir2/subdir/test2.txt",
1391 "subdir2/subdir/test3.txt",
1392 "subdir2/subdir/test4.txt",
1393 "subdir2/subdir/test5.txt",
1394 "subdir2/subdir/sub/test0.txt",
1395 "subdir2/subdir/sub/120-px-file.txt",
1396 );
1397 sort( $expected );
1398
1399 // Actual listing (no trailing slash)
1400 $list = array();
1401 $iter = $this->backend->getFileList( array( 'dir' => "$base/unittest-cont1" ) );
1402 foreach ( $iter as $file ) {
1403 $list[] = $file;
1404 }
1405 sort( $list );
1406
1407 $this->assertEquals( $expected, $list, "Correct file listing ($backendName)." );
1408
1409 // Actual listing (with trailing slash)
1410 $list = array();
1411 $iter = $this->backend->getFileList( array( 'dir' => "$base/unittest-cont1/" ) );
1412 foreach ( $iter as $file ) {
1413 $list[] = $file;
1414 }
1415 sort( $list );
1416
1417 $this->assertEquals( $expected, $list, "Correct file listing ($backendName)." );
1418
1419 // Expected listing
1420 $expected = array(
1421 "test1.txt",
1422 "test2.txt",
1423 "test3.txt",
1424 "test4.txt",
1425 "test5.txt",
1426 "sub/test0.txt",
1427 "sub/120-px-file.txt",
1428 );
1429 sort( $expected );
1430
1431 // Actual listing (no trailing slash)
1432 $list = array();
1433 $iter = $this->backend->getFileList( array( 'dir' => "$base/unittest-cont1/subdir2/subdir" ) );
1434 foreach ( $iter as $file ) {
1435 $list[] = $file;
1436 }
1437 sort( $list );
1438
1439 $this->assertEquals( $expected, $list, "Correct file listing ($backendName)." );
1440
1441 // Actual listing (with trailing slash)
1442 $list = array();
1443 $iter = $this->backend->getFileList( array( 'dir' => "$base/unittest-cont1/subdir2/subdir/" ) );
1444 foreach ( $iter as $file ) {
1445 $list[] = $file;
1446 }
1447 sort( $list );
1448
1449 $this->assertEquals( $expected, $list, "Correct file listing ($backendName)." );
1450
1451 // Actual listing (using iterator second time)
1452 $list = array();
1453 foreach ( $iter as $file ) {
1454 $list[] = $file;
1455 }
1456 sort( $list );
1457
1458 $this->assertEquals( $expected, $list, "Correct file listing ($backendName), second iteration." );
1459
1460 // Expected listing (top files only)
1461 $expected = array(
1462 "test1.txt",
1463 "test2.txt",
1464 "test3.txt",
1465 "test4.txt",
1466 "test5.txt"
1467 );
1468 sort( $expected );
1469
1470 // Actual listing (top files only)
1471 $list = array();
1472 $iter = $this->backend->getTopFileList( array( 'dir' => "$base/unittest-cont1/subdir2/subdir" ) );
1473 foreach ( $iter as $file ) {
1474 $list[] = $file;
1475 }
1476 sort( $list );
1477
1478 $this->assertEquals( $expected, $list, "Correct top file listing ($backendName)." );
1479
1480 foreach ( $files as $file ) { // clean up
1481 $this->backend->doOperation( array( 'op' => 'delete', 'src' => $file ) );
1482 }
1483
1484 $iter = $this->backend->getFileList( array( 'dir' => "$base/unittest-cont1/not/exists" ) );
1485 foreach ( $iter as $iter ) {} // no errors
1486 }
1487
1488 public function testGetDirectoryList() {
1489 $this->backend = $this->singleBackend;
1490 $this->tearDownFiles();
1491 $this->doTestGetDirectoryList();
1492 $this->tearDownFiles();
1493
1494 $this->backend = $this->multiBackend;
1495 $this->tearDownFiles();
1496 $this->doTestGetDirectoryList();
1497 $this->tearDownFiles();
1498 }
1499
1500 private function doTestGetDirectoryList() {
1501 $backendName = $this->backendClass();
1502
1503 $base = $this->baseStorePath();
1504 $files = array(
1505 "$base/unittest-cont1/test1.txt",
1506 "$base/unittest-cont1/test2.txt",
1507 "$base/unittest-cont1/test3.txt",
1508 "$base/unittest-cont1/subdir1/test1.txt",
1509 "$base/unittest-cont1/subdir1/test2.txt",
1510 "$base/unittest-cont1/subdir2/test3.txt",
1511 "$base/unittest-cont1/subdir2/test4.txt",
1512 "$base/unittest-cont1/subdir2/subdir/test1.txt",
1513 "$base/unittest-cont1/subdir3/subdir/test2.txt",
1514 "$base/unittest-cont1/subdir4/subdir/test3.txt",
1515 "$base/unittest-cont1/subdir4/subdir/test4.txt",
1516 "$base/unittest-cont1/subdir4/subdir/test5.txt",
1517 "$base/unittest-cont1/subdir4/subdir/sub/test0.txt",
1518 "$base/unittest-cont1/subdir4/subdir/sub/120-px-file.txt",
1519 );
1520
1521 // Add the files
1522 $ops = array();
1523 foreach ( $files as $file ) {
1524 $this->prepare( array( 'dir' => dirname( $file ) ) );
1525 $ops[] = array( 'op' => 'create', 'content' => 'xxy', 'dst' => $file );
1526 }
1527 $status = $this->backend->doOperations( $ops );
1528 $this->assertGoodStatus( $status,
1529 "Creation of files succeeded ($backendName)." );
1530 $this->assertEquals( true, $status->isOK(),
1531 "Creation of files succeeded with OK status ($backendName)." );
1532
1533 // Expected listing
1534 $expected = array(
1535 "subdir1",
1536 "subdir2",
1537 "subdir3",
1538 "subdir4",
1539 );
1540 sort( $expected );
1541
1542 $this->assertEquals( true,
1543 $this->backend->directoryExists( array( 'dir' => "$base/unittest-cont1/subdir1" ) ),
1544 "Directory exists in ($backendName)." );
1545 $this->assertEquals( true,
1546 $this->backend->directoryExists( array( 'dir' => "$base/unittest-cont1/subdir2/subdir" ) ),
1547 "Directory exists in ($backendName)." );
1548 $this->assertEquals( false,
1549 $this->backend->directoryExists( array( 'dir' => "$base/unittest-cont1/subdir2/test1.txt" ) ),
1550 "Directory does not exists in ($backendName)." );
1551
1552 // Actual listing (no trailing slash)
1553 $list = array();
1554 $iter = $this->backend->getTopDirectoryList( array( 'dir' => "$base/unittest-cont1" ) );
1555 foreach ( $iter as $file ) {
1556 $list[] = $file;
1557 }
1558 sort( $list );
1559
1560 $this->assertEquals( $expected, $list, "Correct top dir listing ($backendName)." );
1561
1562 // Actual listing (with trailing slash)
1563 $list = array();
1564 $iter = $this->backend->getTopDirectoryList( array( 'dir' => "$base/unittest-cont1/" ) );
1565 foreach ( $iter as $file ) {
1566 $list[] = $file;
1567 }
1568 sort( $list );
1569
1570 $this->assertEquals( $expected, $list, "Correct top dir listing ($backendName)." );
1571
1572 // Expected listing
1573 $expected = array(
1574 "subdir",
1575 );
1576 sort( $expected );
1577
1578 // Actual listing (no trailing slash)
1579 $list = array();
1580 $iter = $this->backend->getTopDirectoryList( array( 'dir' => "$base/unittest-cont1/subdir2" ) );
1581 foreach ( $iter as $file ) {
1582 $list[] = $file;
1583 }
1584 sort( $list );
1585
1586 $this->assertEquals( $expected, $list, "Correct top dir listing ($backendName)." );
1587
1588 // Actual listing (with trailing slash)
1589 $list = array();
1590 $iter = $this->backend->getTopDirectoryList( array( 'dir' => "$base/unittest-cont1/subdir2/" ) );
1591 foreach ( $iter as $file ) {
1592 $list[] = $file;
1593 }
1594 sort( $list );
1595
1596 $this->assertEquals( $expected, $list, "Correct top dir listing ($backendName)." );
1597
1598 // Actual listing (using iterator second time)
1599 $list = array();
1600 foreach ( $iter as $file ) {
1601 $list[] = $file;
1602 }
1603 sort( $list );
1604
1605 $this->assertEquals( $expected, $list, "Correct top dir listing ($backendName), second iteration." );
1606
1607 // Expected listing (recursive)
1608 $expected = array(
1609 "subdir1",
1610 "subdir2",
1611 "subdir3",
1612 "subdir4",
1613 "subdir2/subdir",
1614 "subdir3/subdir",
1615 "subdir4/subdir",
1616 "subdir4/subdir/sub",
1617 );
1618 sort( $expected );
1619
1620 // Actual listing (recursive)
1621 $list = array();
1622 $iter = $this->backend->getDirectoryList( array( 'dir' => "$base/unittest-cont1/" ) );
1623 foreach ( $iter as $file ) {
1624 $list[] = $file;
1625 }
1626 sort( $list );
1627
1628 $this->assertEquals( $expected, $list, "Correct dir listing ($backendName)." );
1629
1630 // Expected listing (recursive)
1631 $expected = array(
1632 "subdir",
1633 "subdir/sub",
1634 );
1635 sort( $expected );
1636
1637 // Actual listing (recursive)
1638 $list = array();
1639 $iter = $this->backend->getDirectoryList( array( 'dir' => "$base/unittest-cont1/subdir4" ) );
1640 foreach ( $iter as $file ) {
1641 $list[] = $file;
1642 }
1643 sort( $list );
1644
1645 $this->assertEquals( $expected, $list, "Correct dir listing ($backendName)." );
1646
1647 // Actual listing (recursive, second time)
1648 $list = array();
1649 foreach ( $iter as $file ) {
1650 $list[] = $file;
1651 }
1652 sort( $list );
1653
1654 $this->assertEquals( $expected, $list, "Correct dir listing ($backendName)." );
1655
1656 foreach ( $files as $file ) { // clean up
1657 $this->backend->doOperation( array( 'op' => 'delete', 'src' => $file ) );
1658 }
1659
1660 $iter = $this->backend->getDirectoryList( array( 'dir' => "$base/unittest-cont1/not/exists" ) );
1661 foreach ( $iter as $iter ) {} // no errors
1662 }
1663
1664 public function testLockCalls() {
1665 $this->backend = $this->singleBackend;
1666 $this->doTestLockCalls();
1667 }
1668
1669 private function doTestLockCalls() {
1670 $backendName = $this->backendClass();
1671
1672 for ( $i=0; $i<50; $i++ ) {
1673 $paths = array(
1674 "test1.txt",
1675 "test2.txt",
1676 "test3.txt",
1677 "subdir1",
1678 "subdir1", // duplicate
1679 "subdir1/test1.txt",
1680 "subdir1/test2.txt",
1681 "subdir2",
1682 "subdir2", // duplicate
1683 "subdir2/test3.txt",
1684 "subdir2/test4.txt",
1685 "subdir2/subdir",
1686 "subdir2/subdir/test1.txt",
1687 "subdir2/subdir/test2.txt",
1688 "subdir2/subdir/test3.txt",
1689 "subdir2/subdir/test4.txt",
1690 "subdir2/subdir/test5.txt",
1691 "subdir2/subdir/sub",
1692 "subdir2/subdir/sub/test0.txt",
1693 "subdir2/subdir/sub/120-px-file.txt",
1694 );
1695
1696 $status = $this->backend->lockFiles( $paths, LockManager::LOCK_EX );
1697 $this->assertEquals( array(), $status->errors,
1698 "Locking of files succeeded ($backendName)." );
1699 $this->assertEquals( true, $status->isOK(),
1700 "Locking of files succeeded with OK status ($backendName)." );
1701
1702 $status = $this->backend->lockFiles( $paths, LockManager::LOCK_SH );
1703 $this->assertEquals( array(), $status->errors,
1704 "Locking of files succeeded ($backendName)." );
1705 $this->assertEquals( true, $status->isOK(),
1706 "Locking of files succeeded with OK status ($backendName)." );
1707
1708 $status = $this->backend->unlockFiles( $paths, LockManager::LOCK_SH );
1709 $this->assertEquals( array(), $status->errors,
1710 "Locking of files succeeded ($backendName)." );
1711 $this->assertEquals( true, $status->isOK(),
1712 "Locking of files succeeded with OK status ($backendName)." );
1713
1714 $status = $this->backend->unlockFiles( $paths, LockManager::LOCK_EX );
1715 $this->assertEquals( array(), $status->errors,
1716 "Locking of files succeeded ($backendName)." );
1717 $this->assertEquals( true, $status->isOK(),
1718 "Locking of files succeeded with OK status ($backendName)." );
1719 }
1720 }
1721
1722 // test helper wrapper for backend prepare() function
1723 private function prepare( array $params ) {
1724 $this->dirsToPrune[] = $params['dir'];
1725 return $this->backend->prepare( $params );
1726 }
1727
1728 function tearDownFiles() {
1729 foreach ( $this->filesToPrune as $file ) {
1730 @unlink( $file );
1731 }
1732 $containers = array( 'unittest-cont1', 'unittest-cont2', 'unittest-cont3' );
1733 foreach ( $containers as $container ) {
1734 $this->deleteFiles( $container );
1735 }
1736 foreach ( $this->dirsToPrune as $dir ) {
1737 $this->recursiveClean( $dir );
1738 }
1739 $this->filesToPrune = $this->dirsToPrune = array();
1740 }
1741
1742 private function deleteFiles( $container ) {
1743 $base = $this->baseStorePath();
1744 $iter = $this->backend->getFileList( array( 'dir' => "$base/$container" ) );
1745 if ( $iter ) {
1746 foreach ( $iter as $file ) {
1747 $this->backend->delete( array( 'src' => "$base/$container/$file" ),
1748 array( 'force' => 1, 'nonLocking' => 1 ) );
1749 }
1750 }
1751 }
1752
1753 private function recursiveClean( $dir ) {
1754 $this->backend->clean( array( 'dir' => $dir, 'recursive' => 1 ) );
1755 }
1756
1757 function assertGoodStatus( $status, $msg ) {
1758 $this->assertEquals( print_r( array(), 1 ), print_r( $status->errors, 1 ), $msg );
1759 }
1760
1761 function tearDown() {
1762 parent::tearDown();
1763 }
1764 }