Automatically reset namespace caches when needed

This avoids error-prone code written separately in every test.  In
addition to no existing tests resetting the TitleFormatter (more
services probably need to be reset as well), they mostly reset only the
namespace cache on $wgContLang, which wouldn't help for any other
language.

The parser test runner still doesn't do this, but maybe it should.

Change-Id: I44b7a1aec48f14b0950907fa14bd0df80f674296
This commit is contained in:
Aryeh Gregor 2018-08-01 15:40:47 +03:00
parent f17feae3a9
commit 63d7f2ad13
12 changed files with 153 additions and 134 deletions

View file

@ -244,6 +244,24 @@ class Language {
throw new MWException( "Invalid fallback sequence for language '$code'" );
}
/**
* Intended for tests that may change configuration in a way that invalidates caches.
*
* @since 1.32
*/
public static function clearCaches() {
if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
throw new MWException( __METHOD__ . ' must not be used outside tests' );
}
self::$dataCache = null;
// Reinitialize $dataCache, since it's expected to always be available
self::getLocalisationCache();
self::$mLangObjCache = [];
self::$fallbackLanguageCache = [];
self::$grammarTransformations = null;
self::$languageNameCache = null;
}
/**
* Checks whether any localisation is available for that language tag
* in MediaWiki (MessagesXx.php exists).

View file

@ -628,6 +628,12 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase {
foreach ( $this->mwGlobalsToUnset as $value ) {
unset( $GLOBALS[$value] );
}
if (
array_key_exists( 'wgExtraNamespaces', $this->mwGlobals ) ||
in_array( 'wgExtraNamespaces', $this->mwGlobalsToUnset )
) {
$this->resetNamespaces();
}
$this->mwGlobals = [];
$this->mwGlobalsToUnset = [];
$this->restoreLoggers();
@ -745,6 +751,26 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase {
foreach ( $pairs as $key => $value ) {
$GLOBALS[$key] = $value;
}
if ( array_key_exists( 'wgExtraNamespaces', $pairs ) ) {
$this->resetNamespaces();
}
}
/**
* Must be called whenever namespaces are changed, e.g., $wgExtraNamespaces is altered.
* Otherwise old namespace data will lurk and cause bugs.
*/
private function resetNamespaces() {
MWNamespace::clearCaches();
Language::clearCaches();
// We can't have the TitleFormatter holding on to an old Language object either
// @todo We shouldn't need to reset all the aliases here.
$services = MediaWikiServices::getInstance();
$services->resetServiceForTesting( 'TitleFormatter' );
$services->resetServiceForTesting( 'TitleParser' );
$services->resetServiceForTesting( '_MediaWikiTitleCodec' );
}
/**

View file

@ -12,34 +12,19 @@
class EditPageTest extends MediaWikiLangTestCase {
protected function setUp() {
global $wgExtraNamespaces, $wgNamespaceContentModels, $wgContentHandlers, $wgContLang;
parent::setUp();
$this->setContentLang( $wgContLang );
$this->setMwGlobals( [
'wgExtraNamespaces' => $wgExtraNamespaces,
'wgNamespaceContentModels' => $wgNamespaceContentModels,
'wgContentHandlers' => $wgContentHandlers,
'wgExtraNamespaces' => [
12312 => 'Dummy',
12313 => 'Dummy_talk',
],
'wgNamespaceContentModels' => [ 12312 => 'testing' ],
] );
$wgExtraNamespaces[12312] = 'Dummy';
$wgExtraNamespaces[12313] = 'Dummy_talk';
$wgNamespaceContentModels[12312] = "testing";
$wgContentHandlers["testing"] = 'DummyContentHandlerForTesting';
MWNamespace::clearCaches();
$wgContLang->resetNamespaces(); # reset namespace cache
}
protected function tearDown() {
global $wgContLang;
MWNamespace::clearCaches();
$wgContLang->resetNamespaces(); # reset namespace cache
parent::tearDown();
$this->mergeMwGlobalArrayValue(
'wgContentHandlers',
[ 'testing' => 'DummyContentHandlerForTesting' ]
);
}
/**

View file

@ -26,7 +26,7 @@ class MessageTest extends MediaWikiLangTestCase {
$this->assertSame( $key, $message->getKey() );
$this->assertSame( $params, $message->getParams() );
$this->assertEquals( $expectedLang, $message->getLanguage() );
$this->assertSame( $expectedLang->getCode(), $message->getLanguage()->getCode() );
$messageSpecifier = $this->getMockForAbstractClass( MessageSpecifier::class );
$messageSpecifier->expects( $this->any() )
@ -37,7 +37,7 @@ class MessageTest extends MediaWikiLangTestCase {
$this->assertSame( $key, $message->getKey() );
$this->assertSame( $params, $message->getParams() );
$this->assertEquals( $expectedLang, $message->getLanguage() );
$this->assertSame( $expectedLang->getCode(), $message->getLanguage()->getCode() );
}
public static function provideConstructor() {

View file

@ -27,18 +27,20 @@ class PagePropsTest extends MediaWikiLangTestCase {
private $the_properties;
protected function setUp() {
global $wgExtraNamespaces, $wgNamespaceContentModels, $wgContentHandlers, $wgContLang;
parent::setUp();
$wgExtraNamespaces[12312] = 'Dummy';
$wgExtraNamespaces[12313] = 'Dummy_talk';
$this->setMwGlobals( [
'wgExtraNamespaces' => [
12312 => 'Dummy',
12313 => 'Dummy_talk',
],
'wgNamespaceContentModels' => [ 12312 => 'DUMMY' ],
] );
$wgNamespaceContentModels[12312] = 'DUMMY';
$wgContentHandlers['DUMMY'] = 'DummyContentHandlerForTesting';
MWNamespace::clearCaches();
$wgContLang->resetNamespaces(); # reset namespace cache
$this->mergeMwGlobalArrayValue(
'wgContentHandlers',
[ 'DUMMY' => 'DummyContentHandlerForTesting' ]
);
if ( !$this->the_properties ) {
$this->the_properties = [
@ -72,21 +74,6 @@ class PagePropsTest extends MediaWikiLangTestCase {
}
}
protected function tearDown() {
global $wgExtraNamespaces, $wgNamespaceContentModels, $wgContentHandlers, $wgContLang;
parent::tearDown();
unset( $wgExtraNamespaces[12312] );
unset( $wgExtraNamespaces[12313] );
unset( $wgNamespaceContentModels[12312] );
unset( $wgContentHandlers['DUMMY'] );
MWNamespace::clearCaches();
$wgContLang->resetNamespaces(); # reset namespace cache
}
/**
* Test getting a single property from a single page. The property was
* set in setUp().

View file

@ -50,7 +50,7 @@ class PrefixSearchTest extends MediaWikiLangTestCase {
$this->markTestSkipped( 'Main namespace does not support wikitext.' );
}
// Avoid special pages from extensions interferring with the tests
// Avoid special pages from extensions interfering with the tests
$this->setMwGlobals( [
'wgSpecialPages' => [],
'wgHooks' => [],
@ -61,17 +61,10 @@ class PrefixSearchTest extends MediaWikiLangTestCase {
$this->originalHandlers = TestingAccessWrapper::newFromClass( Hooks::class )->handlers;
TestingAccessWrapper::newFromClass( Hooks::class )->handlers = [];
// Clear caches so that our new namespace appears
MWNamespace::clearCaches();
Language::factory( 'en' )->resetNamespaces();
SpecialPageFactory::resetList();
}
public function tearDown() {
MWNamespace::clearCaches();
Language::factory( 'en' )->resetNamespaces();
parent::tearDown();
TestingAccessWrapper::newFromClass( Hooks::class )->handlers = $this->originalHandlers;

View file

@ -58,8 +58,6 @@ abstract class RevisionDbTestBase extends MediaWikiTestCase {
abstract protected function getMcrTablesToReset();
protected function setUp() {
global $wgContLang;
$this->tablesUsed += $this->getMcrTablesToReset();
parent::setUp();
@ -93,10 +91,6 @@ abstract class RevisionDbTestBase extends MediaWikiTestCase {
$this->getMcrMigrationStage()
);
MWNamespace::clearCaches();
// Reset namespace cache
$wgContLang->resetNamespaces();
$this->overrideMwServices();
if ( !$this->testPage ) {
@ -108,16 +102,6 @@ abstract class RevisionDbTestBase extends MediaWikiTestCase {
}
}
protected function tearDown() {
global $wgContLang;
parent::tearDown();
MWNamespace::clearCaches();
// Reset namespace cache
$wgContLang->resetNamespaces();
}
abstract protected function getContentHandlerUseDB();
private function makeRevisionWithProps( $props = null ) {

View file

@ -12,8 +12,6 @@ use MediaWiki\MediaWikiServices;
class TitleMethodsTest extends MediaWikiLangTestCase {
protected function setUp() {
global $wgContLang;
parent::setUp();
$this->mergeMwGlobalArrayValue(
@ -30,18 +28,6 @@ class TitleMethodsTest extends MediaWikiLangTestCase {
12302 => CONTENT_MODEL_JAVASCRIPT,
]
);
MWNamespace::clearCaches();
$wgContLang->resetNamespaces(); # reset namespace cache
}
protected function tearDown() {
global $wgContLang;
parent::tearDown();
MWNamespace::clearCaches();
$wgContLang->resetNamespaces(); # reset namespace cache
}
public static function provideEquals() {

View file

@ -14,42 +14,25 @@
class ApiEditPageTest extends ApiTestCase {
protected function setUp() {
global $wgExtraNamespaces, $wgNamespaceContentModels, $wgContentHandlers, $wgContLang;
parent::setUp();
$this->setContentLang( $wgContLang );
$this->setMwGlobals( [
'wgExtraNamespaces' => $wgExtraNamespaces,
'wgNamespaceContentModels' => $wgNamespaceContentModels,
'wgContentHandlers' => $wgContentHandlers,
'wgExtraNamespaces' => [
12312 => 'Dummy',
12313 => 'Dummy_talk',
12314 => 'DummyNonText',
12315 => 'DummyNonText_talk',
],
'wgNamespaceContentModels' => [
12312 => 'testing',
12314 => 'testing-nontext',
],
] );
$this->mergeMwGlobalArrayValue( 'wgContentHandlers', [
'testing' => 'DummyContentHandlerForTesting',
'testing-nontext' => 'DummyNonTextContentHandler',
'testing-serialize-error' => 'DummySerializeErrorContentHandler',
] );
$wgExtraNamespaces[12312] = 'Dummy';
$wgExtraNamespaces[12313] = 'Dummy_talk';
$wgExtraNamespaces[12314] = 'DummyNonText';
$wgExtraNamespaces[12315] = 'DummyNonText_talk';
$wgNamespaceContentModels[12312] = "testing";
$wgNamespaceContentModels[12314] = "testing-nontext";
$wgContentHandlers["testing"] = 'DummyContentHandlerForTesting';
$wgContentHandlers["testing-nontext"] = 'DummyNonTextContentHandler';
$wgContentHandlers["testing-serialize-error"] =
'DummySerializeErrorContentHandler';
MWNamespace::clearCaches();
$wgContLang->resetNamespaces(); # reset namespace cache
}
protected function tearDown() {
global $wgContLang;
MWNamespace::clearCaches();
$wgContLang->resetNamespaces(); # reset namespace cache
parent::tearDown();
}
public function testEdit() {

View file

@ -82,6 +82,31 @@ class AuthManagerTest extends \MediaWikiTestCase {
return new \Message( $key, $params, \Language::factory( 'en' ) );
}
/**
* Test two AuthenticationResponses for equality. We don't want to use regular assertEquals
* because that recursively compares members, which leads to false negatives if e.g. Language
* caches are reset.
*
* @param AuthenticationResponse $response1
* @param AuthenticationResponse $response2
* @param string $msg
* @return bool
*/
private function assertResponseEquals(
AuthenticationResponse $expected, AuthenticationResponse $actual, $msg = ''
) {
foreach ( ( new \ReflectionClass( $expected ) )->getProperties() as $prop ) {
$name = $prop->getName();
$usedMsg = ltrim( "$msg ($name)" );
if ( $name === 'message' && $expected->message ) {
$this->assertSame( $expected->message->serialize(), $actual->message->serialize(),
$usedMsg );
} else {
$this->assertEquals( $expected->$name, $actual->$name, $usedMsg );
}
}
}
/**
* Initialize the AuthManagerConfig variable in $this->config
*
@ -1030,7 +1055,7 @@ class AuthManagerTest extends \MediaWikiTestCase {
$this->assertSame( 'http://localhost/', $req->returnToUrl );
$ret->message = $this->message( $ret->message );
$this->assertEquals( $response, $ret, "Response $i, response" );
$this->assertResponseEquals( $response, $ret, "Response $i, response" );
if ( $success ) {
$this->assertSame( $id, $session->getUser()->getId(),
"Response $i, authn" );
@ -2082,7 +2107,7 @@ class AuthManagerTest extends \MediaWikiTestCase {
"Response $i, login marker" );
}
$ret->message = $this->message( $ret->message );
$this->assertEquals( $response, $ret, "Response $i, response" );
$this->assertResponseEquals( $response, $ret, "Response $i, response" );
if ( $success || $response->status === AuthenticationResponse::FAIL ) {
$this->assertNull(
$this->request->getSession()->getSecret( 'AuthManager::accountCreationState' ),
@ -3517,7 +3542,7 @@ class AuthManagerTest extends \MediaWikiTestCase {
$this->assertSame( 'http://localhost/', $req->returnToUrl );
$ret->message = $this->message( $ret->message );
$this->assertEquals( $response, $ret, "Response $i, response" );
$this->assertResponseEquals( $response, $ret, "Response $i, response" );
if ( $response->status === AuthenticationResponse::PASS ||
$response->status === AuthenticationResponse::FAIL
) {

View file

@ -8,7 +8,6 @@ use MediaWiki\MediaWikiServices;
class ContentHandlerTest extends MediaWikiTestCase {
protected function setUp() {
global $wgContLang;
parent::setUp();
$this->setMwGlobals( [
@ -34,20 +33,12 @@ class ContentHandlerTest extends MediaWikiTestCase {
],
] );
// Reset namespace cache
MWNamespace::clearCaches();
$wgContLang->resetNamespaces();
// And LinkCache
// Reset LinkCache
MediaWikiServices::getInstance()->resetServiceForTesting( 'LinkCache' );
}
protected function tearDown() {
global $wgContLang;
// Reset namespace cache
MWNamespace::clearCaches();
$wgContLang->resetNamespaces();
// And LinkCache
// Reset LinkCache
MediaWikiServices::getInstance()->resetServiceForTesting( 'LinkCache' );
parent::tearDown();

View file

@ -1,5 +1,7 @@
<?php
use Wikimedia\TestingAccessWrapper;
class LanguageTest extends LanguageClassesTestCase {
/**
* @covers Language::convertDoubleWidth
@ -1771,6 +1773,45 @@ class LanguageTest extends LanguageClassesTestCase {
$this->assertEquals( "a{$c}b{$c}c{$and}{$s}d", $lang->listToText( [ 'a', 'b', 'c', 'd' ] ) );
}
/**
* @covers Language::clearCaches
*/
public function testClearCaches() {
$languageClass = TestingAccessWrapper::newFromClass( Language::class );
// Populate $dataCache
Language::getLocalisationCache()->getItem( 'zh', 'mainpage' );
$oldCacheObj = Language::$dataCache;
$this->assertNotCount( 0,
TestingAccessWrapper::newFromObject( Language::$dataCache )->loadedItems );
// Populate $mLangObjCache
$lang = Language::factory( 'en' );
$this->assertNotCount( 0, Language::$mLangObjCache );
// Populate $fallbackLanguageCache
Language::getFallbacksIncludingSiteLanguage( 'en' );
$this->assertNotCount( 0, $languageClass->fallbackLanguageCache );
// Populate $grammarTransformations
$lang->getGrammarTransformations();
$this->assertNotNull( $languageClass->grammarTransformations );
// Populate $languageNameCache
Language::fetchLanguageNames();
$this->assertNotNull( $languageClass->languageNameCache );
Language::clearCaches();
$this->assertNotSame( $oldCacheObj, Language::$dataCache );
$this->assertCount( 0,
TestingAccessWrapper::newFromObject( Language::$dataCache )->loadedItems );
$this->assertCount( 0, Language::$mLangObjCache );
$this->assertCount( 0, $languageClass->fallbackLanguageCache );
$this->assertNull( $languageClass->grammarTransformations );
$this->assertNull( $languageClass->languageNameCache );
}
/**
* @dataProvider provideIsSupportedLanguage
* @covers Language::isSupportedLanguage