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 @@ |
324 | 324 | global $wgHooks; |
325 | 325 | |
326 | 326 | $wgHooks['ParserTestParser'][] = 'ParserTestParserHook::setup'; |
327 | | - $wgHooks['ParserGetVariableValueTs'][] = 'ParserTest::getFakeTimestamp'; |
| 327 | + $wgHooks['ParserGetVariableValueTs'][] = 'NewParserTest::getFakeTimestamp'; |
328 | 328 | |
329 | 329 | MagicWord::clearCache(); |
330 | 330 | RepoGroup::destroySingleton(); |
— | — | @@ -708,10 +708,49 @@ |
709 | 709 | |
710 | 710 | foreach ( self::$articles as $name => $info ) { |
711 | 711 | list( $text, $line ) = $info; |
712 | | - ParserTest::addArticle( $name, $text, $line, 'ignoreduplicate' ); |
| 712 | + self::injectArticle( $name, $text, $line, 'ignoreduplicate' ); |
713 | 713 | } |
714 | 714 | } |
| 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; |
715 | 725 | |
| 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 | + |
716 | 755 | /** |
717 | 756 | * Steal a callback function from the primary parser, save it for |
718 | 757 | * application to our scary parser. If the hook is not installed, |
— | — | @@ -849,4 +888,9 @@ |
850 | 889 | return $default; |
851 | 890 | } |
852 | 891 | } |
| 892 | + |
| 893 | + public static function getFakeTimestamp( &$parser, &$ts ) { |
| 894 | + $ts = 123; |
| 895 | + return true; |
| 896 | + } |
853 | 897 | } |
Index: trunk/phase3/tests/phpunit/includes/ParserOptionsTest.php |
— | — | @@ -6,7 +6,6 @@ |
7 | 7 | private $pcache; |
8 | 8 | |
9 | 9 | function setUp() { |
10 | | - ParserTest::setUp(); //reuse setup from parser tests |
11 | 10 | global $wgContLang, $wgUser, $wgLanguageCode; |
12 | 11 | $wgContLang = Language::factory( $wgLanguageCode ); |
13 | 12 | $this->popts = new ParserOptions( $wgUser ); |
Index: trunk/phase3/tests/testHelpers.inc |
— | — | @@ -1,306 +1,5 @@ |
2 | 2 | <?php |
3 | 3 | |
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 | | - |
305 | 4 | class TestFileIterator implements Iterator { |
306 | 5 | private $file; |
307 | 6 | private $fh; |
— | — | @@ -380,7 +79,7 @@ |
381 | 80 | $this->checkSection( 'text' ); |
382 | 81 | $this->checkSection( 'article' ); |
383 | 82 | |
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 ); |
385 | 84 | |
386 | 85 | $this->clearSection(); |
387 | 86 | |
— | — | @@ -452,11 +151,11 @@ |
453 | 152 | } |
454 | 153 | |
455 | 154 | $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'] ), |
461 | 160 | ); |
462 | 161 | |
463 | 162 | return true; |
— | — | @@ -490,6 +189,19 @@ |
491 | 190 | } |
492 | 191 | |
493 | 192 | /** |
| 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 | + /** |
494 | 206 | * Verify the current section data has some value for the given token |
495 | 207 | * name (first parameter). |
496 | 208 | * Throw an exception if it is not set, referencing current section |
— | — | @@ -514,74 +226,3 @@ |
515 | 227 | return true; |
516 | 228 | } |
517 | 229 | } |
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 @@ |
929 | 929 | 'DummyTermColorer' => 'maintenance/term/MWTerm.php', |
930 | 930 | |
931 | 931 | # tests |
932 | | - 'DbTestPreviewer' => 'tests/testHelpers.inc', |
933 | | - 'DbTestRecorder' => 'tests/testHelpers.inc', |
934 | 932 | 'TestFileIterator' => 'tests/testHelpers.inc', |
935 | | - 'TestRecorder' => 'tests/testHelpers.inc', |
936 | 933 | |
937 | 934 | # tests/parser |
938 | | - 'ParserTest' => 'tests/parser/parserTest.inc', |
939 | 935 | 'ParserTestParserHook' => 'tests/parser/parserTestsParserHook.php', |
940 | | - 'ParserTestStaticParserHook' => 'tests/parser/parserTestsStaticParserHook.php', |
941 | 936 | |
942 | 937 | # tests/selenium |
943 | 938 | 'Selenium' => 'tests/selenium/Selenium.php', |