MediaWiki
REL1_21
|
00001 <?php 00002 00010 class NewParserTest extends MediaWikiTestCase { 00011 static protected $articles = array(); // Array of test articles defined by the tests 00012 /* The data provider is run on a different instance than the test, so it must be static 00013 * When running tests from several files, all tests will see all articles. 00014 */ 00015 static protected $backendToUse; 00016 00017 public $keepUploads = false; 00018 public $runDisabled = false; 00019 public $runParsoid = false; 00020 public $regex = ''; 00021 public $showProgress = true; 00022 public $savedInitialGlobals = array(); 00023 public $savedWeirdGlobals = array(); 00024 public $savedGlobals = array(); 00025 public $hooks = array(); 00026 public $functionHooks = array(); 00027 00028 //Fuzz test 00029 public $maxFuzzTestLength = 300; 00030 public $fuzzSeed = 0; 00031 public $memoryLimit = 50; 00032 00033 protected $file = false; 00034 00035 protected function setUp() { 00036 global $wgNamespaceProtection, $wgNamespaceAliases; 00037 global $wgHooks, $IP; 00038 00039 parent::setUp(); 00040 00041 //Setup CLI arguments 00042 if ( $this->getCliArg( 'regex=' ) ) { 00043 $this->regex = $this->getCliArg( 'regex=' ); 00044 } else { 00045 # Matches anything 00046 $this->regex = ''; 00047 } 00048 00049 $this->keepUploads = $this->getCliArg( 'keep-uploads' ); 00050 00051 $tmpGlobals = array(); 00052 00053 $tmpGlobals['wgLanguageCode'] = 'en'; 00054 $tmpGlobals['wgContLang'] = Language::factory( 'en' ); 00055 $tmpGlobals['wgScript'] = '/index.php'; 00056 $tmpGlobals['wgScriptPath'] = '/'; 00057 $tmpGlobals['wgArticlePath'] = '/wiki/$1'; 00058 $tmpGlobals['wgStyleSheetPath'] = '/skins'; 00059 $tmpGlobals['wgStylePath'] = '/skins'; 00060 $tmpGlobals['wgThumbnailScriptPath'] = false; 00061 $tmpGlobals['wgLocalFileRepo'] = array( 00062 'class' => 'LocalRepo', 00063 'name' => 'local', 00064 'url' => 'http://example.com/images', 00065 'hashLevels' => 2, 00066 'transformVia404' => false, 00067 'backend' => 'local-backend' 00068 ); 00069 $tmpGlobals['wgForeignFileRepos'] = array(); 00070 $tmpGlobals['wgEnableParserCache'] = false; 00071 $tmpGlobals['wgHooks'] = $wgHooks; 00072 $tmpGlobals['wgDeferredUpdateList'] = array(); 00073 $tmpGlobals['wgMemc'] = wfGetMainCache(); 00074 $tmpGlobals['messageMemc'] = wfGetMessageCacheStorage(); 00075 $tmpGlobals['parserMemc'] = wfGetParserCacheStorage(); 00076 00077 // $tmpGlobals['wgContLang'] = new StubContLang; 00078 $tmpGlobals['wgUser'] = new User; 00079 $context = new RequestContext(); 00080 $tmpGlobals['wgLang'] = $context->getLanguage(); 00081 $tmpGlobals['wgOut'] = $context->getOutput(); 00082 $tmpGlobals['wgParser'] = new StubObject( 'wgParser', $GLOBALS['wgParserConf']['class'], array( $GLOBALS['wgParserConf'] ) ); 00083 $tmpGlobals['wgRequest'] = $context->getRequest(); 00084 00085 if ( $GLOBALS['wgStyleDirectory'] === false ) { 00086 $tmpGlobals['wgStyleDirectory'] = "$IP/skins"; 00087 } 00088 00089 00090 foreach ( $tmpGlobals as $var => $val ) { 00091 if ( array_key_exists( $var, $GLOBALS ) ) { 00092 $this->savedInitialGlobals[$var] = $GLOBALS[$var]; 00093 } 00094 00095 $GLOBALS[$var] = $val; 00096 } 00097 00098 $this->savedWeirdGlobals['mw_namespace_protection'] = $wgNamespaceProtection[NS_MEDIAWIKI]; 00099 $this->savedWeirdGlobals['image_alias'] = $wgNamespaceAliases['Image']; 00100 $this->savedWeirdGlobals['image_talk_alias'] = $wgNamespaceAliases['Image_talk']; 00101 00102 $wgNamespaceProtection[NS_MEDIAWIKI] = 'editinterface'; 00103 $wgNamespaceAliases['Image'] = NS_FILE; 00104 $wgNamespaceAliases['Image_talk'] = NS_FILE_TALK; 00105 } 00106 00107 protected function tearDown() { 00108 foreach ( $this->savedInitialGlobals as $var => $val ) { 00109 $GLOBALS[$var] = $val; 00110 } 00111 00112 global $wgNamespaceProtection, $wgNamespaceAliases; 00113 00114 $wgNamespaceProtection[NS_MEDIAWIKI] = $this->savedWeirdGlobals['mw_namespace_protection']; 00115 $wgNamespaceAliases['Image'] = $this->savedWeirdGlobals['image_alias']; 00116 $wgNamespaceAliases['Image_talk'] = $this->savedWeirdGlobals['image_talk_alias']; 00117 00118 // Restore backends 00119 RepoGroup::destroySingleton(); 00120 FileBackendGroup::destroySingleton(); 00121 00122 parent::tearDown(); 00123 } 00124 00125 function addDBData() { 00126 $this->tablesUsed[] = 'site_stats'; 00127 $this->tablesUsed[] = 'interwiki'; 00128 # disabled for performance 00129 #$this->tablesUsed[] = 'image'; 00130 00131 # Hack: insert a few Wikipedia in-project interwiki prefixes, 00132 # for testing inter-language links 00133 $this->db->insert( 'interwiki', array( 00134 array( 'iw_prefix' => 'wikipedia', 00135 'iw_url' => 'http://en.wikipedia.org/wiki/$1', 00136 'iw_api' => '', 00137 'iw_wikiid' => '', 00138 'iw_local' => 0 ), 00139 array( 'iw_prefix' => 'meatball', 00140 'iw_url' => 'http://www.usemod.com/cgi-bin/mb.pl?$1', 00141 'iw_api' => '', 00142 'iw_wikiid' => '', 00143 'iw_local' => 0 ), 00144 array( 'iw_prefix' => 'zh', 00145 'iw_url' => 'http://zh.wikipedia.org/wiki/$1', 00146 'iw_api' => '', 00147 'iw_wikiid' => '', 00148 'iw_local' => 1 ), 00149 array( 'iw_prefix' => 'es', 00150 'iw_url' => 'http://es.wikipedia.org/wiki/$1', 00151 'iw_api' => '', 00152 'iw_wikiid' => '', 00153 'iw_local' => 1 ), 00154 array( 'iw_prefix' => 'fr', 00155 'iw_url' => 'http://fr.wikipedia.org/wiki/$1', 00156 'iw_api' => '', 00157 'iw_wikiid' => '', 00158 'iw_local' => 1 ), 00159 array( 'iw_prefix' => 'ru', 00160 'iw_url' => 'http://ru.wikipedia.org/wiki/$1', 00161 'iw_api' => '', 00162 'iw_wikiid' => '', 00163 'iw_local' => 1 ), 00168 ), __METHOD__, array( 'IGNORE' ) 00169 ); 00170 00171 # Update certain things in site_stats 00172 $this->db->insert( 'site_stats', 00173 array( 'ss_row_id' => 1, 'ss_images' => 2, 'ss_good_articles' => 1 ), 00174 __METHOD__ 00175 ); 00176 00177 # Reinitialise the LocalisationCache to match the database state 00178 Language::getLocalisationCache()->unloadAll(); 00179 00180 # Clear the message cache 00181 MessageCache::singleton()->clear(); 00182 00183 $user = User::newFromId( 0 ); 00184 LinkCache::singleton()->clear(); # Avoids the odd failure at creating the nullRevision 00185 00186 # Upload DB table entries for files. 00187 # We will upload the actual files later. Note that if anything causes LocalFile::load() 00188 # to be triggered before then, it will break via maybeUpgrade() setting the fileExists 00189 # member to false and storing it in cache. 00190 $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Foobar.jpg' ) ); 00191 if ( !$this->db->selectField( 'image', '1', array( 'img_name' => $image->getName() ) ) ) { 00192 $image->recordUpload2( 00193 '', // archive name 00194 'Upload of some lame file', 00195 'Some lame file', 00196 array( 00197 'size' => 12345, 00198 'width' => 1941, 00199 'height' => 220, 00200 'bits' => 24, 00201 'media_type' => MEDIATYPE_BITMAP, 00202 'mime' => 'image/jpeg', 00203 'metadata' => serialize( array() ), 00204 'sha1' => wfBaseConvert( '', 16, 36, 31 ), 00205 'fileExists' => true ), 00206 $this->db->timestamp( '20010115123500' ), $user 00207 ); 00208 } 00209 00210 # This image will be blacklisted in [[MediaWiki:Bad image list]] 00211 $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Bad.jpg' ) ); 00212 if ( !$this->db->selectField( 'image', '1', array( 'img_name' => $image->getName() ) ) ) { 00213 $image->recordUpload2( 00214 '', // archive name 00215 'zomgnotcensored', 00216 'Borderline image', 00217 array( 00218 'size' => 12345, 00219 'width' => 320, 00220 'height' => 240, 00221 'bits' => 24, 00222 'media_type' => MEDIATYPE_BITMAP, 00223 'mime' => 'image/jpeg', 00224 'metadata' => serialize( array() ), 00225 'sha1' => wfBaseConvert( '', 16, 36, 31 ), 00226 'fileExists' => true ), 00227 $this->db->timestamp( '20010115123500' ), $user 00228 ); 00229 } 00230 } 00231 00232 //ParserTest setup/teardown functions 00233 00238 protected function setupGlobals( $opts = array(), $config = '' ) { 00239 global $wgFileBackends; 00240 # Find out values for some special options. 00241 $lang = 00242 self::getOptionValue( 'language', $opts, 'en' ); 00243 $variant = 00244 self::getOptionValue( 'variant', $opts, false ); 00245 $maxtoclevel = 00246 self::getOptionValue( 'wgMaxTocLevel', $opts, 999 ); 00247 $linkHolderBatchSize = 00248 self::getOptionValue( 'wgLinkHolderBatchSize', $opts, 1000 ); 00249 00250 $uploadDir = $this->getUploadDir(); 00251 if ( $this->getCliArg( 'use-filebackend=' ) ) { 00252 if ( self::$backendToUse ) { 00253 $backend = self::$backendToUse; 00254 } else { 00255 $name = $this->getCliArg( 'use-filebackend=' ); 00256 $useConfig = array(); 00257 foreach ( $wgFileBackends as $conf ) { 00258 if ( $conf['name'] == $name ) { 00259 $useConfig = $conf; 00260 } 00261 } 00262 $useConfig['name'] = 'local-backend'; // swap name 00263 $class = $conf['class']; 00264 self::$backendToUse = new $class( $useConfig ); 00265 $backend = self::$backendToUse; 00266 } 00267 } else { 00268 $backend = new FSFileBackend( array( 00269 'name' => 'local-backend', 00270 'lockManager' => 'nullLockManager', 00271 'containerPaths' => array( 00272 'local-public' => "$uploadDir", 00273 'local-thumb' => "$uploadDir/thumb", 00274 ) 00275 ) ); 00276 } 00277 00278 $settings = array( 00279 'wgServer' => 'http://example.org', 00280 'wgScript' => '/index.php', 00281 'wgScriptPath' => '/', 00282 'wgArticlePath' => '/wiki/$1', 00283 'wgExtensionAssetsPath' => '/extensions', 00284 'wgActionPaths' => array(), 00285 'wgLocalFileRepo' => array( 00286 'class' => 'LocalRepo', 00287 'name' => 'local', 00288 'url' => 'http://example.com/images', 00289 'hashLevels' => 2, 00290 'transformVia404' => false, 00291 'backend' => $backend 00292 ), 00293 'wgEnableUploads' => self::getOptionValue( 'wgEnableUploads', $opts, true ), 00294 'wgStylePath' => '/skins', 00295 'wgStyleSheetPath' => '/skins', 00296 'wgSitename' => 'MediaWiki', 00297 'wgLanguageCode' => $lang, 00298 'wgDBprefix' => $this->db->getType() != 'oracle' ? 'unittest_' : 'ut_', 00299 'wgRawHtml' => isset( $opts['rawhtml'] ), 00300 'wgLang' => null, 00301 'wgContLang' => null, 00302 'wgNamespacesWithSubpages' => array( NS_MAIN => isset( $opts['subpage'] ) ), 00303 'wgMaxTocLevel' => $maxtoclevel, 00304 'wgCapitalLinks' => true, 00305 'wgNoFollowLinks' => true, 00306 'wgNoFollowDomainExceptions' => array(), 00307 'wgThumbnailScriptPath' => false, 00308 'wgUseImageResize' => true, 00309 'wgUseTeX' => isset( $opts['math'] ), 00310 'wgMathDirectory' => $uploadDir . '/math', 00311 'wgLocaltimezone' => 'UTC', 00312 'wgAllowExternalImages' => true, 00313 'wgUseTidy' => false, 00314 'wgDefaultLanguageVariant' => $variant, 00315 'wgVariantArticlePath' => false, 00316 'wgGroupPermissions' => array( '*' => array( 00317 'createaccount' => true, 00318 'read' => true, 00319 'edit' => true, 00320 'createpage' => true, 00321 'createtalk' => true, 00322 ) ), 00323 'wgNamespaceProtection' => array( NS_MEDIAWIKI => 'editinterface' ), 00324 'wgDefaultExternalStore' => array(), 00325 'wgForeignFileRepos' => array(), 00326 'wgLinkHolderBatchSize' => $linkHolderBatchSize, 00327 'wgExperimentalHtmlIds' => false, 00328 'wgExternalLinkTarget' => false, 00329 'wgAlwaysUseTidy' => false, 00330 'wgHtml5' => true, 00331 'wgWellFormedXml' => true, 00332 'wgAllowMicrodataAttributes' => true, 00333 'wgAdaptiveMessageCache' => true, 00334 'wgUseDatabaseMessages' => true, 00335 ); 00336 00337 if ( $config ) { 00338 $configLines = explode( "\n", $config ); 00339 00340 foreach ( $configLines as $line ) { 00341 list( $var, $value ) = explode( '=', $line, 2 ); 00342 00343 $settings[$var] = eval( "return $value;" ); //??? 00344 } 00345 } 00346 00347 $this->savedGlobals = array(); 00348 00350 wfRunHooks( 'ParserTestGlobals', array( &$settings ) ); 00351 00352 foreach ( $settings as $var => $val ) { 00353 if ( array_key_exists( $var, $GLOBALS ) ) { 00354 $this->savedGlobals[$var] = $GLOBALS[$var]; 00355 } 00356 00357 $GLOBALS[$var] = $val; 00358 } 00359 00360 $langObj = Language::factory( $lang ); 00361 $GLOBALS['wgContLang'] = $langObj; 00362 $context = new RequestContext(); 00363 $GLOBALS['wgLang'] = $context->getLanguage(); 00364 00365 $GLOBALS['wgMemc'] = new EmptyBagOStuff; 00366 $GLOBALS['wgOut'] = $context->getOutput(); 00367 $GLOBALS['wgUser'] = $context->getUser(); 00368 00369 global $wgHooks; 00370 00371 $wgHooks['ParserTestParser'][] = 'ParserTestParserHook::setup'; 00372 $wgHooks['ParserGetVariableValueTs'][] = 'ParserTest::getFakeTimestamp'; 00373 00374 MagicWord::clearCache(); 00375 RepoGroup::destroySingleton(); 00376 FileBackendGroup::destroySingleton(); 00377 00378 # Create dummy files in storage 00379 $this->setupUploads(); 00380 00381 # Publish the articles after we have the final language set 00382 $this->publishTestArticles(); 00383 00384 # The entries saved into RepoGroup cache with previous globals will be wrong. 00385 RepoGroup::destroySingleton(); 00386 FileBackendGroup::destroySingleton(); 00387 MessageCache::destroyInstance(); 00388 00389 return $context; 00390 } 00391 00397 protected function getUploadDir() { 00398 if ( $this->keepUploads ) { 00399 $dir = wfTempDir() . '/mwParser-images'; 00400 00401 if ( is_dir( $dir ) ) { 00402 return $dir; 00403 } 00404 } else { 00405 $dir = wfTempDir() . "/mwParser-" . mt_rand() . "-images"; 00406 } 00407 00408 // wfDebug( "Creating upload directory $dir\n" ); 00409 if ( file_exists( $dir ) ) { 00410 wfDebug( "Already exists!\n" ); 00411 return $dir; 00412 } 00413 00414 return $dir; 00415 } 00416 00423 protected function setupUploads() { 00424 global $IP; 00425 00426 $base = $this->getBaseDir(); 00427 $backend = RepoGroup::singleton()->getLocalRepo()->getBackend(); 00428 $backend->prepare( array( 'dir' => "$base/local-public/3/3a" ) ); 00429 $backend->store( array( 00430 'src' => "$IP/skins/monobook/headbg.jpg", 'dst' => "$base/local-public/3/3a/Foobar.jpg" 00431 ) ); 00432 $backend->prepare( array( 'dir' => "$base/local-public/0/09" ) ); 00433 $backend->store( array( 00434 'src' => "$IP/skins/monobook/headbg.jpg", 'dst' => "$base/local-public/0/09/Bad.jpg" 00435 ) ); 00436 } 00437 00442 protected function teardownGlobals() { 00443 $this->teardownUploads(); 00444 00445 foreach ( $this->savedGlobals as $var => $val ) { 00446 $GLOBALS[$var] = $val; 00447 } 00448 00449 RepoGroup::destroySingleton(); 00450 LinkCache::singleton()->clear(); 00451 } 00452 00456 private function teardownUploads() { 00457 if ( $this->keepUploads ) { 00458 return; 00459 } 00460 00461 $base = $this->getBaseDir(); 00462 // delete the files first, then the dirs. 00463 self::deleteFiles( 00464 array( 00465 "$base/local-public/3/3a/Foobar.jpg", 00466 "$base/local-thumb/3/3a/Foobar.jpg/180px-Foobar.jpg", 00467 "$base/local-thumb/3/3a/Foobar.jpg/200px-Foobar.jpg", 00468 "$base/local-thumb/3/3a/Foobar.jpg/640px-Foobar.jpg", 00469 "$base/local-thumb/3/3a/Foobar.jpg/120px-Foobar.jpg", 00470 "$base/local-thumb/3/3a/Foobar.jpg/1280px-Foobar.jpg", 00471 "$base/local-thumb/3/3a/Foobar.jpg/20px-Foobar.jpg", 00472 "$base/local-thumb/3/3a/Foobar.jpg/270px-Foobar.jpg", 00473 "$base/local-thumb/3/3a/Foobar.jpg/300px-Foobar.jpg", 00474 "$base/local-thumb/3/3a/Foobar.jpg/30px-Foobar.jpg", 00475 "$base/local-thumb/3/3a/Foobar.jpg/360px-Foobar.jpg", 00476 "$base/local-thumb/3/3a/Foobar.jpg/400px-Foobar.jpg", 00477 "$base/local-thumb/3/3a/Foobar.jpg/40px-Foobar.jpg", 00478 "$base/local-thumb/3/3a/Foobar.jpg/70px-Foobar.jpg", 00479 "$base/local-thumb/3/3a/Foobar.jpg/960px-Foobar.jpg", 00480 00481 "$base/local-public/0/09/Bad.jpg", 00482 "$base/local-thumb/0/09/Bad.jpg", 00483 00484 "$base/local-public/math/f/a/5/fa50b8b616463173474302ca3e63586b.png", 00485 ) 00486 ); 00487 } 00488 00493 private static function deleteFiles( $files ) { 00494 $backend = RepoGroup::singleton()->getLocalRepo()->getBackend(); 00495 foreach ( $files as $file ) { 00496 $backend->delete( array( 'src' => $file ), array( 'force' => 1 ) ); 00497 } 00498 foreach ( $files as $file ) { 00499 $tmp = $file; 00500 while ( $tmp = FileBackend::parentStoragePath( $tmp ) ) { 00501 if ( !$backend->clean( array( 'dir' => $tmp ) )->isOK() ) { 00502 break; 00503 } 00504 } 00505 } 00506 } 00507 00508 protected function getBaseDir() { 00509 return 'mwstore://local-backend'; 00510 } 00511 00512 public function parserTestProvider() { 00513 if ( $this->file === false ) { 00514 global $wgParserTestFiles; 00515 $this->file = $wgParserTestFiles[0]; 00516 } 00517 return new TestFileIterator( $this->file, $this ); 00518 } 00519 00523 public function setParserTestFile( $filename ) { 00524 $this->file = $filename; 00525 } 00526 00531 public function testParserTest( $desc, $input, $result, $opts, $config ) { 00532 if ( $this->regex != '' && !preg_match( '/' . $this->regex . '/', $desc ) ) { 00533 $this->assertTrue( true ); // XXX: don't flood output with "test made no assertions" 00534 //$this->markTestSkipped( 'Filtered out by the user' ); 00535 return; 00536 } 00537 00538 if ( !$this->isWikitextNS( NS_MAIN ) ) { 00539 // parser tests frequently assume that the main namespace contains wikitext. 00540 // @todo: When setting up pages, force the content model. Only skip if 00541 // $wgtContentModelUseDB is false. 00542 $this->markTestSkipped( "Main namespace does not support wikitext," 00543 . "skipping parser test: $desc" ); 00544 } 00545 00546 wfDebug( "Running parser test: $desc\n" ); 00547 00548 $opts = $this->parseOptions( $opts ); 00549 $context = $this->setupGlobals( $opts, $config ); 00550 00551 $user = $context->getUser(); 00552 $options = ParserOptions::newFromContext( $context ); 00553 00554 if ( isset( $opts['title'] ) ) { 00555 $titleText = $opts['title']; 00556 } else { 00557 $titleText = 'Parser test'; 00558 } 00559 00560 $local = isset( $opts['local'] ); 00561 $preprocessor = isset( $opts['preprocessor'] ) ? $opts['preprocessor'] : null; 00562 $parser = $this->getParser( $preprocessor ); 00563 00564 $title = Title::newFromText( $titleText ); 00565 00566 if ( isset( $opts['pst'] ) ) { 00567 $out = $parser->preSaveTransform( $input, $title, $user, $options ); 00568 } elseif ( isset( $opts['msg'] ) ) { 00569 $out = $parser->transformMsg( $input, $options, $title ); 00570 } elseif ( isset( $opts['section'] ) ) { 00571 $section = $opts['section']; 00572 $out = $parser->getSection( $input, $section ); 00573 } elseif ( isset( $opts['replace'] ) ) { 00574 $section = $opts['replace'][0]; 00575 $replace = $opts['replace'][1]; 00576 $out = $parser->replaceSection( $input, $section, $replace ); 00577 } elseif ( isset( $opts['comment'] ) ) { 00578 $out = Linker::formatComment( $input, $title, $local ); 00579 } elseif ( isset( $opts['preload'] ) ) { 00580 $out = $parser->getpreloadText( $input, $title, $options ); 00581 } else { 00582 $output = $parser->parse( $input, $title, $options, true, true, 1337 ); 00583 $out = $output->getText(); 00584 00585 if ( isset( $opts['showtitle'] ) ) { 00586 if ( $output->getTitleText() ) { 00587 $title = $output->getTitleText(); 00588 } 00589 00590 $out = "$title\n$out"; 00591 } 00592 00593 if ( isset( $opts['ill'] ) ) { 00594 $out = $this->tidy( implode( ' ', $output->getLanguageLinks() ) ); 00595 } elseif ( isset( $opts['cat'] ) ) { 00596 $outputPage = $context->getOutput(); 00597 $outputPage->addCategoryLinks( $output->getCategories() ); 00598 $cats = $outputPage->getCategoryLinks(); 00599 00600 if ( isset( $cats['normal'] ) ) { 00601 $out = $this->tidy( implode( ' ', $cats['normal'] ) ); 00602 } else { 00603 $out = ''; 00604 } 00605 } 00606 $parser->mPreprocessor = null; 00607 00608 $result = $this->tidy( $result ); 00609 } 00610 00611 $this->teardownGlobals(); 00612 00613 $this->assertEquals( $result, $out, $desc ); 00614 } 00615 00624 function testFuzzTests() { 00625 global $wgParserTestFiles; 00626 00627 $files = $wgParserTestFiles; 00628 00629 if ( $this->getCliArg( 'file=' ) ) { 00630 $files = array( $this->getCliArg( 'file=' ) ); 00631 } 00632 00633 $dict = $this->getFuzzInput( $files ); 00634 $dictSize = strlen( $dict ); 00635 $logMaxLength = log( $this->maxFuzzTestLength ); 00636 00637 ini_set( 'memory_limit', $this->memoryLimit * 1048576 ); 00638 00639 $user = new User; 00640 $opts = ParserOptions::newFromUser( $user ); 00641 $title = Title::makeTitle( NS_MAIN, 'Parser_test' ); 00642 00643 $id = 1; 00644 00645 while ( true ) { 00646 00647 // Generate test input 00648 mt_srand( ++$this->fuzzSeed ); 00649 $totalLength = mt_rand( 1, $this->maxFuzzTestLength ); 00650 $input = ''; 00651 00652 while ( strlen( $input ) < $totalLength ) { 00653 $logHairLength = mt_rand( 0, 1000000 ) / 1000000 * $logMaxLength; 00654 $hairLength = min( intval( exp( $logHairLength ) ), $dictSize ); 00655 $offset = mt_rand( 0, $dictSize - $hairLength ); 00656 $input .= substr( $dict, $offset, $hairLength ); 00657 } 00658 00659 $this->setupGlobals(); 00660 $parser = $this->getParser(); 00661 00662 // Run the test 00663 try { 00664 $parser->parse( $input, $title, $opts ); 00665 $this->assertTrue( true, "Test $id, fuzz seed {$this->fuzzSeed}" ); 00666 } catch ( Exception $exception ) { 00667 $input_dump = sprintf( "string(%d) \"%s\"\n", strlen( $input ), $input ); 00668 00669 $this->assertTrue( false, "Test $id, fuzz seed {$this->fuzzSeed}. \n\nInput: $input_dump\n\nError: {$exception->getMessage()}\n\nBacktrace: {$exception->getTraceAsString()}" ); 00670 } 00671 00672 $this->teardownGlobals(); 00673 $parser->__destruct(); 00674 00675 if ( $id % 100 == 0 ) { 00676 $usage = intval( memory_get_usage( true ) / $this->memoryLimit / 1048576 * 100 ); 00677 //echo "{$this->fuzzSeed}: $numSuccess/$numTotal (mem: $usage%)\n"; 00678 if ( $usage > 90 ) { 00679 $ret = "Out of memory:\n"; 00680 $memStats = $this->getMemoryBreakdown(); 00681 00682 foreach ( $memStats as $name => $usage ) { 00683 $ret .= "$name: $usage\n"; 00684 } 00685 00686 throw new MWException( $ret ); 00687 } 00688 } 00689 00690 $id++; 00691 00692 } 00693 } 00694 00695 //Various getter functions 00696 00700 function getFuzzInput( $filenames ) { 00701 $dict = ''; 00702 00703 foreach ( $filenames as $filename ) { 00704 $contents = file_get_contents( $filename ); 00705 preg_match_all( '/!!\s*input\n(.*?)\n!!\s*result/s', $contents, $matches ); 00706 00707 foreach ( $matches[1] as $match ) { 00708 $dict .= $match . "\n"; 00709 } 00710 } 00711 00712 return $dict; 00713 } 00714 00718 function getMemoryBreakdown() { 00719 $memStats = array(); 00720 00721 foreach ( $GLOBALS as $name => $value ) { 00722 $memStats['$' . $name] = strlen( serialize( $value ) ); 00723 } 00724 00725 $classes = get_declared_classes(); 00726 00727 foreach ( $classes as $class ) { 00728 $rc = new ReflectionClass( $class ); 00729 $props = $rc->getStaticProperties(); 00730 $memStats[$class] = strlen( serialize( $props ) ); 00731 $methods = $rc->getMethods(); 00732 00733 foreach ( $methods as $method ) { 00734 $memStats[$class] += strlen( serialize( $method->getStaticVariables() ) ); 00735 } 00736 } 00737 00738 $functions = get_defined_functions(); 00739 00740 foreach ( $functions['user'] as $function ) { 00741 $rf = new ReflectionFunction( $function ); 00742 $memStats["$function()"] = strlen( serialize( $rf->getStaticVariables() ) ); 00743 } 00744 00745 asort( $memStats ); 00746 00747 return $memStats; 00748 } 00749 00753 function getParser( $preprocessor = null ) { 00754 global $wgParserConf; 00755 00756 $class = $wgParserConf['class']; 00757 $parser = new $class( array( 'preprocessorClass' => $preprocessor ) + $wgParserConf ); 00758 00759 wfRunHooks( 'ParserTestParser', array( &$parser ) ); 00760 00761 return $parser; 00762 } 00763 00764 //Various action functions 00765 00766 public function addArticle( $name, $text, $line ) { 00767 self::$articles[$name] = array( $text, $line ); 00768 } 00769 00770 public function publishTestArticles() { 00771 if ( empty( self::$articles ) ) { 00772 return; 00773 } 00774 00775 foreach ( self::$articles as $name => $info ) { 00776 list( $text, $line ) = $info; 00777 ParserTest::addArticle( $name, $text, $line, 'ignoreduplicate' ); 00778 } 00779 } 00780 00789 public function requireHook( $name ) { 00790 global $wgParser; 00791 $wgParser->firstCallInit(); // make sure hooks are loaded. 00792 return isset( $wgParser->mTagHooks[$name] ); 00793 } 00794 00795 public function requireFunctionHook( $name ) { 00796 global $wgParser; 00797 $wgParser->firstCallInit(); // make sure hooks are loaded. 00798 return isset( $wgParser->mFunctionHooks[$name] ); 00799 } 00800 00801 //Various "cleanup" functions 00802 00810 protected function tidy( $text ) { 00811 global $wgUseTidy; 00812 00813 if ( $wgUseTidy ) { 00814 $text = MWTidy::tidy( $text ); 00815 } 00816 00817 return $text; 00818 } 00819 00823 public function removeEndingNewline( $s ) { 00824 if ( substr( $s, -1 ) === "\n" ) { 00825 return substr( $s, 0, -1 ); 00826 } else { 00827 return $s; 00828 } 00829 } 00830 00831 //Test options parser functions 00832 00833 protected function parseOptions( $instring ) { 00834 $opts = array(); 00835 // foo 00836 // foo=bar 00837 // foo="bar baz" 00838 // foo=[[bar baz]] 00839 // foo=bar,"baz quux" 00840 $regex = '/\b 00841 ([\w-]+) # Key 00842 \b 00843 (?:\s* 00844 = # First sub-value 00845 \s* 00846 ( 00847 " 00848 [^"]* # Quoted val 00849 " 00850 | 00851 \[\[ 00852 [^]]* # Link target 00853 \]\] 00854 | 00855 [\w-]+ # Plain word 00856 ) 00857 (?:\s* 00858 , # Sub-vals 1..N 00859 \s* 00860 ( 00861 "[^"]*" # Quoted val 00862 | 00863 \[\[[^]]*\]\] # Link target 00864 | 00865 [\w-]+ # Plain word 00866 ) 00867 )* 00868 )? 00869 /x'; 00870 00871 if ( preg_match_all( $regex, $instring, $matches, PREG_SET_ORDER ) ) { 00872 foreach ( $matches as $bits ) { 00873 array_shift( $bits ); 00874 $key = strtolower( array_shift( $bits ) ); 00875 if ( count( $bits ) == 0 ) { 00876 $opts[$key] = true; 00877 } elseif ( count( $bits ) == 1 ) { 00878 $opts[$key] = $this->cleanupOption( array_shift( $bits ) ); 00879 } else { 00880 // Array! 00881 $opts[$key] = array_map( array( $this, 'cleanupOption' ), $bits ); 00882 } 00883 } 00884 } 00885 return $opts; 00886 } 00887 00888 protected function cleanupOption( $opt ) { 00889 if ( substr( $opt, 0, 1 ) == '"' ) { 00890 return substr( $opt, 1, -1 ); 00891 } 00892 00893 if ( substr( $opt, 0, 2 ) == '[[' ) { 00894 return substr( $opt, 2, -2 ); 00895 } 00896 return $opt; 00897 } 00898 00905 protected static function getOptionValue( $key, $opts, $default ) { 00906 $key = strtolower( $key ); 00907 00908 if ( isset( $opts[$key] ) ) { 00909 return $opts[$key]; 00910 } else { 00911 return $default; 00912 } 00913 } 00914 }