r108358 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r108357‎ | r108358 | r108359 >
Date:17:28, 8 January 2012
Author:demon
Status:reverted (Comments)
Tags:
Comment:
* Drop old parserTests.php way of running parser tests. You can run parser tests via --group Parser or specifying
the includes/parser/MediaWikiParserTest.php test case
* Drop now unused various test recorder options -- phpunit outputs its data in lots of machine-readable formats, use those if you need to work with
test data
* There's still a lot of duplication between NewParserTest::setUp() and MediaWikiTestCase, but hey one step at a time ;-)
* All tests pass for me (make phpunit && make parser)
Modified paths:
  • /trunk/phase3/includes/AutoLoader.php (modified) (history)
  • /trunk/phase3/tests/parser/parserTest.inc (deleted) (history)
  • /trunk/phase3/tests/parserTests.php (deleted) (history)
  • /trunk/phase3/tests/phpunit/includes/ParserOptionsTest.php (modified) (history)
  • /trunk/phase3/tests/phpunit/includes/parser/NewParserTest.php (modified) (history)
  • /trunk/phase3/tests/testHelpers.inc (modified) (history)

Diff [purge]

Index: trunk/phase3/tests/parserTests.php
@@ -1,92 +0,0 @@
2 -<?php
3 -/**
4 - * MediaWiki parser test suite
5 - *
6 - * Copyright © 2004 Brion Vibber <brion@pobox.com>
7 - * http://www.mediawiki.org/
8 - *
9 - * This program is free software; you can redistribute it and/or modify
10 - * it under the terms of the GNU General Public License as published by
11 - * the Free Software Foundation; either version 2 of the License, or
12 - * (at your option) any later version.
13 - *
14 - * This program is distributed in the hope that it will be useful,
15 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 - * GNU General Public License for more details.
18 - *
19 - * You should have received a copy of the GNU General Public License along
20 - * with this program; if not, write to the Free Software Foundation, Inc.,
21 - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22 - * http://www.gnu.org/copyleft/gpl.html
23 - *
24 - * @file
25 - * @ingroup Testing
26 - */
27 -
28 -$otions = array( 'quick', 'color', 'quiet', 'help', 'show-output', 'record', 'run-disabled' );
29 -$optionsWithArgs = array( 'regex', 'filter', 'seed', 'setversion' );
30 -
31 -require_once( dirname( __FILE__ ) . '/../maintenance/commandLine.inc' );
32 -
33 -if ( isset( $options['help'] ) ) {
34 - echo <<<ENDS
35 -MediaWiki $wgVersion parser test suite
36 -Usage: php parserTests.php [options...]
37 -
38 -Options:
39 - --quick Suppress diff output of failed tests
40 - --quiet Suppress notification of passed tests (shows only failed tests)
41 - --show-output Show expected and actual output
42 - --color[=yes|no] Override terminal detection and force color output on or off
43 - use wgCommandLineDarkBg = true; if your term is dark
44 - --regex Only run tests whose descriptions which match given regex
45 - --filter Alias for --regex
46 - --file=<testfile> Run test cases from a custom file instead of parserTests.txt
47 - --record Record tests in database
48 - --compare Compare with recorded results, without updating the database.
49 - --setversion When using --record, set the version string to use (useful
50 - with git-svn so that you can get the exact revision)
51 - --keep-uploads Re-use the same upload directory for each test, don't delete it
52 - --fuzz Do a fuzz test instead of a normal test
53 - --seed <n> Start the fuzz test from the specified seed
54 - --help Show this help message
55 - --run-disabled run disabled tests
56 -
57 -ENDS;
58 - exit( 0 );
59 -}
60 -
61 -# Cases of weird db corruption were encountered when running tests on earlyish
62 -# versions of SQLite
63 -if ( $wgDBtype == 'sqlite' ) {
64 - $db = wfGetDB( DB_MASTER );
65 - $version = $db->getServerVersion();
66 - if ( version_compare( $version, '3.6' ) < 0 ) {
67 - die( "Parser tests require SQLite version 3.6 or later, you have $version\n" );
68 - }
69 -}
70 -
71 -# There is a convention that the parser should never
72 -# refer to $wgTitle directly, but instead use the title
73 -# passed to it.
74 -$wgTitle = Title::newFromText( 'Parser test script do not use' );
75 -$tester = new ParserTest($options);
76 -
77 -if ( isset( $options['file'] ) ) {
78 - $files = array( $options['file'] );
79 -} else {
80 - // Default parser tests and any set from extensions or local config
81 - $files = $wgParserTestFiles;
82 -}
83 -
84 -# Print out software version to assist with locating regressions
85 -$version = SpecialVersion::getVersion();
86 -echo( "This is MediaWiki version {$version}.\n\n" );
87 -
88 -if ( isset( $options['fuzz'] ) ) {
89 - $tester->fuzzTest( $files );
90 -} else {
91 - $ok = $tester->runTestsFromFiles( $files );
92 - exit ( $ok ? 0 : 1 );
93 -}
Index: trunk/phase3/tests/phpunit/includes/parser/NewParserTest.php
@@ -323,7 +323,7 @@
324324 global $wgHooks;
325325
326326 $wgHooks['ParserTestParser'][] = 'ParserTestParserHook::setup';
327 - $wgHooks['ParserGetVariableValueTs'][] = 'ParserTest::getFakeTimestamp';
 327+ $wgHooks['ParserGetVariableValueTs'][] = 'NewParserTest::getFakeTimestamp';
328328
329329 MagicWord::clearCache();
330330 RepoGroup::destroySingleton();
@@ -708,10 +708,49 @@
709709
710710 foreach ( self::$articles as $name => $info ) {
711711 list( $text, $line ) = $info;
712 - ParserTest::addArticle( $name, $text, $line, 'ignoreduplicate' );
 712+ self::injectArticle( $name, $text, $line, 'ignoreduplicate' );
713713 }
714714 }
 715+
 716+ /**
 717+ * Insert a temporary test article
 718+ * @param $name String: the title, including any prefix
 719+ * @param $text String: the article text
 720+ * @param $line Integer: the input line number, for reporting errors
 721+ * @param $ignoreDuplicate Boolean: whether to silently ignore duplicate pages
 722+ */
 723+ private static function injectArticle( $name, $text, $line = 'unknown', $ignoreDuplicate = '' ) {
 724+ global $wgCapitalLinks;
715725
 726+ $oldCapitalLinks = $wgCapitalLinks;
 727+ $wgCapitalLinks = true; // We only need this from SetupGlobals() See r70917#c8637
 728+
 729+ $text = TestFileIterator::chomp( $text );
 730+ $name = TestFileIterator::chomp( $name );
 731+
 732+ $title = Title::newFromText( $name );
 733+
 734+ if ( is_null( $title ) ) {
 735+ throw new MWException( "invalid title '$name' at line $line\n" );
 736+ }
 737+
 738+ $page = WikiPage::factory( $title );
 739+ $page->loadPageData( 'fromdbmaster' );
 740+
 741+ if ( $page->exists() ) {
 742+ if ( $ignoreDuplicate == 'ignoreduplicate' ) {
 743+ return;
 744+ } else {
 745+ throw new MWException( "duplicate article '$name' at line $line\n" );
 746+ }
 747+ }
 748+
 749+ $page->doEdit( $text, '', EDIT_NEW );
 750+
 751+ $wgCapitalLinks = $oldCapitalLinks;
 752+ }
 753+
 754+
716755 /**
717756 * Steal a callback function from the primary parser, save it for
718757 * application to our scary parser. If the hook is not installed,
@@ -849,4 +888,9 @@
850889 return $default;
851890 }
852891 }
 892+
 893+ public static function getFakeTimestamp( &$parser, &$ts ) {
 894+ $ts = 123;
 895+ return true;
 896+ }
853897 }
Index: trunk/phase3/tests/phpunit/includes/ParserOptionsTest.php
@@ -6,7 +6,6 @@
77 private $pcache;
88
99 function setUp() {
10 - ParserTest::setUp(); //reuse setup from parser tests
1110 global $wgContLang, $wgUser, $wgLanguageCode;
1211 $wgContLang = Language::factory( $wgLanguageCode );
1312 $this->popts = new ParserOptions( $wgUser );
Index: trunk/phase3/tests/testHelpers.inc
@@ -1,306 +1,5 @@
22 <?php
33
4 -class TestRecorder {
5 - var $parent;
6 - var $term;
7 -
8 - function __construct( $parent ) {
9 - $this->parent = $parent;
10 - $this->term = $parent->term;
11 - }
12 -
13 - function start() {
14 - $this->total = 0;
15 - $this->success = 0;
16 - }
17 -
18 - function record( $test, $result ) {
19 - $this->total++;
20 - $this->success += ( $result ? 1 : 0 );
21 - }
22 -
23 - function end() {
24 - // dummy
25 - }
26 -
27 - function report() {
28 - if ( $this->total > 0 ) {
29 - $this->reportPercentage( $this->success, $this->total );
30 - } else {
31 - throw new MWException( "No tests found.\n" );
32 - }
33 - }
34 -
35 - function reportPercentage( $success, $total ) {
36 - $ratio = wfPercent( 100 * $success / $total );
37 - print $this->term->color( 1 ) . "Passed $success of $total tests ($ratio)... ";
38 -
39 - if ( $success == $total ) {
40 - print $this->term->color( 32 ) . "ALL TESTS PASSED!";
41 - } else {
42 - $failed = $total - $success ;
43 - print $this->term->color( 31 ) . "$failed tests failed!";
44 - }
45 -
46 - print $this->term->reset() . "\n";
47 -
48 - return ( $success == $total );
49 - }
50 -}
51 -
52 -class DbTestPreviewer extends TestRecorder {
53 - protected $lb; // /< Database load balancer
54 - protected $db; // /< Database connection to the main DB
55 - protected $curRun; // /< run ID number for the current run
56 - protected $prevRun; // /< run ID number for the previous run, if any
57 - protected $results; // /< Result array
58 -
59 - /**
60 - * This should be called before the table prefix is changed
61 - */
62 - function __construct( $parent ) {
63 - parent::__construct( $parent );
64 -
65 - $this->lb = wfGetLBFactory()->newMainLB();
66 - // This connection will have the wiki's table prefix, not parsertest_
67 - $this->db = $this->lb->getConnection( DB_MASTER );
68 - }
69 -
70 - /**
71 - * Set up result recording; insert a record for the run with the date
72 - * and all that fun stuff
73 - */
74 - function start() {
75 - parent::start();
76 -
77 - if ( ! $this->db->tableExists( 'testrun', __METHOD__ )
78 - || ! $this->db->tableExists( 'testitem', __METHOD__ ) )
79 - {
80 - print "WARNING> `testrun` table not found in database.\n";
81 - $this->prevRun = false;
82 - } else {
83 - // We'll make comparisons against the previous run later...
84 - $this->prevRun = $this->db->selectField( 'testrun', 'MAX(tr_id)' );
85 - }
86 -
87 - $this->results = array();
88 - }
89 -
90 - function record( $test, $result ) {
91 - parent::record( $test, $result );
92 - $this->results[$test] = $result;
93 - }
94 -
95 - function report() {
96 - if ( $this->prevRun ) {
97 - // f = fail, p = pass, n = nonexistent
98 - // codes show before then after
99 - $table = array(
100 - 'fp' => 'previously failing test(s) now PASSING! :)',
101 - 'pn' => 'previously PASSING test(s) removed o_O',
102 - 'np' => 'new PASSING test(s) :)',
103 -
104 - 'pf' => 'previously passing test(s) now FAILING! :(',
105 - 'fn' => 'previously FAILING test(s) removed O_o',
106 - 'nf' => 'new FAILING test(s) :(',
107 - 'ff' => 'still FAILING test(s) :(',
108 - );
109 -
110 - $prevResults = array();
111 -
112 - $res = $this->db->select( 'testitem', array( 'ti_name', 'ti_success' ),
113 - array( 'ti_run' => $this->prevRun ), __METHOD__ );
114 -
115 - foreach ( $res as $row ) {
116 - if ( !$this->parent->regex
117 - || preg_match( "/{$this->parent->regex}/i", $row->ti_name ) )
118 - {
119 - $prevResults[$row->ti_name] = $row->ti_success;
120 - }
121 - }
122 -
123 - $combined = array_keys( $this->results + $prevResults );
124 -
125 - # Determine breakdown by change type
126 - $breakdown = array();
127 - foreach ( $combined as $test ) {
128 - if ( !isset( $prevResults[$test] ) ) {
129 - $before = 'n';
130 - } elseif ( $prevResults[$test] == 1 ) {
131 - $before = 'p';
132 - } else /* if ( $prevResults[$test] == 0 )*/ {
133 - $before = 'f';
134 - }
135 -
136 - if ( !isset( $this->results[$test] ) ) {
137 - $after = 'n';
138 - } elseif ( $this->results[$test] == 1 ) {
139 - $after = 'p';
140 - } else /*if ( $this->results[$test] == 0 ) */ {
141 - $after = 'f';
142 - }
143 -
144 - $code = $before . $after;
145 -
146 - if ( isset( $table[$code] ) ) {
147 - $breakdown[$code][$test] = $this->getTestStatusInfo( $test, $after );
148 - }
149 - }
150 -
151 - # Write out results
152 - foreach ( $table as $code => $label ) {
153 - if ( !empty( $breakdown[$code] ) ) {
154 - $count = count( $breakdown[$code] );
155 - printf( "\n%4d %s\n", $count, $label );
156 -
157 - foreach ( $breakdown[$code] as $differing_test_name => $statusInfo ) {
158 - print " * $differing_test_name [$statusInfo]\n";
159 - }
160 - }
161 - }
162 - } else {
163 - print "No previous test runs to compare against.\n";
164 - }
165 -
166 - print "\n";
167 - parent::report();
168 - }
169 -
170 - /**
171 - * Returns a string giving information about when a test last had a status change.
172 - * Could help to track down when regressions were introduced, as distinct from tests
173 - * which have never passed (which are more change requests than regressions).
174 - */
175 - private function getTestStatusInfo( $testname, $after ) {
176 - // If we're looking at a test that has just been removed, then say when it first appeared.
177 - if ( $after == 'n' ) {
178 - $changedRun = $this->db->selectField ( 'testitem',
179 - 'MIN(ti_run)',
180 - array( 'ti_name' => $testname ),
181 - __METHOD__ );
182 - $appear = $this->db->selectRow ( 'testrun',
183 - array( 'tr_date', 'tr_mw_version' ),
184 - array( 'tr_id' => $changedRun ),
185 - __METHOD__ );
186 -
187 - return "First recorded appearance: "
188 - . date( "d-M-Y H:i:s", strtotime ( $appear->tr_date ) )
189 - . ", " . $appear->tr_mw_version;
190 - }
191 -
192 - // Otherwise, this test has previous recorded results.
193 - // See when this test last had a different result to what we're seeing now.
194 - $conds = array(
195 - 'ti_name' => $testname,
196 - 'ti_success' => ( $after == 'f' ? "1" : "0" ) );
197 -
198 - if ( $this->curRun ) {
199 - $conds[] = "ti_run != " . $this->db->addQuotes ( $this->curRun );
200 - }
201 -
202 - $changedRun = $this->db->selectField ( 'testitem', 'MAX(ti_run)', $conds, __METHOD__ );
203 -
204 - // If no record of ever having had a different result.
205 - if ( is_null ( $changedRun ) ) {
206 - if ( $after == "f" ) {
207 - return "Has never passed";
208 - } else {
209 - return "Has never failed";
210 - }
211 - }
212 -
213 - // Otherwise, we're looking at a test whose status has changed.
214 - // (i.e. it used to work, but now doesn't; or used to fail, but is now fixed.)
215 - // In this situation, give as much info as we can as to when it changed status.
216 - $pre = $this->db->selectRow ( 'testrun',
217 - array( 'tr_date', 'tr_mw_version' ),
218 - array( 'tr_id' => $changedRun ),
219 - __METHOD__ );
220 - $post = $this->db->selectRow ( 'testrun',
221 - array( 'tr_date', 'tr_mw_version' ),
222 - array( "tr_id > " . $this->db->addQuotes ( $changedRun ) ),
223 - __METHOD__,
224 - array( "LIMIT" => 1, "ORDER BY" => 'tr_id' )
225 - );
226 -
227 - if ( $post ) {
228 - $postDate = date( "d-M-Y H:i:s", strtotime ( $post->tr_date ) ) . ", {$post->tr_mw_version}";
229 - } else {
230 - $postDate = 'now';
231 - }
232 -
233 - return ( $after == "f" ? "Introduced" : "Fixed" ) . " between "
234 - . date( "d-M-Y H:i:s", strtotime ( $pre->tr_date ) ) . ", " . $pre->tr_mw_version
235 - . " and $postDate";
236 -
237 - }
238 -
239 - /**
240 - * Commit transaction and clean up for result recording
241 - */
242 - function end() {
243 - $this->lb->commitMasterChanges();
244 - $this->lb->closeAll();
245 - parent::end();
246 - }
247 -
248 -}
249 -
250 -class DbTestRecorder extends DbTestPreviewer {
251 - var $version;
252 -
253 - /**
254 - * Set up result recording; insert a record for the run with the date
255 - * and all that fun stuff
256 - */
257 - function start() {
258 - $this->db->begin();
259 -
260 - if ( ! $this->db->tableExists( 'testrun' )
261 - || ! $this->db->tableExists( 'testitem' ) )
262 - {
263 - print "WARNING> `testrun` table not found in database. Trying to create table.\n";
264 - $this->db->sourceFile( $this->db->patchPath( 'patch-testrun.sql' ) );
265 - echo "OK, resuming.\n";
266 - }
267 -
268 - parent::start();
269 -
270 - $this->db->insert( 'testrun',
271 - array(
272 - 'tr_date' => $this->db->timestamp(),
273 - 'tr_mw_version' => $this->version,
274 - 'tr_php_version' => phpversion(),
275 - 'tr_db_version' => $this->db->getServerVersion(),
276 - 'tr_uname' => php_uname()
277 - ),
278 - __METHOD__ );
279 - if ( $this->db->getType() === 'postgres' ) {
280 - $this->curRun = $this->db->currentSequenceValue( 'testrun_id_seq' );
281 - } else {
282 - $this->curRun = $this->db->insertId();
283 - }
284 - }
285 -
286 - /**
287 - * Record an individual test item's success or failure to the db
288 - *
289 - * @param $test String
290 - * @param $result Boolean
291 - */
292 - function record( $test, $result ) {
293 - parent::record( $test, $result );
294 -
295 - $this->db->insert( 'testitem',
296 - array(
297 - 'ti_run' => $this->curRun,
298 - 'ti_name' => $test,
299 - 'ti_success' => $result ? 1 : 0,
300 - ),
301 - __METHOD__ );
302 - }
303 -}
304 -
3054 class TestFileIterator implements Iterator {
3065 private $file;
3076 private $fh;
@@ -380,7 +79,7 @@
38180 $this->checkSection( 'text' );
38281 $this->checkSection( 'article' );
38382
384 - $this->parserTest->addArticle( ParserTest::chomp( $this->sectionData['article'] ), $this->sectionData['text'], $this->lineNum );
 83+ $this->parserTest->addArticle( self::chomp( $this->sectionData['article'] ), $this->sectionData['text'], $this->lineNum );
38584
38685 $this->clearSection();
38786
@@ -452,11 +151,11 @@
453152 }
454153
455154 $this->test = array(
456 - 'test' => ParserTest::chomp( $this->sectionData['test'] ),
457 - 'input' => ParserTest::chomp( $this->sectionData['input'] ),
458 - 'result' => ParserTest::chomp( $this->sectionData['result'] ),
459 - 'options' => ParserTest::chomp( $this->sectionData['options'] ),
460 - 'config' => ParserTest::chomp( $this->sectionData['config'] ),
 155+ 'test' => self::chomp( $this->sectionData['test'] ),
 156+ 'input' => self::chomp( $this->sectionData['input'] ),
 157+ 'result' => self::chomp( $this->sectionData['result'] ),
 158+ 'options' => self::chomp( $this->sectionData['options'] ),
 159+ 'config' => self::chomp( $this->sectionData['config'] ),
461160 );
462161
463162 return true;
@@ -490,6 +189,19 @@
491190 }
492191
493192 /**
 193+ * Remove last character if it is a newline
 194+ * @group utility
 195+ */
 196+ public static function chomp( $s ) {
 197+ if ( substr( $s, -1 ) === "\n" ) {
 198+ return substr( $s, 0, -1 );
 199+ }
 200+ else {
 201+ return $s;
 202+ }
 203+ }
 204+
 205+ /**
494206 * Verify the current section data has some value for the given token
495207 * name (first parameter).
496208 * Throw an exception if it is not set, referencing current section
@@ -514,74 +226,3 @@
515227 return true;
516228 }
517229 }
518 -
519 -
520 -/**
521 - * A class to delay execution of a parser test hooks.
522 - *
523 - */
524 -class DelayedParserTest {
525 -
526 - /** Initialized on construction */
527 - private $hooks;
528 - private $fnHooks;
529 -
530 - public function __construct() {
531 - $this->reset();
532 - }
533 -
534 - /**
535 - * Init/reset or forgot about the current delayed test.
536 - * Call to this will erase any hooks function that were pending.
537 - */
538 - public function reset() {
539 - $this->hooks = array();
540 - $this->fnHooks = array();
541 - }
542 -
543 - /**
544 - * Called whenever we actually want to run the hook.
545 - * Should be the case if we found the parserTest is not disabled
546 - */
547 - public function unleash( &$parserTest ) {
548 - if( !($parserTest instanceof ParserTest || $parserTest instanceof NewParserTest
549 - ) ) {
550 - throw new MWException( __METHOD__ . " must be passed an instance of ParserTest or NewParserTest classes\n" );
551 - }
552 -
553 - # Trigger delayed hooks. Any failure will make us abort
554 - foreach( $this->hooks as $hook ) {
555 - $ret = $parserTest->requireHook( $hook );
556 - if( !$ret ) {
557 - return false;
558 - }
559 - }
560 -
561 - # Trigger delayed function hooks. Any failure will make us abort
562 - foreach( $this->fnHooks as $fnHook ) {
563 - $ret = $parserTest->requireFunctionHook( $fnHook );
564 - if( !$ret ) {
565 - return false;
566 - }
567 - }
568 -
569 - # Delayed execution was successful.
570 - return true;
571 - }
572 -
573 - /**
574 - * Similar to ParserTest object but does not run anything
575 - * Use unleash() to really execute the hook
576 - */
577 - public function requireHook( $hook ) {
578 - $this->hooks[] = $hook;
579 - }
580 - /**
581 - * Similar to ParserTest object but does not run anything
582 - * Use unleash() to really execute the hook function
583 - */
584 - public function requireFunctionHook( $fnHook ) {
585 - $this->fnHooks[] = $fnHook;
586 - }
587 -
588 -}
Index: trunk/phase3/tests/parser/parserTest.inc
@@ -1,1317 +0,0 @@
2 -<?php
3 -# Copyright (C) 2004, 2010 Brion Vibber <brion@pobox.com>
4 -# http://www.mediawiki.org/
5 -#
6 -# This program is free software; you can redistribute it and/or modify
7 -# it under the terms of the GNU General Public License as published by
8 -# the Free Software Foundation; either version 2 of the License, or
9 -# (at your option) any later version.
10 -#
11 -# This program is distributed in the hope that it will be useful,
12 -# but WITHOUT ANY WARRANTY; without even the implied warranty of
13 -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 -# GNU General Public License for more details.
15 -#
16 -# You should have received a copy of the GNU General Public License along
17 -# with this program; if not, write to the Free Software Foundation, Inc.,
18 -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 -# http://www.gnu.org/copyleft/gpl.html
20 -
21 -/**
22 - * @todo Make this more independent of the configuration (and if possible the database)
23 - * @todo document
24 - * @file
25 - * @ingroup Testing
26 - */
27 -
28 -/**
29 - * @ingroup Testing
30 - */
31 -class ParserTest {
32 - /**
33 - * boolean $color whereas output should be colorized
34 - */
35 - private $color;
36 -
37 - /**
38 - * boolean $showOutput Show test output
39 - */
40 - private $showOutput;
41 -
42 - /**
43 - * boolean $useTemporaryTables Use temporary tables for the temporary database
44 - */
45 - private $useTemporaryTables = true;
46 -
47 - /**
48 - * boolean $databaseSetupDone True if the database has been set up
49 - */
50 - private $databaseSetupDone = false;
51 -
52 - /**
53 - * Our connection to the database
54 - * @var DatabaseBase
55 - */
56 - private $db;
57 -
58 - /**
59 - * Database clone helper
60 - * @var CloneDatabase
61 - */
62 - private $dbClone;
63 -
64 - /**
65 - * string $oldTablePrefix Original table prefix
66 - */
67 - private $oldTablePrefix;
68 -
69 - private $maxFuzzTestLength = 300;
70 - private $fuzzSeed = 0;
71 - private $memoryLimit = 50;
72 - private $uploadDir = null;
73 -
74 - public $regex = "";
75 - private $savedGlobals = array();
76 - /**
77 - * Sets terminal colorization and diff/quick modes depending on OS and
78 - * command-line options (--color and --quick).
79 - */
80 - public function __construct( $options = array() ) {
81 - # Only colorize output if stdout is a terminal.
82 - $this->color = !wfIsWindows() && Maintenance::posix_isatty( 1 );
83 -
84 - if ( isset( $options['color'] ) ) {
85 - switch( $options['color'] ) {
86 - case 'no':
87 - $this->color = false;
88 - break;
89 - case 'yes':
90 - default:
91 - $this->color = true;
92 - break;
93 - }
94 - }
95 -
96 - $this->term = $this->color
97 - ? new AnsiTermColorer()
98 - : new DummyTermColorer();
99 -
100 - $this->showDiffs = !isset( $options['quick'] );
101 - $this->showProgress = !isset( $options['quiet'] );
102 - $this->showFailure = !(
103 - isset( $options['quiet'] )
104 - && ( isset( $options['record'] )
105 - || isset( $options['compare'] ) ) ); // redundant output
106 -
107 - $this->showOutput = isset( $options['show-output'] );
108 -
109 - if ( isset( $options['filter'] ) ) {
110 - $options['regex'] = $options['filter'];
111 - }
112 -
113 - if ( isset( $options['regex'] ) ) {
114 - if ( isset( $options['record'] ) ) {
115 - echo "Warning: --record cannot be used with --regex, disabling --record\n";
116 - unset( $options['record'] );
117 - }
118 - $this->regex = $options['regex'];
119 - } else {
120 - # Matches anything
121 - $this->regex = '';
122 - }
123 -
124 - $this->setupRecorder( $options );
125 - $this->keepUploads = isset( $options['keep-uploads'] );
126 -
127 - if ( isset( $options['seed'] ) ) {
128 - $this->fuzzSeed = intval( $options['seed'] ) - 1;
129 - }
130 -
131 - $this->runDisabled = isset( $options['run-disabled'] );
132 -
133 - $this->hooks = array();
134 - $this->functionHooks = array();
135 - self::setUp();
136 - }
137 -
138 - static function setUp() {
139 - global $wgParser, $wgParserConf, $IP, $messageMemc, $wgMemc,
140 - $wgUser, $wgLang, $wgOut, $wgRequest, $wgStyleDirectory, $wgEnableParserCache,
141 - $wgNamespaceAliases, $wgNamespaceProtection, $wgLocalFileRepo,
142 - $parserMemc, $wgThumbnailScriptPath, $wgScriptPath,
143 - $wgArticlePath, $wgStyleSheetPath, $wgScript, $wgStylePath, $wgExtensionAssetsPath,
144 - $wgMainCacheType, $wgMessageCacheType, $wgParserCacheType;
145 -
146 - $wgScript = '/index.php';
147 - $wgScriptPath = '/';
148 - $wgArticlePath = '/wiki/$1';
149 - $wgStyleSheetPath = '/skins';
150 - $wgStylePath = '/skins';
151 - $wgExtensionAssetsPath = '/extensions';
152 - $wgThumbnailScriptPath = false;
153 - $wgLocalFileRepo = array(
154 - 'class' => 'LocalRepo',
155 - 'name' => 'local',
156 - 'url' => 'http://example.com/images',
157 - 'hashLevels' => 2,
158 - 'transformVia404' => false,
159 - 'backend' => new FSFileBackend( array(
160 - 'name' => 'local-backend',
161 - 'lockManager' => 'fsLockManager',
162 - 'containerPaths' => array(
163 - 'local-public' => wfTempDir() . '/test-repo/public',
164 - 'local-thumb' => wfTempDir() . '/test-repo/thumb',
165 - 'local-temp' => wfTempDir() . '/test-repo/temp',
166 - 'local-deleted' => wfTempDir() . '/test-repo/deleted',
167 - )
168 - ) )
169 - );
170 - $wgNamespaceProtection[NS_MEDIAWIKI] = 'editinterface';
171 - $wgNamespaceAliases['Image'] = NS_FILE;
172 - $wgNamespaceAliases['Image_talk'] = NS_FILE_TALK;
173 -
174 - // XXX: tests won't run without this (for CACHE_DB)
175 - if ( $wgMainCacheType === CACHE_DB ) {
176 - $wgMainCacheType = CACHE_NONE;
177 - }
178 - if ( $wgMessageCacheType === CACHE_DB ) {
179 - $wgMessageCacheType = CACHE_NONE;
180 - }
181 - if ( $wgParserCacheType === CACHE_DB ) {
182 - $wgParserCacheType = CACHE_NONE;
183 - }
184 -
185 - $wgEnableParserCache = false;
186 - DeferredUpdates::clearPendingUpdates();
187 - $wgMemc = wfGetMainCache(); // checks $wgMainCacheType
188 - $messageMemc = wfGetMessageCacheStorage();
189 - $parserMemc = wfGetParserCacheStorage();
190 -
191 - // $wgContLang = new StubContLang;
192 - $wgUser = new User;
193 - $context = new RequestContext;
194 - $wgLang = $context->getLanguage();
195 - $wgOut = $context->getOutput();
196 - $wgParser = new StubObject( 'wgParser', $wgParserConf['class'], array( $wgParserConf ) );
197 - $wgRequest = $context->getRequest();
198 -
199 - if ( $wgStyleDirectory === false ) {
200 - $wgStyleDirectory = "$IP/skins";
201 - }
202 -
203 - }
204 -
205 - public function setupRecorder ( $options ) {
206 - if ( isset( $options['record'] ) ) {
207 - $this->recorder = new DbTestRecorder( $this );
208 - $this->recorder->version = isset( $options['setversion'] ) ?
209 - $options['setversion'] : SpecialVersion::getVersion();
210 - } elseif ( isset( $options['compare'] ) ) {
211 - $this->recorder = new DbTestPreviewer( $this );
212 - } else {
213 - $this->recorder = new TestRecorder( $this );
214 - }
215 - }
216 -
217 - /**
218 - * Remove last character if it is a newline
219 - * @group utility
220 - */
221 - static public function chomp( $s ) {
222 - if ( substr( $s, -1 ) === "\n" ) {
223 - return substr( $s, 0, -1 );
224 - }
225 - else {
226 - return $s;
227 - }
228 - }
229 -
230 - /**
231 - * Run a fuzz test series
232 - * Draw input from a set of test files
233 - */
234 - function fuzzTest( $filenames ) {
235 - $GLOBALS['wgContLang'] = Language::factory( 'en' );
236 - $dict = $this->getFuzzInput( $filenames );
237 - $dictSize = strlen( $dict );
238 - $logMaxLength = log( $this->maxFuzzTestLength );
239 - $this->setupDatabase();
240 - ini_set( 'memory_limit', $this->memoryLimit * 1048576 );
241 -
242 - $numTotal = 0;
243 - $numSuccess = 0;
244 - $user = new User;
245 - $opts = ParserOptions::newFromUser( $user );
246 - $title = Title::makeTitle( NS_MAIN, 'Parser_test' );
247 -
248 - while ( true ) {
249 - // Generate test input
250 - mt_srand( ++$this->fuzzSeed );
251 - $totalLength = mt_rand( 1, $this->maxFuzzTestLength );
252 - $input = '';
253 -
254 - while ( strlen( $input ) < $totalLength ) {
255 - $logHairLength = mt_rand( 0, 1000000 ) / 1000000 * $logMaxLength;
256 - $hairLength = min( intval( exp( $logHairLength ) ), $dictSize );
257 - $offset = mt_rand( 0, $dictSize - $hairLength );
258 - $input .= substr( $dict, $offset, $hairLength );
259 - }
260 -
261 - $this->setupGlobals();
262 - $parser = $this->getParser();
263 -
264 - // Run the test
265 - try {
266 - $parser->parse( $input, $title, $opts );
267 - $fail = false;
268 - } catch ( Exception $exception ) {
269 - $fail = true;
270 - }
271 -
272 - if ( $fail ) {
273 - echo "Test failed with seed {$this->fuzzSeed}\n";
274 - echo "Input:\n";
275 - printf( "string(%d) \"%s\"\n\n", strlen( $input ), $input );
276 - echo "$exception\n";
277 - } else {
278 - $numSuccess++;
279 - }
280 -
281 - $numTotal++;
282 - $this->teardownGlobals();
283 - $parser->__destruct();
284 -
285 - if ( $numTotal % 100 == 0 ) {
286 - $usage = intval( memory_get_usage( true ) / $this->memoryLimit / 1048576 * 100 );
287 - echo "{$this->fuzzSeed}: $numSuccess/$numTotal (mem: $usage%)\n";
288 - if ( $usage > 90 ) {
289 - echo "Out of memory:\n";
290 - $memStats = $this->getMemoryBreakdown();
291 -
292 - foreach ( $memStats as $name => $usage ) {
293 - echo "$name: $usage\n";
294 - }
295 - $this->abort();
296 - }
297 - }
298 - }
299 - }
300 -
301 - /**
302 - * Get an input dictionary from a set of parser test files
303 - */
304 - function getFuzzInput( $filenames ) {
305 - $dict = '';
306 -
307 - foreach ( $filenames as $filename ) {
308 - $contents = file_get_contents( $filename );
309 - preg_match_all( '/!!\s*input\n(.*?)\n!!\s*result/s', $contents, $matches );
310 -
311 - foreach ( $matches[1] as $match ) {
312 - $dict .= $match . "\n";
313 - }
314 - }
315 -
316 - return $dict;
317 - }
318 -
319 - /**
320 - * Get a memory usage breakdown
321 - */
322 - function getMemoryBreakdown() {
323 - $memStats = array();
324 -
325 - foreach ( $GLOBALS as $name => $value ) {
326 - $memStats['$' . $name] = strlen( serialize( $value ) );
327 - }
328 -
329 - $classes = get_declared_classes();
330 -
331 - foreach ( $classes as $class ) {
332 - $rc = new ReflectionClass( $class );
333 - $props = $rc->getStaticProperties();
334 - $memStats[$class] = strlen( serialize( $props ) );
335 - $methods = $rc->getMethods();
336 -
337 - foreach ( $methods as $method ) {
338 - $memStats[$class] += strlen( serialize( $method->getStaticVariables() ) );
339 - }
340 - }
341 -
342 - $functions = get_defined_functions();
343 -
344 - foreach ( $functions['user'] as $function ) {
345 - $rf = new ReflectionFunction( $function );
346 - $memStats["$function()"] = strlen( serialize( $rf->getStaticVariables() ) );
347 - }
348 -
349 - asort( $memStats );
350 -
351 - return $memStats;
352 - }
353 -
354 - function abort() {
355 - $this->abort();
356 - }
357 -
358 - /**
359 - * Run a series of tests listed in the given text files.
360 - * Each test consists of a brief description, wikitext input,
361 - * and the expected HTML output.
362 - *
363 - * Prints status updates on stdout and counts up the total
364 - * number and percentage of passed tests.
365 - *
366 - * @param $filenames Array of strings
367 - * @return Boolean: true if passed all tests, false if any tests failed.
368 - */
369 - public function runTestsFromFiles( $filenames ) {
370 - $ok = false;
371 - $GLOBALS['wgContLang'] = Language::factory( 'en' );
372 - $this->recorder->start();
373 - try {
374 - $this->setupDatabase();
375 - $ok = true;
376 -
377 - foreach ( $filenames as $filename ) {
378 - $tests = new TestFileIterator( $filename, $this );
379 - $ok = $this->runTests( $tests ) && $ok;
380 - }
381 -
382 - $this->teardownDatabase();
383 - $this->recorder->report();
384 - } catch (DBError $e) {
385 - echo $e->getMessage();
386 - }
387 - $this->recorder->end();
388 -
389 - return $ok;
390 - }
391 -
392 - function runTests( $tests ) {
393 - $ok = true;
394 -
395 - foreach ( $tests as $t ) {
396 - $result =
397 - $this->runTest( $t['test'], $t['input'], $t['result'], $t['options'], $t['config'] );
398 - $ok = $ok && $result;
399 - $this->recorder->record( $t['test'], $result );
400 - }
401 -
402 - if ( $this->showProgress ) {
403 - print "\n";
404 - }
405 -
406 - return $ok;
407 - }
408 -
409 - /**
410 - * Get a Parser object
411 - */
412 - function getParser( $preprocessor = null ) {
413 - global $wgParserConf;
414 -
415 - $class = $wgParserConf['class'];
416 - $parser = new $class( array( 'preprocessorClass' => $preprocessor ) + $wgParserConf );
417 -
418 - foreach ( $this->hooks as $tag => $callback ) {
419 - $parser->setHook( $tag, $callback );
420 - }
421 -
422 - foreach ( $this->functionHooks as $tag => $bits ) {
423 - list( $callback, $flags ) = $bits;
424 - $parser->setFunctionHook( $tag, $callback, $flags );
425 - }
426 -
427 - wfRunHooks( 'ParserTestParser', array( &$parser ) );
428 -
429 - return $parser;
430 - }
431 -
432 - /**
433 - * Run a given wikitext input through a freshly-constructed wiki parser,
434 - * and compare the output against the expected results.
435 - * Prints status and explanatory messages to stdout.
436 - *
437 - * @param $desc String: test's description
438 - * @param $input String: wikitext to try rendering
439 - * @param $result String: result to output
440 - * @param $opts Array: test's options
441 - * @param $config String: overrides for global variables, one per line
442 - * @return Boolean
443 - */
444 - public function runTest( $desc, $input, $result, $opts, $config ) {
445 - if ( $this->showProgress ) {
446 - $this->showTesting( $desc );
447 - }
448 -
449 - $opts = $this->parseOptions( $opts );
450 - $context = $this->setupGlobals( $opts, $config );
451 -
452 - $user = $context->getUser();
453 - $options = ParserOptions::newFromContext( $context );
454 -
455 - if ( isset( $opts['title'] ) ) {
456 - $titleText = $opts['title'];
457 - }
458 - else {
459 - $titleText = 'Parser test';
460 - }
461 -
462 - $local = isset( $opts['local'] );
463 - $preprocessor = isset( $opts['preprocessor'] ) ? $opts['preprocessor'] : null;
464 - $parser = $this->getParser( $preprocessor );
465 - $title = Title::newFromText( $titleText );
466 -
467 - if ( isset( $opts['pst'] ) ) {
468 - $out = $parser->preSaveTransform( $input, $title, $user, $options );
469 - } elseif ( isset( $opts['msg'] ) ) {
470 - $out = $parser->transformMsg( $input, $options, $title );
471 - } elseif ( isset( $opts['section'] ) ) {
472 - $section = $opts['section'];
473 - $out = $parser->getSection( $input, $section );
474 - } elseif ( isset( $opts['replace'] ) ) {
475 - $section = $opts['replace'][0];
476 - $replace = $opts['replace'][1];
477 - $out = $parser->replaceSection( $input, $section, $replace );
478 - } elseif ( isset( $opts['comment'] ) ) {
479 - $out = Linker::formatComment( $input, $title, $local );
480 - } elseif ( isset( $opts['preload'] ) ) {
481 - $out = $parser->getpreloadText( $input, $title, $options );
482 - } else {
483 - $output = $parser->parse( $input, $title, $options, true, true, 1337 );
484 - $out = $output->getText();
485 -
486 - if ( isset( $opts['showtitle'] ) ) {
487 - if ( $output->getTitleText() ) {
488 - $title = $output->getTitleText();
489 - }
490 -
491 - $out = "$title\n$out";
492 - }
493 -
494 - if ( isset( $opts['ill'] ) ) {
495 - $out = $this->tidy( implode( ' ', $output->getLanguageLinks() ) );
496 - } elseif ( isset( $opts['cat'] ) ) {
497 - $outputPage = $context->getOutput();
498 - $outputPage->addCategoryLinks( $output->getCategories() );
499 - $cats = $outputPage->getCategoryLinks();
500 -
501 - if ( isset( $cats['normal'] ) ) {
502 - $out = $this->tidy( implode( ' ', $cats['normal'] ) );
503 - } else {
504 - $out = '';
505 - }
506 - }
507 -
508 - $result = $this->tidy( $result );
509 - }
510 -
511 - $this->teardownGlobals();
512 - return $this->showTestResult( $desc, $result, $out );
513 - }
514 -
515 - /**
516 - *
517 - */
518 - function showTestResult( $desc, $result, $out ) {
519 - if ( $result === $out ) {
520 - $this->showSuccess( $desc );
521 - return true;
522 - } else {
523 - $this->showFailure( $desc, $result, $out );
524 - return false;
525 - }
526 - }
527 -
528 - /**
529 - * Use a regex to find out the value of an option
530 - * @param $key String: name of option val to retrieve
531 - * @param $opts Options array to look in
532 - * @param $default Mixed: default value returned if not found
533 - */
534 - private static function getOptionValue( $key, $opts, $default ) {
535 - $key = strtolower( $key );
536 -
537 - if ( isset( $opts[$key] ) ) {
538 - return $opts[$key];
539 - } else {
540 - return $default;
541 - }
542 - }
543 -
544 - private function parseOptions( $instring ) {
545 - $opts = array();
546 - // foo
547 - // foo=bar
548 - // foo="bar baz"
549 - // foo=[[bar baz]]
550 - // foo=bar,"baz quux"
551 - $regex = '/\b
552 - ([\w-]+) # Key
553 - \b
554 - (?:\s*
555 - = # First sub-value
556 - \s*
557 - (
558 - "
559 - [^"]* # Quoted val
560 - "
561 - |
562 - \[\[
563 - [^]]* # Link target
564 - \]\]
565 - |
566 - [\w-]+ # Plain word
567 - )
568 - (?:\s*
569 - , # Sub-vals 1..N
570 - \s*
571 - (
572 - "[^"]*" # Quoted val
573 - |
574 - \[\[[^]]*\]\] # Link target
575 - |
576 - [\w-]+ # Plain word
577 - )
578 - )*
579 - )?
580 - /x';
581 -
582 - if ( preg_match_all( $regex, $instring, $matches, PREG_SET_ORDER ) ) {
583 - foreach ( $matches as $bits ) {
584 - array_shift( $bits );
585 - $key = strtolower( array_shift( $bits ) );
586 - if ( count( $bits ) == 0 ) {
587 - $opts[$key] = true;
588 - } elseif ( count( $bits ) == 1 ) {
589 - $opts[$key] = $this->cleanupOption( array_shift( $bits ) );
590 - } else {
591 - // Array!
592 - $opts[$key] = array_map( array( $this, 'cleanupOption' ), $bits );
593 - }
594 - }
595 - }
596 - return $opts;
597 - }
598 -
599 - private function cleanupOption( $opt ) {
600 - if ( substr( $opt, 0, 1 ) == '"' ) {
601 - return substr( $opt, 1, -1 );
602 - }
603 -
604 - if ( substr( $opt, 0, 2 ) == '[[' ) {
605 - return substr( $opt, 2, -2 );
606 - }
607 - return $opt;
608 - }
609 -
610 - /**
611 - * Set up the global variables for a consistent environment for each test.
612 - * Ideally this should replace the global configuration entirely.
613 - */
614 - private function setupGlobals( $opts = '', $config = '' ) {
615 - # Find out values for some special options.
616 - $lang =
617 - self::getOptionValue( 'language', $opts, 'en' );
618 - $variant =
619 - self::getOptionValue( 'variant', $opts, false );
620 - $maxtoclevel =
621 - self::getOptionValue( 'wgMaxTocLevel', $opts, 999 );
622 - $linkHolderBatchSize =
623 - self::getOptionValue( 'wgLinkHolderBatchSize', $opts, 1000 );
624 -
625 - $settings = array(
626 - 'wgServer' => 'http://Britney-Spears',
627 - 'wgScript' => '/index.php',
628 - 'wgScriptPath' => '/',
629 - 'wgArticlePath' => '/wiki/$1',
630 - 'wgActionPaths' => array(),
631 - 'wgLocalFileRepo' => array(
632 - 'class' => 'LocalRepo',
633 - 'name' => 'local',
634 - 'url' => 'http://example.com/images',
635 - 'hashLevels' => 2,
636 - 'transformVia404' => false,
637 - 'backend' => new FSFileBackend( array(
638 - 'name' => 'local-backend',
639 - 'lockManager' => 'fsLockManager',
640 - 'containerPaths' => array(
641 - 'local-public' => $this->uploadDir,
642 - 'local-thumb' => $this->uploadDir . '/thumb',
643 - 'local-temp' => $this->uploadDir . '/temp',
644 - 'local-deleted' => $this->uploadDir . '/delete',
645 - )
646 - ) )
647 - ),
648 - 'wgEnableUploads' => self::getOptionValue( 'wgEnableUploads', $opts, true ),
649 - 'wgStylePath' => '/skins',
650 - 'wgStyleSheetPath' => '/skins',
651 - 'wgSitename' => 'MediaWiki',
652 - 'wgLanguageCode' => $lang,
653 - 'wgDBprefix' => $this->db->getType() != 'oracle' ? 'parsertest_' : 'pt_',
654 - 'wgRawHtml' => isset( $opts['rawhtml'] ),
655 - 'wgLang' => null,
656 - 'wgContLang' => null,
657 - 'wgNamespacesWithSubpages' => array( 0 => isset( $opts['subpage'] ) ),
658 - 'wgMaxTocLevel' => $maxtoclevel,
659 - 'wgCapitalLinks' => true,
660 - 'wgNoFollowLinks' => true,
661 - 'wgNoFollowDomainExceptions' => array(),
662 - 'wgThumbnailScriptPath' => false,
663 - 'wgUseImageResize' => false,
664 - 'wgLocaltimezone' => 'UTC',
665 - 'wgAllowExternalImages' => true,
666 - 'wgUseTidy' => false,
667 - 'wgDefaultLanguageVariant' => $variant,
668 - 'wgVariantArticlePath' => false,
669 - 'wgGroupPermissions' => array( '*' => array(
670 - 'createaccount' => true,
671 - 'read' => true,
672 - 'edit' => true,
673 - 'createpage' => true,
674 - 'createtalk' => true,
675 - ) ),
676 - 'wgNamespaceProtection' => array( NS_MEDIAWIKI => 'editinterface' ),
677 - 'wgDefaultExternalStore' => array(),
678 - 'wgForeignFileRepos' => array(),
679 - 'wgLinkHolderBatchSize' => $linkHolderBatchSize,
680 - 'wgExperimentalHtmlIds' => false,
681 - 'wgExternalLinkTarget' => false,
682 - 'wgAlwaysUseTidy' => false,
683 - 'wgHtml5' => true,
684 - 'wgCleanupPresentationalAttributes' => true,
685 - 'wgWellFormedXml' => true,
686 - 'wgAllowMicrodataAttributes' => true,
687 - 'wgAdaptiveMessageCache' => true,
688 - 'wgDisableLangConversion' => false,
689 - 'wgDisableTitleConversion' => false,
690 - );
691 -
692 - if ( $config ) {
693 - $configLines = explode( "\n", $config );
694 -
695 - foreach ( $configLines as $line ) {
696 - list( $var, $value ) = explode( '=', $line, 2 );
697 -
698 - $settings[$var] = eval( "return $value;" );
699 - }
700 - }
701 -
702 - $this->savedGlobals = array();
703 -
704 - foreach ( $settings as $var => $val ) {
705 - if ( array_key_exists( $var, $GLOBALS ) ) {
706 - $this->savedGlobals[$var] = $GLOBALS[$var];
707 - }
708 -
709 - $GLOBALS[$var] = $val;
710 - }
711 -
712 - $GLOBALS['wgContLang'] = Language::factory( $lang );
713 - $GLOBALS['wgMemc'] = new EmptyBagOStuff;
714 -
715 - $context = new RequestContext();
716 - $GLOBALS['wgLang'] = $context->getLanguage();
717 - $GLOBALS['wgOut'] = $context->getOutput();
718 -
719 - $GLOBALS['wgUser'] = new User();
720 -
721 - global $wgHooks;
722 -
723 - $wgHooks['ParserTestParser'][] = 'ParserTestParserHook::setup';
724 - $wgHooks['ParserGetVariableValueTs'][] = 'ParserTest::getFakeTimestamp';
725 -
726 - MagicWord::clearCache();
727 -
728 - return $context;
729 - }
730 -
731 - /**
732 - * List of temporary tables to create, without prefix.
733 - * Some of these probably aren't necessary.
734 - */
735 - private function listTables() {
736 - $tables = array( 'user', 'user_properties', 'user_former_groups', 'page', 'page_restrictions',
737 - 'protected_titles', 'revision', 'text', 'pagelinks', 'imagelinks',
738 - 'categorylinks', 'templatelinks', 'externallinks', 'langlinks', 'iwlinks',
739 - 'site_stats', 'hitcounter', 'ipblocks', 'image', 'oldimage',
740 - 'recentchanges', 'watchlist', 'interwiki', 'logging',
741 - 'querycache', 'objectcache', 'job', 'l10n_cache', 'redirect', 'querycachetwo',
742 - 'archive', 'user_groups', 'page_props', 'category', 'msg_resource', 'msg_resource_links'
743 - );
744 -
745 - if ( in_array( $this->db->getType(), array( 'mysql', 'sqlite', 'oracle' ) ) ) {
746 - array_push( $tables, 'searchindex' );
747 - }
748 -
749 - // Allow extensions to add to the list of tables to duplicate;
750 - // may be necessary if they hook into page save or other code
751 - // which will require them while running tests.
752 - wfRunHooks( 'ParserTestTables', array( &$tables ) );
753 -
754 - return $tables;
755 - }
756 -
757 - /**
758 - * Set up a temporary set of wiki tables to work with for the tests.
759 - * Currently this will only be done once per run, and any changes to
760 - * the db will be visible to later tests in the run.
761 - */
762 - public function setupDatabase() {
763 - global $wgDBprefix;
764 -
765 - if ( $this->databaseSetupDone ) {
766 - return;
767 - }
768 -
769 - $this->db = wfGetDB( DB_MASTER );
770 - $dbType = $this->db->getType();
771 -
772 - if ( $wgDBprefix === 'parsertest_' || ( $dbType == 'oracle' && $wgDBprefix === 'pt_' ) ) {
773 - throw new MWException( 'setupDatabase should be called before setupGlobals' );
774 - }
775 -
776 - $this->databaseSetupDone = true;
777 - $this->oldTablePrefix = $wgDBprefix;
778 -
779 - # SqlBagOStuff broke when using temporary tables on r40209 (bug 15892).
780 - # It seems to have been fixed since (r55079?), but regressed at some point before r85701.
781 - # This works around it for now...
782 - ObjectCache::$instances[CACHE_DB] = new HashBagOStuff;
783 -
784 - # CREATE TEMPORARY TABLE breaks if there is more than one server
785 - if ( wfGetLB()->getServerCount() != 1 ) {
786 - $this->useTemporaryTables = false;
787 - }
788 -
789 - $temporary = $this->useTemporaryTables || $dbType == 'postgres';
790 - $prefix = $dbType != 'oracle' ? 'parsertest_' : 'pt_';
791 -
792 - $this->dbClone = new CloneDatabase( $this->db, $this->listTables(), $prefix );
793 - $this->dbClone->useTemporaryTables( $temporary );
794 - $this->dbClone->cloneTableStructure();
795 -
796 - if ( $dbType == 'oracle' ) {
797 - $this->db->query( 'BEGIN FILL_WIKI_INFO; END;' );
798 - # Insert 0 user to prevent FK violations
799 -
800 - # Anonymous user
801 - $this->db->insert( 'user', array(
802 - 'user_id' => 0,
803 - 'user_name' => 'Anonymous' ) );
804 - }
805 -
806 - # Hack: insert a few Wikipedia in-project interwiki prefixes,
807 - # for testing inter-language links
808 - $this->db->insert( 'interwiki', array(
809 - array( 'iw_prefix' => 'wikipedia',
810 - 'iw_url' => 'http://en.wikipedia.org/wiki/$1',
811 - 'iw_api' => '',
812 - 'iw_wikiid' => '',
813 - 'iw_local' => 0 ),
814 - array( 'iw_prefix' => 'meatball',
815 - 'iw_url' => 'http://www.usemod.com/cgi-bin/mb.pl?$1',
816 - 'iw_api' => '',
817 - 'iw_wikiid' => '',
818 - 'iw_local' => 0 ),
819 - array( 'iw_prefix' => 'zh',
820 - 'iw_url' => 'http://zh.wikipedia.org/wiki/$1',
821 - 'iw_api' => '',
822 - 'iw_wikiid' => '',
823 - 'iw_local' => 1 ),
824 - array( 'iw_prefix' => 'es',
825 - 'iw_url' => 'http://es.wikipedia.org/wiki/$1',
826 - 'iw_api' => '',
827 - 'iw_wikiid' => '',
828 - 'iw_local' => 1 ),
829 - array( 'iw_prefix' => 'fr',
830 - 'iw_url' => 'http://fr.wikipedia.org/wiki/$1',
831 - 'iw_api' => '',
832 - 'iw_wikiid' => '',
833 - 'iw_local' => 1 ),
834 - array( 'iw_prefix' => 'ru',
835 - 'iw_url' => 'http://ru.wikipedia.org/wiki/$1',
836 - 'iw_api' => '',
837 - 'iw_wikiid' => '',
838 - 'iw_local' => 1 ),
839 - ) );
840 -
841 -
842 - # Update certain things in site_stats
843 - $this->db->insert( 'site_stats', array( 'ss_row_id' => 1, 'ss_images' => 2, 'ss_good_articles' => 1 ) );
844 -
845 - # Reinitialise the LocalisationCache to match the database state
846 - Language::getLocalisationCache()->unloadAll();
847 -
848 - # Clear the message cache
849 - MessageCache::singleton()->clear();
850 -
851 - $this->uploadDir = $this->setupUploadDir();
852 - $user = User::createNew( 'WikiSysop' );
853 - $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Foobar.jpg' ) );
854 - $image->recordUpload2( '', 'Upload of some lame file', 'Some lame file', array(
855 - 'size' => 12345,
856 - 'width' => 1941,
857 - 'height' => 220,
858 - 'bits' => 24,
859 - 'media_type' => MEDIATYPE_BITMAP,
860 - 'mime' => 'image/jpeg',
861 - 'metadata' => serialize( array() ),
862 - 'sha1' => wfBaseConvert( '', 16, 36, 31 ),
863 - 'fileExists' => true
864 - ), $this->db->timestamp( '20010115123500' ), $user );
865 -
866 - # This image will be blacklisted in [[MediaWiki:Bad image list]]
867 - $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Bad.jpg' ) );
868 - $image->recordUpload2( '', 'zomgnotcensored', 'Borderline image', array(
869 - 'size' => 12345,
870 - 'width' => 320,
871 - 'height' => 240,
872 - 'bits' => 24,
873 - 'media_type' => MEDIATYPE_BITMAP,
874 - 'mime' => 'image/jpeg',
875 - 'metadata' => serialize( array() ),
876 - 'sha1' => wfBaseConvert( '', 16, 36, 31 ),
877 - 'fileExists' => true
878 - ), $this->db->timestamp( '20010115123500' ), $user );
879 - }
880 -
881 - public function teardownDatabase() {
882 - if ( !$this->databaseSetupDone ) {
883 - $this->teardownGlobals();
884 - return;
885 - }
886 - $this->teardownUploadDir( $this->uploadDir );
887 -
888 - $this->dbClone->destroy();
889 - $this->databaseSetupDone = false;
890 -
891 - if ( $this->useTemporaryTables ) {
892 - if( $this->db->getType() == 'sqlite' ) {
893 - # Under SQLite the searchindex table is virtual and need
894 - # to be explicitly destroyed. See bug 29912
895 - # See also MediaWikiTestCase::destroyDB()
896 - wfDebug( __METHOD__ . " explicitly destroying sqlite virtual table parsertest_searchindex\n" );
897 - $this->db->query( "DROP TABLE `parsertest_searchindex`" );
898 - }
899 - # Don't need to do anything
900 - $this->teardownGlobals();
901 - return;
902 - }
903 -
904 - $tables = $this->listTables();
905 -
906 - foreach ( $tables as $table ) {
907 - $sql = $this->db->getType() == 'oracle' ? "DROP TABLE pt_$table DROP CONSTRAINTS" : "DROP TABLE `parsertest_$table`";
908 - $this->db->query( $sql );
909 - }
910 -
911 - if ( $this->db->getType() == 'oracle' )
912 - $this->db->query( 'BEGIN FILL_WIKI_INFO; END;' );
913 -
914 - $this->teardownGlobals();
915 - }
916 -
917 - /**
918 - * Create a dummy uploads directory which will contain a couple
919 - * of files in order to pass existence tests.
920 - *
921 - * @return String: the directory
922 - */
923 - private function setupUploadDir() {
924 - global $IP;
925 -
926 - if ( $this->keepUploads ) {
927 - $dir = wfTempDir() . '/mwParser-images';
928 -
929 - if ( is_dir( $dir ) ) {
930 - return $dir;
931 - }
932 - } else {
933 - $dir = wfTempDir() . "/mwParser-" . mt_rand() . "-images";
934 - }
935 -
936 - // wfDebug( "Creating upload directory $dir\n" );
937 - if ( file_exists( $dir ) ) {
938 - wfDebug( "Already exists!\n" );
939 - return $dir;
940 - }
941 -
942 - wfMkdirParents( $dir . '/3/3a', null, __METHOD__ );
943 - copy( "$IP/skins/monobook/headbg.jpg", "$dir/3/3a/Foobar.jpg" );
944 - wfMkdirParents( $dir . '/0/09', null, __METHOD__ );
945 - copy( "$IP/skins/monobook/headbg.jpg", "$dir/0/09/Bad.jpg" );
946 -
947 - return $dir;
948 - }
949 -
950 - /**
951 - * Restore default values and perform any necessary clean-up
952 - * after each test runs.
953 - */
954 - private function teardownGlobals() {
955 - RepoGroup::destroySingleton();
956 - LinkCache::singleton()->clear();
957 -
958 - foreach ( $this->savedGlobals as $var => $val ) {
959 - $GLOBALS[$var] = $val;
960 - }
961 - }
962 -
963 - /**
964 - * Remove the dummy uploads directory
965 - */
966 - private function teardownUploadDir( $dir ) {
967 - if ( $this->keepUploads ) {
968 - return;
969 - }
970 -
971 - // delete the files first, then the dirs.
972 - self::deleteFiles(
973 - array (
974 - "$dir/3/3a/Foobar.jpg",
975 - "$dir/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg",
976 - "$dir/thumb/3/3a/Foobar.jpg/200px-Foobar.jpg",
977 - "$dir/thumb/3/3a/Foobar.jpg/640px-Foobar.jpg",
978 - "$dir/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg",
979 -
980 - "$dir/0/09/Bad.jpg",
981 -
982 - "$dir/math/f/a/5/fa50b8b616463173474302ca3e63586b.png",
983 - )
984 - );
985 -
986 - self::deleteDirs(
987 - array (
988 - "$dir/3/3a",
989 - "$dir/3",
990 - "$dir/thumb/6/65",
991 - "$dir/thumb/6",
992 - "$dir/thumb/3/3a/Foobar.jpg",
993 - "$dir/thumb/3/3a",
994 - "$dir/thumb/3",
995 -
996 - "$dir/0/09/",
997 - "$dir/0/",
998 - "$dir/thumb",
999 - "$dir/math/f/a/5",
1000 - "$dir/math/f/a",
1001 - "$dir/math/f",
1002 - "$dir/math",
1003 - "$dir",
1004 - )
1005 - );
1006 - }
1007 -
1008 - /**
1009 - * Delete the specified files, if they exist.
1010 - * @param $files Array: full paths to files to delete.
1011 - */
1012 - private static function deleteFiles( $files ) {
1013 - foreach ( $files as $file ) {
1014 - if ( file_exists( $file ) ) {
1015 - unlink( $file );
1016 - }
1017 - }
1018 - }
1019 -
1020 - /**
1021 - * Delete the specified directories, if they exist. Must be empty.
1022 - * @param $dirs Array: full paths to directories to delete.
1023 - */
1024 - private static function deleteDirs( $dirs ) {
1025 - foreach ( $dirs as $dir ) {
1026 - if ( is_dir( $dir ) ) {
1027 - rmdir( $dir );
1028 - }
1029 - }
1030 - }
1031 -
1032 - /**
1033 - * "Running test $desc..."
1034 - */
1035 - protected function showTesting( $desc ) {
1036 - print "Running test $desc... ";
1037 - }
1038 -
1039 - /**
1040 - * Print a happy success message.
1041 - *
1042 - * @param $desc String: the test name
1043 - * @return Boolean
1044 - */
1045 - protected function showSuccess( $desc ) {
1046 - if ( $this->showProgress ) {
1047 - print $this->term->color( '1;32' ) . 'PASSED' . $this->term->reset() . "\n";
1048 - }
1049 -
1050 - return true;
1051 - }
1052 -
1053 - /**
1054 - * Print a failure message and provide some explanatory output
1055 - * about what went wrong if so configured.
1056 - *
1057 - * @param $desc String: the test name
1058 - * @param $result String: expected HTML output
1059 - * @param $html String: actual HTML output
1060 - * @return Boolean
1061 - */
1062 - protected function showFailure( $desc, $result, $html ) {
1063 - if ( $this->showFailure ) {
1064 - if ( !$this->showProgress ) {
1065 - # In quiet mode we didn't show the 'Testing' message before the
1066 - # test, in case it succeeded. Show it now:
1067 - $this->showTesting( $desc );
1068 - }
1069 -
1070 - print $this->term->color( '31' ) . 'FAILED!' . $this->term->reset() . "\n";
1071 -
1072 - if ( $this->showOutput ) {
1073 - print "--- Expected ---\n$result\n--- Actual ---\n$html\n";
1074 - }
1075 -
1076 - if ( $this->showDiffs ) {
1077 - print $this->quickDiff( $result, $html );
1078 - if ( !$this->wellFormed( $html ) ) {
1079 - print "XML error: $this->mXmlError\n";
1080 - }
1081 - }
1082 - }
1083 -
1084 - return false;
1085 - }
1086 -
1087 - /**
1088 - * Run given strings through a diff and return the (colorized) output.
1089 - * Requires writable /tmp directory and a 'diff' command in the PATH.
1090 - *
1091 - * @param $input String
1092 - * @param $output String
1093 - * @param $inFileTail String: tailing for the input file name
1094 - * @param $outFileTail String: tailing for the output file name
1095 - * @return String
1096 - */
1097 - protected function quickDiff( $input, $output, $inFileTail = 'expected', $outFileTail = 'actual' ) {
1098 - # Windows, or at least the fc utility, is retarded
1099 - $slash = wfIsWindows() ? '\\' : '/';
1100 - $prefix = wfTempDir() . "{$slash}mwParser-" . mt_rand();
1101 -
1102 - $infile = "$prefix-$inFileTail";
1103 - $this->dumpToFile( $input, $infile );
1104 -
1105 - $outfile = "$prefix-$outFileTail";
1106 - $this->dumpToFile( $output, $outfile );
1107 -
1108 - $shellInfile = wfEscapeShellArg($infile);
1109 - $shellOutfile = wfEscapeShellArg($outfile);
1110 -
1111 - global $wgDiff3;
1112 - // we assume that people with diff3 also have usual diff
1113 - $diff = ( wfIsWindows() && !$wgDiff3 )
1114 - ? `fc $shellInfile $shellOutfile`
1115 - : `diff -au $shellInfile $shellOutfile`;
1116 - unlink( $infile );
1117 - unlink( $outfile );
1118 -
1119 - return $this->colorDiff( $diff );
1120 - }
1121 -
1122 - /**
1123 - * Write the given string to a file, adding a final newline.
1124 - *
1125 - * @param $data String
1126 - * @param $filename String
1127 - */
1128 - private function dumpToFile( $data, $filename ) {
1129 - $file = fopen( $filename, "wt" );
1130 - fwrite( $file, $data . "\n" );
1131 - fclose( $file );
1132 - }
1133 -
1134 - /**
1135 - * Colorize unified diff output if set for ANSI color output.
1136 - * Subtractions are colored blue, additions red.
1137 - *
1138 - * @param $text String
1139 - * @return String
1140 - */
1141 - protected function colorDiff( $text ) {
1142 - return preg_replace(
1143 - array( '/^(-.*)$/m', '/^(\+.*)$/m' ),
1144 - array( $this->term->color( 34 ) . '$1' . $this->term->reset(),
1145 - $this->term->color( 31 ) . '$1' . $this->term->reset() ),
1146 - $text );
1147 - }
1148 -
1149 - /**
1150 - * Show "Reading tests from ..."
1151 - *
1152 - * @param $path String
1153 - */
1154 - public function showRunFile( $path ) {
1155 - print $this->term->color( 1 ) .
1156 - "Reading tests from \"$path\"..." .
1157 - $this->term->reset() .
1158 - "\n";
1159 - }
1160 -
1161 - /**
1162 - * Insert a temporary test article
1163 - * @param $name String: the title, including any prefix
1164 - * @param $text String: the article text
1165 - * @param $line Integer: the input line number, for reporting errors
1166 - * @param $ignoreDuplicate Boolean: whether to silently ignore duplicate pages
1167 - */
1168 - static public function addArticle( $name, $text, $line = 'unknown', $ignoreDuplicate = '' ) {
1169 - global $wgCapitalLinks;
1170 -
1171 - $oldCapitalLinks = $wgCapitalLinks;
1172 - $wgCapitalLinks = true; // We only need this from SetupGlobals() See r70917#c8637
1173 -
1174 - $text = self::chomp( $text );
1175 - $name = self::chomp( $name );
1176 -
1177 - $title = Title::newFromText( $name );
1178 -
1179 - if ( is_null( $title ) ) {
1180 - throw new MWException( "invalid title '$name' at line $line\n" );
1181 - }
1182 -
1183 - $page = WikiPage::factory( $title );
1184 - $page->loadPageData( 'fromdbmaster' );
1185 -
1186 - if ( $page->exists() ) {
1187 - if ( $ignoreDuplicate == 'ignoreduplicate' ) {
1188 - return;
1189 - } else {
1190 - throw new MWException( "duplicate article '$name' at line $line\n" );
1191 - }
1192 - }
1193 -
1194 - $page->doEdit( $text, '', EDIT_NEW );
1195 -
1196 - $wgCapitalLinks = $oldCapitalLinks;
1197 - }
1198 -
1199 - /**
1200 - * Steal a callback function from the primary parser, save it for
1201 - * application to our scary parser. If the hook is not installed,
1202 - * abort processing of this file.
1203 - *
1204 - * @param $name String
1205 - * @return Bool true if tag hook is present
1206 - */
1207 - public function requireHook( $name ) {
1208 - global $wgParser;
1209 -
1210 - $wgParser->firstCallInit( ); // make sure hooks are loaded.
1211 -
1212 - if ( isset( $wgParser->mTagHooks[$name] ) ) {
1213 - $this->hooks[$name] = $wgParser->mTagHooks[$name];
1214 - } else {
1215 - echo " This test suite requires the '$name' hook extension, skipping.\n";
1216 - return false;
1217 - }
1218 -
1219 - return true;
1220 - }
1221 -
1222 - /**
1223 - * Steal a callback function from the primary parser, save it for
1224 - * application to our scary parser. If the hook is not installed,
1225 - * abort processing of this file.
1226 - *
1227 - * @param $name String
1228 - * @return Bool true if function hook is present
1229 - */
1230 - public function requireFunctionHook( $name ) {
1231 - global $wgParser;
1232 -
1233 - $wgParser->firstCallInit( ); // make sure hooks are loaded.
1234 -
1235 - if ( isset( $wgParser->mFunctionHooks[$name] ) ) {
1236 - $this->functionHooks[$name] = $wgParser->mFunctionHooks[$name];
1237 - } else {
1238 - echo " This test suite requires the '$name' function hook extension, skipping.\n";
1239 - return false;
1240 - }
1241 -
1242 - return true;
1243 - }
1244 -
1245 - /**
1246 - * Run the "tidy" command on text if the $wgUseTidy
1247 - * global is true
1248 - *
1249 - * @param $text String: the text to tidy
1250 - * @return String
1251 - */
1252 - private function tidy( $text ) {
1253 - global $wgUseTidy;
1254 -
1255 - if ( $wgUseTidy ) {
1256 - $text = MWTidy::tidy( $text );
1257 - }
1258 -
1259 - return $text;
1260 - }
1261 -
1262 - private function wellFormed( $text ) {
1263 - $html =
1264 - Sanitizer::hackDocType() .
1265 - '<html>' .
1266 - $text .
1267 - '</html>';
1268 -
1269 - $parser = xml_parser_create( "UTF-8" );
1270 -
1271 - # case folding violates XML standard, turn it off
1272 - xml_parser_set_option( $parser, XML_OPTION_CASE_FOLDING, false );
1273 -
1274 - if ( !xml_parse( $parser, $html, true ) ) {
1275 - $err = xml_error_string( xml_get_error_code( $parser ) );
1276 - $position = xml_get_current_byte_index( $parser );
1277 - $fragment = $this->extractFragment( $html, $position );
1278 - $this->mXmlError = "$err at byte $position:\n$fragment";
1279 - xml_parser_free( $parser );
1280 -
1281 - return false;
1282 - }
1283 -
1284 - xml_parser_free( $parser );
1285 -
1286 - return true;
1287 - }
1288 -
1289 - private function extractFragment( $text, $position ) {
1290 - $start = max( 0, $position - 10 );
1291 - $before = $position - $start;
1292 - $fragment = '...' .
1293 - $this->term->color( 34 ) .
1294 - substr( $text, $start, $before ) .
1295 - $this->term->color( 0 ) .
1296 - $this->term->color( 31 ) .
1297 - $this->term->color( 1 ) .
1298 - substr( $text, $position, 1 ) .
1299 - $this->term->color( 0 ) .
1300 - $this->term->color( 34 ) .
1301 - substr( $text, $position + 1, 9 ) .
1302 - $this->term->color( 0 ) .
1303 - '...';
1304 - $display = str_replace( "\n", ' ', $fragment );
1305 - $caret = ' ' .
1306 - str_repeat( ' ', $before ) .
1307 - $this->term->color( 31 ) .
1308 - '^' .
1309 - $this->term->color( 0 );
1310 -
1311 - return "$display\n$caret";
1312 - }
1313 -
1314 - static function getFakeTimestamp( &$parser, &$ts ) {
1315 - $ts = 123;
1316 - return true;
1317 - }
1318 -}
Index: trunk/phase3/includes/AutoLoader.php
@@ -928,15 +928,10 @@
929929 'DummyTermColorer' => 'maintenance/term/MWTerm.php',
930930
931931 # tests
932 - 'DbTestPreviewer' => 'tests/testHelpers.inc',
933 - 'DbTestRecorder' => 'tests/testHelpers.inc',
934932 'TestFileIterator' => 'tests/testHelpers.inc',
935 - 'TestRecorder' => 'tests/testHelpers.inc',
936933
937934 # tests/parser
938 - 'ParserTest' => 'tests/parser/parserTest.inc',
939935 'ParserTestParserHook' => 'tests/parser/parserTestsParserHook.php',
940 - 'ParserTestStaticParserHook' => 'tests/parser/parserTestsStaticParserHook.php',
941936
942937 # tests/selenium
943938 'Selenium' => 'tests/selenium/Selenium.php',

Follow-up revisions

RevisionCommit summaryAuthorDate
r108360Followup r108358, bring back DelayedParserTestdemon17:32, 8 January 2012
r108404tip to run parser tests...hashar12:47, 9 January 2012
r108475Revert r108358. It's a good fellow and we like to keep it.platonides22:33, 9 January 2012

Comments

#Comment by Hashar (talk | contribs)   08:39, 9 January 2012

So how do we run a single parser test ? Or a set of tests using --filter?

I can understand the need to avoid duplicate code, but that change sounds like a regression to me.

#Comment by Aaron Schulz (talk | contribs)   08:56, 9 January 2012

If anyone needs that then it be added to NewParserTests rather than a duplicated test file (which was also broken by leaking globals)? This file was particularly annoying with FileBackend.

#Comment by 😂 (talk | contribs)   12:17, 9 January 2012

There's still the --regex option too.

#Comment by MaxSem (talk | contribs)   16:24, 9 January 2012

Grr, parserTests.php ran much faster.

#Comment by OverlordQ (talk | contribs)   17:22, 9 January 2012

Also, saying 'if you want to track when something broke, do it yourself' is a cop out when parserTests.php handled that for you nicely.

#Comment by 😂 (talk | contribs)   17:26, 9 January 2012

This is something we could add to our phpunit support I think.

#Comment by Platonides (talk | contribs)   22:29, 9 January 2012

How do you run it on a non-default parsertest file? How do you determine which test fails when doesn't end?

It's fine to remove duplication if you want to, but keep the same interface! (hint: there are some functions you can easily reuse by changing the argument order in the old system)

Try r107358. How is php unit able to test everything ok when 'failed to register a backend with no name'?

There has been a failing test for a long time with both Cite and CategoryTree. It's present in r108357, phpunit doesn't report it.

make parser takes 12'. parserTests.php 35s

Which one do you consider more suitable for bisecting?

#Comment by 😂 (talk | contribs)   22:38, 9 January 2012
  • I was going to add a --file parameter, that's trivial
  • I'm not sure what you mean by the backend-with-no-name error
  • If tests for extensions aren't being run, that's a bug in our phpunit setup, not incentive to keep this old interface
  • 12min? I can run the full suite in 2.5min...
#Comment by Platonides (talk | contribs)   23:24, 9 January 2012
  • I was going to add a --file parameter, that's trivial
    • Not so trivial, but you can still add it.
  • If tests for extensions aren't being run, that's a bug in our phpunit setup, not incentive to keep this old interface
    • They should be running. There is an eval() for that. Although who knows what is exactly run? I don't know why --verbose got removed from phpunit :(
  • I'm not sure what you mean by the backend-with-no-name error
    • It was the exception message that was throwing in that revision (now fixed). Just an example, not particulary useful by itself.
  • 12min? I can run the full suite in 2.5min...
    • Yes, it was a bit odd. make destructive took 09:21, Memory: 290.75Mb but make parser took Time: 12:25, Memory: 338.50Mb, when bisecting on several revisions (and less extensions) it was about 4m.
#Comment by Hashar (talk | contribs)   23:06, 9 January 2012

I have the same concern. The good old parserTests.php is way faster than the PHPUnit based one.

Plus the other bugs mentioned. Maybe we should keep the old one but please do open bugs for all the issues encountered so we can improve the PHPUnit based one.

#Comment by 😂 (talk | contribs)   23:07, 9 January 2012

Other than "phpunit is slow" can anyone point to substantive bugs? The rest sounds like enhancements we can add to phpunit, easily

#Comment by Brion VIBBER (talk | contribs)   23:24, 9 January 2012

Let's make sure those get done first then. :)

A particular feature that I really, really want to make sure we don't lose is the ability to run a particular item or subset of items chosen by regex on the titles:


$ php tests/parserTests.php --regex='[https://bugzilla.wikimedia.org/show_bug.cgi?id=479 bug 479]'
This is MediaWiki version 1.19alpha ([[Special:Code/MediaWiki/108475|r108475]]).

Running test Magic links: internal link to RFC ([https://bugzilla.wikimedia.org/show_bug.cgi?id=479 bug 479])... PASSED
Running test Magic links: RFC ([https://bugzilla.wikimedia.org/show_bug.cgi?id=479 bug 479])... PASSED

Passed 2 of 2 tests (100%)... ALL TESTS PASSED!

That's soooooo useful when working on a particular issue, since you can run a particular set of tests MUCH faster than running the entire data set.


I also notice I see a lock operation on FSRepo that runs on every test. This was failing for me because the lockdir was apparently created by the web user without being world-writable:

PHP Warning:  fopen(/var/www/trunk/images/lockdir/ny5nj5hoqbwqcuzodxglqx3hf4jvvv0.lock): failed to open stream: Permission denied in /var/www/trunk/includes/filerepo/backend/lockmanager/FSLockManager.php on line 93
PHP Stack trace:
PHP   1. {main}() /var/www/trunk/tests/phpunit/phpunit.php:0
PHP   2. MediaWikiPHPUnitCommand::main() /var/www/trunk/tests/phpunit/phpunit.php:60
PHP   3. PHPUnit_TextUI_Command->run() /var/www/trunk/tests/phpunit/MediaWikiPHPUnitCommand.php:44
PHP   4. PHPUnit_TextUI_TestRunner->doRun() /usr/share/php/PHPUnit/TextUI/Command.php:188
PHP   5. PHPUnit_Framework_TestSuite->run() /usr/share/php/PHPUnit/TextUI/TestRunner.php:305
PHP   6. PHPUnit_Framework_TestSuite->run() /usr/share/php/PHPUnit/Framework/TestSuite.php:693
PHP   7. PHPUnit_Framework_TestSuite->run() /usr/share/php/PHPUnit/Framework/TestSuite.php:693
PHP   8. PHPUnit_Framework_TestSuite->run() /usr/share/php/PHPUnit/Framework/TestSuite.php:693
PHP   9. PHPUnit_Framework_TestSuite->run() /usr/share/php/PHPUnit/Framework/TestSuite.php:693
PHP  10. PHPUnit_Framework_TestSuite->runTest() /usr/share/php/PHPUnit/Framework/TestSuite.php:733
PHP  11. MediaWikiTestCase->run() /usr/share/php/PHPUnit/Framework/TestSuite.php:757
PHP  12. NewParserTest->addDBData() /var/www/trunk/tests/phpunit/MediaWikiTestCase.php:64
PHP  13. LocalFile->recordUpload2() /var/www/trunk/tests/phpunit/includes/parser/NewParserTest.php:203
PHP  14. LocalFile->purgeThumbnails() /var/www/trunk/includes/filerepo/file/LocalFile.php:969
PHP  15. LocalFile->purgeThumbList() /var/www/trunk/includes/filerepo/file/LocalFile.php:729
PHP  16. FileRepo->cleanupBatch() /var/www/trunk/includes/filerepo/file/LocalFile.php:766
PHP  17. FileBackendBase->doOperations() /var/www/trunk/includes/filerepo/FileRepo.php:754
PHP  18. FileBackend->doOperationsInternal() /var/www/trunk/includes/filerepo/backend/FileBackend.php:176
PHP  19. FileBackendBase->getScopedFileLocks() /var/www/trunk/includes/filerepo/backend/FileBackend.php:1128
PHP  20. ScopedLock::factory() /var/www/trunk/includes/filerepo/backend/FileBackend.php:560
PHP  21. LockManager->lock() /var/www/trunk/includes/filerepo/backend/lockmanager/LockManager.php:144
PHP  22. FSLockManager->doLock() /var/www/trunk/includes/filerepo/backend/lockmanager/LockManager.php:53
PHP  23. FSLockManager->doSingleLock() /var/www/trunk/includes/filerepo/backend/lockmanager/FSLockManager.php:45
PHP  24. fopen() /var/www/trunk/includes/filerepo/backend/lockmanager/FSLockManager.php:93

This didn't happen when running parserTests.php, so this may indicate that a lot of extra activity is going on in the PHPUnit-based system. Worked around it by fixing the permissions on my directory.


As for speed, I'm afraid that I can confirm rrrreeeeaaaalllllyyyyyy slow behavior running the tests under phpunit; it's been running for a few minutes and it only up to about 12%, while the parserTests.php runs in only 1 minute on my desktop test box. That's not acceptable for a dev workstation where I need to run them interactively on a live instance that I'm editing.

#Comment by OverlordQ (talk | contribs)   23:40, 9 January 2012
#Comment by Platonides (talk | contribs)   23:33, 9 January 2012

phpunit is capable of many things, and of running tests parserTest cannot. Yet, parserTest is very good at running its tests.

Moreover, it has been available forever, so it can be used for pretty much any bisecting (there are gaps, like 62006-64880, or this new one 108358-108475, you have to take into account if it's in maintenance or tests, but has greater availability than phpunit, you don't need an extra framework either).

Status & tagging log