getNewTempDirectory(); if ( $this->getCliArg( 'use-filebackend' ) ) { if ( self::$backendToUse ) { $this->singleBackend = self::$backendToUse; } else { $name = $this->getCliArg( 'use-filebackend' ); $useConfig = []; foreach ( $wgFileBackends as $conf ) { if ( $conf['name'] == $name ) { $useConfig = $conf; break; } } $useConfig['name'] = 'localtesting'; // swap name $useConfig['shardViaHashLevels'] = [ // test sharding 'unittest-cont1' => [ 'levels' => 1, 'base' => 16, 'repeat' => 1 ] ]; if ( isset( $useConfig['fileJournal'] ) ) { $useConfig['fileJournal'] = FileJournal::factory( $useConfig['fileJournal'], $name ); } $useConfig['lockManager'] = LockManagerGroup::singleton()->get( $useConfig['lockManager'] ); $class = $useConfig['class']; self::$backendToUse = new $class( $useConfig ); $this->singleBackend = self::$backendToUse; } } else { $this->singleBackend = new FSFileBackend( [ 'name' => 'localtesting', 'lockManager' => LockManagerGroup::singleton()->get( 'fsLockManager' ), 'wikiId' => wfWikiID(), 'containerPaths' => [ 'unittest-cont1' => "{$tmpDir}/localtesting-cont1", 'unittest-cont2' => "{$tmpDir}/localtesting-cont2" ] ] ); } $this->multiBackend = new FileBackendMultiWrite( [ 'name' => 'localtesting', 'lockManager' => LockManagerGroup::singleton()->get( 'fsLockManager' ), 'parallelize' => 'implicit', 'wikiId' => 'testdb', 'backends' => [ [ 'name' => 'localmultitesting1', 'class' => FSFileBackend::class, 'containerPaths' => [ 'unittest-cont1' => "{$tmpDir}/localtestingmulti1-cont1", 'unittest-cont2' => "{$tmpDir}/localtestingmulti1-cont2" ], 'isMultiMaster' => false ], [ 'name' => 'localmultitesting2', 'class' => FSFileBackend::class, 'containerPaths' => [ 'unittest-cont1' => "{$tmpDir}/localtestingmulti2-cont1", 'unittest-cont2' => "{$tmpDir}/localtestingmulti2-cont2" ], 'isMultiMaster' => true ] ] ] ); } private static function baseStorePath() { return 'mwstore://localtesting'; } private function backendClass() { return get_class( $this->backend ); } /** * @dataProvider provider_testIsStoragePath */ public function testIsStoragePath( $path, $isStorePath ) { $this->assertEquals( $isStorePath, FileBackend::isStoragePath( $path ), "FileBackend::isStoragePath on path '$path'" ); } public static function provider_testIsStoragePath() { return [ [ 'mwstore://', true ], [ 'mwstore://backend', true ], [ 'mwstore://backend/container', true ], [ 'mwstore://backend/container/', true ], [ 'mwstore://backend/container/path', true ], [ 'mwstore://backend//container/', true ], [ 'mwstore://backend//container//', true ], [ 'mwstore://backend//container//path', true ], [ 'mwstore:///', true ], [ 'mwstore:/', false ], [ 'mwstore:', false ], ]; } /** * @dataProvider provider_testSplitStoragePath */ public function testSplitStoragePath( $path, $res ) { $this->assertEquals( $res, FileBackend::splitStoragePath( $path ), "FileBackend::splitStoragePath on path '$path'" ); } public static function provider_testSplitStoragePath() { return [ [ 'mwstore://backend/container', [ 'backend', 'container', '' ] ], [ 'mwstore://backend/container/', [ 'backend', 'container', '' ] ], [ 'mwstore://backend/container/path', [ 'backend', 'container', 'path' ] ], [ 'mwstore://backend/container//path', [ 'backend', 'container', '/path' ] ], [ 'mwstore://backend//container/path', [ null, null, null ] ], [ 'mwstore://backend//container//path', [ null, null, null ] ], [ 'mwstore://', [ null, null, null ] ], [ 'mwstore://backend', [ null, null, null ] ], [ 'mwstore:///', [ null, null, null ] ], [ 'mwstore:/', [ null, null, null ] ], [ 'mwstore:', [ null, null, null ] ] ]; } /** * @dataProvider provider_normalizeStoragePath */ public function testNormalizeStoragePath( $path, $res ) { $this->assertEquals( $res, FileBackend::normalizeStoragePath( $path ), "FileBackend::normalizeStoragePath on path '$path'" ); } public static function provider_normalizeStoragePath() { return [ [ 'mwstore://backend/container', 'mwstore://backend/container' ], [ 'mwstore://backend/container/', 'mwstore://backend/container' ], [ 'mwstore://backend/container/path', 'mwstore://backend/container/path' ], [ 'mwstore://backend/container//path', 'mwstore://backend/container/path' ], [ 'mwstore://backend/container///path', 'mwstore://backend/container/path' ], [ 'mwstore://backend/container///path//to///obj', 'mwstore://backend/container/path/to/obj' ], [ 'mwstore://', null ], [ 'mwstore://backend', null ], [ 'mwstore://backend//container/path', null ], [ 'mwstore://backend//container//path', null ], [ 'mwstore:///', null ], [ 'mwstore:/', null ], [ 'mwstore:', null ], ]; } /** * @dataProvider provider_testParentStoragePath */ public function testParentStoragePath( $path, $res ) { $this->assertEquals( $res, FileBackend::parentStoragePath( $path ), "FileBackend::parentStoragePath on path '$path'" ); } public static function provider_testParentStoragePath() { return [ [ 'mwstore://backend/container/path/to/obj', 'mwstore://backend/container/path/to' ], [ 'mwstore://backend/container/path/to', 'mwstore://backend/container/path' ], [ 'mwstore://backend/container/path', 'mwstore://backend/container' ], [ 'mwstore://backend/container', null ], [ 'mwstore://backend/container/path/to/obj/', 'mwstore://backend/container/path/to' ], [ 'mwstore://backend/container/path/to/', 'mwstore://backend/container/path' ], [ 'mwstore://backend/container/path/', 'mwstore://backend/container' ], [ 'mwstore://backend/container/', null ], ]; } /** * @dataProvider provider_testExtensionFromPath */ public function testExtensionFromPath( $path, $res ) { $this->assertEquals( $res, FileBackend::extensionFromPath( $path ), "FileBackend::extensionFromPath on path '$path'" ); } public static function provider_testExtensionFromPath() { return [ [ 'mwstore://backend/container/path.txt', 'txt' ], [ 'mwstore://backend/container/path.svg.png', 'png' ], [ 'mwstore://backend/container/path', '' ], [ 'mwstore://backend/container/path.', '' ], ]; } /** * @dataProvider provider_testStore */ public function testStore( $op ) { $this->addTmpFiles( $op['src'] ); $this->backend = $this->singleBackend; $this->tearDownFiles(); $this->doTestStore( $op ); $this->tearDownFiles(); $this->backend = $this->multiBackend; $this->tearDownFiles(); $this->doTestStore( $op ); $this->tearDownFiles(); } private function doTestStore( $op ) { $backendName = $this->backendClass(); $source = $op['src']; $dest = $op['dst']; $this->prepare( [ 'dir' => dirname( $dest ) ] ); file_put_contents( $source, "Unit test file" ); if ( isset( $op['overwrite'] ) || isset( $op['overwriteSame'] ) ) { $this->backend->store( $op ); } $status = $this->backend->doOperation( $op ); $this->assertGoodStatus( $status, "Store from $source to $dest succeeded without warnings ($backendName)." ); $this->assertEquals( true, $status->isOK(), "Store from $source to $dest succeeded ($backendName)." ); $this->assertEquals( [ 0 => true ], $status->success, "Store from $source to $dest has proper 'success' field in Status ($backendName)." ); $this->assertEquals( true, file_exists( $source ), "Source file $source still exists ($backendName)." ); $this->assertEquals( true, $this->backend->fileExists( [ 'src' => $dest ] ), "Destination file $dest exists ($backendName)." ); $this->assertEquals( filesize( $source ), $this->backend->getFileSize( [ 'src' => $dest ] ), "Destination file $dest has correct size ($backendName)." ); $props1 = FSFile::getPropsFromPath( $source ); $props2 = $this->backend->getFileProps( [ 'src' => $dest ] ); $this->assertEquals( $props1, $props2, "Source and destination have the same props ($backendName)." ); $this->assertBackendPathsConsistent( [ $dest ], true ); } public static function provider_testStore() { $cases = []; $tmpName = TempFSFile::factory( "unittests_", 'txt', wfTempDir() )->getPath(); $toPath = self::baseStorePath() . '/unittest-cont1/e/fun/obj1.txt'; $op = [ 'op' => 'store', 'src' => $tmpName, 'dst' => $toPath ]; $cases[] = [ $op ]; $op2 = $op; $op2['overwrite'] = true; $cases[] = [ $op2 ]; $op3 = $op; $op3['overwriteSame'] = true; $cases[] = [ $op3 ]; return $cases; } /** * @dataProvider provider_testCopy */ public function testCopy( $op, $srcContent, $dstContent, $okStatus, $okSyncStatus ) { $this->backend = $this->singleBackend; $this->tearDownFiles(); $this->doTestCopy( $op, $srcContent, $dstContent, $okStatus, $okSyncStatus ); $this->tearDownFiles(); $this->backend = $this->multiBackend; $this->tearDownFiles(); $this->doTestCopy( $op, $srcContent, $dstContent, $okStatus, $okSyncStatus ); $this->tearDownFiles(); } private function doTestCopy( $op, $srcContent, $dstContent, $okStatus, $okSyncStatus ) { $backendName = $this->backendClass(); $source = $op['src']; $dest = $op['dst']; $this->prepare( [ 'dir' => dirname( $source ) ] ); $this->prepare( [ 'dir' => dirname( $dest ) ] ); if ( is_string( $srcContent ) ) { $status = $this->backend->create( [ 'content' => $srcContent, 'dst' => $source ] ); $this->assertGoodStatus( $status, "Creation of $source succeeded ($backendName)." ); } if ( is_string( $dstContent ) ) { $status = $this->backend->create( [ 'content' => $dstContent, 'dst' => $dest ] ); $this->assertGoodStatus( $status, "Creation of $dest succeeded ($backendName)." ); } $status = $this->backend->doOperation( $op ); if ( $okStatus ) { $this->assertGoodStatus( $status, "Copy from $source to $dest succeeded without warnings ($backendName)." ); $this->assertEquals( true, $status->isOK(), "Copy from $source to $dest succeeded ($backendName)." ); $this->assertEquals( [ 0 => true ], $status->success, "Copy from $source to $dest has proper 'success' field in Status ($backendName)." ); if ( !is_string( $srcContent ) ) { $this->assertSame( is_string( $dstContent ), $this->backend->fileExists( [ 'src' => $dest ] ), "Destination file $dest unchanged after no-op copy ($backendName)." ); $this->assertSame( $dstContent, $this->backend->getFileContents( [ 'src' => $dest ] ), "Destination file $dest unchanged after no-op copy ($backendName)." ); } else { $this->assertEquals( $this->backend->getFileSize( [ 'src' => $source ] ), $this->backend->getFileSize( [ 'src' => $dest ] ), "Destination file $dest has correct size ($backendName)." ); $props1 = $this->backend->getFileProps( [ 'src' => $source ] ); $props2 = $this->backend->getFileProps( [ 'src' => $dest ] ); $this->assertEquals( $props1, $props2, "Source and destination have the same props ($backendName)." ); } } else { $this->assertBadStatus( $status, "Copy from $source to $dest fails ($backendName)." ); $this->assertSame( is_string( $dstContent ), (bool)$this->backend->fileExists( [ 'src' => $dest ] ), "Destination file $dest unchanged after failed copy ($backendName)." ); $this->assertSame( $dstContent, $this->backend->getFileContents( [ 'src' => $dest ] ), "Destination file $dest unchanged after failed copy ($backendName)." ); } $this->assertSame( is_string( $srcContent ), (bool)$this->backend->fileExists( [ 'src' => $source ] ), "Source file $source unchanged after copy ($backendName)." ); $this->assertSame( $srcContent, $this->backend->getFileContents( [ 'src' => $source ] ), "Source file $source unchanged after copy ($backendName)." ); if ( is_string( $dstContent ) ) { $this->assertTrue( (bool)$this->backend->fileExists( [ 'src' => $dest ] ), "Destination file $dest exists after copy ($backendName)." ); } $this->assertBackendPathsConsistent( [ $source, $dest ], $okSyncStatus ); } /** * @return array (op, source exists, dest exists, op succeeds, sync check succeeds) */ public static function provider_testCopy() { $cases = []; $source = self::baseStorePath() . '/unittest-cont1/e/file.txt'; $dest = self::baseStorePath() . '/unittest-cont2/a/fileCopied.txt'; $opBase = [ 'op' => 'copy', 'src' => $source, 'dst' => $dest ]; $op = $opBase; $cases[] = [ $op, 'yyy', false, true, true ]; $op = $opBase; $op['overwrite'] = true; $cases[] = [ $op, 'yyy', false, true, true ]; $op = $opBase; $op['overwrite'] = true; $cases[] = [ $op, 'yyy', 'xxx', true, true ]; $op = $opBase; $op['overwriteSame'] = true; $cases[] = [ $op, 'yyy', false, true, true ]; $op = $opBase; $op['overwriteSame'] = true; $cases[] = [ $op, 'yyy', 'yyy', true, true ]; $op = $opBase; $op['overwriteSame'] = true; $cases[] = [ $op, 'yyy', 'zzz', false, true ]; $op = $opBase; $op['ignoreMissingSource'] = true; $cases[] = [ $op, 'xxx', false, true, true ]; $op = $opBase; $op['ignoreMissingSource'] = true; $cases[] = [ $op, false, false, true, true ]; $op = $opBase; $op['ignoreMissingSource'] = true; $cases[] = [ $op, false, 'xxx', true, true ]; $op = $opBase; $op['src'] = 'mwstore://wrongbackend/unittest-cont1/e/file.txt'; $op['ignoreMissingSource'] = true; $cases[] = [ $op, false, false, false, false ]; return $cases; } /** * @dataProvider provider_testMove */ public function testMove( $op, $srcContent, $dstContent, $okStatus, $okSyncStatus ) { $this->backend = $this->singleBackend; $this->tearDownFiles(); $this->doTestMove( $op, $srcContent, $dstContent, $okStatus, $okSyncStatus ); $this->tearDownFiles(); $this->backend = $this->multiBackend; $this->tearDownFiles(); $this->doTestMove( $op, $srcContent, $dstContent, $okStatus, $okSyncStatus ); $this->tearDownFiles(); } private function doTestMove( $op, $srcContent, $dstContent, $okStatus, $okSyncStatus ) { $backendName = $this->backendClass(); $source = $op['src']; $dest = $op['dst']; $this->prepare( [ 'dir' => dirname( $source ) ] ); $this->prepare( [ 'dir' => dirname( $dest ) ] ); if ( is_string( $srcContent ) ) { $status = $this->backend->create( [ 'content' => $srcContent, 'dst' => $source ] ); $this->assertGoodStatus( $status, "Creation of $source succeeded ($backendName)." ); } if ( is_string( $dstContent ) ) { $status = $this->backend->create( [ 'content' => $dstContent, 'dst' => $dest ] ); $this->assertGoodStatus( $status, "Creation of $dest succeeded ($backendName)." ); } $oldSrcProps = $this->backend->getFileProps( [ 'src' => $source ] ); $status = $this->backend->doOperation( $op ); if ( $okStatus ) { $this->assertGoodStatus( $status, "Move from $source to $dest succeeded without warnings ($backendName)." ); $this->assertEquals( true, $status->isOK(), "Move from $source to $dest succeeded ($backendName)." ); $this->assertEquals( [ 0 => true ], $status->success, "Move from $source to $dest has proper 'success' field in Status ($backendName)." ); if ( !is_string( $srcContent ) ) { $this->assertSame( is_string( $dstContent ), $this->backend->fileExists( [ 'src' => $dest ] ), "Destination file $dest unchanged after no-op move ($backendName)." ); $this->assertSame( $dstContent, $this->backend->getFileContents( [ 'src' => $dest ] ), "Destination file $dest unchanged after no-op move ($backendName)." ); } else { $this->assertEquals( $this->backend->getFileSize( [ 'src' => $dest ] ), strlen( $srcContent ), "Destination file $dest has correct size ($backendName)." ); $this->assertEquals( $oldSrcProps, $this->backend->getFileProps( [ 'src' => $dest ] ), "Source and destination have the same props ($backendName)." ); } } else { $this->assertBadStatus( $status, "Move from $source to $dest fails ($backendName)." ); $this->assertSame( is_string( $dstContent ), (bool)$this->backend->fileExists( [ 'src' => $dest ] ), "Destination file $dest unchanged after failed move ($backendName)." ); $this->assertSame( $dstContent, $this->backend->getFileContents( [ 'src' => $dest ] ), "Destination file $dest unchanged after failed move ($backendName)." ); $this->assertSame( is_string( $srcContent ), (bool)$this->backend->fileExists( [ 'src' => $source ] ), "Source file $source unchanged after failed move ($backendName)." ); $this->assertSame( $srcContent, $this->backend->getFileContents( [ 'src' => $source ] ), "Source file $source unchanged after failed move ($backendName)." ); } if ( is_string( $dstContent ) ) { $this->assertTrue( (bool)$this->backend->fileExists( [ 'src' => $dest ] ), "Destination file $dest exists after move ($backendName)." ); } $this->assertBackendPathsConsistent( [ $source, $dest ], $okSyncStatus ); } /** * @return array (op, source exists, dest exists, op succeeds, sync check succeeds) */ public static function provider_testMove() { $cases = []; $source = self::baseStorePath() . '/unittest-cont1/e/file.txt'; $dest = self::baseStorePath() . '/unittest-cont2/a/fileMoved.txt'; $opBase = [ 'op' => 'move', 'src' => $source, 'dst' => $dest ]; $op = $opBase; $cases[] = [ $op, 'yyy', false, true, true ]; $op = $opBase; $op['overwrite'] = true; $cases[] = [ $op, 'yyy', false, true, true ]; $op = $opBase; $op['overwrite'] = true; $cases[] = [ $op, 'yyy', 'xxx', true, true ]; $op = $opBase; $op['overwriteSame'] = true; $cases[] = [ $op, 'yyy', false, true, true ]; $op = $opBase; $op['overwriteSame'] = true; $cases[] = [ $op, 'yyy', 'yyy', true, true ]; $op = $opBase; $op['overwriteSame'] = true; $cases[] = [ $op, 'yyy', 'zzz', false, true ]; $op = $opBase; $op['ignoreMissingSource'] = true; $cases[] = [ $op, 'xxx', false, true, true ]; $op = $opBase; $op['ignoreMissingSource'] = true; $cases[] = [ $op, false, false, true, true ]; $op = $opBase; $op['ignoreMissingSource'] = true; $cases[] = [ $op, false, 'xxx', true, true ]; $op = $opBase; $op['src'] = 'mwstore://wrongbackend/unittest-cont1/e/file.txt'; $op['ignoreMissingSource'] = true; $cases[] = [ $op, false, false, false, false ]; return $cases; } /** * @dataProvider provider_testDelete */ public function testDelete( $op, $srcContent, $okStatus, $okSyncStatus ) { $this->backend = $this->singleBackend; $this->tearDownFiles(); $this->doTestDelete( $op, $srcContent, $okStatus, $okSyncStatus ); $this->tearDownFiles(); $this->backend = $this->multiBackend; $this->tearDownFiles(); $this->doTestDelete( $op, $srcContent, $okStatus, $okSyncStatus ); $this->tearDownFiles(); } private function doTestDelete( $op, $srcContent, $okStatus, $okSyncStatus ) { $backendName = $this->backendClass(); $source = $op['src']; $this->prepare( [ 'dir' => dirname( $source ) ] ); if ( is_string( $srcContent ) ) { $status = $this->backend->doOperation( [ 'op' => 'create', 'content' => $srcContent, 'dst' => $source ] ); $this->assertGoodStatus( $status, "Creation of file at $source succeeded ($backendName)." ); } $status = $this->backend->doOperation( $op ); if ( $okStatus ) { $this->assertGoodStatus( $status, "Deletion of file at $source succeeded without warnings ($backendName)." ); $this->assertEquals( true, $status->isOK(), "Deletion of file at $source succeeded ($backendName)." ); $this->assertEquals( [ 0 => true ], $status->success, "Deletion of file at $source has proper 'success' field in Status ($backendName)." ); } else { $this->assertFalse( $status->isOK(), "Deletion of file at $source failed ($backendName)." ); } $this->assertFalse( (bool)$this->backend->fileExists( [ 'src' => $source ] ), "Source file $source does not exist after move ($backendName)." ); $this->assertFalse( $this->backend->getFileSize( [ 'src' => $source ] ), "Source file $source has correct size (false) ($backendName)." ); $props1 = $this->backend->getFileProps( [ 'src' => $source ] ); $this->assertFalse( $props1['fileExists'], "Source file $source does not exist according to props ($backendName)." ); $this->assertBackendPathsConsistent( [ $source ], $okSyncStatus ); } /** * @return array (op, source content, op succeeds, sync check succeeds) */ public static function provider_testDelete() { $cases = []; $source = self::baseStorePath() . '/unittest-cont1/e/myfacefile.txt'; $baseOp = [ 'op' => 'delete', 'src' => $source ]; $op = $baseOp; $cases[] = [ $op, 'xxx', true, true ]; $op = $baseOp; $op['ignoreMissingSource'] = true; $cases[] = [ $op, 'xxx', true, true ]; $op = $baseOp; $cases[] = [ $op, false, false, true ]; $op = $baseOp; $op['ignoreMissingSource'] = true; $cases[] = [ $op, false, true, true ]; $op = $baseOp; $op['ignoreMissingSource'] = true; $op['src'] = 'mwstore://wrongbackend/unittest-cont1/e/file.txt'; $cases[] = [ $op, false, false, false ]; return $cases; } /** * @dataProvider provider_testDescribe */ public function testDescribe( $op, $withSource, $okStatus ) { $this->backend = $this->singleBackend; $this->tearDownFiles(); $this->doTestDescribe( $op, $withSource, $okStatus ); $this->tearDownFiles(); $this->backend = $this->multiBackend; $this->tearDownFiles(); $this->doTestDescribe( $op, $withSource, $okStatus ); $this->tearDownFiles(); } private function doTestDescribe( $op, $withSource, $okStatus ) { $backendName = $this->backendClass(); $source = $op['src']; $this->prepare( [ 'dir' => dirname( $source ) ] ); if ( $withSource ) { $status = $this->backend->doOperation( [ 'op' => 'create', 'content' => 'blahblah', 'dst' => $source, 'headers' => [ 'Content-Disposition' => 'xxx' ] ] ); $this->assertGoodStatus( $status, "Creation of file at $source succeeded ($backendName)." ); if ( $this->backend->hasFeatures( FileBackend::ATTR_HEADERS ) ) { $attr = $this->backend->getFileXAttributes( [ 'src' => $source ] ); $this->assertHasHeaders( [ 'Content-Disposition' => 'xxx' ], $attr ); } $status = $this->backend->describe( [ 'src' => $source, 'headers' => [ 'Content-Disposition' => '' ] ] ); // remove $this->assertGoodStatus( $status, "Removal of header for $source succeeded ($backendName)." ); if ( $this->backend->hasFeatures( FileBackend::ATTR_HEADERS ) ) { $attr = $this->backend->getFileXAttributes( [ 'src' => $source ] ); $this->assertFalse( isset( $attr['headers']['content-disposition'] ), "File 'Content-Disposition' header removed." ); } } $status = $this->backend->doOperation( $op ); if ( $okStatus ) { $this->assertGoodStatus( $status, "Describe of file at $source succeeded without warnings ($backendName)." ); $this->assertEquals( true, $status->isOK(), "Describe of file at $source succeeded ($backendName)." ); $this->assertEquals( [ 0 => true ], $status->success, "Describe of file at $source has proper 'success' field in Status ($backendName)." ); if ( $this->backend->hasFeatures( FileBackend::ATTR_HEADERS ) ) { $attr = $this->backend->getFileXAttributes( [ 'src' => $source ] ); $this->assertHasHeaders( $op['headers'], $attr ); } } else { $this->assertFalse( $status->isOK(), "Describe of file at $source failed ($backendName)." ); } $this->assertBackendPathsConsistent( [ $source ], true ); } private function assertHasHeaders( array $headers, array $attr ) { foreach ( $headers as $n => $v ) { if ( $n !== '' ) { $this->assertTrue( isset( $attr['headers'][strtolower( $n )] ), "File has '$n' header." ); $this->assertEquals( $v, $attr['headers'][strtolower( $n )], "File has '$n' header value." ); } else { $this->assertFalse( isset( $attr['headers'][strtolower( $n )] ), "File does not have '$n' header." ); } } } public static function provider_testDescribe() { $cases = []; $source = self::baseStorePath() . '/unittest-cont1/e/myfacefile.txt'; $op = [ 'op' => 'describe', 'src' => $source, 'headers' => [ 'Content-Disposition' => 'inline' ], ]; $cases[] = [ $op, // operation true, // with source true // succeeds ]; $cases[] = [ $op, // operation false, // without source false // fails ]; return $cases; } /** * @dataProvider provider_testCreate */ public function testCreate( $op, $alreadyExists, $okStatus, $newSize ) { $this->backend = $this->singleBackend; $this->tearDownFiles(); $this->doTestCreate( $op, $alreadyExists, $okStatus, $newSize ); $this->tearDownFiles(); $this->backend = $this->multiBackend; $this->tearDownFiles(); $this->doTestCreate( $op, $alreadyExists, $okStatus, $newSize ); $this->tearDownFiles(); } private function doTestCreate( $op, $alreadyExists, $okStatus, $newSize ) { $backendName = $this->backendClass(); $dest = $op['dst']; $this->prepare( [ 'dir' => dirname( $dest ) ] ); $oldText = 'blah...blah...waahwaah'; if ( $alreadyExists ) { $status = $this->backend->doOperation( [ 'op' => 'create', 'content' => $oldText, 'dst' => $dest ] ); $this->assertGoodStatus( $status, "Creation of file at $dest succeeded ($backendName)." ); } $status = $this->backend->doOperation( $op ); if ( $okStatus ) { $this->assertGoodStatus( $status, "Creation of file at $dest succeeded without warnings ($backendName)." ); $this->assertEquals( true, $status->isOK(), "Creation of file at $dest succeeded ($backendName)." ); $this->assertEquals( [ 0 => true ], $status->success, "Creation of file at $dest has proper 'success' field in Status ($backendName)." ); } else { $this->assertFalse( $status->isOK(), "Creation of file at $dest failed ($backendName)." ); } $this->assertEquals( true, $this->backend->fileExists( [ 'src' => $dest ] ), "Destination file $dest exists after creation ($backendName)." ); $props1 = $this->backend->getFileProps( [ 'src' => $dest ] ); $this->assertEquals( true, $props1['fileExists'], "Destination file $dest exists according to props ($backendName)." ); if ( $okStatus ) { // file content is what we saved $this->assertEquals( $newSize, $props1['size'], "Destination file $dest has expected size according to props ($backendName)." ); $this->assertEquals( $newSize, $this->backend->getFileSize( [ 'src' => $dest ] ), "Destination file $dest has correct size ($backendName)." ); } else { // file content is some other previous text $this->assertEquals( strlen( $oldText ), $props1['size'], "Destination file $dest has original size according to props ($backendName)." ); $this->assertEquals( strlen( $oldText ), $this->backend->getFileSize( [ 'src' => $dest ] ), "Destination file $dest has original size according to props ($backendName)." ); } $this->assertBackendPathsConsistent( [ $dest ], true ); } /** * @dataProvider provider_testCreate */ public static function provider_testCreate() { $cases = []; $dest = self::baseStorePath() . '/unittest-cont2/a/myspacefile.txt'; $op = [ 'op' => 'create', 'content' => 'test test testing', 'dst' => $dest ]; $cases[] = [ $op, // operation false, // no dest already exists true, // succeeds strlen( $op['content'] ) ]; $op2 = $op; $op2['content'] = "\n"; $cases[] = [ $op2, // operation false, // no dest already exists true, // succeeds strlen( $op2['content'] ) ]; $op2 = $op; $op2['content'] = "fsf\n waf 3kt"; $cases[] = [ $op2, // operation true, // dest already exists false, // fails strlen( $op2['content'] ) ]; $op2 = $op; $op2['content'] = "egm'g gkpe gpqg eqwgwqg"; $op2['overwrite'] = true; $cases[] = [ $op2, // operation true, // dest already exists true, // succeeds strlen( $op2['content'] ) ]; $op2 = $op; $op2['content'] = "39qjmg3-qg"; $op2['overwriteSame'] = true; $cases[] = [ $op2, // operation true, // dest already exists false, // succeeds strlen( $op2['content'] ) ]; return $cases; } /** * @dataProvider provider_quickOperations */ public function testDoQuickOperations( $files, $createOps, $copyOps, $moveOps, $overSelfOps, $deleteOps, $batchSize ) { $this->backend = $this->singleBackend; $this->doTestDoQuickOperations( $files, $createOps, $copyOps, $moveOps, $overSelfOps, $deleteOps, $batchSize ); $this->tearDownFiles(); $this->backend = $this->multiBackend; $this->doTestDoQuickOperations( $files, $createOps, $copyOps, $moveOps, $overSelfOps, $deleteOps, $batchSize ); $this->tearDownFiles(); } private function doTestDoQuickOperations( $files, $createOps, $copyOps, $moveOps, $overSelfOps, $deleteOps, $batchSize ) { $backendName = $this->backendClass(); foreach ( $files as $path ) { $status = $this->prepare( [ 'dir' => dirname( $path ) ] ); $this->assertGoodStatus( $status, "Preparing $path succeeded without warnings ($backendName)." ); } foreach ( array_chunk( $createOps, $batchSize ) as $batchOps ) { $this->assertGoodStatus( $this->backend->doQuickOperations( $batchOps ), "Creation of source files succeeded ($backendName)." ); } foreach ( $files as $file ) { $this->assertTrue( $this->backend->fileExists( [ 'src' => $file ] ), "File $file exists." ); } foreach ( array_chunk( $copyOps, $batchSize ) as $batchOps ) { $this->assertGoodStatus( $this->backend->doQuickOperations( $batchOps ), "Quick copy of source files succeeded ($backendName)." ); } foreach ( $files as $file ) { $this->assertTrue( $this->backend->fileExists( [ 'src' => "$file-2" ] ), "File $file-2 exists." ); } foreach ( array_chunk( $moveOps, $batchSize ) as $batchOps ) { $this->assertGoodStatus( $this->backend->doQuickOperations( $batchOps ), "Quick move of source files succeeded ($backendName)." ); } foreach ( $files as $file ) { $this->assertTrue( $this->backend->fileExists( [ 'src' => "$file-3" ] ), "File $file-3 move in." ); $this->assertFalse( $this->backend->fileExists( [ 'src' => "$file-2" ] ), "File $file-2 moved away." ); } foreach ( array_chunk( $overSelfOps, $batchSize ) as $batchOps ) { $this->assertGoodStatus( $this->backend->doQuickOperations( $batchOps ), "Quick copy/move of source files over themselves succeeded ($backendName)." ); } foreach ( $files as $file ) { $this->assertTrue( $this->backend->fileExists( [ 'src' => $file ] ), "File $file still exists after copy/move over self." ); } foreach ( array_chunk( $deleteOps, $batchSize ) as $batchOps ) { $this->assertGoodStatus( $this->backend->doQuickOperations( $batchOps ), "Quick deletion of source files succeeded ($backendName)." ); } foreach ( $files as $file ) { $this->assertFalse( $this->backend->fileExists( [ 'src' => $file ] ), "File $file purged." ); $this->assertFalse( $this->backend->fileExists( [ 'src' => "$file-3" ] ), "File $file-3 purged." ); } } public function provider_quickOperations() { $base = self::baseStorePath(); $files = [ "$base/unittest-cont1/e/fileA.a", "$base/unittest-cont1/e/fileB.a", "$base/unittest-cont1/e/fileC.a" ]; $createOps = $copyOps = $moveOps = $overSelfOps = $deleteOps = []; foreach ( $files as $path ) { $createOps[] = [ 'op' => 'create', 'dst' => $path, 'content' => 52525 ]; $createOps[] = [ 'op' => 'create', 'dst' => "$path-x", 'content' => 832 ]; $createOps[] = [ 'op' => 'null' ]; $copyOps[] = [ 'op' => 'copy', 'src' => $path, 'dst' => "$path-2" ]; $copyOps[] = [ 'op' => 'copy', 'src' => "$path-nothing", 'dst' => "$path-nowhere", 'ignoreMissingSource' => true ]; $copyOps[] = [ 'op' => 'null' ]; $moveOps[] = [ 'op' => 'move', 'src' => "$path-2", 'dst' => "$path-3" ]; $moveOps[] = [ 'op' => 'move', 'src' => "$path-nothing", 'dst' => "$path-nowhere", 'ignoreMissingSource' => true ]; $moveOps[] = [ 'op' => 'null' ]; $overSelfOps[] = [ 'op' => 'copy', 'src' => $path, 'dst' => $path ]; $overSelfOps[] = [ 'op' => 'move', 'src' => $path, 'dst' => $path ]; $deleteOps[] = [ 'op' => 'delete', 'src' => $path ]; $deleteOps[] = [ 'op' => 'delete', 'src' => "$path-3" ]; $deleteOps[] = [ 'op' => 'delete', 'src' => "$path-gone", 'ignoreMissingSource' => true ]; $deleteOps[] = [ 'op' => 'null' ]; } return [ [ $files, $createOps, $copyOps, $moveOps, $overSelfOps, $deleteOps, 1 ], [ $files, $createOps, $copyOps, $moveOps, $overSelfOps, $deleteOps, 2 ], [ $files, $createOps, $copyOps, $moveOps, $overSelfOps, $deleteOps, 100 ] ]; } /** * @dataProvider provider_testConcatenate */ public function testConcatenate( $op, $srcs, $srcsContent, $alreadyExists, $okStatus ) { $this->backend = $this->singleBackend; $this->tearDownFiles(); $this->doTestConcatenate( $op, $srcs, $srcsContent, $alreadyExists, $okStatus ); $this->tearDownFiles(); $this->backend = $this->multiBackend; $this->tearDownFiles(); $this->doTestConcatenate( $op, $srcs, $srcsContent, $alreadyExists, $okStatus ); $this->tearDownFiles(); } private function doTestConcatenate( $params, $srcs, $srcsContent, $alreadyExists, $okStatus ) { $backendName = $this->backendClass(); $expContent = ''; // Create sources $ops = []; foreach ( $srcs as $i => $source ) { $this->prepare( [ 'dir' => dirname( $source ) ] ); $ops[] = [ 'op' => 'create', // operation 'dst' => $source, // source 'content' => $srcsContent[$i] ]; $expContent .= $srcsContent[$i]; } $status = $this->backend->doOperations( $ops ); $this->assertGoodStatus( $status, "Creation of source files succeeded ($backendName)." ); $dest = $params['dst'] = $this->getNewTempFile(); if ( $alreadyExists ) { $ok = file_put_contents( $dest, 'blah...blah...waahwaah' ) !== false; $this->assertEquals( true, $ok, "Creation of file at $dest succeeded ($backendName)." ); } else { $ok = file_put_contents( $dest, '' ) !== false; $this->assertEquals( true, $ok, "Creation of 0-byte file at $dest succeeded ($backendName)." ); } // Combine the files into one $status = $this->backend->concatenate( $params ); if ( $okStatus ) { $this->assertGoodStatus( $status, "Creation of concat file at $dest succeeded without warnings ($backendName)." ); $this->assertEquals( true, $status->isOK(), "Creation of concat file at $dest succeeded ($backendName)." ); } else { $this->assertFalse( $status->isOK(), "Creation of concat file at $dest failed ($backendName)." ); } if ( $okStatus ) { $this->assertEquals( true, is_file( $dest ), "Dest concat file $dest exists after creation ($backendName)." ); } else { $this->assertEquals( true, is_file( $dest ), "Dest concat file $dest exists after failed creation ($backendName)." ); } $contents = file_get_contents( $dest ); $this->assertNotEquals( false, $contents, "File at $dest exists ($backendName)." ); if ( $okStatus ) { $this->assertEquals( $expContent, $contents, "Concat file at $dest has correct contents ($backendName)." ); } else { $this->assertNotEquals( $expContent, $contents, "Concat file at $dest has correct contents ($backendName)." ); } } public static function provider_testConcatenate() { $cases = []; $srcs = [ self::baseStorePath() . '/unittest-cont1/e/file1.txt', self::baseStorePath() . '/unittest-cont1/e/file2.txt', self::baseStorePath() . '/unittest-cont1/e/file3.txt', self::baseStorePath() . '/unittest-cont1/e/file4.txt', self::baseStorePath() . '/unittest-cont1/e/file5.txt', self::baseStorePath() . '/unittest-cont1/e/file6.txt', self::baseStorePath() . '/unittest-cont1/e/file7.txt', self::baseStorePath() . '/unittest-cont1/e/file8.txt', self::baseStorePath() . '/unittest-cont1/e/file9.txt', self::baseStorePath() . '/unittest-cont1/e/file10.txt' ]; $content = [ 'egfage', 'ageageag', 'rhokohlr', 'shgmslkg', 'kenga', 'owagmal', 'kgmae', 'g eak;g', 'lkaem;a', 'legma' ]; $params = [ 'srcs' => $srcs ]; $cases[] = [ $params, // operation $srcs, // sources $content, // content for each source false, // no dest already exists true, // succeeds ]; $cases[] = [ $params, // operation $srcs, // sources $content, // content for each source true, // dest already exists false, // succeeds ]; return $cases; } /** * @dataProvider provider_testGetFileStat */ public function testGetFileStat( $path, $content, $alreadyExists ) { $this->backend = $this->singleBackend; $this->tearDownFiles(); $this->doTestGetFileStat( $path, $content, $alreadyExists ); $this->tearDownFiles(); $this->backend = $this->multiBackend; $this->tearDownFiles(); $this->doTestGetFileStat( $path, $content, $alreadyExists ); $this->tearDownFiles(); } private function doTestGetFileStat( $path, $content, $alreadyExists ) { $backendName = $this->backendClass(); if ( $alreadyExists ) { $this->prepare( [ 'dir' => dirname( $path ) ] ); $status = $this->create( [ 'dst' => $path, 'content' => $content ] ); $this->assertGoodStatus( $status, "Creation of file at $path succeeded ($backendName)." ); $size = $this->backend->getFileSize( [ 'src' => $path ] ); $time = $this->backend->getFileTimestamp( [ 'src' => $path ] ); $stat = $this->backend->getFileStat( [ 'src' => $path ] ); $this->assertEquals( strlen( $content ), $size, "Correct file size of '$path'" ); $this->assertTrue( abs( time() - wfTimestamp( TS_UNIX, $time ) ) < 10, "Correct file timestamp of '$path'" ); $size = $stat['size']; $time = $stat['mtime']; $this->assertEquals( strlen( $content ), $size, "Correct file size of '$path'" ); $this->assertTrue( abs( time() - wfTimestamp( TS_UNIX, $time ) ) < 10, "Correct file timestamp of '$path'" ); $this->backend->clearCache( [ $path ] ); $size = $this->backend->getFileSize( [ 'src' => $path ] ); $this->assertEquals( strlen( $content ), $size, "Correct file size of '$path'" ); $this->backend->preloadCache( [ $path ] ); $size = $this->backend->getFileSize( [ 'src' => $path ] ); $this->assertEquals( strlen( $content ), $size, "Correct file size of '$path'" ); } else { $size = $this->backend->getFileSize( [ 'src' => $path ] ); $time = $this->backend->getFileTimestamp( [ 'src' => $path ] ); $stat = $this->backend->getFileStat( [ 'src' => $path ] ); $this->assertFalse( $size, "Correct file size of '$path'" ); $this->assertFalse( $time, "Correct file timestamp of '$path'" ); $this->assertFalse( $stat, "Correct file stat of '$path'" ); } } public static function provider_testGetFileStat() { $cases = []; $base = self::baseStorePath(); $cases[] = [ "$base/unittest-cont1/e/b/z/some_file.txt", "some file contents", true ]; $cases[] = [ "$base/unittest-cont1/e/b/some-other_file.txt", "", true ]; $cases[] = [ "$base/unittest-cont1/e/b/some-diff_file.txt", null, false ]; return $cases; } /** * @dataProvider provider_testGetFileStat */ public function testStreamFile( $path, $content, $alreadyExists ) { $this->backend = $this->singleBackend; $this->tearDownFiles(); $this->doTestStreamFile( $path, $content, $alreadyExists ); $this->tearDownFiles(); $this->backend = $this->multiBackend; $this->tearDownFiles(); $this->doTestStreamFile( $path, $content, $alreadyExists ); $this->tearDownFiles(); } private function doTestStreamFile( $path, $content ) { $backendName = $this->backendClass(); if ( $content !== null ) { $this->prepare( [ 'dir' => dirname( $path ) ] ); $status = $this->create( [ 'dst' => $path, 'content' => $content ] ); $this->assertGoodStatus( $status, "Creation of file at $path succeeded ($backendName)." ); ob_start(); $this->backend->streamFile( [ 'src' => $path, 'headless' => 1, 'allowOB' => 1 ] ); $data = ob_get_contents(); ob_end_clean(); $this->assertEquals( $content, $data, "Correct content streamed from '$path'" ); } else { // 404 case ob_start(); $this->backend->streamFile( [ 'src' => $path, 'headless' => 1, 'allowOB' => 1 ] ); $data = ob_get_contents(); ob_end_clean(); $this->assertRegExp( '#