backend = $this->getBackend(); } protected function tearDown(): void { $this->tearDownFiles(); } private static function baseStorePath() { return 'mwstore://localtesting'; } private function backendClass() { return get_class( $this->backend ); } /** * @dataProvider provider_testStore */ public function testStore( $op ) { $this->addTmpFiles( $op['src'] ); $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->assertStatusGood( $status, "Store from $source to $dest succeeded without warnings ($backendName)." ); $this->assertEquals( [ 0 => true ], $status->success, "Store from $source to $dest has proper 'success' field in Status ($backendName)." ); $this->assertTrue( is_file( $source ), "Source file $source still exists ($backendName)." ); $this->assertTrue( $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 ) { $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->assertStatusGood( $status, "Creation of $source succeeded ($backendName)." ); } if ( is_string( $dstContent ) ) { $status = $this->backend->create( [ 'content' => $dstContent, 'dst' => $dest ] ); $this->assertStatusGood( $status, "Creation of $dest succeeded ($backendName)." ); } $status = $this->backend->doOperation( $op ); if ( $okStatus ) { $this->assertStatusGood( $status, "Copy from $source to $dest succeeded without warnings ($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->assertStatusNotOK( $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 ) { $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->assertStatusGood( $status, "Creation of $source succeeded ($backendName)." ); } if ( is_string( $dstContent ) ) { $status = $this->backend->create( [ 'content' => $dstContent, 'dst' => $dest ] ); $this->assertStatusGood( $status, "Creation of $dest succeeded ($backendName)." ); } $oldSrcProps = $this->backend->getFileProps( [ 'src' => $source ] ); $status = $this->backend->doOperation( $op ); if ( $okStatus ) { $this->assertStatusGood( $status, "Move from $source to $dest succeeded without warnings ($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->assertStatusNotOK( $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 ) { $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->assertStatusGood( $status, "Creation of file at $source succeeded ($backendName)." ); } $status = $this->backend->doOperation( $op ); if ( $okStatus ) { $this->assertStatusGood( $status, "Deletion of file at $source succeeded without warnings ($backendName)." ); $this->assertEquals( [ 0 => true ], $status->success, "Deletion of file at $source has proper 'success' field in Status ($backendName)." ); } else { $this->assertStatusNotOK( $status, "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 ) { $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->assertStatusGood( $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->assertStatusGood( $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->assertStatusGood( $status, "Describe of file at $source succeeded without warnings ($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->assertStatusNotOK( $status, "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 ) { $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->assertStatusGood( $status, "Creation of file at $dest succeeded ($backendName)." ); } $status = $this->backend->doOperation( $op ); if ( $okStatus ) { $this->assertStatusGood( $status, "Creation of file at $dest succeeded without warnings ($backendName)." ); $this->assertEquals( [ 0 => true ], $status->success, "Creation of file at $dest has proper 'success' field in Status ($backendName)." ); } else { $this->assertStatusNotOK( $status, "Creation of file at $dest failed ($backendName)." ); } $this->assertTrue( $this->backend->fileExists( [ 'src' => $dest ] ), "Destination file $dest exists after creation ($backendName)." ); $props1 = $this->backend->getFileProps( [ 'src' => $dest ] ); $this->assertTrue( $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 ) { $backendName = $this->backendClass(); foreach ( $files as $path ) { $status = $this->prepare( [ 'dir' => dirname( $path ) ] ); $this->assertStatusGood( $status, "Preparing $path succeeded without warnings ($backendName)." ); } foreach ( array_chunk( $createOps, $batchSize ) as $batchOps ) { $this->assertStatusGood( $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->assertStatusGood( $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->assertStatusGood( $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->assertStatusGood( $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->assertStatusGood( $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 static 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( $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->assertStatusGood( $status, "Creation of source files succeeded ($backendName)." ); $dest = $params['dst'] = $this->getNewTempFile(); if ( $alreadyExists ) { $ok = file_put_contents( $dest, 'blah...blah...waahwaah' ) !== false; $this->assertTrue( $ok, "Creation of file at $dest succeeded ($backendName)." ); } else { $ok = file_put_contents( $dest, '' ) !== false; $this->assertTrue( $ok, "Creation of 0-byte file at $dest succeeded ($backendName)." ); } // Combine the files into one $status = $this->backend->concatenate( $params ); if ( $okStatus ) { $this->assertStatusGood( $status, "Creation of concat file at $dest succeeded without warnings ($backendName)." ); } else { $this->assertStatusNotOK( $status, "Creation of concat file at $dest failed ($backendName)." ); } if ( $okStatus ) { $this->assertTrue( is_file( $dest ), "Dest concat file $dest exists after creation ($backendName)." ); } else { $this->assertTrue( is_file( $dest ), "Dest concat file $dest exists after failed creation ($backendName)." ); } $contents = file_get_contents( $dest ); $this->assertIsString( $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 ) { $backendName = $this->backendClass(); if ( $alreadyExists ) { $this->prepare( [ 'dir' => dirname( $path ) ] ); $status = $this->create( [ 'dst' => $path, 'content' => $content ] ); $this->assertStatusGood( $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 ) { $backendName = $this->backendClass(); if ( $content !== null ) { $this->prepare( [ 'dir' => dirname( $path ) ] ); $status = $this->create( [ 'dst' => $path, 'content' => $content ] ); $this->assertStatusGood( $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->assertMatchesRegularExpression( '#