Refactor parser tests
Merge the PHPUnit parser test runner with the old parserTests.inc, taking the good bits of both. Reviewed, pared down and documented the setup code. parserTests.php is now a frontend to a fully featured parser test system, with lots of developer options, whereas PHPUnit provides a simpler interface with increased isolation between test cases. Performance of both frontends is much improved, perhaps 2x faster for parserTests.php and 10x faster for PHPUnit. General: * Split out the pre-Setup.php global variable configuration from phpunit.php into a new class called TestSetup, also called it from parserTests.php. * Factored out the setup of TestsAutoLoader into a static method in Maintenance. * In Setup.php improved "caches" debug output. PHPUnit frontend: * Delete the entire contents of NewParserTest and replace it with a small wrapper around ParserTestRunner. It doesn't inherit from MediaWikiTestCase anymore since integrating the setup code was an unnecessary complication. * Rename MediaWikiParserTest to ParserTestTopLevelSuite and made it an instantiable TestSuite class instead of just a static method. Got rid of the eval(), just construct TestCase objects directly with a specified name, it works just as well. * Introduce ParserTestFileSuite for per-file setup. * Remove parser-related options from phpunit.php, since we don't support them anymore. Note that --filter now works just as well as --regex used to. * Add CoreParserTestSuite, equivalent to ExtensionsParserTestSuite, for clarity. * Make it possible to call MediaWikiTestCase::setupTestDB() more than once, as is implied by the documentation. parserTests.php frontend: * Made parserTests.php into a Maintenance subclass, moved CLI-specific code to it. * Renamed ParserTest to ParserTestRunner, this is now the generic backend. * Add --upload-dir option which sets up an FSFileBackend, similar to the old default behaviour Test file reading and interpretation: * Rename TestFileIterator to TestFileReader, and make it read and buffer an entire file, instead of iterating. * The previous code had an associative array representation of test specifications. Used this form more widely to pass around test data. * Remove the idea of !!hooks copying hooks from $wgParser, this is unnecessary now that all extensions use ParserFirstCallInit. Resurrect an old interpretation of the feature which was accidentally broken: if a named hook does not exist, skip all tests in the file. * Got rid of the "subtest" idea for tidy variants, instead use a human-readable description that appears in the output. * When all tests in a file are filtered or skipped, don't create the articles in them. This greatly speeds up execution time when --regex matches a small number of tests. It may possibly break extensions, but they would have been randomly broken anyway since there is no guarantee of test file execution order. * Remove integrated testing of OutputPage::addCategoryLinks() category link formatting, life is complicated enough already. It can go in OutputPageTest if that's a thing we really need. Result recording and display: * Make TestRecorder into a generic plugin interface for progress output etc., which needs to be abstracted for PHPUnit integration. * Introduce MultiTestRecorder for recorder chaining, instead of using a long inheritance chain. All test recorders now directly inherit from TestRecorder. * Move all console-related code to the new ParserTestPrinter. * Introduce PhpunitTestRecorder, which is the recorder for the PHPUnit frontend. Most events are ignored since they are never emitted in the PHPUnit frontend, which does not call runTests(). * Put more information into ParserTestResult and use it more often. Setup and teardown: * Introduce a new API for setup/teardown where setup functions return a ScopedCallback object which automatically performs the corresponding teardown when it goes out of scope. * Rename setUp() to staticSetup(), rewrite. There was a lot of cruft in here which was simply copied from Setup.php without review, and had nothing to do with parser tests. * Rename setupGlobals() to perTestSetup(), mostly rewrite. For performance, give staticSetup() precedence in cases where they were both setting up the same thing. * In support of merged setup code, allow Hooks::clear() to be called from parserTests.php. * Remove wgFileExtensions -- it is only used by UploadBase which we don't call. * Remove wgUseImageResize -- superseded by MockMediaHandlerFactory which I imported from NewParserTest. * Import MockFileBackend from NewParserTest. But instead of customising the configuration globals, I injected services. * Remove thumbnail deletion from upload teardown. This makes glob handling as in the old parserTests.php unnecessary. * Remove math file from upload teardown, math is actually an extension now! Also, the relevant parser tests were removed from the Math extension two years ago in favour of unit tests. * Make addArticle() private, and introduce addArticles() instead, which allows setup/teardown to be done once for each batch of articles instead of every time. * Remove $wgNamespaceAliases and $wgNamespaceProtection setup. These were copied in from Setup.php in 2010, and are redundant since we do actually run Setup.php. * Use NullLockManager, don't set up a temporary directory just for this alone. Fuzz tests: * Use the new TestSetup class. * Updated for ParserTestRunner interface change. * Remove some obsolete references to fuzz tests from the two frontends where they used to reside. Bug: T41473 Change-Id: Ia8e17008cb9d9b62ce5645e15a41a3b402f4026a
This commit is contained in:
parent
df29a359f8
commit
6117fb244f
30 changed files with 1799 additions and 2425 deletions
|
|
@ -64,7 +64,7 @@ class Hooks {
|
|||
* @throws MWException If not in testing mode.
|
||||
*/
|
||||
public static function clear( $name ) {
|
||||
if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
|
||||
if ( !defined( 'MW_PHPUNIT_TEST' ) && !defined( 'MW_PARSER_TEST' ) ) {
|
||||
throw new MWException( 'Cannot reset hooks in operation.' );
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -674,7 +674,7 @@ $parserMemc = wfGetParserCacheStorage();
|
|||
|
||||
wfDebugLog( 'caches',
|
||||
'cluster: ' . get_class( $wgMemc ) .
|
||||
', WAN: ' . $wgMainWANCache .
|
||||
', WAN: ' . ( $wgMainWANCache === CACHE_NONE ? 'CACHE_NONE' : $wgMainWANCache ) .
|
||||
', stash: ' . $wgMainStash .
|
||||
', message: ' . get_class( $messageMemc ) .
|
||||
', parser: ' . get_class( $parserMemc ) .
|
||||
|
|
|
|||
|
|
@ -1488,6 +1488,14 @@ abstract class Maintenance {
|
|||
|
||||
return fgets( STDIN, 1024 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this to set up the autoloader to allow classes to be used from the
|
||||
* tests directory.
|
||||
*/
|
||||
public static function requireTestsAutoloader() {
|
||||
require_once __DIR__ . '/../tests/common/TestsAutoLoader.php';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ class CheckLess extends Maintenance {
|
|||
// NOTE (phuedx, 2014-03-26) wgAutoloadClasses isn't set up
|
||||
// by either of the dependencies at the top of the file, so
|
||||
// require it here.
|
||||
require_once __DIR__ . '/../tests/common/TestsAutoLoader.php';
|
||||
self::requireTestsAutoloader();
|
||||
|
||||
// If phpunit isn't available by autoloader try pulling it in
|
||||
if ( !class_exists( 'PHPUnit_Framework_TestCase' ) ) {
|
||||
|
|
|
|||
111
tests/common/TestSetup.php
Normal file
111
tests/common/TestSetup.php
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
<?php
|
||||
|
||||
use MediaWiki\MediaWikiServices;
|
||||
|
||||
/**
|
||||
* Common code for test environment initialisation and teardown
|
||||
*/
|
||||
class TestSetup {
|
||||
/**
|
||||
* This should be called before Setup.php, e.g. from the finalSetup() method
|
||||
* of a Maintenance subclass
|
||||
*/
|
||||
public static function applyInitialConfig() {
|
||||
global $wgMainCacheType, $wgMessageCacheType, $wgParserCacheType, $wgMainWANCache;
|
||||
global $wgMainStash;
|
||||
global $wgLanguageConverterCacheType, $wgUseDatabaseMessages;
|
||||
global $wgLocaltimezone, $wgLocalisationCacheConf;
|
||||
global $wgDevelopmentWarnings;
|
||||
global $wgSessionProviders, $wgSessionPbkdf2Iterations;
|
||||
global $wgJobTypeConf;
|
||||
global $wgAuthManagerConfig, $wgAuth;
|
||||
|
||||
// wfWarn should cause tests to fail
|
||||
$wgDevelopmentWarnings = true;
|
||||
|
||||
// Make sure all caches and stashes are either disabled or use
|
||||
// in-process cache only to prevent tests from using any preconfigured
|
||||
// cache meant for the local wiki from outside the test run.
|
||||
// See also MediaWikiTestCase::run() which mocks CACHE_DB and APC.
|
||||
|
||||
// Disabled in DefaultSettings, override local settings
|
||||
$wgMainWANCache =
|
||||
$wgMainCacheType = CACHE_NONE;
|
||||
// Uses CACHE_ANYTHING in DefaultSettings, use hash instead of db
|
||||
$wgMessageCacheType =
|
||||
$wgParserCacheType =
|
||||
$wgSessionCacheType =
|
||||
$wgLanguageConverterCacheType = 'hash';
|
||||
// Uses db-replicated in DefaultSettings
|
||||
$wgMainStash = 'hash';
|
||||
// Use memory job queue
|
||||
$wgJobTypeConf = [
|
||||
'default' => [ 'class' => 'JobQueueMemory', 'order' => 'fifo' ],
|
||||
];
|
||||
|
||||
$wgUseDatabaseMessages = false; # Set for future resets
|
||||
|
||||
// Assume UTC for testing purposes
|
||||
$wgLocaltimezone = 'UTC';
|
||||
|
||||
$wgLocalisationCacheConf['storeClass'] = 'LCStoreNull';
|
||||
|
||||
// Generic MediaWiki\Session\SessionManager configuration for tests
|
||||
// We use CookieSessionProvider because things might be expecting
|
||||
// cookies to show up in a FauxRequest somewhere.
|
||||
$wgSessionProviders = [
|
||||
[
|
||||
'class' => MediaWiki\Session\CookieSessionProvider::class,
|
||||
'args' => [ [
|
||||
'priority' => 30,
|
||||
'callUserSetCookiesHook' => true,
|
||||
] ],
|
||||
],
|
||||
];
|
||||
|
||||
// Single-iteration PBKDF2 session secret derivation, for speed.
|
||||
$wgSessionPbkdf2Iterations = 1;
|
||||
|
||||
// Generic AuthManager configuration for testing
|
||||
$wgAuthManagerConfig = [
|
||||
'preauth' => [],
|
||||
'primaryauth' => [
|
||||
[
|
||||
'class' => MediaWiki\Auth\TemporaryPasswordPrimaryAuthenticationProvider::class,
|
||||
'args' => [ [
|
||||
'authoritative' => false,
|
||||
] ],
|
||||
],
|
||||
[
|
||||
'class' => MediaWiki\Auth\LocalPasswordPrimaryAuthenticationProvider::class,
|
||||
'args' => [ [
|
||||
'authoritative' => true,
|
||||
] ],
|
||||
],
|
||||
],
|
||||
'secondaryauth' => [],
|
||||
];
|
||||
$wgAuth = new MediaWiki\Auth\AuthManagerAuthPlugin();
|
||||
|
||||
// Bug 44192 Do not attempt to send a real e-mail
|
||||
Hooks::clear( 'AlternateUserMailer' );
|
||||
Hooks::register(
|
||||
'AlternateUserMailer',
|
||||
function () {
|
||||
return false;
|
||||
}
|
||||
);
|
||||
// xdebug's default of 100 is too low for MediaWiki
|
||||
ini_set( 'xdebug.max_nesting_level', 1000 );
|
||||
|
||||
// Bug T116683 serialize_precision of 100
|
||||
// may break testing against floating point values
|
||||
// treated with PHP's serialize()
|
||||
ini_set( 'serialize_precision', 17 );
|
||||
|
||||
// TODO: we should call MediaWikiTestCase::prepareServices( new GlobalVarConfig() ) here.
|
||||
// But PHPUnit may not be loaded yet, so we have to wait until just
|
||||
// before PHPUnit_TextUI_Command::main() is executed.
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -22,10 +22,29 @@
|
|||
*/
|
||||
|
||||
global $wgAutoloadClasses;
|
||||
$testDir = __DIR__ . '/..';
|
||||
$testDir = __DIR__ . "/..";
|
||||
|
||||
$wgAutoloadClasses += [
|
||||
|
||||
# tests/common
|
||||
'TestSetup' => "$testDir/common/TestSetup.php",
|
||||
|
||||
# tests/parser
|
||||
'DbTestPreviewer' => "$testDir/parser/DbTestPreviewer.php",
|
||||
'DbTestRecorder' => "$testDir/parser/DbTestRecorder.php",
|
||||
'DjVuSupport' => "$testDir/parser/DjVuSupport.php",
|
||||
'TestRecorder' => "$testDir/parser/TestRecorder.php",
|
||||
'MultiTestRecorder' => "$testDir/parser/MultiTestRecorder.php",
|
||||
'ParserTestRunner' => "$testDir/parser/ParserTestRunner.php",
|
||||
'ParserTestParserHook' => "$testDir/parser/ParserTestParserHook.php",
|
||||
'ParserTestPrinter' => "$testDir/parser/ParserTestPrinter.php",
|
||||
'ParserTestResult' => "$testDir/parser/ParserTestResult.php",
|
||||
'ParserTestResultNormalizer' => "$testDir/parser/ParserTestResultNormalizer.php",
|
||||
'PhpunitTestRecorder' => "$testDir/parser/PhpunitTestRecorder.php",
|
||||
'TestFileReader' => "$testDir/parser/TestFileReader.php",
|
||||
'TestRecorder' => "$testDir/parser/TestRecorder.php",
|
||||
'TidySupport' => "$testDir/parser/TidySupport.php",
|
||||
|
||||
# tests/phpunit
|
||||
'MediaWikiTestCase' => "$testDir/phpunit/MediaWikiTestCase.php",
|
||||
'MediaWikiPHPUnitTestListener' => "$testDir/phpunit/MediaWikiPHPUnitTestListener.php",
|
||||
|
|
@ -85,6 +104,9 @@ $wgAutoloadClasses += [
|
|||
# tests/phpunit/includes/page
|
||||
'WikiPageTest' => "$testDir/phpunit/includes/page/WikiPageTest.php",
|
||||
|
||||
# tests/phpunit/includes/parser
|
||||
'ParserIntegrationTest' => "$testDir/phpunit/includes/parser/ParserIntegrationTest.php",
|
||||
|
||||
# tests/phpunit/includes/password
|
||||
'PasswordTestCase' => "$testDir/phpunit/includes/password/PasswordTestCase.php",
|
||||
|
||||
|
|
@ -98,6 +120,13 @@ $wgAutoloadClasses += [
|
|||
'MediaWiki\\Session\\TestBagOStuff' => "$testDir/phpunit/includes/session/TestBagOStuff.php",
|
||||
'MediaWiki\\Session\\TestUtils' => "$testDir/phpunit/includes/session/TestUtils.php",
|
||||
|
||||
# tests/phpunit/includes/site
|
||||
'SiteTest' => "$testDir/phpunit/includes/site/SiteTest.php",
|
||||
'TestSites' => "$testDir/phpunit/includes/site/TestSites.php",
|
||||
|
||||
# tests/phpunit/includes/specialpage
|
||||
'SpecialPageTestHelper' => "$testDir/phpunit/includes/specialpage/SpecialPageTestHelper.php",
|
||||
|
||||
# tests/phpunit/includes/specials
|
||||
'SpecialPageTestBase' => "$testDir/phpunit/includes/specials/SpecialPageTestBase.php",
|
||||
'SpecialPageExecutor' => "$testDir/phpunit/includes/specials/SpecialPageExecutor.php",
|
||||
|
|
@ -129,29 +158,7 @@ $wgAutoloadClasses += [
|
|||
=> "$testDir/phpunit/mocks/session/DummySessionBackend.php",
|
||||
'DummySessionProvider' => "$testDir/phpunit/mocks/session/DummySessionProvider.php",
|
||||
|
||||
# tests/parser
|
||||
'DbTestPreviewer' => "$testDir/parser/DbTestPreviewer.php",
|
||||
'DbTestRecorder' => "$testDir/parser/DbTestRecorder.php",
|
||||
'DelayedParserTest' => "$testDir/parser/DelayedParserTest.php",
|
||||
'DjVuSupport' => "$testDir/parser/DjVuSupport.php",
|
||||
'ITestRecorder' => "$testDir/parser/ITestRecorder.php",
|
||||
'ParserIntegrationTest' => "$testDir/phpunit/includes/parser/ParserIntegrationTest.php",
|
||||
'ParserTestRunner' => "$testDir/parser/ParserTestRunner.php",
|
||||
'ParserTestParserHook' => "$testDir/parser/ParserTestParserHook.php",
|
||||
'ParserTestResult' => "$testDir/parser/ParserTestResult.php",
|
||||
'ParserTestResultNormalizer' => "$testDir/parser/ParserTestResultNormalizer.php",
|
||||
'TestFileDataProvider' => "$testDir/parser/TestFileDataProvider.php",
|
||||
'TestFileReader' => "$testDir/parser/TestFileReader.php",
|
||||
'TestRecorder' => "$testDir/parser/TestRecorder.php",
|
||||
'TidySupport' => "$testDir/parser/TidySupport.php",
|
||||
|
||||
# tests/phpunit/includes/site
|
||||
'SiteTest' => "$testDir/phpunit/includes/site/SiteTest.php",
|
||||
'TestSites' => "$testDir/phpunit/includes/site/TestSites.php",
|
||||
|
||||
# tests/phpunit/includes/specialpage
|
||||
'SpecialPageTestHelper' => "$testDir/phpunit/includes/specialpage/SpecialPageTestHelper.php",
|
||||
|
||||
# tests/phpunit/suites
|
||||
# tests/suites
|
||||
'ParserTestFileSuite' => "$testDir/phpunit/suites/ParserTestFileSuite.php",
|
||||
'ParserTestTopLevelSuite' => "$testDir/phpunit/suites/ParserTestTopLevelSuite.php",
|
||||
];
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
*/
|
||||
|
||||
class DbTestPreviewer extends TestRecorder {
|
||||
protected $filter; // /< Test name filter callback
|
||||
protected $lb; // /< Database load balancer
|
||||
protected $db; // /< Database connection to the main DB
|
||||
protected $curRun; // /< run ID number for the current run
|
||||
|
|
@ -28,14 +29,10 @@ class DbTestPreviewer extends TestRecorder {
|
|||
|
||||
/**
|
||||
* This should be called before the table prefix is changed
|
||||
* @param TestRecorder $parent
|
||||
*/
|
||||
function __construct( $parent ) {
|
||||
parent::__construct( $parent );
|
||||
|
||||
$this->lb = wfGetLBFactory()->newMainLB();
|
||||
// This connection will have the wiki's table prefix, not parsertest_
|
||||
$this->db = $this->lb->getConnection( DB_MASTER );
|
||||
function __construct( $db, $filter = false ) {
|
||||
$this->db = $db;
|
||||
$this->filter = $filter;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -43,8 +40,6 @@ class DbTestPreviewer extends TestRecorder {
|
|||
* and all that fun stuff
|
||||
*/
|
||||
function start() {
|
||||
parent::start();
|
||||
|
||||
if ( !$this->db->tableExists( 'testrun', __METHOD__ )
|
||||
|| !$this->db->tableExists( 'testitem', __METHOD__ )
|
||||
) {
|
||||
|
|
@ -58,17 +53,8 @@ class DbTestPreviewer extends TestRecorder {
|
|||
$this->results = [];
|
||||
}
|
||||
|
||||
function getName( $test, $subtest ) {
|
||||
if ( $subtest ) {
|
||||
return "$test subtest #$subtest";
|
||||
} else {
|
||||
return $test;
|
||||
}
|
||||
}
|
||||
|
||||
function record( $test, $subtest, $result ) {
|
||||
parent::record( $test, $subtest, $result );
|
||||
$this->results[ $this->getName( $test, $subtest ) ] = $result;
|
||||
function record( $test, ParserTestResult $result ) {
|
||||
$this->results[$test['desc']] = $result->isSuccess() ? 1 : 0;
|
||||
}
|
||||
|
||||
function report() {
|
||||
|
|
@ -90,11 +76,10 @@ class DbTestPreviewer extends TestRecorder {
|
|||
|
||||
$res = $this->db->select( 'testitem', [ 'ti_name', 'ti_success' ],
|
||||
[ 'ti_run' => $this->prevRun ], __METHOD__ );
|
||||
$filter = $this->filter;
|
||||
|
||||
foreach ( $res as $row ) {
|
||||
if ( !$this->parent->regex
|
||||
|| preg_match( "/{$this->parent->regex}/i", $row->ti_name )
|
||||
) {
|
||||
if ( !$filter || $filter( $row->ti_name ) ) {
|
||||
$prevResults[$row->ti_name] = $row->ti_success;
|
||||
}
|
||||
}
|
||||
|
|
@ -143,7 +128,6 @@ class DbTestPreviewer extends TestRecorder {
|
|||
}
|
||||
|
||||
print "\n";
|
||||
parent::report();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -216,13 +200,5 @@ class DbTestPreviewer extends TestRecorder {
|
|||
. date( "d-M-Y H:i:s", strtotime( $pre->tr_date ) ) . ", " . $pre->tr_mw_version
|
||||
. " and $postDate";
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the DB connection
|
||||
*/
|
||||
function end() {
|
||||
$this->lb->closeAll();
|
||||
parent::end();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,8 +19,13 @@
|
|||
* @ingroup Testing
|
||||
*/
|
||||
|
||||
class DbTestRecorder extends DbTestPreviewer {
|
||||
class DbTestRecorder extends TestRecorder {
|
||||
public $version;
|
||||
private $db;
|
||||
|
||||
public function __construct( IDatabase $db ) {
|
||||
$this->db = $db;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up result recording; insert a record for the run with the date
|
||||
|
|
@ -37,8 +42,6 @@ class DbTestRecorder extends DbTestPreviewer {
|
|||
echo "OK, resuming.\n";
|
||||
}
|
||||
|
||||
parent::start();
|
||||
|
||||
$this->db->insert( 'testrun',
|
||||
[
|
||||
'tr_date' => $this->db->timestamp(),
|
||||
|
|
@ -58,17 +61,15 @@ class DbTestRecorder extends DbTestPreviewer {
|
|||
/**
|
||||
* Record an individual test item's success or failure to the db
|
||||
*
|
||||
* @param string $test
|
||||
* @param bool $result
|
||||
* @param array $test
|
||||
* @param ParserTestResult $result
|
||||
*/
|
||||
function record( $test, $subtest, $result ) {
|
||||
parent::record( $test, $subtest, $result );
|
||||
|
||||
function record( $test, ParserTestResult $result ) {
|
||||
$this->db->insert( 'testitem',
|
||||
[
|
||||
'ti_run' => $this->curRun,
|
||||
'ti_name' => $this->getName( $test, $subtest ),
|
||||
'ti_success' => $result ? 1 : 0,
|
||||
'ti_name' => $test['desc'],
|
||||
'ti_success' => $result->isSuccess() ? 1 : 0,
|
||||
],
|
||||
__METHOD__ );
|
||||
}
|
||||
|
|
@ -78,7 +79,6 @@ class DbTestRecorder extends DbTestPreviewer {
|
|||
*/
|
||||
function end() {
|
||||
$this->db->commit( __METHOD__ );
|
||||
parent::end();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,118 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
* http://www.gnu.org/copyleft/gpl.html
|
||||
*
|
||||
* @file
|
||||
* @ingroup Testing
|
||||
*/
|
||||
|
||||
/**
|
||||
* A class to delay execution of a parser test hooks.
|
||||
*/
|
||||
class DelayedParserTest {
|
||||
|
||||
/** Initialized on construction */
|
||||
private $hooks;
|
||||
private $fnHooks;
|
||||
private $transparentHooks;
|
||||
|
||||
public function __construct() {
|
||||
$this->reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Init/reset or forgot about the current delayed test.
|
||||
* Call to this will erase any hooks function that were pending.
|
||||
*/
|
||||
public function reset() {
|
||||
$this->hooks = [];
|
||||
$this->fnHooks = [];
|
||||
$this->transparentHooks = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Called whenever we actually want to run the hook.
|
||||
* Should be the case if we found the parserTest is not disabled
|
||||
* @param ParserTestRunner|ParserIntegrationTest $parserTest
|
||||
* @return bool
|
||||
* @throws MWException
|
||||
*/
|
||||
public function unleash( &$parserTest ) {
|
||||
if ( !( $parserTest instanceof ParserTestRunner
|
||||
|| $parserTest instanceof ParserIntegrationTest )
|
||||
) {
|
||||
throw new MWException( __METHOD__ . " must be passed an instance of " .
|
||||
"ParserTestRunner or ParserIntegrationTest classes\n" );
|
||||
}
|
||||
|
||||
# Trigger delayed hooks. Any failure will make us abort
|
||||
foreach ( $this->hooks as $hook ) {
|
||||
$ret = $parserTest->requireHook( $hook );
|
||||
if ( !$ret ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
# Trigger delayed function hooks. Any failure will make us abort
|
||||
foreach ( $this->fnHooks as $fnHook ) {
|
||||
$ret = $parserTest->requireFunctionHook( $fnHook );
|
||||
if ( !$ret ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
# Trigger delayed transparent hooks. Any failure will make us abort
|
||||
foreach ( $this->transparentHooks as $hook ) {
|
||||
$ret = $parserTest->requireTransparentHook( $hook );
|
||||
if ( !$ret ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
# Delayed execution was successful.
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to ParserTestRunner object but does not run anything
|
||||
* Use unleash() to really execute the hook
|
||||
* @param string $hook
|
||||
*/
|
||||
public function requireHook( $hook ) {
|
||||
$this->hooks[] = $hook;
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to ParserTestRunner object but does not run anything
|
||||
* Use unleash() to really execute the hook function
|
||||
* @param string $fnHook
|
||||
*/
|
||||
public function requireFunctionHook( $fnHook ) {
|
||||
$this->fnHooks[] = $fnHook;
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to ParserTestRunner object but does not run anything
|
||||
* Use unleash() to really execute the hook function
|
||||
* @param string $hook
|
||||
*/
|
||||
public function requireTransparentHook( $hook ) {
|
||||
$this->transparentHooks[] = $hook;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -1,61 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
* http://www.gnu.org/copyleft/gpl.html
|
||||
*
|
||||
* @file
|
||||
* @ingroup Testing
|
||||
*/
|
||||
|
||||
/**
|
||||
* Interface to record parser test results.
|
||||
*
|
||||
* The ITestRecorder is a very simple interface to record the result of
|
||||
* MediaWiki parser tests. One should call start() before running the
|
||||
* full parser tests and end() once all the tests have been finished.
|
||||
* After each test, you should use record() to keep track of your tests
|
||||
* results. Finally, report() is used to generate a summary of your
|
||||
* test run, one could dump it to the console for human consumption or
|
||||
* register the result in a database for tracking purposes.
|
||||
*
|
||||
* @since 1.22
|
||||
*/
|
||||
interface ITestRecorder {
|
||||
|
||||
/**
|
||||
* Called at beginning of the parser test run
|
||||
*/
|
||||
public function start();
|
||||
|
||||
/**
|
||||
* Called after each test
|
||||
* @param string $test
|
||||
* @param integer $subtest
|
||||
* @param bool $result
|
||||
*/
|
||||
public function record( $test, $subtest, $result );
|
||||
|
||||
/**
|
||||
* Called before finishing the test run
|
||||
*/
|
||||
public function report();
|
||||
|
||||
/**
|
||||
* Called at the end of the parser test run
|
||||
*/
|
||||
public function end();
|
||||
|
||||
}
|
||||
|
||||
55
tests/parser/MultiTestRecorder.php
Normal file
55
tests/parser/MultiTestRecorder.php
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* This is a TestRecorder representing a collection of other TestRecorders.
|
||||
* It proxies calls to all constituent objects.
|
||||
*/
|
||||
class MultiTestRecorder extends TestRecorder {
|
||||
private $recorders = [];
|
||||
|
||||
public function addRecorder( TestRecorder $recorder ) {
|
||||
$this->recorders[] = $recorder;
|
||||
}
|
||||
|
||||
private function proxy( $funcName, $args ) {
|
||||
foreach ( $this->recorders as $recorder ) {
|
||||
call_user_func_array( [ $recorder, $funcName ], $args );
|
||||
}
|
||||
}
|
||||
|
||||
public function start() {
|
||||
$this->proxy( __FUNCTION__, func_get_args() );
|
||||
}
|
||||
|
||||
public function startTest( $test ) {
|
||||
$this->proxy( __FUNCTION__, func_get_args() );
|
||||
}
|
||||
|
||||
public function startSuite( $path ) {
|
||||
$this->proxy( __FUNCTION__, func_get_args() );
|
||||
}
|
||||
|
||||
public function endSuite( $path ) {
|
||||
$this->proxy( __FUNCTION__, func_get_args() );
|
||||
}
|
||||
|
||||
public function record( $test, ParserTestResult $result ) {
|
||||
$this->proxy( __FUNCTION__, func_get_args() );
|
||||
}
|
||||
|
||||
public function warning( $message ) {
|
||||
$this->proxy( __FUNCTION__, func_get_args() );
|
||||
}
|
||||
|
||||
public function skipped( $test, $subtest ) {
|
||||
$this->proxy( __FUNCTION__, func_get_args() );
|
||||
}
|
||||
|
||||
public function report() {
|
||||
$this->proxy( __FUNCTION__, func_get_args() );
|
||||
}
|
||||
|
||||
public function end() {
|
||||
$this->proxy( __FUNCTION__, func_get_args() );
|
||||
}
|
||||
}
|
||||
326
tests/parser/ParserTestPrinter.php
Normal file
326
tests/parser/ParserTestPrinter.php
Normal file
|
|
@ -0,0 +1,326 @@
|
|||
<?php
|
||||
/**
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
* http://www.gnu.org/copyleft/gpl.html
|
||||
*
|
||||
* @file
|
||||
* @ingroup Testing
|
||||
*/
|
||||
|
||||
/**
|
||||
* This is a TestRecorder responsible for printing information about progress,
|
||||
* success and failure to the console. It is specific to the parserTests.php
|
||||
* frontend.
|
||||
*/
|
||||
class ParserTestPrinter extends TestRecorder {
|
||||
private $total;
|
||||
private $success;
|
||||
private $skipped;
|
||||
private $term;
|
||||
private $showDiffs;
|
||||
private $showProgress;
|
||||
private $showFailure;
|
||||
private $showOutput;
|
||||
private $useDwdiff;
|
||||
private $markWhitespace;
|
||||
private $xmlError;
|
||||
|
||||
function __construct( $term, $options ) {
|
||||
$this->term = $term;
|
||||
$options += [
|
||||
'showDiffs' => true,
|
||||
'showProgress' => true,
|
||||
'showFailure' => true,
|
||||
'showOutput' => false,
|
||||
'useDwdiff' => false,
|
||||
'markWhitespace' => false,
|
||||
];
|
||||
$this->showDiffs = $options['showDiffs'];
|
||||
$this->showProgress = $options['showProgress'];
|
||||
$this->showFailure = $options['showFailure'];
|
||||
$this->showOutput = $options['showOutput'];
|
||||
$this->useDwdiff = $options['useDwdiff'];
|
||||
$this->markWhitespace = $options['markWhitespace'];
|
||||
}
|
||||
|
||||
public function start() {
|
||||
$this->total = 0;
|
||||
$this->success = 0;
|
||||
$this->skipped = 0;
|
||||
}
|
||||
|
||||
public function startTest( $test ) {
|
||||
if ( $this->showProgress ) {
|
||||
$this->showTesting( $test['desc'] );
|
||||
}
|
||||
}
|
||||
|
||||
private function showTesting( $desc ) {
|
||||
print "Running test $desc... ";
|
||||
}
|
||||
|
||||
/**
|
||||
* Show "Reading tests from ..."
|
||||
*
|
||||
* @param string $path
|
||||
*/
|
||||
public function startSuite( $path ) {
|
||||
print $this->term->color( 1 ) .
|
||||
"Running parser tests from \"$path\"..." .
|
||||
$this->term->reset() .
|
||||
"\n";
|
||||
}
|
||||
|
||||
public function endSuite( $path ) {
|
||||
print "\n";
|
||||
}
|
||||
|
||||
public function record( $test, ParserTestResult $result ) {
|
||||
$this->total++;
|
||||
$this->success += ( $result->isSuccess() ? 1 : 0 );
|
||||
|
||||
if ( $result->isSuccess() ) {
|
||||
$this->showSuccess( $result );
|
||||
} else {
|
||||
$this->showFailure( $result );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Print a happy success message.
|
||||
*
|
||||
* @param ParserTestResult $testResult
|
||||
* @return bool
|
||||
*/
|
||||
private function showSuccess( ParserTestResult $testResult ) {
|
||||
if ( $this->showProgress ) {
|
||||
print $this->term->color( '1;32' ) . 'PASSED' . $this->term->reset() . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Print a failure message and provide some explanatory output
|
||||
* about what went wrong if so configured.
|
||||
*
|
||||
* @param ParserTestResult $testResult
|
||||
* @return bool
|
||||
*/
|
||||
private function showFailure( ParserTestResult $testResult ) {
|
||||
if ( $this->showFailure ) {
|
||||
if ( !$this->showProgress ) {
|
||||
# In quiet mode we didn't show the 'Testing' message before the
|
||||
# test, in case it succeeded. Show it now:
|
||||
$this->showTesting( $testResult->getDescription() );
|
||||
}
|
||||
|
||||
print $this->term->color( '31' ) . 'FAILED!' . $this->term->reset() . "\n";
|
||||
|
||||
if ( $this->showOutput ) {
|
||||
print "--- Expected ---\n{$testResult->expected}\n";
|
||||
print "--- Actual ---\n{$testResult->actual}\n";
|
||||
}
|
||||
|
||||
if ( $this->showDiffs ) {
|
||||
print $this->quickDiff( $testResult->expected, $testResult->actual );
|
||||
if ( !$this->wellFormed( $testResult->actual ) ) {
|
||||
print "XML error: $this->xmlError\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run given strings through a diff and return the (colorized) output.
|
||||
* Requires writable /tmp directory and a 'diff' command in the PATH.
|
||||
*
|
||||
* @param string $input
|
||||
* @param string $output
|
||||
* @param string $inFileTail Tailing for the input file name
|
||||
* @param string $outFileTail Tailing for the output file name
|
||||
* @return string
|
||||
*/
|
||||
private function quickDiff( $input, $output,
|
||||
$inFileTail = 'expected', $outFileTail = 'actual'
|
||||
) {
|
||||
if ( $this->markWhitespace ) {
|
||||
$pairs = [
|
||||
"\n" => '¶',
|
||||
' ' => '·',
|
||||
"\t" => '→'
|
||||
];
|
||||
$input = strtr( $input, $pairs );
|
||||
$output = strtr( $output, $pairs );
|
||||
}
|
||||
|
||||
# Windows, or at least the fc utility, is retarded
|
||||
$slash = wfIsWindows() ? '\\' : '/';
|
||||
$prefix = wfTempDir() . "{$slash}mwParser-" . mt_rand();
|
||||
|
||||
$infile = "$prefix-$inFileTail";
|
||||
$this->dumpToFile( $input, $infile );
|
||||
|
||||
$outfile = "$prefix-$outFileTail";
|
||||
$this->dumpToFile( $output, $outfile );
|
||||
|
||||
$shellInfile = wfEscapeShellArg( $infile );
|
||||
$shellOutfile = wfEscapeShellArg( $outfile );
|
||||
|
||||
global $wgDiff3;
|
||||
// we assume that people with diff3 also have usual diff
|
||||
if ( $this->useDwdiff ) {
|
||||
$shellCommand = 'dwdiff -Pc';
|
||||
} else {
|
||||
$shellCommand = ( wfIsWindows() && !$wgDiff3 ) ? 'fc' : 'diff -au';
|
||||
}
|
||||
|
||||
$diff = wfShellExec( "$shellCommand $shellInfile $shellOutfile" );
|
||||
|
||||
unlink( $infile );
|
||||
unlink( $outfile );
|
||||
|
||||
if ( $this->useDwdiff ) {
|
||||
return $diff;
|
||||
} else {
|
||||
return $this->colorDiff( $diff );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the given string to a file, adding a final newline.
|
||||
*
|
||||
* @param string $data
|
||||
* @param string $filename
|
||||
*/
|
||||
private function dumpToFile( $data, $filename ) {
|
||||
$file = fopen( $filename, "wt" );
|
||||
fwrite( $file, $data . "\n" );
|
||||
fclose( $file );
|
||||
}
|
||||
|
||||
/**
|
||||
* Colorize unified diff output if set for ANSI color output.
|
||||
* Subtractions are colored blue, additions red.
|
||||
*
|
||||
* @param string $text
|
||||
* @return string
|
||||
*/
|
||||
private function colorDiff( $text ) {
|
||||
return preg_replace(
|
||||
[ '/^(-.*)$/m', '/^(\+.*)$/m' ],
|
||||
[ $this->term->color( 34 ) . '$1' . $this->term->reset(),
|
||||
$this->term->color( 31 ) . '$1' . $this->term->reset() ],
|
||||
$text );
|
||||
}
|
||||
|
||||
private function wellFormed( $text ) {
|
||||
$html =
|
||||
Sanitizer::hackDocType() .
|
||||
'<html>' .
|
||||
$text .
|
||||
'</html>';
|
||||
|
||||
$parser = xml_parser_create( "UTF-8" );
|
||||
|
||||
# case folding violates XML standard, turn it off
|
||||
xml_parser_set_option( $parser, XML_OPTION_CASE_FOLDING, false );
|
||||
|
||||
if ( !xml_parse( $parser, $html, true ) ) {
|
||||
$err = xml_error_string( xml_get_error_code( $parser ) );
|
||||
$position = xml_get_current_byte_index( $parser );
|
||||
$fragment = $this->extractFragment( $html, $position );
|
||||
$this->xmlError = "$err at byte $position:\n$fragment";
|
||||
xml_parser_free( $parser );
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
xml_parser_free( $parser );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function extractFragment( $text, $position ) {
|
||||
$start = max( 0, $position - 10 );
|
||||
$before = $position - $start;
|
||||
$fragment = '...' .
|
||||
$this->term->color( 34 ) .
|
||||
substr( $text, $start, $before ) .
|
||||
$this->term->color( 0 ) .
|
||||
$this->term->color( 31 ) .
|
||||
$this->term->color( 1 ) .
|
||||
substr( $text, $position, 1 ) .
|
||||
$this->term->color( 0 ) .
|
||||
$this->term->color( 34 ) .
|
||||
substr( $text, $position + 1, 9 ) .
|
||||
$this->term->color( 0 ) .
|
||||
'...';
|
||||
$display = str_replace( "\n", ' ', $fragment );
|
||||
$caret = ' ' .
|
||||
str_repeat( ' ', $before ) .
|
||||
$this->term->color( 31 ) .
|
||||
'^' .
|
||||
$this->term->color( 0 );
|
||||
|
||||
return "$display\n$caret";
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a warning to the user
|
||||
*/
|
||||
public function warning( $message ) {
|
||||
echo "$message\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark a test skipped
|
||||
*/
|
||||
public function skipped( $test, $subtest ) {
|
||||
if ( $this->showProgress ) {
|
||||
print $this->term->color( '1;33' ) . 'SKIPPED' . $this->term->reset() . "\n";
|
||||
}
|
||||
$this->skipped++;
|
||||
}
|
||||
|
||||
public function report() {
|
||||
if ( $this->total > 0 ) {
|
||||
$this->reportPercentage( $this->success, $this->total );
|
||||
} else {
|
||||
print $this->term->color( 31 ) . "No tests found." . $this->term->reset() . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
private function reportPercentage( $success, $total ) {
|
||||
$ratio = wfPercent( 100 * $success / $total );
|
||||
print $this->term->color( 1 ) . "Passed $success of $total tests ($ratio)";
|
||||
if ( $this->skipped ) {
|
||||
print ", skipped {$this->skipped}";
|
||||
}
|
||||
print "... ";
|
||||
|
||||
if ( $success == $total ) {
|
||||
print $this->term->color( 32 ) . "ALL TESTS PASSED!";
|
||||
} else {
|
||||
$failed = $total - $success;
|
||||
print $this->term->color( 31 ) . "$failed tests failed!";
|
||||
}
|
||||
|
||||
print $this->term->reset() . "\n";
|
||||
|
||||
return ( $success == $total );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -12,26 +12,22 @@
|
|||
* @since 1.22
|
||||
*/
|
||||
class ParserTestResult {
|
||||
/**
|
||||
* Description of the parser test.
|
||||
*
|
||||
* This is usually the text used to describe a parser test in the .txt
|
||||
* files. It is initialized on a construction and you most probably
|
||||
* never want to change it.
|
||||
*/
|
||||
public $description;
|
||||
/** The test info array */
|
||||
public $test;
|
||||
/** Text that was expected */
|
||||
public $expected;
|
||||
/** Actual text rendered */
|
||||
public $actual;
|
||||
|
||||
/**
|
||||
* @param string $description A short text describing the parser test
|
||||
* usually the text in the parser test .txt file. The description
|
||||
* is later available using the property $description.
|
||||
* @param array $test The test info array from TestIterator
|
||||
* @param string $expected The normalized expected output
|
||||
* @param string $actual The actual output
|
||||
*/
|
||||
public function __construct( $description ) {
|
||||
$this->description = $description;
|
||||
public function __construct( $test, $expected, $actual ) {
|
||||
$this->test = $test;
|
||||
$this->expected = $expected;
|
||||
$this->actual = $actual;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -41,4 +37,8 @@ class ParserTestResult {
|
|||
public function isSuccess() {
|
||||
return $this->expected === $this->actual;
|
||||
}
|
||||
|
||||
public function getDescription() {
|
||||
return $this->test['desc'];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
16
tests/parser/PhpunitTestRecorder.php
Normal file
16
tests/parser/PhpunitTestRecorder.php
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
class PhpunitTestRecorder extends TestRecorder {
|
||||
private $testCase;
|
||||
|
||||
public function setTestCase( PHPUnit_Framework_TestCase $testCase ) {
|
||||
$this->testCase = $testCase;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark a test skipped
|
||||
*/
|
||||
public function skipped( $test, $reason ) {
|
||||
$this->testCase->markTestSkipped( "SKIPPED: $reason" );
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,12 @@
|
|||
Parser tests are run using our PHPUnit test suite in tests/phpunit:
|
||||
Parser tests can be run either via PHPUnit or by using the standalone
|
||||
parserTests.php in this directory. The standalone version provides more
|
||||
options.
|
||||
|
||||
To run parser tests via PHPUnit:
|
||||
|
||||
$ cd tests/phpunit
|
||||
./phpunit.php --group Parser
|
||||
./phpunit.php --testsuite parsertests
|
||||
|
||||
You can optionally filter by title using --regex. I.e. :
|
||||
You can optionally filter by title using --filter, e.g.
|
||||
|
||||
./phpunit.php --group Parser --regex="Bug 6200"
|
||||
./phpunit.php --testsuite parsertests --filter="Bug 6200"
|
||||
|
|
|
|||
|
|
@ -1,42 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
* http://www.gnu.org/copyleft/gpl.html
|
||||
*
|
||||
* @file
|
||||
* @ingroup Testing
|
||||
*/
|
||||
|
||||
/**
|
||||
* An iterator for use as a phpunit data provider. Provides the test arguments
|
||||
* in the order expected by ParserIntegrationTest::testParserTest().
|
||||
*/
|
||||
class TestFileDataProvider extends TestFileReader {
|
||||
function current() {
|
||||
$test = parent::current();
|
||||
if ( $test ) {
|
||||
return [
|
||||
$test['test'],
|
||||
$test['input'],
|
||||
$test['result'],
|
||||
$test['options'],
|
||||
$test['config'],
|
||||
];
|
||||
} else {
|
||||
return $test;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -19,27 +19,43 @@
|
|||
* @ingroup Testing
|
||||
*/
|
||||
|
||||
class TestFileReader implements Iterator {
|
||||
class TestFileReader {
|
||||
private $file;
|
||||
private $fh;
|
||||
/**
|
||||
* @var ParserTestRunner|ParserTestTopLevelSuite An instance of ParserTestRunner
|
||||
* (parserTests.php) or ParserTestTopLevelSuite (phpunit)
|
||||
*/
|
||||
private $parserTest;
|
||||
private $index = 0;
|
||||
private $test;
|
||||
private $section = null;
|
||||
/** String|null: current test section being analyzed */
|
||||
private $sectionData = [];
|
||||
private $lineNum;
|
||||
private $eof;
|
||||
# Create a fake parser tests which never run anything unless
|
||||
# asked to do so. This will avoid running hooks for a disabled test
|
||||
private $delayedParserTest;
|
||||
private $nextSubTest = 0;
|
||||
private $lineNum = 0;
|
||||
private $runDisabled;
|
||||
private $runParsoid;
|
||||
private $regex;
|
||||
|
||||
function __construct( $file, $parserTest ) {
|
||||
private $articles = [];
|
||||
private $requirements = [];
|
||||
private $tests = [];
|
||||
|
||||
public static function read( $file, array $options = [] ) {
|
||||
$reader = new self( $file, $options );
|
||||
$reader->execute();
|
||||
|
||||
$requirements = [];
|
||||
foreach ( $reader->requirements as $type => $reqsOfType ) {
|
||||
foreach ( $reqsOfType as $name => $unused ) {
|
||||
$requirements[] = [
|
||||
'type' => $type,
|
||||
'name' => $name
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'requirements' => $requirements,
|
||||
'tests' => $reader->tests,
|
||||
'articles' => $reader->articles
|
||||
];
|
||||
}
|
||||
|
||||
private function __construct( $file, $options ) {
|
||||
$this->file = $file;
|
||||
$this->fh = fopen( $this->file, "rt" );
|
||||
|
||||
|
|
@ -47,64 +63,23 @@ class TestFileReader implements Iterator {
|
|||
throw new MWException( "Couldn't open file '$file'\n" );
|
||||
}
|
||||
|
||||
$this->parserTest = $parserTest;
|
||||
$this->delayedParserTest = new DelayedParserTest();
|
||||
|
||||
$this->lineNum = $this->index = 0;
|
||||
$options = $options + [
|
||||
'runDisabled' => false,
|
||||
'runParsoid' => false,
|
||||
'regex' => '//',
|
||||
];
|
||||
$this->runDisabled = $options['runDisabled'];
|
||||
$this->runParsoid = $options['runParsoid'];
|
||||
$this->regex = $options['regex'];
|
||||
}
|
||||
|
||||
function rewind() {
|
||||
if ( fseek( $this->fh, 0 ) ) {
|
||||
throw new MWException( "Couldn't fseek to the start of '$this->file'\n" );
|
||||
}
|
||||
|
||||
$this->index = -1;
|
||||
$this->lineNum = 0;
|
||||
$this->eof = false;
|
||||
$this->next();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function current() {
|
||||
return $this->test;
|
||||
}
|
||||
|
||||
function key() {
|
||||
return $this->index;
|
||||
}
|
||||
|
||||
function next() {
|
||||
if ( $this->readNextTest() ) {
|
||||
$this->index++;
|
||||
return true;
|
||||
} else {
|
||||
$this->eof = true;
|
||||
}
|
||||
}
|
||||
|
||||
function valid() {
|
||||
return $this->eof != true;
|
||||
}
|
||||
|
||||
function setupCurrentTest() {
|
||||
private function addCurrentTest() {
|
||||
// "input" and "result" are old section names allowed
|
||||
// for backwards-compatibility.
|
||||
$input = $this->checkSection( [ 'wikitext', 'input' ], false );
|
||||
$result = $this->checkSection( [ 'html/php', 'html/*', 'html', 'result' ], false );
|
||||
// some tests have "with tidy" and "without tidy" variants
|
||||
// Some tests have "with tidy" and "without tidy" variants
|
||||
$tidy = $this->checkSection( [ 'html/php+tidy', 'html+tidy' ], false );
|
||||
if ( $tidy != false ) {
|
||||
if ( $this->nextSubTest == 0 ) {
|
||||
if ( $result != false ) {
|
||||
$this->nextSubTest = 1; // rerun non-tidy variant later
|
||||
}
|
||||
$result = $tidy;
|
||||
} else {
|
||||
$this->nextSubTest = 0; // go on to next test after this
|
||||
$tidy = false;
|
||||
}
|
||||
}
|
||||
|
||||
if ( !isset( $this->sectionData['options'] ) ) {
|
||||
$this->sectionData['options'] = '';
|
||||
|
|
@ -115,50 +90,35 @@ class TestFileReader implements Iterator {
|
|||
}
|
||||
|
||||
$isDisabled = preg_match( '/\\bdisabled\\b/i', $this->sectionData['options'] ) &&
|
||||
!$this->parserTest->runDisabled;
|
||||
!$this->runDisabled;
|
||||
$isParsoidOnly = preg_match( '/\\bparsoid\\b/i', $this->sectionData['options'] ) &&
|
||||
$result == 'html' &&
|
||||
!$this->parserTest->runParsoid;
|
||||
$isFiltered = !preg_match( "/" . $this->parserTest->regex . "/i", $this->sectionData['test'] );
|
||||
!$this->runParsoid;
|
||||
$isFiltered = !preg_match( $this->regex, $this->sectionData['test'] );
|
||||
if ( $input == false || $result == false || $isDisabled || $isParsoidOnly || $isFiltered ) {
|
||||
# disabled test
|
||||
return false;
|
||||
// Disabled test
|
||||
return;
|
||||
}
|
||||
|
||||
# We are really going to run the test, run pending hooks and hooks function
|
||||
wfDebug( __METHOD__ . " unleashing delayed test for: {$this->sectionData['test']}" );
|
||||
$hooksResult = $this->delayedParserTest->unleash( $this->parserTest );
|
||||
if ( !$hooksResult ) {
|
||||
# Some hook reported an issue. Abort.
|
||||
throw new MWException( "Problem running requested parser hook from the test file" );
|
||||
}
|
||||
|
||||
$this->test = [
|
||||
$test = [
|
||||
'test' => ParserTestRunner::chomp( $this->sectionData['test'] ),
|
||||
'subtest' => $this->nextSubTest,
|
||||
'input' => ParserTestRunner::chomp( $this->sectionData[$input] ),
|
||||
'result' => ParserTestRunner::chomp( $this->sectionData[$result] ),
|
||||
'options' => ParserTestRunner::chomp( $this->sectionData['options'] ),
|
||||
'config' => ParserTestRunner::chomp( $this->sectionData['config'] ),
|
||||
];
|
||||
if ( $tidy != false ) {
|
||||
$this->test['options'] .= " tidy";
|
||||
$test['desc'] = $test['test'];
|
||||
$this->tests[] = $test;
|
||||
|
||||
if ( $tidy !== false ) {
|
||||
$test['options'] .= " tidy";
|
||||
$test['desc'] .= ' (with tidy)';
|
||||
$test['result'] = ParserTestRunner::chomp( $this->sectionData[$tidy] );
|
||||
$this->tests[] = $test;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function readNextTest() {
|
||||
# Run additional subtests of previous test
|
||||
while ( $this->nextSubTest > 0 ) {
|
||||
if ( $this->setupCurrentTest() ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
$this->clearSection();
|
||||
# Reset hooks for the delayed test object
|
||||
$this->delayedParserTest->reset();
|
||||
|
||||
private function execute() {
|
||||
while ( false !== ( $line = fgets( $this->fh ) ) ) {
|
||||
$this->lineNum++;
|
||||
$matches = [];
|
||||
|
|
@ -170,7 +130,7 @@ class TestFileReader implements Iterator {
|
|||
$this->checkSection( 'text' );
|
||||
$this->checkSection( 'article' );
|
||||
|
||||
$this->parserTest->addArticle(
|
||||
$this->addArticle(
|
||||
ParserTestRunner::chomp( $this->sectionData['article'] ),
|
||||
$this->sectionData['text'], $this->lineNum );
|
||||
|
||||
|
|
@ -186,7 +146,7 @@ class TestFileReader implements Iterator {
|
|||
$line = trim( $line );
|
||||
|
||||
if ( $line ) {
|
||||
$this->delayedParserTest->requireHook( $line );
|
||||
$this->addRequirement( 'hook', $line );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -202,7 +162,7 @@ class TestFileReader implements Iterator {
|
|||
$line = trim( $line );
|
||||
|
||||
if ( $line ) {
|
||||
$this->delayedParserTest->requireFunctionHook( $line );
|
||||
$this->addRequirement( 'functionHook', $line );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -218,7 +178,7 @@ class TestFileReader implements Iterator {
|
|||
$line = trim( $line );
|
||||
|
||||
if ( $line ) {
|
||||
$this->delayedParserTest->requireTransparentHook( $line );
|
||||
$this->addRequirement( 'transparentHook', $line );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -229,14 +189,8 @@ class TestFileReader implements Iterator {
|
|||
|
||||
if ( $this->section == 'end' ) {
|
||||
$this->checkSection( 'test' );
|
||||
do {
|
||||
if ( $this->setupCurrentTest() ) {
|
||||
return true;
|
||||
}
|
||||
} while ( $this->nextSubTest > 0 );
|
||||
# go on to next test (since this was disabled)
|
||||
$this->addCurrentTest();
|
||||
$this->clearSection();
|
||||
$this->delayedParserTest->reset();
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -254,8 +208,6 @@ class TestFileReader implements Iterator {
|
|||
$this->sectionData[$this->section] .= $line;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -320,5 +272,18 @@ class TestFileReader implements Iterator {
|
|||
|
||||
return array_values( $tokens )[0];
|
||||
}
|
||||
|
||||
private function addArticle( $name, $text, $line ) {
|
||||
$this->articles[] = [
|
||||
'name' => $name,
|
||||
'text' => $text,
|
||||
'line' => $line,
|
||||
'file' => $this->file
|
||||
];
|
||||
}
|
||||
|
||||
private function addRequirement( $type, $name ) {
|
||||
$this->requirements[$type][$name] = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,51 +19,76 @@
|
|||
* @ingroup Testing
|
||||
*/
|
||||
|
||||
class TestRecorder implements ITestRecorder {
|
||||
public $parent;
|
||||
public $term;
|
||||
/**
|
||||
* Interface to record parser test results.
|
||||
*
|
||||
* The TestRecorder is an class hierarchy to record the result of
|
||||
* MediaWiki parser tests. One should call start() before running the
|
||||
* full parser tests and end() once all the tests have been finished.
|
||||
* After each test, you should use record() to keep track of your tests
|
||||
* results. Finally, report() is used to generate a summary of your
|
||||
* test run, one could dump it to the console for human consumption or
|
||||
* register the result in a database for tracking purposes.
|
||||
*
|
||||
* @since 1.22
|
||||
*/
|
||||
abstract class TestRecorder {
|
||||
|
||||
function __construct( $parent ) {
|
||||
$this->parent = $parent;
|
||||
$this->term = $parent->term;
|
||||
/**
|
||||
* Called at beginning of the parser test run
|
||||
*/
|
||||
public function start() {
|
||||
}
|
||||
|
||||
function start() {
|
||||
$this->total = 0;
|
||||
$this->success = 0;
|
||||
/**
|
||||
* Called before starting a test
|
||||
*/
|
||||
public function startTest( $test ) {
|
||||
}
|
||||
|
||||
function record( $test, $subtest, $result ) {
|
||||
$this->total++;
|
||||
$this->success += ( $result ? 1 : 0 );
|
||||
/**
|
||||
* Called before starting an input file
|
||||
*/
|
||||
public function startSuite( $path ) {
|
||||
}
|
||||
|
||||
function end() {
|
||||
// dummy
|
||||
/**
|
||||
* Called after ending an input file
|
||||
*/
|
||||
public function endSuite( $path ) {
|
||||
}
|
||||
|
||||
function report() {
|
||||
if ( $this->total > 0 ) {
|
||||
$this->reportPercentage( $this->success, $this->total );
|
||||
} else {
|
||||
throw new MWException( "No tests found.\n" );
|
||||
}
|
||||
/**
|
||||
* Called after each test
|
||||
* @param array $test
|
||||
* @param ParserTestResult $result
|
||||
*/
|
||||
public function record( $test, ParserTestResult $result ) {
|
||||
}
|
||||
|
||||
function reportPercentage( $success, $total ) {
|
||||
$ratio = wfPercent( 100 * $success / $total );
|
||||
print $this->term->color( 1 ) . "Passed $success of $total tests ($ratio)... ";
|
||||
|
||||
if ( $success == $total ) {
|
||||
print $this->term->color( 32 ) . "ALL TESTS PASSED!";
|
||||
} else {
|
||||
$failed = $total - $success;
|
||||
print $this->term->color( 31 ) . "$failed tests failed!";
|
||||
}
|
||||
|
||||
print $this->term->reset() . "\n";
|
||||
|
||||
return ( $success == $total );
|
||||
/**
|
||||
* Show a warning to the user
|
||||
*/
|
||||
public function warning( $message ) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark a test skipped
|
||||
*/
|
||||
public function skipped( $test, $subtest ) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called before finishing the test run
|
||||
*/
|
||||
public function report() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called at the end of the parser test run
|
||||
*/
|
||||
public function end() {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,13 +22,16 @@ class ParserFuzzTest extends Maintenance {
|
|||
}
|
||||
|
||||
function finalSetup() {
|
||||
require_once __DIR__ . '/../common/TestsAutoLoader.php';
|
||||
self::requireTestsAutoloader();
|
||||
TestSetup::applyInitialConfig();
|
||||
}
|
||||
|
||||
function execute() {
|
||||
$files = $this->getOption( 'file', [ __DIR__ . '/parserTests.txt' ] );
|
||||
$this->seed = intval( $this->getOption( 'seed', 1 ) ) - 1;
|
||||
$this->parserTest = new ParserTestRunner;
|
||||
$this->parserTest = new ParserTestRunner(
|
||||
new MultiTestRecorder,
|
||||
[] );
|
||||
$this->fuzzTest( $files );
|
||||
}
|
||||
|
||||
|
|
@ -38,11 +41,23 @@ class ParserFuzzTest extends Maintenance {
|
|||
* @param array $filenames
|
||||
*/
|
||||
function fuzzTest( $filenames ) {
|
||||
$GLOBALS['wgContLang'] = Language::factory( 'en' );
|
||||
$dict = $this->getFuzzInput( $filenames );
|
||||
$dictSize = strlen( $dict );
|
||||
$logMaxLength = log( $this->maxFuzzTestLength );
|
||||
$this->parserTest->setupDatabase();
|
||||
|
||||
$teardown = $this->parserTest->staticSetup();
|
||||
$teardown = $this->parserTest->setupDatabase( $teardown );
|
||||
$teardown = $this->parserTest->setupUploads( $teardown );
|
||||
|
||||
$fakeTest = [
|
||||
'test' => '',
|
||||
'desc' => '',
|
||||
'input' => '',
|
||||
'result' => '',
|
||||
'options' => '',
|
||||
'config' => ''
|
||||
];
|
||||
|
||||
ini_set( 'memory_limit', $this->memoryLimit * 1048576 * 2 );
|
||||
|
||||
$numTotal = 0;
|
||||
|
|
@ -64,7 +79,7 @@ class ParserFuzzTest extends Maintenance {
|
|||
$input .= substr( $dict, $offset, $hairLength );
|
||||
}
|
||||
|
||||
$this->parserTest->setupGlobals();
|
||||
$perTestTeardown = $this->parserTest->perTestSetup( $fakeTest );
|
||||
$parser = $this->parserTest->getParser();
|
||||
|
||||
// Run the test
|
||||
|
|
@ -85,8 +100,7 @@ class ParserFuzzTest extends Maintenance {
|
|||
}
|
||||
|
||||
$numTotal++;
|
||||
$this->parserTest->teardownGlobals();
|
||||
$parser->__destruct();
|
||||
ScopedCallback::consume( $perTestTeardown );
|
||||
|
||||
if ( $numTotal % 100 == 0 ) {
|
||||
$usage = intval( memory_get_usage( true ) / $this->memoryLimit / 1048576 * 100 );
|
||||
|
|
|
|||
|
|
@ -24,73 +24,170 @@
|
|||
* @ingroup Testing
|
||||
*/
|
||||
|
||||
// Some methods which are discouraged for normal code throw exceptions unless
|
||||
// we declare this is just a test.
|
||||
define( 'MW_PARSER_TEST', true );
|
||||
|
||||
$options = [ 'quick', 'color', 'quiet', 'help', 'show-output',
|
||||
'record', 'run-disabled', 'run-parsoid', 'dwdiff', 'mark-ws' ];
|
||||
$optionsWithArgs = [ 'regex', 'filter', 'seed', 'setversion', 'file', 'norm' ];
|
||||
require __DIR__ . '/../../maintenance/Maintenance.php';
|
||||
|
||||
require_once __DIR__ . '/../../maintenance/commandLine.inc';
|
||||
require_once __DIR__ . '/../common/TestsAutoLoader.php';
|
||||
class ParserTestsMaintenance extends Maintenance {
|
||||
function __construct() {
|
||||
parent::__construct();
|
||||
$this->addDescription( 'Run parser tests' );
|
||||
|
||||
if ( isset( $options['help'] ) ) {
|
||||
echo <<<ENDS
|
||||
MediaWiki $wgVersion parser test suite
|
||||
Usage: php parserTests.php [options...]
|
||||
$this->addOption( 'quick', 'Suppress diff output of failed tests' );
|
||||
$this->addOption( 'quiet', 'Suppress notification of passed tests (shows only failed tests)' );
|
||||
$this->addOption( 'show-output', 'Show expected and actual output' );
|
||||
$this->addOption( 'color', '[=yes|no] Override terminal detection and force ' .
|
||||
'color output on or off. Use wgCommandLineDarkBg = true; if your term is dark',
|
||||
false, true );
|
||||
$this->addOption( 'regex', 'Only run tests whose descriptions which match given regex',
|
||||
false, true );
|
||||
$this->addOption( 'filter', 'Alias for --regex', false, true );
|
||||
$this->addOption( 'file', 'Run test cases from a custom file instead of parserTests.txt',
|
||||
false, true, false, true );
|
||||
$this->addOption( 'record', 'Record tests in database' );
|
||||
$this->addOption( 'compare', 'Compare with recorded results, without updating the database.' );
|
||||
$this->addOption( 'setversion', 'When using --record, set the version string to use (useful' .
|
||||
'with "git rev-parse HEAD" to get the exact revision)',
|
||||
false, true );
|
||||
$this->addOption( 'keep-uploads', 'Re-use the same upload directory for each ' .
|
||||
'test, don\'t delete it' );
|
||||
$this->addOption( 'file-backend', 'Use the file backend with the given name,' .
|
||||
'and upload files to it, instead of creating a mock file backend.', false, true );
|
||||
$this->addOption( 'upload-dir', 'Specify the upload directory to use. Useful in ' .
|
||||
'conjunction with --keep-uploads. Causes a real (non-mock) file backend to ' .
|
||||
'be used.', false, true );
|
||||
$this->addOption( 'run-disabled', 'run disabled tests' );
|
||||
$this->addOption( 'run-parsoid', 'run parsoid tests (normally disabled)' );
|
||||
$this->addOption( 'dwdiff', 'Use dwdiff to display diff output' );
|
||||
$this->addOption( 'mark-ws', 'Mark whitespace in diffs by replacing it with symbols' );
|
||||
$this->addOption( 'norm', 'Apply a comma-separated list of normalization functions to ' .
|
||||
'both the expected and actual output in order to resolve ' .
|
||||
'irrelevant differences. The accepted normalization functions ' .
|
||||
'are: removeTbody to remove <tbody> tags; and trimWhitespace ' .
|
||||
'to trim whitespace from the start and end of text nodes.',
|
||||
false, true );
|
||||
$this->addOption( 'use-tidy-config', 'Use the wiki\'s Tidy configuration instead of known-good' .
|
||||
'defaults.' );
|
||||
}
|
||||
|
||||
Options:
|
||||
--quick Suppress diff output of failed tests
|
||||
--quiet Suppress notification of passed tests (shows only failed tests)
|
||||
--show-output Show expected and actual output
|
||||
--color[=yes|no] Override terminal detection and force color output on or off
|
||||
use wgCommandLineDarkBg = true; if your term is dark
|
||||
--regex Only run tests whose descriptions which match given regex
|
||||
--filter Alias for --regex
|
||||
--file=<testfile> Run test cases from a custom file instead of parserTests.txt
|
||||
--record Record tests in database
|
||||
--compare Compare with recorded results, without updating the database.
|
||||
--setversion When using --record, set the version string to use (useful
|
||||
with git-svn so that you can get the exact revision)
|
||||
--keep-uploads Re-use the same upload directory for each test, don't delete it
|
||||
--run-disabled run disabled tests
|
||||
--run-parsoid run parsoid tests (normally disabled)
|
||||
--dwdiff Use dwdiff to display diff output
|
||||
--mark-ws Mark whitespace in diffs by replacing it with symbols
|
||||
--norm=<funcs> Apply a comma-separated list of normalization functions to
|
||||
both the expected and actual output in order to resolve
|
||||
irrelevant differences. The accepted normalization functions
|
||||
are: removeTbody to remove <tbody> tags; and trimWhitespace
|
||||
to trim whitespace from the start and end of text nodes.
|
||||
--use-tidy-config Use the wiki's Tidy configuration instead of known-good
|
||||
defaults.
|
||||
--help Show this help message
|
||||
public function finalSetup() {
|
||||
parent::finalSetup();
|
||||
self::requireTestsAutoloader();
|
||||
TestSetup::applyInitialConfig();
|
||||
}
|
||||
|
||||
ENDS;
|
||||
exit( 0 );
|
||||
}
|
||||
public function execute() {
|
||||
global $wgParserTestFiles, $wgDBtype;
|
||||
|
||||
# Cases of weird db corruption were encountered when running tests on earlyish
|
||||
# versions of SQLite
|
||||
if ( $wgDBtype == 'sqlite' ) {
|
||||
$db = wfGetDB( DB_MASTER );
|
||||
$version = $db->getServerVersion();
|
||||
if ( version_compare( $version, '3.6' ) < 0 ) {
|
||||
die( "Parser tests require SQLite version 3.6 or later, you have $version\n" );
|
||||
// Cases of weird db corruption were encountered when running tests on earlyish
|
||||
// versions of SQLite
|
||||
if ( $wgDBtype == 'sqlite' ) {
|
||||
$db = wfGetDB( DB_MASTER );
|
||||
$version = $db->getServerVersion();
|
||||
if ( version_compare( $version, '3.6' ) < 0 ) {
|
||||
die( "Parser tests require SQLite version 3.6 or later, you have $version\n" );
|
||||
}
|
||||
}
|
||||
|
||||
// Print out software version to assist with locating regressions
|
||||
$version = SpecialVersion::getVersion( 'nodb' );
|
||||
echo "This is MediaWiki version {$version}.\n\n";
|
||||
|
||||
// Only colorize output if stdout is a terminal.
|
||||
$color = !wfIsWindows() && Maintenance::posix_isatty( 1 );
|
||||
|
||||
if ( $this->hasOption( 'color' ) ) {
|
||||
switch ( $this->getOption( 'color' ) ) {
|
||||
case 'no':
|
||||
$color = false;
|
||||
break;
|
||||
case 'yes':
|
||||
default:
|
||||
$color = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$record = $this->hasOption( 'record' );
|
||||
$compare = $this->hasOption( 'compare' );
|
||||
|
||||
$regex = $this->getOption( 'filter', $this->getOption( 'regex', false ) );
|
||||
if ( $regex !== false ) {
|
||||
$regex = "/$regex/i";
|
||||
|
||||
if ( $record ) {
|
||||
echo "Warning: --record cannot be used with --regex, disabling --record\n";
|
||||
$record = false;
|
||||
}
|
||||
}
|
||||
|
||||
$term = $color
|
||||
? new AnsiTermColorer()
|
||||
: new DummyTermColorer();
|
||||
|
||||
$recorder = new MultiTestRecorder;
|
||||
|
||||
$recorder->addRecorder( new ParserTestPrinter(
|
||||
$term,
|
||||
[
|
||||
'showDiffs' => !$this->hasOption( 'quick' ),
|
||||
'showProgress' => !$this->hasOption( 'quiet' ),
|
||||
'showFailure' => !$this->hasOption( 'quiet' )
|
||||
|| ( !$record && !$compare ), // redundant output
|
||||
'showOutput' => $this->hasOption( 'show-output' ),
|
||||
'useDwdiff' => $this->hasOption( 'dwdiff' ),
|
||||
'markWhitespace' => $this->hasOption( 'mark-ws' ),
|
||||
]
|
||||
) );
|
||||
|
||||
$recorderLB = false;
|
||||
if ( $record || $compare ) {
|
||||
$recorderLB = wfGetLBFactory()->newMainLB();
|
||||
// This connection will have the wiki's table prefix, not parsertest_
|
||||
$recorderDB = $recorderLB->getConnection( DB_MASTER );
|
||||
|
||||
// Add recorder before previewer because recorder will create the
|
||||
// DB table if it doesn't exist
|
||||
if ( $record ) {
|
||||
$recorder->addRecorder( new DbTestRecorder( $recorderDB ) );
|
||||
}
|
||||
$recorder->addRecorder( new DbTestPreviewer(
|
||||
$recorderDB,
|
||||
function ( $name ) use ( $regex ) {
|
||||
// Filter reports of old tests by the filter regex
|
||||
if ( $regex === false ) {
|
||||
return true;
|
||||
} else {
|
||||
return (bool)preg_match( $regex, $name );
|
||||
}
|
||||
} ) );
|
||||
}
|
||||
|
||||
// Default parser tests and any set from extensions or local config
|
||||
$files = $this->getOption( 'file', $wgParserTestFiles );
|
||||
|
||||
$norm = $this->hasOption( 'norm' ) ? explode( ',', $this->getOption( 'norm' ) ) : [];
|
||||
|
||||
$tester = new ParserTestRunner( $recorder, [
|
||||
'norm' => $norm,
|
||||
'regex' => $regex,
|
||||
'keep-uploads' => $this->hasOption( 'keep-uploads' ),
|
||||
'run-disabled' => $this->hasOption( 'run-disabled' ),
|
||||
'run-parsoid' => $this->hasOption( 'run-parsoid' ),
|
||||
'use-tidy-config' => $this->hasOption( 'use-tidy-config' ),
|
||||
'file-backend' => $this->getOption( 'file-backend' ),
|
||||
'upload-dir' => $this->getOption( 'upload-dir' ),
|
||||
] );
|
||||
|
||||
$ok = $tester->runTestsFromFiles( $files );
|
||||
if ( $recorderLB ) {
|
||||
$recorderLB->closeAll();
|
||||
}
|
||||
return $ok ? 0 : 1;
|
||||
}
|
||||
}
|
||||
|
||||
$tester = new ParserTestRunner( $options );
|
||||
|
||||
if ( isset( $options['file'] ) ) {
|
||||
$files = [ $options['file'] ];
|
||||
} else {
|
||||
// Default parser tests and any set from extensions or local config
|
||||
$files = $wgParserTestFiles;
|
||||
}
|
||||
|
||||
# Print out software version to assist with locating regressions
|
||||
$version = SpecialVersion::getVersion( 'nodb' );
|
||||
echo "This is MediaWiki version {$version}.\n\n";
|
||||
|
||||
$ok = $tester->runTestsFromFiles( $files );
|
||||
exit( $ok ? 0 : 1 );
|
||||
$maintClass = 'ParserTestsMaintenance';
|
||||
require_once RUN_MAINTENANCE_IF_MAIN;
|
||||
|
|
|
|||
|
|
@ -14646,7 +14646,7 @@ cat
|
|||
!! wikitext
|
||||
[[Category:MediaWiki User's Guide]]
|
||||
!! html
|
||||
<a href="/wiki/Category:MediaWiki_User%27s_Guide" title="Category:MediaWiki User's Guide">MediaWiki User's Guide</a>
|
||||
cat=MediaWiki_User's_Guide sort=
|
||||
!! end
|
||||
|
||||
!! test
|
||||
|
|
@ -14665,7 +14665,7 @@ cat
|
|||
!! wikitext
|
||||
[[Category:MediaWiki User's Guide|Foo]]
|
||||
!! html
|
||||
<a href="/wiki/Category:MediaWiki_User%27s_Guide" title="Category:MediaWiki User's Guide">MediaWiki User's Guide</a>
|
||||
cat=MediaWiki_User's_Guide sort=Foo
|
||||
!! end
|
||||
|
||||
!! test
|
||||
|
|
@ -14675,7 +14675,7 @@ cat
|
|||
!! wikitext
|
||||
[[Category:MediaWiki User's Guide|MediaWiki User's Guide]]
|
||||
!! html
|
||||
<a href="/wiki/Category:MediaWiki_User%27s_Guide" title="Category:MediaWiki User's Guide">MediaWiki User's Guide</a>
|
||||
cat=MediaWiki_User's_Guide sort=MediaWiki User's Guide
|
||||
!! end
|
||||
|
||||
!! test
|
||||
|
|
@ -19785,7 +19785,7 @@ language=sr cat
|
|||
!! wikitext
|
||||
[[Category:МедиаWики Усер'с Гуиде]]
|
||||
!! html
|
||||
<a href="/wiki/%D0%9A%D0%B0%D1%82%D0%B5%D0%B3%D0%BE%D1%80%D0%B8%D1%98%D0%B0:MediaWiki_User%27s_Guide" title="Категорија:MediaWiki User's Guide">MediaWiki User's Guide</a>
|
||||
cat=МедиаWики_Усер'с_Гуиде sort=
|
||||
!! end
|
||||
|
||||
|
||||
|
|
@ -19814,7 +19814,7 @@ parsoid=wt2html
|
|||
!! wikitext
|
||||
[[A]][[Category:分类]]
|
||||
!! html/php
|
||||
<a href="/wiki/Category:%E5%88%86%E7%B1%BB" title="Category:分类">分类</a>
|
||||
cat=分类 sort=
|
||||
!! html/parsoid
|
||||
<p><a rel="mw:WikiLink" href="A" title="A">A</a></p>
|
||||
<link rel="mw:PageProp/Category" href="Category:分类"/>
|
||||
|
|
|
|||
|
|
@ -43,26 +43,17 @@ coverage:
|
|||
|
||||
parser:
|
||||
${PU} --group Parser
|
||||
parserfuzz:
|
||||
@echo "******************************************************************"
|
||||
@echo "* This WILL kill your computer by eating all memory AND all swap *"
|
||||
@echo "* *"
|
||||
@echo "* If you are on a production machine. ABORT NOW!! *"
|
||||
@echo "* Press control+C to stop *"
|
||||
@echo "* *"
|
||||
@echo "******************************************************************"
|
||||
${PU} --group Parser,ParserFuzz
|
||||
noparser:
|
||||
${PU} --exclude-group Parser,Broken,ParserFuzz,Stub
|
||||
${PU} --exclude-group Parser,Broken,Stub
|
||||
|
||||
safe:
|
||||
${PU} --exclude-group Broken,ParserFuzz,Destructive,Stub
|
||||
${PU} --exclude-group Broken,Destructive,Stub
|
||||
|
||||
databaseless:
|
||||
${PU} --exclude-group Broken,ParserFuzz,Destructive,Database,Stub
|
||||
${PU} --exclude-group Broken,Destructive,Database,Stub
|
||||
|
||||
database:
|
||||
${PU} --exclude-group Broken,ParserFuzz,Destructive,Stub --group Database
|
||||
${PU} --exclude-group Broken,Destructive,Stub --group Database
|
||||
|
||||
list-groups:
|
||||
${PU} --list-groups
|
||||
|
|
|
|||
|
|
@ -122,9 +122,8 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
|
|||
public static function setUpBeforeClass() {
|
||||
parent::setUpBeforeClass();
|
||||
|
||||
// NOTE: Usually, PHPUnitMaintClass::finalSetup already called this,
|
||||
// but let's make doubly sure.
|
||||
self::prepareServices( new GlobalVarConfig() );
|
||||
// Get the service locator, and reset services if it's not done already
|
||||
self::$serviceLocator = self::prepareServices( new GlobalVarConfig() );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -180,28 +179,26 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
|
|||
*
|
||||
* @param Config $bootstrapConfig The bootstrap config to use with the new
|
||||
* MediaWikiServices. Only used for the first call to this method.
|
||||
* @return MediaWikiServices
|
||||
*/
|
||||
public static function prepareServices( Config $bootstrapConfig ) {
|
||||
static $servicesPrepared = false;
|
||||
static $services = null;
|
||||
|
||||
if ( $servicesPrepared ) {
|
||||
return;
|
||||
} else {
|
||||
$servicesPrepared = true;
|
||||
if ( !$services ) {
|
||||
$services = self::resetGlobalServices( $bootstrapConfig );
|
||||
}
|
||||
|
||||
self::resetGlobalServices( $bootstrapConfig );
|
||||
return $services;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset global services, and install testing environment.
|
||||
* This is the testing equivalent of MediaWikiServices::resetGlobalInstance().
|
||||
* This should only be used to set up the testing environment, not when
|
||||
* running unit tests. Use overrideMwServices() for that.
|
||||
* running unit tests. Use MediaWikiTestCase::overrideMwServices() for that.
|
||||
*
|
||||
* @see MediaWikiServices::resetGlobalInstance()
|
||||
* @see prepareServices()
|
||||
* @see overrideMwServices()
|
||||
* @see MediaWikiTestCase::overrideMwServices()
|
||||
*
|
||||
* @param Config|null $bootstrapConfig The bootstrap config to use with the new
|
||||
* MediaWikiServices.
|
||||
|
|
@ -214,11 +211,12 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
|
|||
|
||||
MediaWikiServices::resetGlobalInstance( $testConfig );
|
||||
|
||||
self::$serviceLocator = MediaWikiServices::getInstance();
|
||||
$serviceLocator = MediaWikiServices::getInstance();
|
||||
self::installTestServices(
|
||||
$oldConfigFactory,
|
||||
self::$serviceLocator
|
||||
$serviceLocator
|
||||
);
|
||||
return $serviceLocator;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1122,15 +1120,15 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
|
|||
* @throws MWException If the database table prefix is already $prefix
|
||||
*/
|
||||
public static function setupTestDB( DatabaseBase $db, $prefix ) {
|
||||
if ( self::$dbSetup ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( $db->tablePrefix() === $prefix ) {
|
||||
throw new MWException(
|
||||
'Cannot run unit tests, the database prefix is already "' . $prefix . '"' );
|
||||
}
|
||||
|
||||
if ( self::$dbSetup ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: the below should be re-written as soon as LBFactory, LoadBalancer,
|
||||
// and DatabaseBase no longer use global state.
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -16,12 +16,10 @@ require_once dirname( dirname( __DIR__ ) ) . "/maintenance/Maintenance.php";
|
|||
class PHPUnitMaintClass extends Maintenance {
|
||||
|
||||
public static $additionalOptions = [
|
||||
'regex' => false,
|
||||
'file' => false,
|
||||
'use-filebackend' => false,
|
||||
'use-bagostuff' => false,
|
||||
'use-jobqueue' => false,
|
||||
'keep-uploads' => false,
|
||||
'use-normal-tables' => false,
|
||||
'reuse-db' => false,
|
||||
'wiki' => false,
|
||||
|
|
@ -42,22 +40,10 @@ class PHPUnitMaintClass extends Maintenance {
|
|||
false, # not required
|
||||
false # no arg needed
|
||||
);
|
||||
$this->addOption(
|
||||
'regex',
|
||||
'Only run parser tests that match the given regex.',
|
||||
false,
|
||||
true
|
||||
);
|
||||
$this->addOption( 'file', 'File describing parser tests.', false, true );
|
||||
$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(
|
||||
'keep-uploads',
|
||||
'Re-use the same upload directory for each test, don\'t delete it.',
|
||||
false,
|
||||
false
|
||||
);
|
||||
$this->addOption( 'use-normal-tables', 'Use normal DB tables.', false, false );
|
||||
$this->addOption(
|
||||
'reuse-db', 'Init DB only if tables are missing and keep after finish.',
|
||||
|
|
@ -69,104 +55,10 @@ class PHPUnitMaintClass extends Maintenance {
|
|||
public function finalSetup() {
|
||||
parent::finalSetup();
|
||||
|
||||
global $wgMainCacheType, $wgMessageCacheType, $wgParserCacheType, $wgMainWANCache;
|
||||
global $wgMainStash;
|
||||
global $wgLanguageConverterCacheType, $wgUseDatabaseMessages;
|
||||
global $wgLocaltimezone, $wgLocalisationCacheConf;
|
||||
global $wgDevelopmentWarnings;
|
||||
global $wgSessionProviders, $wgSessionPbkdf2Iterations;
|
||||
global $wgJobTypeConf;
|
||||
global $wgAuthManagerConfig, $wgAuth;
|
||||
|
||||
// Inject test autoloader
|
||||
require_once __DIR__ . '/../common/TestsAutoLoader.php';
|
||||
self::requireTestsAutoloader();
|
||||
|
||||
// wfWarn should cause tests to fail
|
||||
$wgDevelopmentWarnings = true;
|
||||
|
||||
// Make sure all caches and stashes are either disabled or use
|
||||
// in-process cache only to prevent tests from using any preconfigured
|
||||
// cache meant for the local wiki from outside the test run.
|
||||
// See also MediaWikiTestCase::run() which mocks CACHE_DB and APC.
|
||||
|
||||
// Disabled in DefaultSettings, override local settings
|
||||
$wgMainWANCache =
|
||||
$wgMainCacheType = CACHE_NONE;
|
||||
// Uses CACHE_ANYTHING in DefaultSettings, use hash instead of db
|
||||
$wgMessageCacheType =
|
||||
$wgParserCacheType =
|
||||
$wgSessionCacheType =
|
||||
$wgLanguageConverterCacheType = 'hash';
|
||||
// Uses db-replicated in DefaultSettings
|
||||
$wgMainStash = 'hash';
|
||||
// Use memory job queue
|
||||
$wgJobTypeConf = [
|
||||
'default' => [ 'class' => 'JobQueueMemory', 'order' => 'fifo' ],
|
||||
];
|
||||
|
||||
$wgUseDatabaseMessages = false; # Set for future resets
|
||||
|
||||
// Assume UTC for testing purposes
|
||||
$wgLocaltimezone = 'UTC';
|
||||
|
||||
$wgLocalisationCacheConf['storeClass'] = 'LCStoreNull';
|
||||
|
||||
// Generic MediaWiki\Session\SessionManager configuration for tests
|
||||
// We use CookieSessionProvider because things might be expecting
|
||||
// cookies to show up in a FauxRequest somewhere.
|
||||
$wgSessionProviders = [
|
||||
[
|
||||
'class' => MediaWiki\Session\CookieSessionProvider::class,
|
||||
'args' => [ [
|
||||
'priority' => 30,
|
||||
'callUserSetCookiesHook' => true,
|
||||
] ],
|
||||
],
|
||||
];
|
||||
|
||||
// Single-iteration PBKDF2 session secret derivation, for speed.
|
||||
$wgSessionPbkdf2Iterations = 1;
|
||||
|
||||
// Generic AuthManager configuration for testing
|
||||
$wgAuthManagerConfig = [
|
||||
'preauth' => [],
|
||||
'primaryauth' => [
|
||||
[
|
||||
'class' => MediaWiki\Auth\TemporaryPasswordPrimaryAuthenticationProvider::class,
|
||||
'args' => [ [
|
||||
'authoritative' => false,
|
||||
] ],
|
||||
],
|
||||
[
|
||||
'class' => MediaWiki\Auth\LocalPasswordPrimaryAuthenticationProvider::class,
|
||||
'args' => [ [
|
||||
'authoritative' => true,
|
||||
] ],
|
||||
],
|
||||
],
|
||||
'secondaryauth' => [],
|
||||
];
|
||||
$wgAuth = new MediaWiki\Auth\AuthManagerAuthPlugin();
|
||||
|
||||
// Bug 44192 Do not attempt to send a real e-mail
|
||||
Hooks::clear( 'AlternateUserMailer' );
|
||||
Hooks::register(
|
||||
'AlternateUserMailer',
|
||||
function () {
|
||||
return false;
|
||||
}
|
||||
);
|
||||
// xdebug's default of 100 is too low for MediaWiki
|
||||
ini_set( 'xdebug.max_nesting_level', 1000 );
|
||||
|
||||
// Bug T116683 serialize_precision of 100
|
||||
// may break testing against floating point values
|
||||
// treated with PHP's serialize()
|
||||
ini_set( 'serialize_precision', 17 );
|
||||
|
||||
// TODO: we should call MediaWikiTestCase::prepareServices( new GlobalVarConfig() ) here.
|
||||
// But PHPUnit may not be loaded yet, so we have to wait until just
|
||||
// before PHPUnit_TextUI_Command::main() is executed.
|
||||
TestSetup::applyInitialConfig();
|
||||
}
|
||||
|
||||
public function execute() {
|
||||
|
|
|
|||
|
|
@ -20,12 +20,14 @@
|
|||
<testsuites>
|
||||
<testsuite name="includes">
|
||||
<directory>includes</directory>
|
||||
<!-- Parser tests must be invoked via their suite -->
|
||||
<exclude>includes/parser/ParserIntegrationTest.php</exclude>
|
||||
</testsuite>
|
||||
<testsuite name="languages">
|
||||
<directory>languages</directory>
|
||||
</testsuite>
|
||||
<testsuite name="parsertests">
|
||||
<file>suites/ParserTestTopLevelSuite.php</file>
|
||||
<file>suites/CoreParserTestSuite.php</file>
|
||||
<file>suites/ExtensionsParserTestSuite.php</file>
|
||||
</testsuite>
|
||||
<testsuite name="skins">
|
||||
|
|
@ -55,7 +57,6 @@
|
|||
<exclude>
|
||||
<group>Utility</group>
|
||||
<group>Broken</group>
|
||||
<group>ParserFuzz</group>
|
||||
<group>Stub</group>
|
||||
</exclude>
|
||||
</groups>
|
||||
|
|
|
|||
10
tests/phpunit/suites/CoreParserTestSuite.php
Normal file
10
tests/phpunit/suites/CoreParserTestSuite.php
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
class CoreParserTestSuite extends PHPUnit_Framework_TestSuite {
|
||||
|
||||
public static function suite() {
|
||||
return ParserTestTopLevelSuite::suite( ParserTestTopLevelSuite::CORE_ONLY );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
28
tests/phpunit/suites/ParserTestFileSuite.php
Normal file
28
tests/phpunit/suites/ParserTestFileSuite.php
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* This is the suite class for running tests within a single .txt source file.
|
||||
* It is not invoked directly. Use --filter to select files, or
|
||||
* use parserTests.php.
|
||||
*/
|
||||
class ParserTestFileSuite extends PHPUnit_Framework_TestSuite {
|
||||
private $ptRunner;
|
||||
private $ptFileName;
|
||||
private $ptFileInfo;
|
||||
|
||||
function __construct( $runner, $name, $fileName ) {
|
||||
parent::__construct( $name );
|
||||
$this->ptRunner = $runner;
|
||||
$this->ptFileName = $fileName;
|
||||
$this->ptFileInfo = TestFileReader::read( $this->ptFileName );
|
||||
|
||||
foreach ( $this->ptFileInfo['tests'] as $test ) {
|
||||
$this->addTest( new ParserIntegrationTest( $runner, $fileName, $test ),
|
||||
[ 'Database', 'Parser' ] );
|
||||
}
|
||||
}
|
||||
|
||||
function setUp() {
|
||||
$this->ptRunner->addArticles( $this->ptFileInfo[ 'articles'] );
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
<?php
|
||||
require_once __DIR__ . '/../includes/parser/ParserIntegrationTest.php';
|
||||
|
||||
/**
|
||||
* The UnitTest must be either a class that inherits from MediaWikiTestCase
|
||||
|
|
@ -10,7 +9,12 @@ require_once __DIR__ . '/../includes/parser/ParserIntegrationTest.php';
|
|||
* @group ParserTests
|
||||
* @group Database
|
||||
*/
|
||||
class ParserTestTopLevelSuite {
|
||||
class ParserTestTopLevelSuite extends PHPUnit_Framework_TestSuite {
|
||||
/** @var ParserTestRunner */
|
||||
private $ptRunner;
|
||||
|
||||
/** @var ScopedCallback */
|
||||
private $ptTeardownScope;
|
||||
|
||||
/**
|
||||
* @defgroup filtering_constants Filtering constants
|
||||
|
|
@ -52,6 +56,15 @@ class ParserTestTopLevelSuite {
|
|||
* @return PHPUnit_Framework_TestSuite
|
||||
*/
|
||||
public static function suite( $flags = self::CORE_ONLY ) {
|
||||
return new self( $flags );
|
||||
}
|
||||
|
||||
function __construct( $flags ) {
|
||||
parent::__construct();
|
||||
|
||||
$this->ptRecorder = new PhpunitTestRecorder;
|
||||
$this->ptRunner = new ParserTestRunner( $this->ptRecorder );
|
||||
|
||||
if ( is_string( $flags ) ) {
|
||||
$flags = self::CORE_ONLY;
|
||||
}
|
||||
|
|
@ -83,7 +96,6 @@ class ParserTestTopLevelSuite {
|
|||
self::debug( 'parser tests files: '
|
||||
. implode( ' ', $filesToTest ) );
|
||||
|
||||
$suite = new PHPUnit_Framework_TestSuite;
|
||||
$testList = [];
|
||||
$counter = 0;
|
||||
foreach ( $filesToTest as $fileName ) {
|
||||
|
|
@ -93,7 +105,6 @@ class ParserTestTopLevelSuite {
|
|||
// things, which is good enough for our purposes.
|
||||
$extensionName = basename( dirname( $fileName ) );
|
||||
$testsName = $extensionName . '__' . basename( $fileName, '.txt' );
|
||||
$escapedFileName = strtr( $fileName, [ "'" => "\\'", '\\' => '\\\\' ] );
|
||||
$parserTestClassName = ucfirst( $testsName );
|
||||
|
||||
// Official spec for class names: http://php.net/manual/en/language.oop5.basic.php
|
||||
|
|
@ -102,30 +113,38 @@ class ParserTestTopLevelSuite {
|
|||
preg_replace( '/[^a-zA-Z0-9_\x7f-\xff]/', '_', $parserTestClassName );
|
||||
|
||||
if ( isset( $testList[$parserTestClassName] ) ) {
|
||||
// If a conflict happens, gives a very unclear fatal.
|
||||
// So as a last ditch effort to prevent that eventuality, if there
|
||||
// is a conflict, append a number.
|
||||
// If there is a conflict, append a number.
|
||||
$counter++;
|
||||
$parserTestClassName .= $counter;
|
||||
}
|
||||
$testList[$parserTestClassName] = true;
|
||||
$parserTestClassDefinition = <<<EOT
|
||||
/**
|
||||
* @group Database
|
||||
* @group Parser
|
||||
* @group ParserTests
|
||||
* @group ParserTests_$parserTestClassName
|
||||
*/
|
||||
class $parserTestClassName extends ParserIntegrationTest {
|
||||
protected \$file = '$escapedFileName';
|
||||
}
|
||||
EOT;
|
||||
|
||||
eval( $parserTestClassDefinition );
|
||||
// Previously we actually created a class here, with eval(). We now
|
||||
// just override the name.
|
||||
|
||||
self::debug( "Adding test class $parserTestClassName" );
|
||||
$suite->addTestSuite( $parserTestClassName );
|
||||
$this->addTest( new ParserTestFileSuite(
|
||||
$this->ptRunner, $parserTestClassName, $fileName ) );
|
||||
}
|
||||
}
|
||||
|
||||
public function setUp() {
|
||||
wfDebug( __METHOD__ );
|
||||
$db = wfGetDB( DB_MASTER );
|
||||
$type = $db->getType();
|
||||
$prefix = $type === 'oracle' ?
|
||||
MediaWikiTestCase::ORA_DB_PREFIX : MediaWikiTestCase::DB_PREFIX;
|
||||
MediaWikiTestCase::setupTestDB( $db, $prefix );
|
||||
$teardown = $this->ptRunner->setDatabase( $db );
|
||||
$teardown = $this->ptRunner->setupUploads( $teardown );
|
||||
$this->ptTeardownScope = $teardown;
|
||||
}
|
||||
|
||||
public function tearDown() {
|
||||
wfDebug( __METHOD__ );
|
||||
if ( $this->ptTeardownScope ) {
|
||||
ScopedCallback::consume( $this->ptTeardownScope );
|
||||
}
|
||||
return $suite;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Reference in a new issue