Revert r108358. It's a good fellow and we like to keep it.
This commit is contained in:
parent
f97b70db2d
commit
4fc7769069
6 changed files with 1725 additions and 66 deletions
|
|
@ -928,10 +928,15 @@ $wgAutoloadLocalClasses = array(
|
|||
'DummyTermColorer' => 'maintenance/term/MWTerm.php',
|
||||
|
||||
# tests
|
||||
'DbTestPreviewer' => 'tests/testHelpers.inc',
|
||||
'DbTestRecorder' => 'tests/testHelpers.inc',
|
||||
'TestFileIterator' => 'tests/testHelpers.inc',
|
||||
'TestRecorder' => 'tests/testHelpers.inc',
|
||||
|
||||
# tests/parser
|
||||
'ParserTest' => 'tests/parser/parserTest.inc',
|
||||
'ParserTestParserHook' => 'tests/parser/parserTestsParserHook.php',
|
||||
'ParserTestStaticParserHook' => 'tests/parser/parserTestsStaticParserHook.php',
|
||||
|
||||
# tests/selenium
|
||||
'Selenium' => 'tests/selenium/Selenium.php',
|
||||
|
|
|
|||
1317
tests/parser/parserTest.inc
Normal file
1317
tests/parser/parserTest.inc
Normal file
File diff suppressed because it is too large
Load diff
92
tests/parserTests.php
Normal file
92
tests/parserTests.php
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
<?php
|
||||
/**
|
||||
* MediaWiki parser test suite
|
||||
*
|
||||
* Copyright © 2004 Brion Vibber <brion@pobox.com>
|
||||
* http://www.mediawiki.org/
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
$otions = array( 'quick', 'color', 'quiet', 'help', 'show-output', 'record', 'run-disabled' );
|
||||
$optionsWithArgs = array( 'regex', 'filter', 'seed', 'setversion' );
|
||||
|
||||
require_once( dirname( __FILE__ ) . '/../maintenance/commandLine.inc' );
|
||||
|
||||
if ( isset( $options['help'] ) ) {
|
||||
echo <<<ENDS
|
||||
MediaWiki $wgVersion parser test suite
|
||||
Usage: php parserTests.php [options...]
|
||||
|
||||
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
|
||||
--fuzz Do a fuzz test instead of a normal test
|
||||
--seed <n> Start the fuzz test from the specified seed
|
||||
--help Show this help message
|
||||
--run-disabled run disabled tests
|
||||
|
||||
ENDS;
|
||||
exit( 0 );
|
||||
}
|
||||
|
||||
# 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" );
|
||||
}
|
||||
}
|
||||
|
||||
# There is a convention that the parser should never
|
||||
# refer to $wgTitle directly, but instead use the title
|
||||
# passed to it.
|
||||
$wgTitle = Title::newFromText( 'Parser test script do not use' );
|
||||
$tester = new ParserTest($options);
|
||||
|
||||
if ( isset( $options['file'] ) ) {
|
||||
$files = array( $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();
|
||||
echo( "This is MediaWiki version {$version}.\n\n" );
|
||||
|
||||
if ( isset( $options['fuzz'] ) ) {
|
||||
$tester->fuzzTest( $files );
|
||||
} else {
|
||||
$ok = $tester->runTestsFromFiles( $files );
|
||||
exit ( $ok ? 0 : 1 );
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ class ParserOptionsTest extends MediaWikiTestCase {
|
|||
private $pcache;
|
||||
|
||||
function setUp() {
|
||||
ParserTest::setUp(); //reuse setup from parser tests
|
||||
global $wgContLang, $wgUser, $wgLanguageCode;
|
||||
$wgContLang = Language::factory( $wgLanguageCode );
|
||||
$this->popts = new ParserOptions( $wgUser );
|
||||
|
|
|
|||
|
|
@ -323,7 +323,7 @@ class NewParserTest extends MediaWikiTestCase {
|
|||
global $wgHooks;
|
||||
|
||||
$wgHooks['ParserTestParser'][] = 'ParserTestParserHook::setup';
|
||||
$wgHooks['ParserGetVariableValueTs'][] = 'NewParserTest::getFakeTimestamp';
|
||||
$wgHooks['ParserGetVariableValueTs'][] = 'ParserTest::getFakeTimestamp';
|
||||
|
||||
MagicWord::clearCache();
|
||||
RepoGroup::destroySingleton();
|
||||
|
|
@ -708,48 +708,9 @@ class NewParserTest extends MediaWikiTestCase {
|
|||
|
||||
foreach ( self::$articles as $name => $info ) {
|
||||
list( $text, $line ) = $info;
|
||||
self::injectArticle( $name, $text, $line, 'ignoreduplicate' );
|
||||
ParserTest::addArticle( $name, $text, $line, 'ignoreduplicate' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert a temporary test article
|
||||
* @param $name String: the title, including any prefix
|
||||
* @param $text String: the article text
|
||||
* @param $line Integer: the input line number, for reporting errors
|
||||
* @param $ignoreDuplicate Boolean: whether to silently ignore duplicate pages
|
||||
*/
|
||||
private static function injectArticle( $name, $text, $line = 'unknown', $ignoreDuplicate = '' ) {
|
||||
global $wgCapitalLinks;
|
||||
|
||||
$oldCapitalLinks = $wgCapitalLinks;
|
||||
$wgCapitalLinks = true; // We only need this from SetupGlobals() See r70917#c8637
|
||||
|
||||
$text = TestFileIterator::chomp( $text );
|
||||
$name = TestFileIterator::chomp( $name );
|
||||
|
||||
$title = Title::newFromText( $name );
|
||||
|
||||
if ( is_null( $title ) ) {
|
||||
throw new MWException( "invalid title '$name' at line $line\n" );
|
||||
}
|
||||
|
||||
$page = WikiPage::factory( $title );
|
||||
$page->loadPageData( 'fromdbmaster' );
|
||||
|
||||
if ( $page->exists() ) {
|
||||
if ( $ignoreDuplicate == 'ignoreduplicate' ) {
|
||||
return;
|
||||
} else {
|
||||
throw new MWException( "duplicate article '$name' at line $line\n" );
|
||||
}
|
||||
}
|
||||
|
||||
$page->doEdit( $text, '', EDIT_NEW );
|
||||
|
||||
$wgCapitalLinks = $oldCapitalLinks;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Steal a callback function from the primary parser, save it for
|
||||
|
|
@ -884,9 +845,4 @@ class NewParserTest extends MediaWikiTestCase {
|
|||
return $default;
|
||||
}
|
||||
}
|
||||
|
||||
public static function getFakeTimestamp( &$parser, &$ts ) {
|
||||
$ts = 123;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,306 @@
|
|||
<?php
|
||||
|
||||
class TestRecorder {
|
||||
var $parent;
|
||||
var $term;
|
||||
|
||||
function __construct( $parent ) {
|
||||
$this->parent = $parent;
|
||||
$this->term = $parent->term;
|
||||
}
|
||||
|
||||
function start() {
|
||||
$this->total = 0;
|
||||
$this->success = 0;
|
||||
}
|
||||
|
||||
function record( $test, $result ) {
|
||||
$this->total++;
|
||||
$this->success += ( $result ? 1 : 0 );
|
||||
}
|
||||
|
||||
function end() {
|
||||
// dummy
|
||||
}
|
||||
|
||||
function report() {
|
||||
if ( $this->total > 0 ) {
|
||||
$this->reportPercentage( $this->success, $this->total );
|
||||
} else {
|
||||
throw new MWException( "No tests found.\n" );
|
||||
}
|
||||
}
|
||||
|
||||
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 );
|
||||
}
|
||||
}
|
||||
|
||||
class DbTestPreviewer extends TestRecorder {
|
||||
protected $lb; // /< Database load balancer
|
||||
protected $db; // /< Database connection to the main DB
|
||||
protected $curRun; // /< run ID number for the current run
|
||||
protected $prevRun; // /< run ID number for the previous run, if any
|
||||
protected $results; // /< Result array
|
||||
|
||||
/**
|
||||
* This should be called before the table prefix is changed
|
||||
*/
|
||||
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 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up result recording; insert a record for the run with the date
|
||||
* and all that fun stuff
|
||||
*/
|
||||
function start() {
|
||||
parent::start();
|
||||
|
||||
if ( ! $this->db->tableExists( 'testrun', __METHOD__ )
|
||||
|| ! $this->db->tableExists( 'testitem', __METHOD__ ) )
|
||||
{
|
||||
print "WARNING> `testrun` table not found in database.\n";
|
||||
$this->prevRun = false;
|
||||
} else {
|
||||
// We'll make comparisons against the previous run later...
|
||||
$this->prevRun = $this->db->selectField( 'testrun', 'MAX(tr_id)' );
|
||||
}
|
||||
|
||||
$this->results = array();
|
||||
}
|
||||
|
||||
function record( $test, $result ) {
|
||||
parent::record( $test, $result );
|
||||
$this->results[$test] = $result;
|
||||
}
|
||||
|
||||
function report() {
|
||||
if ( $this->prevRun ) {
|
||||
// f = fail, p = pass, n = nonexistent
|
||||
// codes show before then after
|
||||
$table = array(
|
||||
'fp' => 'previously failing test(s) now PASSING! :)',
|
||||
'pn' => 'previously PASSING test(s) removed o_O',
|
||||
'np' => 'new PASSING test(s) :)',
|
||||
|
||||
'pf' => 'previously passing test(s) now FAILING! :(',
|
||||
'fn' => 'previously FAILING test(s) removed O_o',
|
||||
'nf' => 'new FAILING test(s) :(',
|
||||
'ff' => 'still FAILING test(s) :(',
|
||||
);
|
||||
|
||||
$prevResults = array();
|
||||
|
||||
$res = $this->db->select( 'testitem', array( 'ti_name', 'ti_success' ),
|
||||
array( 'ti_run' => $this->prevRun ), __METHOD__ );
|
||||
|
||||
foreach ( $res as $row ) {
|
||||
if ( !$this->parent->regex
|
||||
|| preg_match( "/{$this->parent->regex}/i", $row->ti_name ) )
|
||||
{
|
||||
$prevResults[$row->ti_name] = $row->ti_success;
|
||||
}
|
||||
}
|
||||
|
||||
$combined = array_keys( $this->results + $prevResults );
|
||||
|
||||
# Determine breakdown by change type
|
||||
$breakdown = array();
|
||||
foreach ( $combined as $test ) {
|
||||
if ( !isset( $prevResults[$test] ) ) {
|
||||
$before = 'n';
|
||||
} elseif ( $prevResults[$test] == 1 ) {
|
||||
$before = 'p';
|
||||
} else /* if ( $prevResults[$test] == 0 )*/ {
|
||||
$before = 'f';
|
||||
}
|
||||
|
||||
if ( !isset( $this->results[$test] ) ) {
|
||||
$after = 'n';
|
||||
} elseif ( $this->results[$test] == 1 ) {
|
||||
$after = 'p';
|
||||
} else /*if ( $this->results[$test] == 0 ) */ {
|
||||
$after = 'f';
|
||||
}
|
||||
|
||||
$code = $before . $after;
|
||||
|
||||
if ( isset( $table[$code] ) ) {
|
||||
$breakdown[$code][$test] = $this->getTestStatusInfo( $test, $after );
|
||||
}
|
||||
}
|
||||
|
||||
# Write out results
|
||||
foreach ( $table as $code => $label ) {
|
||||
if ( !empty( $breakdown[$code] ) ) {
|
||||
$count = count( $breakdown[$code] );
|
||||
printf( "\n%4d %s\n", $count, $label );
|
||||
|
||||
foreach ( $breakdown[$code] as $differing_test_name => $statusInfo ) {
|
||||
print " * $differing_test_name [$statusInfo]\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
print "No previous test runs to compare against.\n";
|
||||
}
|
||||
|
||||
print "\n";
|
||||
parent::report();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string giving information about when a test last had a status change.
|
||||
* Could help to track down when regressions were introduced, as distinct from tests
|
||||
* which have never passed (which are more change requests than regressions).
|
||||
*/
|
||||
private function getTestStatusInfo( $testname, $after ) {
|
||||
// If we're looking at a test that has just been removed, then say when it first appeared.
|
||||
if ( $after == 'n' ) {
|
||||
$changedRun = $this->db->selectField ( 'testitem',
|
||||
'MIN(ti_run)',
|
||||
array( 'ti_name' => $testname ),
|
||||
__METHOD__ );
|
||||
$appear = $this->db->selectRow ( 'testrun',
|
||||
array( 'tr_date', 'tr_mw_version' ),
|
||||
array( 'tr_id' => $changedRun ),
|
||||
__METHOD__ );
|
||||
|
||||
return "First recorded appearance: "
|
||||
. date( "d-M-Y H:i:s", strtotime ( $appear->tr_date ) )
|
||||
. ", " . $appear->tr_mw_version;
|
||||
}
|
||||
|
||||
// Otherwise, this test has previous recorded results.
|
||||
// See when this test last had a different result to what we're seeing now.
|
||||
$conds = array(
|
||||
'ti_name' => $testname,
|
||||
'ti_success' => ( $after == 'f' ? "1" : "0" ) );
|
||||
|
||||
if ( $this->curRun ) {
|
||||
$conds[] = "ti_run != " . $this->db->addQuotes ( $this->curRun );
|
||||
}
|
||||
|
||||
$changedRun = $this->db->selectField ( 'testitem', 'MAX(ti_run)', $conds, __METHOD__ );
|
||||
|
||||
// If no record of ever having had a different result.
|
||||
if ( is_null ( $changedRun ) ) {
|
||||
if ( $after == "f" ) {
|
||||
return "Has never passed";
|
||||
} else {
|
||||
return "Has never failed";
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, we're looking at a test whose status has changed.
|
||||
// (i.e. it used to work, but now doesn't; or used to fail, but is now fixed.)
|
||||
// In this situation, give as much info as we can as to when it changed status.
|
||||
$pre = $this->db->selectRow ( 'testrun',
|
||||
array( 'tr_date', 'tr_mw_version' ),
|
||||
array( 'tr_id' => $changedRun ),
|
||||
__METHOD__ );
|
||||
$post = $this->db->selectRow ( 'testrun',
|
||||
array( 'tr_date', 'tr_mw_version' ),
|
||||
array( "tr_id > " . $this->db->addQuotes ( $changedRun ) ),
|
||||
__METHOD__,
|
||||
array( "LIMIT" => 1, "ORDER BY" => 'tr_id' )
|
||||
);
|
||||
|
||||
if ( $post ) {
|
||||
$postDate = date( "d-M-Y H:i:s", strtotime ( $post->tr_date ) ) . ", {$post->tr_mw_version}";
|
||||
} else {
|
||||
$postDate = 'now';
|
||||
}
|
||||
|
||||
return ( $after == "f" ? "Introduced" : "Fixed" ) . " between "
|
||||
. date( "d-M-Y H:i:s", strtotime ( $pre->tr_date ) ) . ", " . $pre->tr_mw_version
|
||||
. " and $postDate";
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Commit transaction and clean up for result recording
|
||||
*/
|
||||
function end() {
|
||||
$this->lb->commitMasterChanges();
|
||||
$this->lb->closeAll();
|
||||
parent::end();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class DbTestRecorder extends DbTestPreviewer {
|
||||
var $version;
|
||||
|
||||
/**
|
||||
* Set up result recording; insert a record for the run with the date
|
||||
* and all that fun stuff
|
||||
*/
|
||||
function start() {
|
||||
$this->db->begin();
|
||||
|
||||
if ( ! $this->db->tableExists( 'testrun' )
|
||||
|| ! $this->db->tableExists( 'testitem' ) )
|
||||
{
|
||||
print "WARNING> `testrun` table not found in database. Trying to create table.\n";
|
||||
$this->db->sourceFile( $this->db->patchPath( 'patch-testrun.sql' ) );
|
||||
echo "OK, resuming.\n";
|
||||
}
|
||||
|
||||
parent::start();
|
||||
|
||||
$this->db->insert( 'testrun',
|
||||
array(
|
||||
'tr_date' => $this->db->timestamp(),
|
||||
'tr_mw_version' => $this->version,
|
||||
'tr_php_version' => phpversion(),
|
||||
'tr_db_version' => $this->db->getServerVersion(),
|
||||
'tr_uname' => php_uname()
|
||||
),
|
||||
__METHOD__ );
|
||||
if ( $this->db->getType() === 'postgres' ) {
|
||||
$this->curRun = $this->db->currentSequenceValue( 'testrun_id_seq' );
|
||||
} else {
|
||||
$this->curRun = $this->db->insertId();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Record an individual test item's success or failure to the db
|
||||
*
|
||||
* @param $test String
|
||||
* @param $result Boolean
|
||||
*/
|
||||
function record( $test, $result ) {
|
||||
parent::record( $test, $result );
|
||||
|
||||
$this->db->insert( 'testitem',
|
||||
array(
|
||||
'ti_run' => $this->curRun,
|
||||
'ti_name' => $test,
|
||||
'ti_success' => $result ? 1 : 0,
|
||||
),
|
||||
__METHOD__ );
|
||||
}
|
||||
}
|
||||
|
||||
class TestFileIterator implements Iterator {
|
||||
private $file;
|
||||
private $fh;
|
||||
|
|
@ -76,7 +377,7 @@ class TestFileIterator implements Iterator {
|
|||
$this->checkSection( 'text' );
|
||||
$this->checkSection( 'article' );
|
||||
|
||||
$this->parserTest->addArticle( self::chomp( $this->sectionData['article'] ), $this->sectionData['text'], $this->lineNum );
|
||||
$this->parserTest->addArticle( ParserTest::chomp( $this->sectionData['article'] ), $this->sectionData['text'], $this->lineNum );
|
||||
|
||||
$this->clearSection();
|
||||
|
||||
|
|
@ -148,11 +449,11 @@ class TestFileIterator implements Iterator {
|
|||
}
|
||||
|
||||
$this->test = array(
|
||||
'test' => self::chomp( $this->sectionData['test'] ),
|
||||
'input' => self::chomp( $this->sectionData['input'] ),
|
||||
'result' => self::chomp( $this->sectionData['result'] ),
|
||||
'options' => self::chomp( $this->sectionData['options'] ),
|
||||
'config' => self::chomp( $this->sectionData['config'] ),
|
||||
'test' => ParserTest::chomp( $this->sectionData['test'] ),
|
||||
'input' => ParserTest::chomp( $this->sectionData['input'] ),
|
||||
'result' => ParserTest::chomp( $this->sectionData['result'] ),
|
||||
'options' => ParserTest::chomp( $this->sectionData['options'] ),
|
||||
'config' => ParserTest::chomp( $this->sectionData['config'] ),
|
||||
);
|
||||
|
||||
return true;
|
||||
|
|
@ -185,19 +486,6 @@ class TestFileIterator implements Iterator {
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove last character if it is a newline
|
||||
* @group utility
|
||||
*/
|
||||
public static function chomp( $s ) {
|
||||
if ( substr( $s, -1 ) === "\n" ) {
|
||||
return substr( $s, 0, -1 );
|
||||
}
|
||||
else {
|
||||
return $s;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify the current section data has some value for the given token
|
||||
* name (first parameter).
|
||||
|
|
@ -225,7 +513,7 @@ class TestFileIterator implements Iterator {
|
|||
}
|
||||
|
||||
/**
|
||||
* A class to delay execution of a parser test hooks.
|
||||
* A class to delay execution of a parser test hooks.
|
||||
*/
|
||||
class DelayedParserTest {
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue