diff options
-rwxr-xr-x | tests/phpunit/phpunit.php | 461 |
1 files changed, 60 insertions, 401 deletions
diff --git a/tests/phpunit/phpunit.php b/tests/phpunit/phpunit.php index cf10a20c6d35..61a7934f2d0b 100755 --- a/tests/phpunit/phpunit.php +++ b/tests/phpunit/phpunit.php @@ -7,90 +7,32 @@ */ use MediaWiki\MediaWikiServices; -use MediaWiki\Shell\Shell; use Wikimedia\Rdbms\IDatabase; use Wikimedia\Rdbms\LBFactory; -if ( defined( 'MEDIAWIKI' ) ) { - exit( 'Wrong entry point?' ); -} - -define( 'MW_ENTRY_POINT', 'cli' ); - -// Bail on old versions of PHP, or if composer has not been run yet to install -// dependencies. -require_once __DIR__ . '/../../includes/PHPVersionCheck.php'; -wfEntryPointCheck( 'text' ); - -// Some extensions rely on MW_INSTALL_PATH to find core files to include. Setting it here helps them -// if they're included by a core script (like DatabaseUpdater) after Maintenance.php has already -// been run. -if ( strval( getenv( 'MW_INSTALL_PATH' ) ) === '' ) { - putenv( 'MW_INSTALL_PATH=' . realpath( __DIR__ . '/../..' ) ); -} - class PHPUnitMaintClass { /** - * Accessible via getConfig() - * - * @var Config|null - */ - private $config; - /** * Array of desired/allowed params * @var array[] - * @phan-var array<string,array{desc:string,require:bool,withArg:string,shortName:string,multiOccurrence:bool}> */ private $mParams = []; - /** @var array Mapping short parameters to long ones */ - private $mShortParamsMap = []; - /** - * Generic options added by addDefaultParams() - * @var array[] - * @phan-var array<string,array{desc:string,require:bool,withArg:string,shortName:string,multiOccurrence:bool}> - */ - private $mGenericParameters = []; - /** - * Generic options which might or not be supported by the script - * @var array[] - * @phan-var array<string,array{desc:string,require:bool,withArg:string,shortName:string,multiOccurrence:bool}> - */ - private $mDependentParameters = []; /** @var array This is the list of options that were actually passed */ private $mOptions = []; /** @var bool Have we already loaded our user input? */ private $mInputLoaded = false; - /** - * Used to read the options in the order they were passed. - * Useful for option chaining (Ex. dumpBackup.php). It will - * be an empty array if the options are passed in through - * loadParamsAndArgs( $self, $opts, $args ). - * - * This is an array of arrays where - * 0 => the option and 1 => parameter value. - * - * @var array - */ - private $orderedOptions = []; - public function __construct() { - $this->addOption( 'help', 'Display this help message', false, false, 'h' ); - $this->addOption( 'wiki', 'For specifying the wiki ID', false, true ); - $this->mGenericParameters = $this->mParams; - $this->addOption( 'dbuser', 'The DB user to use for this script', false, true ); - $this->addOption( 'dbpass', 'The password to use for this script', false, true ); - $this->addOption( 'dbgroupdefault', 'The default DB group to use.', false, true ); - $this->mDependentParameters = array_diff_key( $this->mParams, $this->mGenericParameters ); - register_shutdown_function( [ $this, 'outputChanneled' ], false ); - $this->addOption( 'use-filebackend', 'Use filebackend', false, true ); - $this->addOption( 'use-bagostuff', 'Use bagostuff', false, true ); - $this->addOption( 'use-jobqueue', 'Use jobqueue', false, true ); - $this->addOption( 'use-normal-tables', 'Use normal DB tables.', false, false ); + $this->addOption( 'help', 'Display this help message' ); + $this->addOption( 'wiki', 'For specifying the wiki ID', true ); + $this->addOption( 'dbuser', 'The DB user to use for this script', true ); + $this->addOption( 'dbpass', 'The password to use for this script', true ); + $this->addOption( 'dbgroupdefault', 'The default DB group to use.', true ); + $this->addOption( 'use-filebackend', 'Use filebackend', true ); + $this->addOption( 'use-bagostuff', 'Use bagostuff', true ); + $this->addOption( 'use-jobqueue', 'Use jobqueue', true ); + $this->addOption( 'use-normal-tables', 'Use normal DB tables.' ); $this->addOption( - 'reuse-db', 'Init DB only if tables are missing and keep after finish.', - false, - false + 'reuse-db', 'Init DB only if tables are missing and keep after finish.' ); } @@ -100,45 +42,28 @@ class PHPUnitMaintClass { * * @param string $name The name of the param (help, version, etc) * @param string $description The description of the param to show on --help - * @param bool $required Is the param required? * @param bool $withArg Is an argument required with this option? - * @param string|bool $shortName Character to use as short name - * @param bool $multiOccurrence Can this option be passed multiple times? */ - private function addOption( $name, $description, $required = false, - $withArg = false, $shortName = false, $multiOccurrence = false - ) { + private function addOption( $name, $description, $withArg = false ) { $this->mParams[$name] = [ 'desc' => $description, - 'require' => $required, - 'withArg' => $withArg, - 'shortName' => $shortName, - 'multiOccurrence' => $multiOccurrence + 'withArg' => $withArg ]; - - if ( $shortName !== false ) { - $this->mShortParamsMap[$shortName] = $name; - } } public function setup() { + global $wgCommandLineMode; + // Set a flag which can be used to detect when other scripts have been entered // through this entry point or not. define( 'MW_PHPUNIT_TEST', true ); - global $IP, $wgCommandLineMode; - # Abort if called from a web server # wfIsCLI() is not available yet if ( PHP_SAPI !== 'cli' && PHP_SAPI !== 'phpdbg' ) { $this->fatalError( 'This script must be run from the command line' ); } - if ( $IP === null ) { - $this->fatalError( "\$IP not set, aborting!\n" . - '(Did you forget to call parent::__construct() in your maintenance script?)' ); - } - # Make sure we can handle script parameters if ( !ini_get( 'register_argc_argv' ) ) { $this->fatalError( 'Cannot get command line arguments, register_argc_argv is set to false' ); @@ -153,9 +78,9 @@ class PHPUnitMaintClass { $this->loadParamsAndArgs(); - # Set the memory limit + # Disable the memory limit as it's not needed for tests. # Note we need to set it again later in cache LocalSettings changed it - $this->adjustMemoryLimit(); + ini_set( 'memory_limit', -1 ); # Set max execution time to 0 (no limit). PHP.net says that # "When running PHP from the command line the default setting is 0." @@ -191,7 +116,6 @@ class PHPUnitMaintClass { * @param string $name The name of the param * @param mixed|null $default Anything you want, default null * @return mixed - * @return-taint none */ private function getOption( $name, $default = null ) { if ( $this->hasOption( $name ) ) { @@ -205,35 +129,6 @@ class PHPUnitMaintClass { } /** - * Normally we disable the memory_limit when running admin scripts. - * Some scripts may wish to actually set a limit, however, to avoid - * blowing up unexpectedly. We also support a --memory-limit option, - * to allow sysadmins to explicitly set one if they'd prefer to override - * defaults (or for people using Suhosin which yells at you for trying - * to disable the limits) - * @stable for overriding - * @return string - */ - private function memoryLimit() { - $limit = $this->getOption( 'memory-limit', 'max' ); - $limit = trim( $limit, "\" '" ); // trim quotes in case someone misunderstood - return $limit; - } - - /** - * Adjusts PHP's memory limit to better suit our needs, if needed. - */ - private function adjustMemoryLimit() { - $limit = $this->memoryLimit(); - if ( $limit == 'max' ) { - $limit = -1; // no memory limit - } - if ( $limit != 'default' ) { - ini_set( 'memory_limit', $limit ); - } - } - - /** * Process command line arguments * $mOptions becomes an array with keys set to the option names * $mArgs becomes a zero-based array containing the non-option arguments @@ -255,12 +150,10 @@ class PHPUnitMaintClass { * Load params and arguments from a given array * of command-line arguments * - * @since 1.27 * @param array $argv */ private function loadWithArgv( $argv ) { $options = []; - $this->orderedOptions = []; # Parse arguments for ( $arg = reset( $argv ); $arg !== false; $arg = next( $argv ) ) { @@ -270,7 +163,7 @@ class PHPUnitMaintClass { if ( isset( $this->mParams[$option] ) && $this->mParams[$option]['withArg'] ) { $param = next( $argv ); if ( $param === false ) { - $this->error( "\nERROR: $option parameter needs a value after it\n" ); + echo "\nERROR: $option parameter needs a value after it\n"; $this->maybeHelp( true ); } @@ -284,14 +177,10 @@ class PHPUnitMaintClass { $argLength = strlen( $arg ); for ( $p = 1; $p < $argLength; $p++ ) { $option = $arg[$p]; - if ( !isset( $this->mParams[$option] ) && isset( $this->mShortParamsMap[$option] ) ) { - $option = $this->mShortParamsMap[$option]; - } - if ( isset( $this->mParams[$option]['withArg'] ) && $this->mParams[$option]['withArg'] ) { $param = next( $argv ); if ( $param === false ) { - $this->error( "\nERROR: $option parameter needs a value after it\n" ); + echo "\nERROR: $option parameter needs a value after it\n"; $this->maybeHelp( true ); } $this->setParam( $options, $option, $param ); @@ -312,7 +201,7 @@ class PHPUnitMaintClass { * * @param bool $force Whether to force the help to show, default false */ - private function maybeHelp( $force = false ) { + public function maybeHelp( $force = false ) { if ( !$force && !$this->hasOption( 'help' ) ) { return; } @@ -327,28 +216,16 @@ class PHPUnitMaintClass { * This sets the param in the options array based on * whether or not it can be specified multiple times. * - * @since 1.27 * @param array &$options * @param string $option * @param mixed $value */ private function setParam( &$options, $option, $value ) { - $this->orderedOptions[] = [ $option, $value ]; - - if ( isset( $this->mParams[$option] ) ) { - $multi = $this->mParams[$option]['multiOccurrence']; - } else { - $multi = false; - } $exists = array_key_exists( $option, $options ); - if ( $multi && $exists ) { - $options[$option][] = $value; - } elseif ( $multi ) { - $options[$option] = [ $value ]; - } elseif ( !$exists ) { + if ( !$exists ) { $options[$option] = $value; } else { - $this->error( "\nERROR: $option parameter given twice\n" ); + echo "\nERROR: $option parameter given twice\n"; $this->maybeHelp( true ); } } @@ -357,7 +234,6 @@ class PHPUnitMaintClass { /** * Handle the special variables that are global to all scripts - * @stable for overriding */ private function loadSpecialVars() { if ( $this->hasOption( 'dbuser' ) ) { @@ -371,73 +247,16 @@ class PHPUnitMaintClass { /** * Output a message and terminate the current script. * - * @stable for overriding * @param string $msg Error message * @param int $exitCode PHP exit status. Should be in range 1-254. - * @since 1.31 */ private function fatalError( $msg, $exitCode = 1 ) { - $this->error( $msg ); + echo $msg; exit( $exitCode ); } - /** - * Throw an error to the user. Doesn't respect --quiet, so don't use - * this for non-error output - * @stable for overriding - * @param string $err The error to display - */ - private function error( $err ) { - $this->outputChanneled( false ); - print $err; - } - - private $atLineStart = true; - private $lastChannel = null; - - /** - * Clean up channeled output. Output a newline if necessary. - */ - private function cleanupChanneled() { - if ( !$this->atLineStart ) { - print "\n"; - $this->atLineStart = true; - } - } - - /** - * Message outputter with channeled message support. Messages on the - * same channel are concatenated, but any intervening messages in another - * channel start a new line. - * @param string $msg The message without trailing newline - * @param string|null $channel Channel identifier or null for no - * channel. Channel comparison uses ===. - */ - public function outputChanneled( $msg, $channel = null ) { - if ( $msg === false ) { - $this->cleanupChanneled(); - - return; - } - - // End the current line if necessary - if ( !$this->atLineStart && $channel !== $this->lastChannel ) { - print "\n"; - } - - print $msg; - - $this->atLineStart = false; - if ( $channel === null ) { - // For unchanneled messages, output trailing newline immediately - print "\n"; - $this->atLineStart = true; - } - $this->lastChannel = $channel; - } - public function finalSetup() { - global $wgCommandLineMode, $wgServer, $wgShowExceptionDetails, $wgShowHostnames; + global $wgCommandLineMode, $wgShowExceptionDetails, $wgShowHostnames; global $wgDBadminuser, $wgDBadminpassword, $wgDBDefaultGroup; global $wgDBuser, $wgDBpassword, $wgDBservers, $wgLBFactoryConf; @@ -448,11 +267,6 @@ class PHPUnitMaintClass { # Same with these $wgCommandLineMode = true; - # Override $wgServer - if ( $this->hasOption( 'server' ) ) { - $wgServer = $this->getOption( 'server', $wgServer ); - } - # If these were passed, use them if ( $this->mDbUser ) { $wgDBadminuser = $this->mDbUser; @@ -499,7 +313,7 @@ class PHPUnitMaintClass { set_time_limit( 0 ); Wikimedia\restoreWarnings(); - $this->adjustMemoryLimit(); + ini_set( 'memory_limit', -1 ); require_once __DIR__ . '/../common/TestsAutoLoader.php'; @@ -512,7 +326,7 @@ class PHPUnitMaintClass { // Deregister handler from MWExceptionHandler::installHandle so that PHPUnit's own handler // stays in tact. // Has to in execute() instead of finalSetup(), because finalSetup() runs before - // doMaintenance.php includes Setup.php, which calls MWExceptionHandler::installHandle(). + // Setup.php is included, which calls MWExceptionHandler::installHandle(). restore_error_handler(); $this->forceFormatServerArgv(); @@ -571,158 +385,32 @@ class PHPUnitMaintClass { $_SERVER['argv'] = $argv; } - /** - * Get the terminal size as a two-element array where the first element - * is the width (number of columns) and the second element is the height - * (number of rows). - * - * @return array - */ - private static function getTermSize() { - static $termSize = null; - - if ( $termSize !== null ) { - return $termSize; - } - - $default = [ 80, 50 ]; - - if ( wfIsWindows() || Shell::isDisabled() ) { - $termSize = $default; - - return $termSize; - } - - // It's possible to get the screen size with VT-100 terminal escapes, - // but reading the responses is not possible without setting raw mode - // (unless you want to require the user to press enter), and that - // requires an ioctl(), which we can't do. So we have to shell out to - // something that can do the relevant syscalls. There are a few - // options. Linux and Mac OS X both have "stty size" which does the - // job directly. - $result = Shell::command( 'stty', 'size' )->passStdin()->execute(); - if ( $result->getExitCode() !== 0 || - !preg_match( '/^(\d+) (\d+)$/', $result->getStdout(), $m ) - ) { - $termSize = $default; - - return $termSize; - } - - $termSize = [ intval( $m[2] ), intval( $m[1] ) ]; - - return $termSize; - } - - /** - * @since 1.24 - * @stable for overriding - * @return Config - */ - private function getConfig() { - if ( $this->config === null ) { - $this->config = MediaWikiServices::getInstance()->getMainConfig(); - } - - return $this->config; - } - - /** - * Throw some output to the user. Scripts can call this with no fears, - * as we handle all --quiet stuff here - * @stable for overriding - * @param string $out The text to show to the user - * @param mixed|null $channel Unique identifier for the channel. See function outputChanneled. - */ - private function output( $out, $channel = null ) { - // This is sometimes called very early, before Setup.php is included. - if ( class_exists( MediaWikiServices::class ) ) { - // Try to periodically flush buffered metrics to avoid OOMs - $stats = MediaWikiServices::getInstance()->getStatsdDataFactory(); - if ( $stats->getDataCount() > 1000 ) { - MediaWiki::emitBufferedStatsdData( $stats, $this->getConfig() ); - } - } - - if ( $channel === null ) { - $this->cleanupChanneled(); - print $out; - } else { - $out = preg_replace( '/\n\z/', '', $out ); - $this->outputChanneled( $out, $channel ); - } - } - private function showHelp() { - $screenWidth = self::getTermSize()[0]; $tab = " "; - $descWidth = $screenWidth - ( 2 * strlen( $tab ) ); + $descWidth = 80 - ( 2 * strlen( $tab ) ); ksort( $this->mParams ); $output = "\nUsage: php tests/phpunit.php"; - // ... append parameters ... - if ( $this->mParams ) { - $output .= " [--" . implode( "|--", array_keys( $this->mParams ) ) . "]"; - } + $output .= " [--" . implode( "|--", array_keys( $this->mParams ) ) . "]"; - $this->output( "$output\n\n" ); - - $this->formatHelpItems( - $this->mGenericParameters, - 'Generic maintenance parameters', - $descWidth, $tab - ); - - $this->formatHelpItems( - $this->mDependentParameters, - 'Script dependent parameters', - $descWidth, $tab - ); - - // Script-specific parameters not defined on construction by - // Maintenance::addDefaultParams() - $scriptSpecificParams = array_diff_key( - # all script parameters: - $this->mParams, - # remove the Maintenance default parameters: - $this->mGenericParameters, - $this->mDependentParameters - ); + echo "$output\n\n"; + echo "MediaWiki specific parameters:\n"; - $this->formatHelpItems( - $scriptSpecificParams, - 'Script specific parameters', - $descWidth, $tab - ); - - $this->output( "PHPUnit options are also accepted:\n\n" ); - $command = new MediaWikiPHPUnitCommand(); - $command->publicShowHelp(); - } - - private function formatHelpItems( array $items, $heading, $descWidth, $tab ) { - if ( $items === [] ) { - return; - } - - $this->output( "$heading:\n" ); - - foreach ( $items as $name => $info ) { - if ( $info['shortName'] !== false ) { - $name .= ' (-' . $info['shortName'] . ')'; - } - $this->output( - wordwrap( + foreach ( $this->mParams as $name => $info ) { + echo wordwrap( "$tab--$name: " . $info['desc'], $descWidth, "\n$tab$tab" - ) . "\n" - ); + ) . "\n"; } - $this->output( "\n" ); + echo "\n"; + + echo "PHPUnit options:\n\n"; + $command = new MediaWikiPHPUnitCommand(); + $command->publicShowHelp(); } /** @@ -732,11 +420,7 @@ class PHPUnitMaintClass { public function loadSettings() { global $wgCommandLineMode, $IP; - if ( defined( "MW_CONFIG_FILE" ) ) { - $settingsFile = MW_CONFIG_FILE; - } else { - $settingsFile = "$IP/LocalSettings.php"; - } + $settingsFile = "$IP/LocalSettings.php"; if ( isset( $this->mOptions['wiki'] ) ) { $bits = explode( '-', $this->mOptions['wiki'], 2 ); define( 'MW_DB', $bits[0] ); @@ -745,8 +429,7 @@ class PHPUnitMaintClass { if ( !is_readable( $settingsFile ) ) { $this->fatalError( "A copy of your installation's LocalSettings.php\n" . - "must exist and be readable in the source directory.\n" . - "Use --conf to specify it." ); + "must exist and be readable in the source directory." ); } $wgCommandLineMode = true; @@ -754,16 +437,7 @@ class PHPUnitMaintClass { } /** - * @since 1.24 - * @param Config $config - */ - public function setConfig( Config $config ) { - $this->config = $config; - } - - /** * Set triggers like when to try to run deferred updates - * @since 1.28 */ public function setAgentAndTriggers() { if ( function_exists( 'posix_getpwuid' ) ) { @@ -778,17 +452,16 @@ class PHPUnitMaintClass { $lbFactory->setAgentName( mb_strlen( $agent ) > 15 ? mb_substr( $agent, 0, 15 ) . '...' : $agent ); - self::setLBFactoryTriggers( $lbFactory, $this->getConfig() ); + self::setLBFactoryTriggers( $lbFactory ); } /** * @param LBFactory $lbFactory - * @param Config $config - * @since 1.28 */ - private static function setLBFactoryTriggers( LBFactory $lbFactory, Config $config ) { + private static function setLBFactoryTriggers( LBFactory $lbFactory ) { $services = MediaWikiServices::getInstance(); $stats = $services->getStatsdDataFactory(); + $config = $services->getMainConfig(); // Hook into period lag checks which often happen in long-running scripts $lbFactory->setWaitForReplicationListener( __METHOD__, @@ -815,53 +488,39 @@ class PHPUnitMaintClass { } ); } +} - /** - * Run some validation checks on the params, etc - * @stable for overriding - */ - public function validateParamsAndArgs() { - $die = false; - # Check to make sure we've got all the required options - foreach ( $this->mParams as $opt => $info ) { - if ( $info['require'] && !$this->hasOption( $opt ) ) { - $this->error( "Param $opt required!" ); - $die = true; - } - } +if ( defined( 'MEDIAWIKI' ) ) { + exit( 'Wrong entry point?' ); +} - $this->maybeHelp( $die ); - } +define( 'MW_ENTRY_POINT', 'cli' ); + +if ( strval( getenv( 'MW_INSTALL_PATH' ) ) === '' ) { + putenv( 'MW_INSTALL_PATH=' . realpath( __DIR__ . '/../..' ) ); } // Define the MediaWiki entrypoint define( 'MEDIAWIKI', true ); -// This environment variable is ensured present by Maintenance.php. $IP = getenv( 'MW_INSTALL_PATH' ); -// Get an object to start us off -$maintenance = new PHPUnitMaintClass(); - -// Basic sanity checks and such -$maintenance->setup(); +$wrapper = new PHPUnitMaintClass(); +$wrapper->setup(); // Define how settings are loaded (e.g. LocalSettings.php) -if ( !defined( 'MW_CONFIG_FILE' ) ) { - define( 'MW_CONFIG_FILE', $maintenance->loadSettings() ); -} +define( 'MW_CONFIG_FILE', $wrapper->loadSettings() ); -function wfMaintenanceSetup() { +function wfPHPUnitSetup() { // phpcs:ignore MediaWiki.NamingConventions.ValidGlobalName.allowedPrefix - global $maintenance; - $maintenance->finalSetup(); + global $wrapper; + $wrapper->finalSetup(); } -define( 'MW_SETUP_CALLBACK', 'wfMaintenanceSetup' ); +define( 'MW_SETUP_CALLBACK', 'wfPHPUnitSetup' ); require_once "$IP/includes/Setup.php"; -$maintenance->setConfig( MediaWikiServices::getInstance()->getMainConfig() ); -$maintenance->setAgentAndTriggers(); -$maintenance->validateParamsAndArgs(); -$maintenance->execute(); +$wrapper->setAgentAndTriggers(); +$wrapper->maybeHelp( false ); +$wrapper->execute(); |