We always wrap the local cluster cache, and there are no subclasses of WANObjectCache. It was never documented or recommended how these would be used. It is a left-over from the original 2015 Multi-DC plan in which WANObjectCache would work differently. See task for details. Note that this requires no configuration changes, even in the theoretical case of these variables being used, as the only option is to use the main cache, and that's also the default. * Update WAN overrides to override the underlying main cache instead. * Fix EditPageTest which was previously implicitly using a 'hash' as main cache but also relying on wan cache to be 'none'. The part that it actually needs is the 'none'. When WAN cache is enabled, testUpdateNoMinor fails due to an edit conflict because one of the edits it makes is made with a current timestamp whereas it expects to simulate wpEdittime in the year 2012 which, when caching is enabled, is ignored and becomes the current time instead. I don't understand exactly why, but I'm going to conserve that behaviour for now. * Fix TemplateCategoriesTest, which was failing due to an unexpected cache hit: > [objectcache] fetchOrRegenerate(…:page:10:…): volatile hit This could be solved in a more realistic way by splitting the test, or by explicitly resetting services half-way the test to clear WikiPageFactory, PageStore and WANCache process state. For now, keep the prior behaviour of no cache in this test. Bug: T305093 Bug: T329680 Depends-On: If890622eed0d0f8b4bd73d36ba1815a3d760ea05 Depends-On: Ie1def75208822bdf19bb2cfd7e6edf32c2000e6b Depends-On: I35cce61dc3ee90dcee3dd6f0b36f84133be029ed Change-Id: I53781a8c06ebb2583f6ca83dd91bbfe8a5c88b13
1009 lines
33 KiB
PHP
1009 lines
33 KiB
PHP
<?php
|
|
|
|
use MediaWiki\MainConfigNames;
|
|
use MediaWiki\MainConfigSchema;
|
|
use MediaWiki\Settings\Config\ArrayConfigBuilder;
|
|
use MediaWiki\Settings\Config\ConfigSchemaAggregator;
|
|
use MediaWiki\Settings\Config\PhpIniSink;
|
|
use MediaWiki\Settings\DynamicDefaultValues;
|
|
use MediaWiki\Settings\SettingsBuilder;
|
|
use MediaWiki\Settings\Source\ReflectionSchemaSource;
|
|
|
|
class SetupDynamicConfigTest extends MediaWikiUnitTestCase {
|
|
/** @var string */
|
|
private $originalDefaultTimezone;
|
|
|
|
/** @var string[] */
|
|
private $originalServerVars;
|
|
|
|
/**
|
|
* Stop $wgLocaltimezone from clobbering the default, and make sure the timezone is UTC
|
|
*
|
|
* @before
|
|
*/
|
|
public function saveTimezoneSetUp(): void {
|
|
$this->originalDefaultTimezone = date_default_timezone_get();
|
|
date_default_timezone_set( 'UTC' );
|
|
}
|
|
|
|
/**
|
|
* @after
|
|
*/
|
|
public function restoreTimezoneTearDown(): void {
|
|
date_default_timezone_set( $this->originalDefaultTimezone );
|
|
}
|
|
|
|
/**
|
|
* @before
|
|
*/
|
|
public function serverVarsSetUp(): void {
|
|
$this->originalServerVars = $_SERVER;
|
|
}
|
|
|
|
/**
|
|
* @after
|
|
*/
|
|
public function serverVarsTearDown(): void {
|
|
$_SERVER = $this->originalServerVars;
|
|
}
|
|
|
|
/**
|
|
* Returns a callback that replaces its single input with all occurrences of $find replaced
|
|
* with $replace. Arrays are traversed recursively, and values that are neither arrays nor
|
|
* strings are returned as-is. $find and $replace may be arrays of the same length, in which
|
|
* case all replacements are performed.
|
|
*
|
|
* @param string|string[] $find
|
|
* @param string|string[] $replace
|
|
* @return Closure
|
|
*/
|
|
private static function recursiveReplaceCallback( $find, $replace ): Closure {
|
|
$callback = static function ( $val ) use ( $find, $replace, &$callback ) {
|
|
if ( is_array( $val ) ) {
|
|
return array_map( $callback, $val );
|
|
}
|
|
if ( !is_string( $val ) ) {
|
|
return $val;
|
|
}
|
|
return str_replace( $find, $replace, $val );
|
|
};
|
|
return $callback;
|
|
}
|
|
|
|
/**
|
|
* This method is made necessary by the fact that the dynamic default for DummyLanguageCodes is
|
|
* based on long lists that are returned from static methods that we can't override.
|
|
* Copy-pasting those lists into the expected results would be both fragile and hard to read.
|
|
*
|
|
* @param array $dummyLanguageCodes Value that DummyLanguageCodes was set to in configuration
|
|
* @param array $extraLanguageCodes Value that ExtraLanguageCodes was set to in configuration
|
|
* @return array Expected value after dynamic defaults
|
|
*/
|
|
private static function getExpectedDummyLanguageCodes( array $dummyLanguageCodes,
|
|
array $extraLanguageCodes = MainConfigSchema::ExtraLanguageCodes['default']
|
|
): array {
|
|
$expected = $dummyLanguageCodes + [ 'qqq' => 'qqq', 'qqx' => 'qqx' ] + $extraLanguageCodes
|
|
+ LanguageCode::getDeprecatedCodeMapping();
|
|
|
|
// Copy-paste from SetupDynamicConfig
|
|
foreach ( LanguageCode::getNonstandardLanguageCodeMapping() as $code => $bcp47 ) {
|
|
$bcp47 = strtolower( $bcp47 ); // force case-insensitivity
|
|
if ( !isset( $expected[$bcp47] ) ) {
|
|
$expected[$bcp47] = $expected[$code] ?? $code;
|
|
}
|
|
}
|
|
|
|
return $expected;
|
|
}
|
|
|
|
public static function provideGlobals(): Generator {
|
|
$expectedDefault = [
|
|
'ScriptPath' => '/wiki',
|
|
'Script' => '/wiki/index.php',
|
|
'LoadScript' => '/wiki/load.php',
|
|
'RestPath' => '/wiki/rest.php',
|
|
// XXX $wgUsePathInfo set based on PHP_SAPI, no good way to test?
|
|
// XXX Testing $wgArticlePath doesn't seem to work
|
|
'ResourceBasePath' => '/wiki',
|
|
'StylePath' => '/wiki/skins',
|
|
'LocalStylePath' => '/wiki/skins',
|
|
'ExtensionAssetsPath' => '/wiki/extensions',
|
|
'Logo' => '/wiki/resources/assets/change-your-logo.svg',
|
|
'UploadPath' => '/wiki/images',
|
|
'UploadDirectory' => '/install/path/images',
|
|
'ReadOnlyFile' => '/install/path/images/lock_yBgMBwiR',
|
|
'FileCacheDirectory' => '/install/path/images/cache',
|
|
'DeletedDirectory' => '/install/path/images/deleted',
|
|
'SharedPrefix' => '',
|
|
'SharedSchema' => null,
|
|
'MetaNamespace' => 'MediaWiki',
|
|
'EnableUserEmailMuteList' => false,
|
|
'EnableUserEmailBlacklist' => false,
|
|
'NamespaceProtection' => [ NS_MEDIAWIKI => 'editinterface' ],
|
|
'LockManagers' => [ [
|
|
'name' => 'fsLockManager',
|
|
'class' => FSLockManager::class,
|
|
'lockDirectory' => '/install/path/images/lockdir',
|
|
], [
|
|
'name' => 'nullLockManager',
|
|
'class' => NullLockManager::class,
|
|
] ],
|
|
// Can't really test this properly without being able to mock global functions
|
|
'ShowEXIF' => function_exists( 'exif_read_data' ),
|
|
'GalleryOptions' => [
|
|
'imagesPerRow' => 0,
|
|
'imageWidth' => 120,
|
|
'imageHeight' => 120,
|
|
'captionLength' => true,
|
|
'showBytes' => true,
|
|
'showDimensions' => true,
|
|
'mode' => 'traditional',
|
|
],
|
|
'LocalFileRepo' => [
|
|
'class' => LocalRepo::class,
|
|
'name' => 'local',
|
|
'directory' => '/install/path/images',
|
|
'scriptDirUrl' => '/wiki',
|
|
'favicon' => '/favicon.ico',
|
|
'url' => '/wiki/images',
|
|
'hashLevels' => 2,
|
|
'thumbScriptUrl' => false,
|
|
'transformVia404' => false,
|
|
'deletedDir' => '/install/path/images/deleted',
|
|
'deletedHashLevels' => 3,
|
|
'updateCompatibleMetadata' => false,
|
|
'reserializeMetadata' => false,
|
|
'backend' => 'local-backend',
|
|
],
|
|
'CookiePrefix' => 'my_wiki',
|
|
'Localtimezone' => 'UTC',
|
|
'LocalTZoffset' => 0,
|
|
'DBerrorLogTZ' => 'UTC',
|
|
'CanonicalNamespaceNames' => NamespaceInfo::CANONICAL_NAMES,
|
|
'DummyLanguageCodes' => self::getExpectedDummyLanguageCodes( [] ),
|
|
'SlaveLagWarning' => 10,
|
|
'DatabaseReplicaLagWarning' => 10,
|
|
'SlaveLagCritical' => 30,
|
|
'DatabaseReplicaLagCritical' => 30,
|
|
// This will be wrong if LocalSettings.php was last touched before May 16, 2003.
|
|
'CacheEpoch' => static function (): string {
|
|
// We need a callback that will be evaluated at test time, because otherwise this
|
|
// doesn't work on CI for some reason.
|
|
$ret = max( '20030516000000', gmdate( 'YmdHis', @filemtime( MW_CONFIG_FILE ) ) );
|
|
return $ret;
|
|
},
|
|
'RateLimits' => MainConfigSchema::getDefaultValue( 'RateLimits' ),
|
|
];
|
|
|
|
foreach (
|
|
self::provideGlobalsInternal( $expectedDefault ) as $desc => $arr
|
|
) {
|
|
if ( is_array( $arr[1] ) ) {
|
|
$arr[1] += $expectedDefault;
|
|
}
|
|
yield $desc => $arr;
|
|
}
|
|
}
|
|
|
|
private static function provideGlobalsInternal( array $expectedDefault ): Generator {
|
|
yield 'Nothing set' => [ [], [] ];
|
|
yield '$wgScriptPath set' => [
|
|
[ 'ScriptPath' => '/mywiki' ],
|
|
array_map( self::recursiveReplaceCallback( '/wiki', '/mywiki' ), $expectedDefault ),
|
|
];
|
|
yield '$wgResourceBasePath set' => [
|
|
[ 'ResourceBasePath' => '/resources' ],
|
|
[
|
|
'ResourceBasePath' => '/resources',
|
|
'ExtensionAssetsPath' => '/resources/extensions',
|
|
'StylePath' => '/resources/skins',
|
|
'Logo' => '/resources/resources/assets/change-your-logo.svg',
|
|
],
|
|
];
|
|
yield '$wgUploadDirectory set' => [
|
|
[ 'UploadDirectory' => '/uploads' ],
|
|
[
|
|
'UploadDirectory' => '/uploads',
|
|
'ReadOnlyFile' => '/uploads/lock_yBgMBwiR',
|
|
'FileCacheDirectory' => '/uploads/cache',
|
|
'DeletedDirectory' => '/uploads/deleted',
|
|
'LockManagers' => [ [
|
|
'name' => 'fsLockManager',
|
|
'class' => FSLockManager::class,
|
|
'lockDirectory' => '/uploads/lockdir',
|
|
], [
|
|
'name' => 'nullLockManager',
|
|
'class' => NullLockManager::class,
|
|
] ],
|
|
'LocalFileRepo' => [
|
|
'class' => LocalRepo::class,
|
|
'name' => 'local',
|
|
'directory' => '/uploads',
|
|
'scriptDirUrl' => '/wiki',
|
|
'favicon' => '/favicon.ico',
|
|
'url' => '/wiki/images',
|
|
'hashLevels' => 2,
|
|
'thumbScriptUrl' => false,
|
|
'transformVia404' => false,
|
|
'deletedDir' => '/uploads/deleted',
|
|
'deletedHashLevels' => 3,
|
|
'updateCompatibleMetadata' => false,
|
|
'reserializeMetadata' => false,
|
|
'backend' => 'local-backend',
|
|
],
|
|
],
|
|
];
|
|
yield '$wgCacheDirectory set' => [
|
|
[ 'CacheDirectory' => '/cache' ],
|
|
[ 'CacheDirectory' => '/cache' ],
|
|
];
|
|
yield '$wgDBprefix set' => [
|
|
[ 'DBprefix' => 'prefix_' ],
|
|
[
|
|
'DBprefix' => 'prefix_',
|
|
'SharedPrefix' => 'prefix_',
|
|
'CookiePrefix' => 'my_wiki_prefix_',
|
|
],
|
|
];
|
|
yield '$wgDBmwschema set' => [
|
|
[ 'DBmwschema' => 'schema' ],
|
|
[
|
|
'DBmwschema' => 'schema',
|
|
'SharedSchema' => 'schema',
|
|
],
|
|
];
|
|
yield '$wgSitename set' => [
|
|
[ 'Sitename' => 'my site' ],
|
|
[
|
|
'Sitename' => 'my site',
|
|
'MetaNamespace' => 'my_site',
|
|
],
|
|
];
|
|
yield '$wgProhibitedFileExtensions set' => [
|
|
[ 'ProhibitedFileExtensions' => [ 'evil' ] ],
|
|
[
|
|
'ProhibitedFileExtensions' => [ 'evil' ],
|
|
'FileBlacklist' => [ 'evil' ],
|
|
],
|
|
];
|
|
yield '$wgProhibitedFileExtensions and $wgFileBlacklist set' => [
|
|
[
|
|
'ProhibitedFileExtensions' => [ 'evil' ],
|
|
'FileBlacklist' => [ 'eviler' ],
|
|
],
|
|
[
|
|
'ProhibitedFileExtensions' => [ 'evil', 'eviler' ],
|
|
'FileBlacklist' => [ 'eviler' ],
|
|
],
|
|
];
|
|
yield '$wgMimeTypeExclusions set' => [
|
|
[ 'MimeTypeExclusions' => [ 'evil' ] ],
|
|
[
|
|
'MimeTypeExclusions' => [ 'evil' ],
|
|
'MimeTypeBlacklist' => [ 'evil' ],
|
|
],
|
|
];
|
|
yield '$wgMimeTypeExclusions and $wgMimeTypeBlacklist set' => [
|
|
[
|
|
'MimeTypeExclusions' => [ 'evil' ],
|
|
'MimeTypeBlacklist' => [ 'eviler' ],
|
|
],
|
|
[
|
|
'MimeTypeExclusions' => [ 'evil', 'eviler' ],
|
|
'MimeTypeBlacklist' => [ 'eviler' ],
|
|
],
|
|
];
|
|
yield '$wgEnableUserEmailMuteList set' => [
|
|
[ 'EnableUserEmailMuteList' => true ],
|
|
[
|
|
'EnableUserEmailMuteList' => true,
|
|
'EnableUserEmailBlacklist' => true,
|
|
],
|
|
];
|
|
yield '$wgEnableUserEmailMuteList and $wgEnableUserEmailBlacklist both true' => [
|
|
[
|
|
'EnableUserEmailMuteList' => true,
|
|
'EnableUserEmailBlacklist' => true,
|
|
],
|
|
[
|
|
'EnableUserEmailMuteList' => true,
|
|
'EnableUserEmailBlacklist' => true,
|
|
],
|
|
];
|
|
yield '$wgEnableUserEmailMuteList true and $wgEnableUserEmailBlacklist false' => [
|
|
[
|
|
'EnableUserEmailMuteList' => true,
|
|
'EnableUserEmailBlacklist' => false,
|
|
],
|
|
[
|
|
'EnableUserEmailMuteList' => false,
|
|
'EnableUserEmailBlacklist' => false,
|
|
],
|
|
];
|
|
yield '$wgShortPagesNamespaceExclusions set' => [
|
|
[ 'ShortPagesNamespaceExclusions' => [ NS_TALK ] ],
|
|
[
|
|
'ShortPagesNamespaceExclusions' => [ NS_TALK ],
|
|
'ShortPagesNamespaceBlacklist' => [ NS_TALK ],
|
|
],
|
|
];
|
|
yield '$wgShortPagesNamespaceBlacklist set' => [
|
|
[ 'ShortPagesNamespaceBlacklist' => [ NS_PROJECT ] ],
|
|
[
|
|
'ShortPagesNamespaceExclusions' => [ NS_PROJECT ],
|
|
'ShortPagesNamespaceBlacklist' => [ NS_PROJECT ],
|
|
],
|
|
];
|
|
yield '$wgShortPagesNamespaceExclusions and $wgShortPagesNamespaceBlacklist set' => [
|
|
[
|
|
'ShortPagesNamespaceExclusions' => [ NS_TALK ],
|
|
'ShortPagesNamespaceBlacklist' => [ NS_PROJECT ],
|
|
],
|
|
[
|
|
'ShortPagesNamespaceExclusions' => [ NS_PROJECT ],
|
|
'ShortPagesNamespaceBlacklist' => [ NS_PROJECT ],
|
|
],
|
|
];
|
|
yield '$wgFileExtension contains something from $wgProhibitedFileExtensions' => [
|
|
[
|
|
'FileExtensions' => [ 'a', 'b', 'c' ],
|
|
'ProhibitedFileExtensions' => [ 'b', 'd' ],
|
|
],
|
|
[
|
|
'FileExtensions' => [ 'a', 'c' ],
|
|
'ProhibitedFileExtensions' => [ 'b', 'd' ],
|
|
],
|
|
];
|
|
yield '$wgRightsIcon old path convention' => [
|
|
[
|
|
'StylePath' => '/style',
|
|
'ResourceBasePath' => '/resources',
|
|
'RightsIcon' => '/style/common/images/rights.png',
|
|
],
|
|
[
|
|
'StylePath' => '/style',
|
|
'ResourceBasePath' => '/resources',
|
|
'ExtensionAssetsPath' => '/resources/extensions',
|
|
'Logo' => '/resources/resources/assets/change-your-logo.svg',
|
|
'RightsIcon' => '/resources/resources/assets/licenses/rights.png',
|
|
],
|
|
];
|
|
yield 'Empty $wgFooterIcons[\'copyright\'][\'copyright\'], $wgRightsIcon set' => [
|
|
[
|
|
'RightsIcon' => 'ico',
|
|
'FooterIcons' => [ 'copyright' => [ 'copyright' => [] ] ],
|
|
],
|
|
[
|
|
'FooterIcons' => [ 'copyright' => [ 'copyright' => [
|
|
'url' => null,
|
|
'src' => 'ico',
|
|
'alt' => null,
|
|
] ] ],
|
|
],
|
|
];
|
|
yield 'Empty $wgFooterIcons[\'copyright\'][\'copyright\'], $wgRightsText set' => [
|
|
[
|
|
'RightsText' => 'text',
|
|
'FooterIcons' => [ 'copyright' => [ 'copyright' => [] ] ],
|
|
],
|
|
[
|
|
'FooterIcons' => [ 'copyright' => [ 'copyright' => [
|
|
'url' => null,
|
|
'src' => null,
|
|
'alt' => 'text',
|
|
] ] ],
|
|
],
|
|
];
|
|
yield '$wgFooterIcons[\'poweredby\'][\'mediawiki\'][\'src\'] === null' => [
|
|
[
|
|
'ResourceBasePath' => '/resources',
|
|
'FooterIcons' => [ 'poweredby' => [ 'mediawiki' => [ 'src' => null ] ] ],
|
|
],
|
|
[
|
|
'ResourceBasePath' => '/resources',
|
|
'ExtensionAssetsPath' => '/resources/extensions',
|
|
'StylePath' => '/resources/skins',
|
|
'Logo' => '/resources/resources/assets/change-your-logo.svg',
|
|
'FooterIcons' => [ 'poweredby' => [ 'mediawiki' => [
|
|
'src' => '/resources/resources/assets/poweredby_mediawiki_88x31.png',
|
|
'srcset' => '/resources/resources/assets/poweredby_mediawiki_132x47.png 1.5x,' .
|
|
' /resources/resources/assets/poweredby_mediawiki_176x62.png 2x',
|
|
] ] ],
|
|
],
|
|
];
|
|
yield '$wgFooterIcons[\'poweredby\'][\'mediawiki\'][\'src\'] === \'\'' => [
|
|
[ 'FooterIcons' => [ 'poweredby' => [ 'mediawiki' => [ 'src' => '' ] ] ] ],
|
|
[ 'FooterIcons' => [ 'poweredby' => [ 'mediawiki' => [ 'src' => '' ] ] ] ],
|
|
];
|
|
yield '$wgFooterIcons[\'poweredby\']=== []' => [
|
|
[ 'FooterIcons' => [ 'poweredby' => [] ] ],
|
|
[ 'FooterIcons' => [ 'poweredby' => [] ] ],
|
|
];
|
|
yield '$wgNamespaceProtection set only for non-NS_MEDIAWIKI' => [
|
|
[ 'NamespaceProtection' => [ NS_PROJECT => 'editprotected' ] ],
|
|
[ 'NamespaceProtection' => [
|
|
NS_PROJECT => 'editprotected',
|
|
NS_MEDIAWIKI => 'editinterface',
|
|
] ],
|
|
];
|
|
yield '$wgNamespaceProtection[NS_MEDIAWIKI] not editinterface' => [
|
|
[ 'NamespaceProtection' => [ NS_MEDIAWIKI => 'editprotected' ] ],
|
|
[ 'NamespaceProtection' => [ NS_MEDIAWIKI => 'editinterface' ] ],
|
|
];
|
|
yield 'Custom lock manager' => [
|
|
[ 'LockManagers' => [ [ 'foo' ] ] ],
|
|
[ 'LockManagers' => [ [ 'foo' ], [
|
|
'name' => 'fsLockManager',
|
|
'class' => FSLockManager::class,
|
|
'lockDirectory' => '/install/path/images/lockdir',
|
|
], [
|
|
'name' => 'nullLockManager',
|
|
'class' => NullLockManager::class,
|
|
] ] ],
|
|
];
|
|
yield 'Customize some $wgGalleryOptions' => [
|
|
[ 'GalleryOptions' => [
|
|
'imagesPerRow' => 42,
|
|
'imageHeight' => -1,
|
|
'showBytes' => null,
|
|
'mode' => 'iconoclastic',
|
|
'custom' => 'Rembrandt',
|
|
] ],
|
|
[ 'GalleryOptions' => [
|
|
'imagesPerRow' => 42,
|
|
'imageHeight' => -1,
|
|
'showBytes' => null,
|
|
'mode' => 'iconoclastic',
|
|
'custom' => 'Rembrandt',
|
|
'imageWidth' => 120,
|
|
'captionLength' => true,
|
|
'showDimensions' => true,
|
|
] ],
|
|
];
|
|
yield 'Set $wgLocalFileRepo' => [
|
|
[ 'LocalFileRepo' => [ 'name' => 'asdfgh' ] ],
|
|
[ 'LocalFileRepo' => [
|
|
'name' => 'asdfgh', 'backend' => 'asdfgh-backend'
|
|
] ],
|
|
];
|
|
$sharedUploadsExpected = [
|
|
'class' => FileRepo::class,
|
|
'name' => 'shared',
|
|
'directory' => null,
|
|
'url' => null,
|
|
'hashLevels' => 2,
|
|
'thumbScriptUrl' => false,
|
|
'transformVia404' => false,
|
|
'descBaseUrl' => 'https://commons.wikimedia.org/wiki/File:',
|
|
'fetchDescription' => false,
|
|
'backend' => 'shared-backend',
|
|
];
|
|
yield '$wgUseSharedUploads' => [
|
|
[ 'UseSharedUploads' => true ],
|
|
[ 'ForeignFileRepos' => [ $sharedUploadsExpected ] ],
|
|
];
|
|
$sharedUploadsDBnameExpected = [
|
|
'class' => ForeignDBRepo::class,
|
|
'name' => 'shared',
|
|
'directory' => null,
|
|
'url' => null,
|
|
'hashLevels' => 2,
|
|
'thumbScriptUrl' => false,
|
|
'transformVia404' => false,
|
|
'dbType' => 'mysql',
|
|
'dbServer' => 'localhost',
|
|
'dbUser' => 'wikiuser',
|
|
'dbPassword' => '',
|
|
'dbName' => 'shared_uploads',
|
|
'dbFlags' => DBO_DEFAULT,
|
|
'tablePrefix' => '',
|
|
'hasSharedCache' => true,
|
|
'descBaseUrl' => 'https://commons.wikimedia.org/wiki/File:',
|
|
'fetchDescription' => false,
|
|
'backend' => 'shared-backend',
|
|
];
|
|
yield '$wgUseSharedUploads and $wgSharedUploadDBname' => [
|
|
[
|
|
'UseSharedUploads' => true,
|
|
'SharedUploadDBname' => 'shared_uploads',
|
|
],
|
|
[ 'ForeignFileRepos' => [ $sharedUploadsDBnameExpected ] ],
|
|
];
|
|
$instantCommonsExpected = [
|
|
'class' => ForeignAPIRepo::class,
|
|
'name' => 'wikimediacommons',
|
|
'apibase' => 'https://commons.wikimedia.org/w/api.php',
|
|
'url' => 'https://upload.wikimedia.org/wikipedia/commons',
|
|
'thumbUrl' => 'https://upload.wikimedia.org/wikipedia/commons/thumb',
|
|
'hashLevels' => 2,
|
|
'transformVia404' => true,
|
|
'fetchDescription' => true,
|
|
'descriptionCacheExpiry' => 43200,
|
|
'apiThumbCacheExpiry' => 0,
|
|
'directory' => '/install/path/images',
|
|
'backend' => 'wikimediacommons-backend',
|
|
];
|
|
yield '$wgUseInstantCommons' => [
|
|
[ 'UseInstantCommons' => true ],
|
|
[ 'ForeignFileRepos' => [ $instantCommonsExpected ] ],
|
|
];
|
|
yield '$wgUseSharedUploads and $wgUseInstantCommons' => [
|
|
[
|
|
'UseSharedUploads' => true,
|
|
'UseInstantCommons' => true,
|
|
],
|
|
[ 'ForeignFileRepos' => [
|
|
$sharedUploadsExpected, $instantCommonsExpected ]
|
|
],
|
|
];
|
|
yield '$wgUseSharedUploads and $wgSharedUploadDBname and $wgUseInstantCommons' => [
|
|
[
|
|
'UseSharedUploads' => true,
|
|
'SharedUploadDBname' => 'shared_uploads',
|
|
'UseInstantCommons' => true,
|
|
],
|
|
[ 'ForeignFileRepos' => [
|
|
$sharedUploadsDBnameExpected, $instantCommonsExpected ]
|
|
],
|
|
];
|
|
yield 'ForeignAPIRepo with no directory' => [
|
|
[
|
|
'ForeignFileRepos' => [ [
|
|
'name' => 'foreigner',
|
|
'class' => ForeignAPIRepo::class,
|
|
] ],
|
|
'UploadDirectory' => '/upload',
|
|
],
|
|
[ 'ForeignFileRepos' => [ [
|
|
'name' => 'foreigner',
|
|
'class' => ForeignAPIRepo::class,
|
|
'directory' => '/upload',
|
|
'backend' => 'foreigner-backend',
|
|
] ] ] + array_map(
|
|
self::recursiveReplaceCallback( '/install/path/images', '/upload' ),
|
|
$expectedDefault ),
|
|
];
|
|
yield '$wgDefaultUserOptions days too high' => [
|
|
[
|
|
'DefaultUserOptions' => [
|
|
'rcdays' => 100,
|
|
'watchlistdays' => 75,
|
|
],
|
|
'RCMaxAge' => 36 * 3600,
|
|
],
|
|
[ 'DefaultUserOptions' => [
|
|
'rcdays' => 2.0,
|
|
'watchlistdays' => 2.0,
|
|
'timecorrection' => 'System|0',
|
|
] ],
|
|
];
|
|
yield '$wgSharedDB set' => [
|
|
[ 'SharedDB' => 'shared_db' ],
|
|
[ 'CookiePrefix' => 'shared_db' ],
|
|
];
|
|
yield '$wgSharedDB and $wgSharedPrefix set' => [
|
|
[ 'SharedDB' => 'shared_db', 'SharedPrefix' => 'shared_prefix' ],
|
|
[
|
|
'CookiePrefix' => 'shared_db_shared_prefix',
|
|
'SharedPrefix' => 'shared_prefix',
|
|
],
|
|
];
|
|
yield '$wgSharedPrefix set with no $wgSharedDB' => [
|
|
[ 'SharedPrefix' => 'shared_prefix' ],
|
|
[ 'SharedPrefix' => 'shared_prefix' ],
|
|
];
|
|
yield '$wgSharedDB set but user not in $wgSharedTables' => [
|
|
[ 'SharedDB' => 'shared_db', 'SharedTables' => [ 'user_properties' ] ],
|
|
[],
|
|
];
|
|
yield '$wgSharedDB and $wgSharedPrefix set but user not in $wgSharedTables' => [
|
|
[
|
|
'SharedDB' => 'shared_db',
|
|
'SharedPrefix' => 'shared_prefix',
|
|
'SharedTables' => [ 'user_properties' ],
|
|
],
|
|
[ 'SharedPrefix' => 'shared_prefix' ],
|
|
];
|
|
yield '$wgCookiePrefix with allowed punctuation' => [
|
|
[ 'CookiePrefix' => "~!@#$%^&*()_-]{}|:<>/?\t\r\n\f" ],
|
|
[ 'CookiePrefix' => "~!@#$%^&*()_-]{}|:<>/?\t\r\n\f" ],
|
|
];
|
|
yield '$wgCookiePrefix with bad characters' => [
|
|
[ 'CookiePrefix' => 'n=o,t;a l+l.o"w\'e\\d[' ],
|
|
[ 'CookiePrefix' => 'n_o_t_a_l_l_o_w_e_d_' ],
|
|
];
|
|
yield '$wgEnableEmail set to false' => [
|
|
[
|
|
'EnableEmail' => false,
|
|
'AllowHTMLEmail' => true,
|
|
'EmailAuthentication' => true,
|
|
'EnableUserEmail' => true,
|
|
'EnotifFromEditor' => true,
|
|
'EnotifImpersonal' => true,
|
|
'EnotifMaxRecips' => 0,
|
|
'EnotifMinorEdits' => true,
|
|
'EnotifRevealEditorAddress' => true,
|
|
'EnotifUseRealName' => true,
|
|
'EnotifUserTalk' => true,
|
|
'EnotifWatchlist' => true,
|
|
'GroupPermissions' => [ 'user' => [ 'sendemail' => true ] ],
|
|
'UserEmailUseReplyTo' => true,
|
|
'UsersNotifiedOnAllChanges' => [ 'Admin' ],
|
|
], [
|
|
'AllowHTMLEmail' => false,
|
|
'EmailAuthentication' => false,
|
|
'EnableUserEmail' => false,
|
|
'EnotifFromEditor' => false,
|
|
'EnotifImpersonal' => false,
|
|
'EnotifMaxRecips' => 0,
|
|
'EnotifMinorEdits' => false,
|
|
'EnotifRevealEditorAddress' => false,
|
|
'EnotifUseRealName' => false,
|
|
'EnotifUserTalk' => false,
|
|
'EnotifWatchlist' => false,
|
|
'GroupPermissions' => [ 'user' => [] ],
|
|
'UserEmailUseReplyTo' => false,
|
|
'UsersNotifiedOnAllChanges' => [],
|
|
],
|
|
];
|
|
yield 'Change PHP default timezone' => [
|
|
[ 'DefaultUserOptions' => [
|
|
'rcdays' => 0,
|
|
'watchlistdays' => 0,
|
|
] ],
|
|
[
|
|
'Localtimezone' => 'America/Phoenix',
|
|
'LocalTZoffset' => -7 * 60,
|
|
'DefaultUserOptions' => [
|
|
'rcdays' => 0,
|
|
'watchlistdays' => 0,
|
|
'timecorrection' => 'System|' . (string)( -7 * 60 ),
|
|
],
|
|
'DBerrorLogTZ' => 'America/Phoenix',
|
|
],
|
|
static function ( self $testObj ): void {
|
|
// Pick a timezone with no DST
|
|
$testObj->assertTrue( date_default_timezone_set( 'America/Phoenix' ) );
|
|
},
|
|
];
|
|
yield '$wgDBerrorLogTZ set' => [
|
|
[ 'DBerrorLogTZ' => 'America/Phoenix' ],
|
|
[ 'DBerrorLogTZ' => 'America/Phoenix' ],
|
|
];
|
|
yield 'Setting $wgCanonicalNamespaceNames does nothing' => [
|
|
[ 'CanonicalNamespaceNames' => [ NS_MAIN => 'abc' ] ],
|
|
[],
|
|
];
|
|
yield 'Setting $wgExtraNamespaces does not affect $wgCanonicalNamespaceNames' => [
|
|
[ 'ExtraNamespaces' => [ 100 => 'Extra' ] ],
|
|
[],
|
|
];
|
|
yield '$wgDummyLanguageCodes set' => [
|
|
[ 'DummyLanguageCodes' => [ 'qqq' => 'qqqq', 'foo' => 'bar', 'bh' => 'hb' ] ],
|
|
[ 'DummyLanguageCodes' => self::getExpectedDummyLanguageCodes(
|
|
[ 'qqq' => 'qqqq', 'foo' => 'bar', 'bh' => 'hb' ] ) ],
|
|
];
|
|
yield '$wgExtraLanguageCodes set' => [
|
|
[ 'ExtraLanguageCodes' => [ 'foo' => 'bar' ] ],
|
|
[ 'DummyLanguageCodes' => self::getExpectedDummyLanguageCodes( [],
|
|
[ 'foo' => 'bar' ] ) ],
|
|
];
|
|
yield '$wgDummyLanguageCodes and $wgExtraLanguageCodes set' => [
|
|
[
|
|
'DummyLanguageCodes' => [ 'foo' => 'bar', 'abc' => 'def' ],
|
|
'ExtraLanguageCodes' => [ 'foo' => 'baz', 'ghi' => 'jkl' ],
|
|
],
|
|
[ 'DummyLanguageCodes' => self::getExpectedDummyLanguageCodes(
|
|
[ 'foo' => 'bar', 'abc' => 'def' ], [ 'foo' => 'baz', 'ghi' => 'jkl' ] ) ],
|
|
];
|
|
yield '$wgDatabaseReplicaLagWarning set' => [
|
|
[ 'DatabaseReplicaLagWarning' => 20 ],
|
|
[ 'DatabaseReplicaLagWarning' => 20, 'SlaveLagWarning' => 20 ],
|
|
];
|
|
yield '$wgSlaveLagWarning set' => [
|
|
[ 'SlaveLagWarning' => 20 ],
|
|
[ 'DatabaseReplicaLagWarning' => 20, 'SlaveLagWarning' => 20 ],
|
|
];
|
|
// XXX The settings are out of sync, this doesn't look intended
|
|
yield '$wgDatabaseReplicaLagWarning and $wgSlaveLagWarning set' => [
|
|
[ 'DatabaseReplicaLagWarning' => 20, 'SlaveLagWarning' => 30 ],
|
|
[ 'DatabaseReplicaLagWarning' => 20, 'SlaveLagWarning' => 30 ],
|
|
];
|
|
yield '$wgDatabaseReplicaLagCritical set' => [
|
|
[ 'DatabaseReplicaLagCritical' => 40 ],
|
|
[ 'DatabaseReplicaLagCritical' => 40, 'SlaveLagCritical' => 40 ],
|
|
];
|
|
yield '$wgSlaveLagCritical set' => [
|
|
[ 'SlaveLagCritical' => 40 ],
|
|
[ 'DatabaseReplicaLagCritical' => 40, 'SlaveLagCritical' => 40 ],
|
|
];
|
|
// XXX The settings are out of sync, this doesn't look intended
|
|
yield '$wgDatabaseReplicaLagCritical and $wgSlaveLagCritical set' => [
|
|
[ 'DatabaseReplicaLagCritical' => 40, 'SlaveLagCritical' => 60 ],
|
|
[ 'DatabaseReplicaLagCritical' => 40, 'SlaveLagCritical' => 60 ],
|
|
];
|
|
yield '$wgCacheEpoch set to before LocalSettings touched' => [
|
|
[ 'CacheEpoch' => static function () use ( $expectedDefault ): string {
|
|
return $expectedDefault['CacheEpoch']() - 1;
|
|
} ],
|
|
[ 'CacheEpoch' => static function () use ( $expectedDefault ): string {
|
|
$expected = $expectedDefault['CacheEpoch']();
|
|
// If the file exists, its mtime is later than what we set $wgCacheEpoch to and so
|
|
// it should override what we set.
|
|
return file_exists( MW_CONFIG_FILE ) ? $expected : $expected - 1;
|
|
} ],
|
|
];
|
|
yield '$wgCacheEpoch set to after LocalSettings touched' => [
|
|
[ 'CacheEpoch' => static function () use ( $expectedDefault ): string {
|
|
return $expectedDefault['CacheEpoch']() + 1;
|
|
} ],
|
|
[ 'CacheEpoch' => static function () use ( $expectedDefault ): string {
|
|
return $expectedDefault['CacheEpoch']() + 1;
|
|
} ],
|
|
];
|
|
yield '$wgInvalidateCacheOnLocalSettingsChange false' => [
|
|
[ 'InvalidateCacheOnLocalSettingsChange' => false ],
|
|
[ 'CacheEpoch' => '20030516000000' ],
|
|
];
|
|
yield '$wgNewUserLog is true' => [
|
|
[ 'NewUserLog' => true ],
|
|
static function ( self $testObj, array $vars ) use ( $expectedDefault ): array {
|
|
$testObj->assertContains( 'newusers', $vars['LogTypes'] );
|
|
$testObj->assertSame( 'newuserlogpage', $vars['LogNames']['newusers'] );
|
|
$testObj->assertSame( 'newuserlogpagetext', $vars['LogHeaders']['newusers'] );
|
|
$testObj->assertSame( NewUsersLogFormatter::class,
|
|
$vars['LogActionsHandlers']['newusers/newusers'] );
|
|
$testObj->assertSame( NewUsersLogFormatter::class,
|
|
$vars['LogActionsHandlers']['newusers/create'] );
|
|
$testObj->assertSame( NewUsersLogFormatter::class,
|
|
$vars['LogActionsHandlers']['newusers/create2'] );
|
|
$testObj->assertSame( NewUsersLogFormatter::class,
|
|
$vars['LogActionsHandlers']['newusers/byemail'] );
|
|
$testObj->assertSame( NewUsersLogFormatter::class,
|
|
$vars['LogActionsHandlers']['newusers/autocreate'] );
|
|
|
|
return $expectedDefault;
|
|
},
|
|
];
|
|
yield '$wgNewUserLog is false`' => [
|
|
[ 'NewUserLog' => false ],
|
|
static function ( self $testObj, array $vars ) use ( $expectedDefault ): array {
|
|
// Test that the new user log is not added to any of the various logging globals
|
|
$testObj->assertNotContains( 'newusers', $vars['LogTypes'] );
|
|
$testObj->assertArrayNotHasKey( 'newusers', $vars['LogNames'] );
|
|
$testObj->assertArrayNotHasKey( 'newusers', $vars['LogHeaders'] );
|
|
foreach ( $vars['LogActionsHandlers'] as $key => $unused ) {
|
|
$testObj->assertStringStartsNotWith( 'newusers', $key );
|
|
}
|
|
|
|
return $expectedDefault;
|
|
},
|
|
];
|
|
yield '$wgPageCreationLog is true' => [
|
|
[ 'PageCreationLog' => true ],
|
|
static function ( self $testObj, array $vars ) use ( $expectedDefault ): array {
|
|
$testObj->assertContains( 'create', $vars['LogTypes'] );
|
|
$testObj->assertSame( LogFormatter::class,
|
|
$vars['LogActionsHandlers']['create/create'] );
|
|
|
|
return $expectedDefault;
|
|
},
|
|
];
|
|
yield '$wgPageCreationLog is false`' => [
|
|
[ 'PageCreationLog' => false ],
|
|
static function ( self $testObj, array $vars ) use ( $expectedDefault ): array {
|
|
$testObj->assertNotContains( 'create', $vars['LogTypes'] );
|
|
$testObj->assertArrayNotHasKey( 'create/create', $vars['LogActionsHandlers'] );
|
|
|
|
return $expectedDefault;
|
|
},
|
|
];
|
|
yield '$wgPageLanguageUseDB is true' => [
|
|
[ 'PageLanguageUseDB' => true ],
|
|
static function ( self $testObj, array $vars ) use ( $expectedDefault ): array {
|
|
$testObj->assertContains( 'pagelang', $vars['LogTypes'] );
|
|
$testObj->assertSame( PageLangLogFormatter::class,
|
|
$vars['LogActionsHandlers']['pagelang/pagelang'] );
|
|
|
|
return $expectedDefault;
|
|
},
|
|
];
|
|
yield '$wgPageLanguageUseDB is false' => [
|
|
[ 'PageLanguageUseDB' => false ],
|
|
static function ( self $testObj, array $vars ) use ( $expectedDefault ): array {
|
|
$testObj->assertNotContains( 'pagelang', $vars['LogTypes'] );
|
|
$testObj->assertArrayNotHasKey( 'pagelang/pagelang',
|
|
$vars['LogActionsHandlers'] );
|
|
|
|
return $expectedDefault;
|
|
},
|
|
];
|
|
yield '$wgForceHTTPS is true, not HTTPS' => [
|
|
[ 'ForceHTTPS' => true ],
|
|
[ 'CookieSecure' => true ],
|
|
static function () {
|
|
$_SERVER['HTTPS'] = null;
|
|
$_SERVER['HTTP_X_FORWARDED_PROTO'] = null;
|
|
}
|
|
];
|
|
yield '$wgForceHTTPS is true, HTTPS' => [
|
|
[ 'ForceHTTPS' => true ],
|
|
[ 'CookieSecure' => true ],
|
|
static function () {
|
|
$_SERVER['HTTPS'] = 'on';
|
|
$_SERVER['HTTP_X_FORWARDED_PROTO'] = 'https';
|
|
}
|
|
];
|
|
yield '$wgForceHTTPS is false, not HTTPS' => [
|
|
[ 'ForceHTTPS' => false ],
|
|
[ 'CookieSecure' => false ],
|
|
static function () {
|
|
$_SERVER['HTTPS'] = null;
|
|
$_SERVER['HTTP_X_FORWARDED_PROTO'] = null;
|
|
}
|
|
];
|
|
yield '$wgForceHTTPS is false, HTTPS off' => [
|
|
[ 'ForceHTTPS' => false ],
|
|
[ 'CookieSecure' => false ],
|
|
static function () {
|
|
$_SERVER['HTTPS'] = 'off';
|
|
$_SERVER['HTTP_X_FORWARDED_PROTO'] = null;
|
|
}
|
|
];
|
|
yield '$wgForceHTTPS is false, HTTPS' => [
|
|
[ 'ForceHTTPS' => false ],
|
|
[ 'CookieSecure' => true ],
|
|
static function () {
|
|
$_SERVER['HTTPS'] = 'on';
|
|
$_SERVER['HTTP_X_FORWARDED_PROTO'] = null;
|
|
}
|
|
];
|
|
yield '$wgForceHTTPS is false, forwarded HTTPS' => [
|
|
[ 'ForceHTTPS' => false ],
|
|
[ 'CookieSecure' => true ],
|
|
static function () {
|
|
$_SERVER['HTTPS'] = null;
|
|
$_SERVER['HTTP_X_FORWARDED_PROTO'] = 'https';
|
|
}
|
|
];
|
|
yield '$wgMinimalPasswordLength' => [
|
|
[ 'MinimalPasswordLength' => 17 ],
|
|
static function ( self $testObj, array $vars ) use ( $expectedDefault ): array {
|
|
$testObj->assertSame( 17,
|
|
$vars['PasswordPolicy']['policies']['default']['MinimalPasswordLength'] );
|
|
return $expectedDefault;
|
|
},
|
|
];
|
|
yield '$wgMaximalPasswordLength' => [
|
|
[ 'MaximalPasswordLength' => 17 ],
|
|
static function ( self $testObj, array $vars ) use ( $expectedDefault ): array {
|
|
$testObj->assertSame( 17,
|
|
$vars['PasswordPolicy']['policies']['default']['MaximalPasswordLength'] );
|
|
return $expectedDefault;
|
|
},
|
|
];
|
|
yield 'Bogus $wgPHPSessionHandling' => [
|
|
[ 'PHPSessionHandling' => 'bogus' ],
|
|
[ 'PHPSessionHandling' => 'warn' ],
|
|
];
|
|
yield 'Enable $wgPHPSessionHandling' => [
|
|
[ 'PHPSessionHandling' => 'enable' ],
|
|
[ 'PHPSessionHandling' => 'enable' ],
|
|
];
|
|
yield 'Disable $wgPHPSessionHandling' => [
|
|
[ 'PHPSessionHandling' => 'disable' ],
|
|
[ 'PHPSessionHandling' => 'disable' ],
|
|
];
|
|
|
|
// use old deprecated rate limit names
|
|
$rateLimits = [
|
|
'emailuser' => [
|
|
'newbie' => [ 1, 86400 ],
|
|
],
|
|
'changetag' => [
|
|
'ip' => [ 1, 60 ],
|
|
'newbie' => [ 2, 60 ],
|
|
],
|
|
];
|
|
|
|
yield 'renamed $wgRateLimits' => [
|
|
[ 'RateLimits' => $rateLimits + $expectedDefault['RateLimits'] ],
|
|
static function ( self $testObj, array $vars ) use ( $expectedDefault, $rateLimits ): array {
|
|
$testObj->assertSame( $rateLimits['emailuser'], $vars['RateLimits']['sendemail'], 'emailuser' );
|
|
$testObj->assertSame( $rateLimits['changetag'], $vars['RateLimits']['changetags'], 'changetag' );
|
|
return [];
|
|
}
|
|
];
|
|
// XXX No obvious way to test MW_NO_SESSION, because constants can't be undefined
|
|
}
|
|
|
|
/**
|
|
* Test that if the variables $test are set after DefaultSettings.php is loaded, then
|
|
* DynamicDefaultValues and SetupDynamicConfig.php will result in the variables
|
|
* in $expected being set to the given values.
|
|
* (This does not test that other variables aren't also set.)
|
|
*
|
|
* @dataProvider provideGlobals
|
|
* @covers \MediaWiki\Settings\DynamicDefaultValues
|
|
*
|
|
* @param array $test Keys are the names of variables, values are their values. If a value is
|
|
* callable, it will be called with no arguments to obtain the value.
|
|
* @param array|callable $expected Keys are the names of variables, values are their values. If
|
|
* a value is callable, it will be called with no arguments to obtain the value. If $expected
|
|
* is callable, then it will be called with $this and get_defined_vars() as arguments, and
|
|
* the returned array will be used for $expected.
|
|
* @param ?callable $setup Run before SetupDynamicConfig is included. $this is passed as an
|
|
* argument, and a ScopedCallback may optionally be returned.
|
|
*/
|
|
public function testGlobals( array $test, $expected, ?callable $setup = null ): void {
|
|
$IP = '/install/path';
|
|
|
|
$configBuilder = new ArrayConfigBuilder();
|
|
$schemaSource = new ReflectionSchemaSource( MainConfigSchema::class );
|
|
$schemaDeclarations = $schemaSource->load()['config-schema'];
|
|
|
|
$schema = new ConfigSchemaAggregator();
|
|
|
|
// Declare schemas and apply default values
|
|
foreach ( $schemaDeclarations as $key => $sch ) {
|
|
$schema->addSchema( $key, $sch );
|
|
|
|
$val = $schema->getDefaultFor( $key );
|
|
$configBuilder->set( $key, $val );
|
|
}
|
|
|
|
// Apply test values
|
|
foreach ( $test as $key => $val ) {
|
|
// Some defaults don't work properly on CI if evaluated in the provider,
|
|
// so use a callback.
|
|
if ( is_callable( $val ) ) {
|
|
$val = $val();
|
|
}
|
|
|
|
$configBuilder->set( $key, $val );
|
|
}
|
|
|
|
$configBuilder->set( MainConfigNames::BaseDirectory, $IP );
|
|
|
|
if ( $setup ) {
|
|
$scopedCallback = $setup( $this );
|
|
}
|
|
|
|
$dynamicDefaults = new DynamicDefaultValues( $schema );
|
|
$dynamicDefaults->applyDynamicDefaults( $configBuilder );
|
|
$config = $configBuilder->build();
|
|
|
|
// Put the config variables into the local scope, so SetupDynamicConfig.php can use them.
|
|
foreach ( $config as $key => $val ) {
|
|
$var = "wg$key";
|
|
$$var = $val;
|
|
}
|
|
|
|
// phpcs:ignore MediaWiki.VariableAnalysis.MisleadingGlobalNames.Misleading$wgSettings
|
|
$wgSettings = new SettingsBuilder(
|
|
MW_INSTALL_PATH,
|
|
$this->createNoOpMock( ExtensionRegistry::class ),
|
|
$configBuilder,
|
|
$this->createNoOpMock( PhpIniSink::class )
|
|
);
|
|
require MW_INSTALL_PATH . '/includes/SetupDynamicConfig.php';
|
|
|
|
if ( is_callable( $expected ) ) {
|
|
$vars = get_defined_vars();
|
|
|
|
$newKeys = array_map( static function ( $key ) {
|
|
return substr( $key, 2 );
|
|
}, array_keys( $vars ) );
|
|
$vars = array_combine( $newKeys, $vars );
|
|
|
|
$expected = $expected( $this, $vars );
|
|
}
|
|
|
|
foreach ( $expected as $key => $val ) {
|
|
$var = "wg$key";
|
|
$this->assertSame( is_callable( $val ) ? $val() : $val,
|
|
$$var, "Unexpected value for \$$var" );
|
|
}
|
|
}
|
|
|
|
}
|