Revert r108358. It's a good fellow and we like to keep it.

This commit is contained in:
Platonides 2012-01-09 22:33:00 +00:00
parent f97b70db2d
commit 4fc7769069
6 changed files with 1725 additions and 66 deletions

View file

@ -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

File diff suppressed because it is too large Load diff

92
tests/parserTests.php Normal file
View 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 );
}

View file

@ -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 );

View file

@ -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;
}
}

View file

@ -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 {