From 9ee2b6f7fc05496fd063bd4e183d9340ed8c0cce Mon Sep 17 00:00:00 2001 From: Aaron Schulz Date: Wed, 18 Dec 2013 17:41:02 -0800 Subject: [PATCH] Added a MemoryFileBackend class and made MockFileBackend subclass it * This backend passes all filebackend and parser tests * Fixed setupUploads() in parser tests to just use create() instead of using store() and having a race condition in the process * Fixed 'use-filebackend=' for Parser tests bug: 58094 Change-Id: Ib0c38183cb7f9f2325da98c8a8a1eb2b8e39a7aa --- includes/AutoLoader.php | 1 + includes/filebackend/MemoryFileBackend.php | 258 ++++++++++++++++++ .../phpunit/includes/parser/NewParserTest.php | 20 +- .../mocks/filebackend/MockFileBackend.php | 87 +----- 4 files changed, 267 insertions(+), 99 deletions(-) create mode 100644 includes/filebackend/MemoryFileBackend.php diff --git a/includes/AutoLoader.php b/includes/AutoLoader.php index 55fef90d83..9a9ce17e61 100644 --- a/includes/AutoLoader.php +++ b/includes/AutoLoader.php @@ -547,6 +547,7 @@ $wgAutoloadLocalClasses = array( 'FSFileBackendDirList' => 'includes/filebackend/FSFileBackend.php', 'FSFileBackendFileList' => 'includes/filebackend/FSFileBackend.php', 'FSFileOpHandle' => 'includes/filebackend/FSFileBackend.php', + 'MemoryFileBackend' => 'includes/filebackend/MemoryFileBackend.php', 'SwiftFileBackend' => 'includes/filebackend/SwiftFileBackend.php', 'SwiftFileBackendList' => 'includes/filebackend/SwiftFileBackend.php', 'SwiftFileBackendDirList' => 'includes/filebackend/SwiftFileBackend.php', diff --git a/includes/filebackend/MemoryFileBackend.php b/includes/filebackend/MemoryFileBackend.php new file mode 100644 index 0000000000..3924c12e79 --- /dev/null +++ b/includes/filebackend/MemoryFileBackend.php @@ -0,0 +1,258 @@ + (data,mtime) */ + protected $files = array(); + + public function isPathUsableInternal( $storagePath ) { + return true; + } + + protected function doCreateInternal( array $params ) { + $status = Status::newGood(); + + $dst = $this->resolveHashKey( $params['dst'] ); + if ( $dst === null ) { + $status->fatal( 'backend-fail-invalidpath', $params['dst'] ); + return $status; + } + + $this->files[$dst] = array( + 'data' => $params['content'], + 'mtime' => wfTimestamp( TS_MW, time() ) + ); + + return $status; + } + + protected function doStoreInternal( array $params ) { + $status = Status::newGood(); + + $dst = $this->resolveHashKey( $params['dst'] ); + if ( $dst === null ) { + $status->fatal( 'backend-fail-invalidpath', $params['dst'] ); + return $status; + } + + wfSuppressWarnings(); + $data = file_get_contents( $params['src'] ); + wfRestoreWarnings(); + if ( $data === false ) { // source doesn't exist? + $status->fatal( 'backend-fail-store', $params['src'], $params['dst'] ); + return $status; + } + + $this->files[$dst] = array( + 'data' => $data, + 'mtime' => wfTimestamp( TS_MW, time() ) + ); + + return $status; + } + + protected function doCopyInternal( array $params ) { + $status = Status::newGood(); + + $src = $this->resolveHashKey( $params['src'] ); + if ( $src === null ) { + $status->fatal( 'backend-fail-invalidpath', $params['src'] ); + return $status; + } + + $dst = $this->resolveHashKey( $params['dst'] ); + if ( $dst === null ) { + $status->fatal( 'backend-fail-invalidpath', $params['dst'] ); + return $status; + } + + if ( !isset( $this->files[$src] ) ) { + if ( empty( $params['ignoreMissingSource'] ) ) { + $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] ); + } + return $status; + } + + $this->files[$dst] = array( + 'data' => $this->files[$src]['data'], + 'mtime' => wfTimestamp( TS_MW, time() ) + ); + + return $status; + } + + protected function doDeleteInternal( array $params ) { + $status = Status::newGood(); + + $src = $this->resolveHashKey( $params['src'] ); + if ( $src === null ) { + $status->fatal( 'backend-fail-invalidpath', $params['src'] ); + return $status; + } + + if ( !isset( $this->files[$src] ) ) { + if ( empty( $params['ignoreMissingSource'] ) ) { + $status->fatal( 'backend-fail-delete', $params['src'] ); + } + return $status; + } + + unset( $this->files[$src] ); + + return $status; + } + + protected function doGetFileStat( array $params ) { + $src = $this->resolveHashKey( $params['src'] ); + if ( $src === null ) { + return null; + } + + if ( isset( $this->files[$src] ) ) { + return array( + 'mtime' => $this->files[$src]['mtime'], + 'size' => strlen( $this->files[$src]['data'] ), + ); + } + + return false; + } + + protected function doGetLocalCopyMulti( array $params ) { + $tmpFiles = array(); // (path => TempFSFile) + foreach ( $params['srcs'] as $srcPath ) { + $src = $this->resolveHashKey( $srcPath ); + if ( $src === null || !isset( $this->files[$src] ) ) { + $fsFile = null; + } else { + $fsFile = TempFSFile::factory( 'localcopy_' ); + if ( $fsFile ) { + $bytes = file_put_contents( $fsFile->getPath(), $this->files[$src]['data'] ); + if ( $bytes !== strlen( $this->files[$src]['data'] ) ) { + $fsFile = null; + } + } + } + $tmpFiles[$srcPath] = $fsFile; + } + return $tmpFiles; + } + + protected function doStreamFile( array $params ) { + $status = Status::newGood(); + + $src = $this->resolveHashKey( $params['src'] ); + if ( $src === null || !isset( $this->files[$src] ) ) { + $status->fatal( 'backend-fail-stream', $params['src'] ); + return $status; + } + + print $this->files[$src]['data']; + + return $status; + } + + protected function doDirectoryExists( $container, $dir, array $params ) { + $prefix = rtrim( "$container/$dir", '/' ) . '/'; + foreach ( $this->files as $path => $data ) { + if ( strpos( $path, $prefix ) === 0 ) { + return true; + } + } + return false; + } + + public function getDirectoryListInternal( $container, $dir, array $params ) { + $dirs = array(); + $prefix = rtrim( "$container/$dir", '/' ) . '/'; + $prefixLen = strlen( $prefix ); + foreach ( $this->files as $path => $data ) { + if ( strpos( $path, $prefix ) === 0 ) { + $relPath = substr( $path, $prefixLen ); + if ( $relPath === false ) { + continue; + } elseif ( strpos( $relPath, '/' ) === false ) { + continue; // just a file + } + $parts = array_slice( explode( '/', $relPath ), 0, -1 ); // last part is file name + if ( !empty( $params['topOnly'] ) ) { + $dirs[$parts[0]] = 1; // top directory + } else { + $current = ''; + foreach ( $parts as $part ) { // all directories + $dir = ( $current === '' ) ? $part : "$current/$part"; + $dirs[$dir] = 1; + $current = $dir; + } + } + } + } + return array_keys( $dirs ); + } + + public function getFileListInternal( $container, $dir, array $params ) { + $files = array(); + $prefix = rtrim( "$container/$dir", '/' ) . '/'; + $prefixLen = strlen( $prefix ); + foreach ( $this->files as $path => $data ) { + if ( strpos( $path, $prefix ) === 0 ) { + $relPath = substr( $path, $prefixLen ); + if ( $relPath === false ) { + continue; + } elseif ( !empty( $params['topOnly'] ) && strpos( $relPath, '/' ) !== false ) { + continue; + } + $files[] = $relPath; + } + } + return $files; + } + + protected function directoriesAreVirtual() { + return true; + } + + /** + * Get the absolute file system path for a storage path + * + * @param string $storagePath Storage path + * @return string|null + */ + protected function resolveHashKey( $storagePath ) { + list( $fullCont, $relPath ) = $this->resolveStoragePathReal( $storagePath ); + if ( $relPath === null ) { + return null; // invalid + } + return ( $relPath !== '' ) ? "$fullCont/$relPath" : $fullCont; + } +} diff --git a/tests/phpunit/includes/parser/NewParserTest.php b/tests/phpunit/includes/parser/NewParserTest.php index 124b477ffc..c74244415d 100644 --- a/tests/phpunit/includes/parser/NewParserTest.php +++ b/tests/phpunit/includes/parser/NewParserTest.php @@ -308,7 +308,7 @@ class NewParserTest extends MediaWikiTestCase { $useConfig['name'] = 'local-backend'; // swap name unset( $useConfig['lockManager'] ); unset( $useConfig['fileJournal'] ); - $class = $conf['class']; + $class = $useConfig['class']; self::$backendToUse = new $class( $useConfig ); $backend = self::$backendToUse; } @@ -318,11 +318,7 @@ class NewParserTest extends MediaWikiTestCase { # informations. $backend = new MockFileBackend( array( 'name' => 'local-backend', - 'wikiId' => wfWikiId(), - 'containerPaths' => array( - 'local-public' => "$uploadDir", - 'local-thumb' => "$uploadDir/thumb", - ) + 'wikiId' => wfWikiId() ) ); } @@ -448,16 +444,12 @@ class NewParserTest extends MediaWikiTestCase { ) ); // No helpful SVG file to copy, so make one ourselves - $tmpDir = wfTempDir(); - $tempFsFile = new TempFSFile( "$tmpDir/Foobar.svg" ); - $tempFsFile->autocollect(); // destroy file when $tempFsFile leaves scope - file_put_contents( "$tmpDir/Foobar.svg", - '' . - 'Foo' ); + $data = '' . + 'Foo'; $backend->prepare( array( 'dir' => "$base/local-public/f/ff" ) ); - $backend->quickStore( array( - 'src' => "$tmpDir/Foobar.svg", 'dst' => "$base/local-public/f/ff/Foobar.svg" + $backend->quickCreate( array( + 'content' => $data, 'dst' => "$base/local-public/f/ff/Foobar.svg" ) ); } diff --git a/tests/phpunit/mocks/filebackend/MockFileBackend.php b/tests/phpunit/mocks/filebackend/MockFileBackend.php index 49aefbd147..de8590e30a 100644 --- a/tests/phpunit/mocks/filebackend/MockFileBackend.php +++ b/tests/phpunit/mocks/filebackend/MockFileBackend.php @@ -28,95 +28,12 @@ * @ingroup FileBackend * @since 1.22 */ -class MockFileBackend extends FileBackendStore { - - protected $mocked = array(); - - /** Poor man debugging */ - protected function debug( $msg = '' ) { - wfDebug( wfGetCaller() . "$msg\n" ); - } - - public function isPathUsableInternal( $storagePath ) { - return true; - } - - protected function doCreateInternal( array $params ) { - if ( isset( $params['content'] ) ) { - $content = $params['content']; - } else { - $content = 'Default mocked file content'; - } - $this->debug( serialize( $params ) ); - $dst = $params['dst']; - $this->mocked[$dst] = $content; - return Status::newGood(); - } - - protected function doStoreInternal( array $params ) { - $this->debug( serialize( $params ) ); - return $this->doCreateInternal( $params ); - } - - protected function doCopyInternal( array $params ) { - $this->debug( serialize( $params ) ); - $src = $params['src']; - $dst = $params['dst']; - $this->mocked[$dst] = $this->mocked[$src]; - return Status::newGood(); - } - - protected function doDeleteInternal( array $params ) { - $this->debug( serialize( $params ) ); - $src = $params['src']; - unset( $this->mocked[$src] ); - return Status::newGood(); - } - - protected function doGetFileStat( array $params ) { - $src = $params['src']; - if ( array_key_exists( $src, $this->mocked ) ) { - $this->debug( "('$src') found" ); - return array( - 'mtime' => wfTimestamp( TS_MW ), - 'size' => strlen( $this->mocked[$src] ), - # No sha1, stat does not need it. - ); - } else { - $this->debug( "('$src') not found" ); - return false; - } - } - +class MockFileBackend extends MemoryFileBackend { protected function doGetLocalCopyMulti( array $params ) { $tmpFiles = array(); // (path => MockFSFile) - - $this->debug( '(' . serialize( $params ) . ')' ); foreach ( $params['srcs'] as $src ) { - $tmpFiles[$src] = new MockFSFile( - wfTempDir() . '/' . wfRandomString( 32 ) - ); + $tmpFiles[$src] = new MockFSFile( wfTempDir() . '/' . wfRandomString( 32 ) ); } return $tmpFiles; } - - protected function doDirectoryExists( $container, $dir, array $params ) { - $this->debug(); - return true; - } - - public function getDirectoryListInternal( $container, $dir, array $params ) { - $this->debug(); - return array(); - } - - public function getFileListInternal( $container, $dir, array $params ) { - $this->debug(); - return array(); - } - - protected function directoriesAreVirtual() { - $this->debug(); - return true; - } } -- 2.20.1