/** * @return LockManagerGroup */ public static function singleton() { if (self::$instance == null) { self::$instance = new self(); self::$instance->initFromGlobals(); } return self::$instance; }
protected function setUp() { global $wgFileBackends; parent::setUp(); $uniqueId = time() . '-' . mt_rand(); $tmpPrefix = wfTempDir() . '/filebackend-unittest-' . $uniqueId; if ($this->getCliArg('use-filebackend=')) { if (self::$backendToUse) { $this->singleBackend = self::$backendToUse; } else { $name = $this->getCliArg('use-filebackend='); $useConfig = array(); foreach ($wgFileBackends as $conf) { if ($conf['name'] == $name) { $useConfig = $conf; break; } } $useConfig['name'] = 'localtesting'; // swap name $useConfig['shardViaHashLevels'] = array('unittest-cont1' => array('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(array('name' => 'localtesting', 'lockManager' => LockManagerGroup::singleton()->get('fsLockManager'), 'wikiId' => wfWikiID(), 'containerPaths' => array('unittest-cont1' => "{$tmpPrefix}-localtesting-cont1", 'unittest-cont2' => "{$tmpPrefix}-localtesting-cont2"))); } $this->multiBackend = new FileBackendMultiWrite(array('name' => 'localtesting', 'lockManager' => LockManagerGroup::singleton()->get('fsLockManager'), 'parallelize' => 'implicit', 'wikiId' => wfWikiId() . $uniqueId, 'backends' => array(array('name' => 'localmultitesting1', 'class' => 'FSFileBackend', 'containerPaths' => array('unittest-cont1' => "{$tmpPrefix}-localtestingmulti1-cont1", 'unittest-cont2' => "{$tmpPrefix}-localtestingmulti1-cont2"), 'isMultiMaster' => false), array('name' => 'localmultitesting2', 'class' => 'FSFileBackend', 'containerPaths' => array('unittest-cont1' => "{$tmpPrefix}-localtestingmulti2-cont1", 'unittest-cont2' => "{$tmpPrefix}-localtestingmulti2-cont2"), 'isMultiMaster' => true)))); $this->filesToPrune = array(); }
protected function setUp() { global $wgFileBackends; parent::setUp(); # Forge a FSRepo object to not have to rely on local wiki settings $tmpPrefix = wfTempDir() . '/storebatch-test-' . time() . '-' . mt_rand(); if ($this->getCliArg('use-filebackend=')) { $name = $this->getCliArg('use-filebackend='); $useConfig = array(); foreach ($wgFileBackends as $conf) { if ($conf['name'] == $name) { $useConfig = $conf; } } $useConfig['lockManager'] = LockManagerGroup::singleton()->get($useConfig['lockManager']); unset($useConfig['fileJournal']); $useConfig['name'] = 'local-testing'; // swap name $class = $useConfig['class']; $backend = new $class($useConfig); } else { $backend = new FSFileBackend(array('name' => 'local-testing', 'wikiId' => wfWikiID(), 'containerPaths' => array('unittests-public' => "{$tmpPrefix}-public", 'unittests-thumb' => "{$tmpPrefix}-thumb", 'unittests-temp' => "{$tmpPrefix}-temp", 'unittests-deleted' => "{$tmpPrefix}-deleted"))); } $this->repo = new FileRepo(array('name' => 'unittests', 'backend' => $backend)); $this->date = gmdate("YmdHis"); $this->createdFiles = array(); }
protected function setUp() { global $wgFileBackends; parent::setUp(); $tmpDir = $this->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'] = ['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' => wfWikiID() . wfRandomString(), 'backends' => [['name' => 'localmultitesting1', 'class' => 'FSFileBackend', 'containerPaths' => ['unittest-cont1' => "{$tmpDir}/localtestingmulti1-cont1", 'unittest-cont2' => "{$tmpDir}/localtestingmulti1-cont2"], 'isMultiMaster' => false], ['name' => 'localmultitesting2', 'class' => 'FSFileBackend', 'containerPaths' => ['unittest-cont1' => "{$tmpDir}/localtestingmulti2-cont1", 'unittest-cont2' => "{$tmpDir}/localtestingmulti2-cont2"], 'isMultiMaster' => true]]]); }
/** * Create a new backend instance from configuration. * This should only be called from within FileBackendGroup. * * $config includes: * 'name' : The unique name of this backend. * This should consist of alphanumberic, '-', and '_' characters. * This name should not be changed after use. * 'wikiId' : Prefix to container names that is unique to this wiki. * This should consist of alphanumberic, '-', and '_' characters. * 'lockManager' : Registered name of a file lock manager to use. * 'readOnly' : Write operations are disallowed if this is a non-empty string. * It should be an explanation for the backend being read-only. * * @param $config Array */ public function __construct(array $config) { $this->name = $config['name']; if (!preg_match('!^[a-zA-Z0-9-_]{1,255}$!', $this->name)) { throw new MWException("Backend name `{$this->name}` is invalid."); } $this->wikiId = isset($config['wikiId']) ? $config['wikiId'] : wfWikiID(); // e.g. "my_wiki-en_" $this->lockManager = $config['lockManager'] instanceof LockManager ? $config['lockManager'] : LockManagerGroup::singleton()->get($config['lockManager']); $this->readOnly = isset($config['readOnly']) ? (string) $config['readOnly'] : ''; }
/** * @param array $info * @throws MWException */ function __construct(array $info) { if (!isset($info['backend'])) { // B/C settings... $directory = $info['directory']; $deletedDir = isset($info['deletedDir']) ? $info['deletedDir'] : false; $thumbDir = isset($info['thumbDir']) ? $info['thumbDir'] : "{$directory}/thumb"; $transcodedDir = isset($info['transcodedDir']) ? $info['transcodedDir'] : "{$directory}/transcoded"; $fileMode = isset($info['fileMode']) ? $info['fileMode'] : 0644; $repoName = $info['name']; // Get the FS backend configuration $backend = new FSFileBackend(['name' => $info['name'] . '-backend', 'wikiId' => wfWikiID(), 'lockManager' => LockManagerGroup::singleton(wfWikiID())->get('fsLockManager'), 'containerPaths' => ["{$repoName}-public" => "{$directory}", "{$repoName}-temp" => "{$directory}/temp", "{$repoName}-thumb" => $thumbDir, "{$repoName}-transcoded" => $transcodedDir, "{$repoName}-deleted" => $deletedDir], 'fileMode' => $fileMode, 'tmpDirectory' => wfTempDir()]); // Update repo config to use this backend $info['backend'] = $backend; } parent::__construct($info); if (!$this->backend instanceof FSFileBackend) { throw new MWException("FSRepo only supports FSFileBackend."); } }
/** * Get the backend object with a given name * * @param string $name * @return FileBackend * @throws FileBackendException */ public function get($name) { if (!isset($this->backends[$name])) { throw new FileBackendException("No backend defined with the name `{$name}`."); } // Lazy-load the actual backend instance if (!isset($this->backends[$name]['instance'])) { $class = $this->backends[$name]['class']; $config = $this->backends[$name]['config']; $config['wikiId'] = isset($config['wikiId']) ? $config['wikiId'] : wfWikiID(); // e.g. "my_wiki-en_" $config['lockManager'] = LockManagerGroup::singleton($config['wikiId'])->get($config['lockManager']); $config['fileJournal'] = isset($config['fileJournal']) ? FileJournal::factory($config['fileJournal'], $name) : FileJournal::factory(array('class' => 'NullFileJournal'), $name); $config['wanCache'] = ObjectCache::getMainWANInstance(); $this->backends[$name]['instance'] = new $class($config); } return $this->backends[$name]['instance']; }
/** * Create a new backend instance from configuration. * This should only be called from within FileBackendGroup. * * $config includes: * - name : The unique name of this backend. * This should consist of alphanumberic, '-', and '_' characters. * This name should not be changed after use (e.g. with journaling). * Note that the name is *not* used in actual container names. * - wikiId : Prefix to container names that is unique to this backend. * If not provided, this defaults to the current wiki ID. * It should only consist of alphanumberic, '-', and '_' characters. * This ID is what avoids collisions if multiple logical backends * use the same storage system, so this should be set carefully. * - lockManager : Registered name of a file lock manager to use. * - fileJournal : File journal configuration; see FileJournal::factory(). * Journals simply log changes to files stored in the backend. * - readOnly : Write operations are disallowed if this is a non-empty string. * It should be an explanation for the backend being read-only. * - parallelize : When to do file operations in parallel (when possible). * Allowed values are "implicit", "explicit" and "off". * - concurrency : How many file operations can be done in parallel. * * @param array $config * @throws MWException */ public function __construct( array $config ) { $this->name = $config['name']; if ( !preg_match( '!^[a-zA-Z0-9-_]{1,255}$!', $this->name ) ) { throw new MWException( "Backend name `{$this->name}` is invalid." ); } $this->wikiId = isset( $config['wikiId'] ) ? $config['wikiId'] : wfWikiID(); // e.g. "my_wiki-en_" $this->lockManager = ( $config['lockManager'] instanceof LockManager ) ? $config['lockManager'] : LockManagerGroup::singleton( $this->wikiId )->get( $config['lockManager'] ); $this->fileJournal = isset( $config['fileJournal'] ) ? ( ( $config['fileJournal'] instanceof FileJournal ) ? $config['fileJournal'] : FileJournal::factory( $config['fileJournal'], $this->name ) ) : FileJournal::factory( array( 'class' => 'NullFileJournal' ), $this->name ); $this->readOnly = isset( $config['readOnly'] ) ? (string)$config['readOnly'] : ''; $this->parallelize = isset( $config['parallelize'] ) ? (string)$config['parallelize'] : 'off'; $this->concurrency = isset( $config['concurrency'] ) ? (int)$config['concurrency'] : 50; }
/** * Destroy the singleton instances */ public static function destroySingletons() { self::$instances = array(); }
protected function prepareEnvironment() { global $wgMemc; // Don't share DB, storage, or memcached connections MediaWikiServices::resetChildProcessServices(); FileBackendGroup::destroySingleton(); LockManagerGroup::destroySingletons(); JobQueueGroup::destroySingletons(); ObjectCache::clear(); RedisConnectionPool::destroySingletons(); $wgMemc = null; }
protected function setUp() { parent::setUp(); $this->backend = TestingAccessWrapper::newFromObject(new SwiftFileBackend(array('name' => 'local-swift-testing', 'class' => 'SwiftFileBackend', 'wikiId' => 'unit-testing', 'lockManager' => LockManagerGroup::singleton()->get('fsLockManager'), 'swiftAuthUrl' => 'http://127.0.0.1:8080/auth', 'swiftUser' => 'test:tester', 'swiftKey' => 'testing', 'swiftTempUrlKey' => 'b3968d0207b54ece87cccc06515a89d4'))); }
/** * Create a new backend instance from configuration. * This should only be called from within FileBackendGroup. * * @param array $config Parameters include: * - name : The unique name of this backend. * This should consist of alphanumberic, '-', and '_' characters. * This name should not be changed after use (e.g. with journaling). * Note that the name is *not* used in actual container names. * - wikiId : Prefix to container names that is unique to this backend. * It should only consist of alphanumberic, '-', and '_' characters. * This ID is what avoids collisions if multiple logical backends * use the same storage system, so this should be set carefully. * - lockManager : LockManager object to use for any file locking. * If not provided, then no file locking will be enforced. * - fileJournal : FileJournal object to use for logging changes to files. * If not provided, then change journaling will be disabled. * - readOnly : Write operations are disallowed if this is a non-empty string. * It should be an explanation for the backend being read-only. * - parallelize : When to do file operations in parallel (when possible). * Allowed values are "implicit", "explicit" and "off". * - concurrency : How many file operations can be done in parallel. * @throws FileBackendException */ public function __construct(array $config) { $this->name = $config['name']; if (!preg_match('!^[a-zA-Z0-9-_]{1,255}$!', $this->name)) { throw new FileBackendException("Backend name `{$this->name}` is invalid."); } if (!isset($config['wikiId'])) { $config['wikiId'] = wfWikiID(); wfDeprecated(__METHOD__ . ' called without "wikiID".', '1.23'); } if (isset($config['lockManager']) && !is_object($config['lockManager'])) { $config['lockManager'] = LockManagerGroup::singleton($config['wikiId'])->get($config['lockManager']); wfDeprecated(__METHOD__ . ' called with non-object "lockManager".', '1.23'); } $this->wikiId = $config['wikiId']; // e.g. "my_wiki-en_" $this->lockManager = isset($config['lockManager']) ? $config['lockManager'] : new NullLockManager(array()); $this->fileJournal = isset($config['fileJournal']) ? $config['fileJournal'] : FileJournal::factory(array('class' => 'NullFileJournal'), $this->name); $this->readOnly = isset($config['readOnly']) ? (string) $config['readOnly'] : ''; $this->parallelize = isset($config['parallelize']) ? (string) $config['parallelize'] : 'off'; $this->concurrency = isset($config['concurrency']) ? (int) $config['concurrency'] : 50; }
protected function prepareEnvironment() { global $wgMemc; // Don't share DB, storage, or memcached connections wfGetLBFactory()->destroyInstance(); FileBackendGroup::destroySingleton(); LockManagerGroup::destroySingletons(); ObjectCache::clear(); $wgMemc = null; }
/** * Do any setup which can be done once for all tests, independent of test * options, except for database setup. * * Public setup functions in this class return a ScopedCallback object. When * this object is destroyed by going out of scope, teardown of the * corresponding test setup is performed. * * Teardown objects may be chained by passing a ScopedCallback from a * previous setup stage as the $nextTeardown parameter. This enforces the * convention that teardown actions are taken in reverse order to the * corresponding setup actions. When $nextTeardown is specified, a * ScopedCallback will be returned which first tears down the current * setup stage, and then tears down the previous setup stage which was * specified by $nextTeardown. * * @param ScopedCallback|null $nextTeardown * @return ScopedCallback */ public function staticSetup($nextTeardown = null) { // A note on coding style: // The general idea here is to keep setup code together with // corresponding teardown code, in a fine-grained manner. We have two // arrays: $setup and $teardown. The code snippets in the $setup array // are executed at the end of the method, before it returns, and the // code snippets in the $teardown array are executed in reverse order // when the Wikimedia\ScopedCallback object is consumed. // Because it is a common operation to save, set and restore global // variables, we have an additional convention: when the array key of // $setup is a string, the string is taken to be the name of the global // variable, and the element value is taken to be the desired new value. // It's acceptable to just do the setup immediately, instead of adding // a closure to $setup, except when the setup action depends on global // variable initialisation being done first. In this case, you have to // append a closure to $setup after the global variable is appended. // When you add to setup functions in this class, please keep associated // setup and teardown actions together in the source code, and please // add comments explaining why the setup action is necessary. $setup = []; $teardown = []; $teardown[] = $this->markSetupDone('staticSetup'); // Some settings which influence HTML output $setup['wgSitename'] = 'MediaWiki'; $setup['wgServer'] = 'http://example.org'; $setup['wgServerName'] = 'example.org'; $setup['wgScriptPath'] = ''; $setup['wgScript'] = '/index.php'; $setup['wgResourceBasePath'] = ''; $setup['wgStylePath'] = '/skins'; $setup['wgExtensionAssetsPath'] = '/extensions'; $setup['wgArticlePath'] = '/wiki/$1'; $setup['wgActionPaths'] = []; $setup['wgVariantArticlePath'] = false; $setup['wgUploadNavigationUrl'] = false; $setup['wgCapitalLinks'] = true; $setup['wgNoFollowLinks'] = true; $setup['wgNoFollowDomainExceptions'] = ['no-nofollow.org']; $setup['wgExternalLinkTarget'] = false; $setup['wgExperimentalHtmlIds'] = false; $setup['wgLocaltimezone'] = 'UTC'; $setup['wgHtml5'] = true; $setup['wgDisableLangConversion'] = false; $setup['wgDisableTitleConversion'] = false; // "extra language links" // see https://gerrit.wikimedia.org/r/111390 $setup['wgExtraInterlanguageLinkPrefixes'] = ['mul']; // All FileRepo changes should be done here by injecting services, // there should be no need to change global variables. RepoGroup::setSingleton($this->createRepoGroup()); $teardown[] = function () { RepoGroup::destroySingleton(); }; // Set up null lock managers $setup['wgLockManagers'] = [['name' => 'fsLockManager', 'class' => 'NullLockManager'], ['name' => 'nullLockManager', 'class' => 'NullLockManager']]; $reset = function () { LockManagerGroup::destroySingletons(); }; $setup[] = $reset; $teardown[] = $reset; // This allows article insertion into the prefixed DB $setup['wgDefaultExternalStore'] = false; // This might slightly reduce memory usage $setup['wgAdaptiveMessageCache'] = true; // This is essential and overrides disabling of database messages in TestSetup $setup['wgUseDatabaseMessages'] = true; $reset = function () { MessageCache::destroyInstance(); }; $setup[] = $reset; $teardown[] = $reset; // It's not necessary to actually convert any files $setup['wgSVGConverter'] = 'null'; $setup['wgSVGConverters'] = ['null' => 'echo "1">$output']; // Fake constant timestamp Hooks::register('ParserGetVariableValueTs', 'ParserTestRunner::getFakeTimestamp'); $teardown[] = function () { Hooks::clear('ParserGetVariableValueTs'); }; $this->appendNamespaceSetup($setup, $teardown); // Set up interwikis and append teardown function $teardown[] = $this->setupInterwikis(); // This affects title normalization in links. It invalidates // MediaWikiTitleCodec objects. $setup['wgLocalInterwikis'] = ['local', 'mi']; $reset = function () { $this->resetTitleServices(); }; $setup[] = $reset; $teardown[] = $reset; // Set up a mock MediaHandlerFactory MediaWikiServices::getInstance()->disableService('MediaHandlerFactory'); MediaWikiServices::getInstance()->redefineService('MediaHandlerFactory', function () { return new MockMediaHandlerFactory(); }); $teardown[] = function () { MediaWikiServices::getInstance()->resetServiceForTesting('MediaHandlerFactory'); }; // SqlBagOStuff broke when using temporary tables on r40209 (bug 15892). // It seems to have been fixed since (r55079?), but regressed at some point before r85701. // This works around it for now... global $wgObjectCaches; $setup['wgObjectCaches'] = [CACHE_DB => $wgObjectCaches['hash']] + $wgObjectCaches; if (isset(ObjectCache::$instances[CACHE_DB])) { $savedCache = ObjectCache::$instances[CACHE_DB]; ObjectCache::$instances[CACHE_DB] = new HashBagOStuff(); $teardown[] = function () use($savedCache) { ObjectCache::$instances[CACHE_DB] = $savedCache; }; } $teardown[] = $this->executeSetupSnippets($setup); // Schedule teardown snippets in reverse order return $this->createTeardownObject($teardown, $nextTeardown); }
/** * Get the config array for a backend object with a given name * * @param string $name * @return array Parameters to FileBackend::__construct() * @throws InvalidArgumentException */ public function config($name) { if (!isset($this->backends[$name])) { throw new InvalidArgumentException("No backend defined with the name `{$name}`."); } $class = $this->backends[$name]['class']; $config = $this->backends[$name]['config']; $config['class'] = $class; $config += ['wikiId' => wfWikiID(), 'mimeCallback' => [$this, 'guessMimeInternal'], 'obResetFunc' => 'wfResetOutputBuffers', 'streamMimeFunc' => ['StreamFile', 'contentTypeFromPath'], 'tmpDirectory' => wfTempDir(), 'statusWrapper' => ['Status', 'wrap'], 'wanCache' => MediaWikiServices::getInstance()->getMainWANObjectCache(), 'srvCache' => ObjectCache::getLocalServerInstance('hash'), 'logger' => LoggerFactory::getInstance('FileOperation'), 'profiler' => Profiler::instance()]; $config['lockManager'] = LockManagerGroup::singleton($config['wikiId'])->get($config['lockManager']); $config['fileJournal'] = isset($config['fileJournal']) ? FileJournal::factory($config['fileJournal'], $name) : FileJournal::factory(['class' => 'NullFileJournal'], $name); return $config; }
/** * Destroy the singleton instance, so that a new one will be created next * time singleton() is called. */ public static function destroySingleton() { self::$instance = null; }