|
MediaWiki
REL1_23
|
00001 <?php 00002 00012 class NewParserTest extends MediaWikiTestCase { 00013 static protected $articles = array(); // Array of test articles defined by the tests 00014 /* The data provider is run on a different instance than the test, so it must be static 00015 * When running tests from several files, all tests will see all articles. 00016 */ 00017 static protected $backendToUse; 00018 00019 public $keepUploads = false; 00020 public $runDisabled = false; 00021 public $runParsoid = false; 00022 public $regex = ''; 00023 public $showProgress = true; 00024 public $savedWeirdGlobals = array(); 00025 public $savedGlobals = array(); 00026 public $hooks = array(); 00027 public $functionHooks = array(); 00028 00029 //Fuzz test 00030 public $maxFuzzTestLength = 300; 00031 public $fuzzSeed = 0; 00032 public $memoryLimit = 50; 00033 00034 protected $file = false; 00035 00036 public static function setUpBeforeClass() { 00037 // Inject ParserTest well-known interwikis 00038 ParserTest::setupInterwikis(); 00039 } 00040 00041 protected function setUp() { 00042 global $wgNamespaceAliases, $wgContLang; 00043 global $wgHooks, $IP; 00044 00045 parent::setUp(); 00046 00047 //Setup CLI arguments 00048 if ( $this->getCliArg( 'regex=' ) ) { 00049 $this->regex = $this->getCliArg( 'regex=' ); 00050 } else { 00051 # Matches anything 00052 $this->regex = ''; 00053 } 00054 00055 $this->keepUploads = $this->getCliArg( 'keep-uploads' ); 00056 00057 $tmpGlobals = array(); 00058 00059 $tmpGlobals['wgLanguageCode'] = 'en'; 00060 $tmpGlobals['wgContLang'] = Language::factory( 'en' ); 00061 $tmpGlobals['wgSitename'] = 'MediaWiki'; 00062 $tmpGlobals['wgServer'] = 'http://example.org'; 00063 $tmpGlobals['wgScript'] = '/index.php'; 00064 $tmpGlobals['wgScriptPath'] = '/'; 00065 $tmpGlobals['wgArticlePath'] = '/wiki/$1'; 00066 $tmpGlobals['wgActionPaths'] = array(); 00067 $tmpGlobals['wgVariantArticlePath'] = false; 00068 $tmpGlobals['wgExtensionAssetsPath'] = '/extensions'; 00069 $tmpGlobals['wgStylePath'] = '/skins'; 00070 $tmpGlobals['wgEnableUploads'] = true; 00071 $tmpGlobals['wgThumbnailScriptPath'] = false; 00072 $tmpGlobals['wgLocalFileRepo'] = array( 00073 'class' => 'LocalRepo', 00074 'name' => 'local', 00075 'url' => 'http://example.com/images', 00076 'hashLevels' => 2, 00077 'transformVia404' => false, 00078 'backend' => 'local-backend' 00079 ); 00080 $tmpGlobals['wgForeignFileRepos'] = array(); 00081 $tmpGlobals['wgDefaultExternalStore'] = array(); 00082 $tmpGlobals['wgEnableParserCache'] = false; 00083 $tmpGlobals['wgCapitalLinks'] = true; 00084 $tmpGlobals['wgNoFollowLinks'] = true; 00085 $tmpGlobals['wgNoFollowDomainExceptions'] = array(); 00086 $tmpGlobals['wgExternalLinkTarget'] = false; 00087 $tmpGlobals['wgThumbnailScriptPath'] = false; 00088 $tmpGlobals['wgUseImageResize'] = true; 00089 $tmpGlobals['wgAllowExternalImages'] = true; 00090 $tmpGlobals['wgRawHtml'] = false; 00091 $tmpGlobals['wgUseTidy'] = false; 00092 $tmpGlobals['wgAlwaysUseTidy'] = false; 00093 $tmpGlobals['wgWellFormedXml'] = true; 00094 $tmpGlobals['wgAllowMicrodataAttributes'] = true; 00095 $tmpGlobals['wgExperimentalHtmlIds'] = false; 00096 $tmpGlobals['wgAdaptiveMessageCache'] = true; 00097 $tmpGlobals['wgUseDatabaseMessages'] = true; 00098 $tmpGlobals['wgLocaltimezone'] = 'UTC'; 00099 $tmpGlobals['wgDeferredUpdateList'] = array(); 00100 $tmpGlobals['wgGroupPermissions'] = array( 00101 '*' => array( 00102 'createaccount' => true, 00103 'read' => true, 00104 'edit' => true, 00105 'createpage' => true, 00106 'createtalk' => true, 00107 ) ); 00108 $tmpGlobals['wgNamespaceProtection'] = array( NS_MEDIAWIKI => 'editinterface' ); 00109 00110 $tmpGlobals['wgParser'] = new StubObject( 00111 'wgParser', $GLOBALS['wgParserConf']['class'], 00112 array( $GLOBALS['wgParserConf'] ) ); 00113 00114 $tmpGlobals['wgFileExtensions'][] = 'svg'; 00115 $tmpGlobals['wgSVGConverter'] = 'rsvg'; 00116 $tmpGlobals['wgSVGConverters']['rsvg'] = 00117 '$path/rsvg-convert -w $width -h $height $input -o $output'; 00118 00119 if ( $GLOBALS['wgStyleDirectory'] === false ) { 00120 $tmpGlobals['wgStyleDirectory'] = "$IP/skins"; 00121 } 00122 00123 # Replace all media handlers with a mock. We do not need to generate 00124 # actual thumbnails to do parser testing, we only care about receiving 00125 # a ThumbnailImage properly initialized. 00126 global $wgMediaHandlers; 00127 foreach ( $wgMediaHandlers as $type => $handler ) { 00128 $tmpGlobals['wgMediaHandlers'][$type] = 'MockBitmapHandler'; 00129 } 00130 // Vector images have to be handled slightly differently 00131 $tmpGlobals['wgMediaHandlers']['image/svg+xml'] = 'MockSvgHandler'; 00132 00133 $tmpHooks = $wgHooks; 00134 $tmpHooks['ParserTestParser'][] = 'ParserTestParserHook::setup'; 00135 $tmpHooks['ParserGetVariableValueTs'][] = 'ParserTest::getFakeTimestamp'; 00136 $tmpGlobals['wgHooks'] = $tmpHooks; 00137 # add a namespace shadowing a interwiki link, to test 00138 # proper precedence when resolving links. (bug 51680) 00139 $tmpGlobals['wgExtraNamespaces'] = array( 100 => 'MemoryAlpha' ); 00140 00141 $this->setMwGlobals( $tmpGlobals ); 00142 00143 $this->savedWeirdGlobals['image_alias'] = $wgNamespaceAliases['Image']; 00144 $this->savedWeirdGlobals['image_talk_alias'] = $wgNamespaceAliases['Image_talk']; 00145 00146 $wgNamespaceAliases['Image'] = NS_FILE; 00147 $wgNamespaceAliases['Image_talk'] = NS_FILE_TALK; 00148 00149 MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache 00150 $wgContLang->resetNamespaces(); # reset namespace cache 00151 } 00152 00153 protected function tearDown() { 00154 global $wgNamespaceAliases, $wgContLang; 00155 00156 $wgNamespaceAliases['Image'] = $this->savedWeirdGlobals['image_alias']; 00157 $wgNamespaceAliases['Image_talk'] = $this->savedWeirdGlobals['image_talk_alias']; 00158 00159 // Restore backends 00160 RepoGroup::destroySingleton(); 00161 FileBackendGroup::destroySingleton(); 00162 00163 // Remove temporary pages from the link cache 00164 LinkCache::singleton()->clear(); 00165 00166 // Restore message cache (temporary pages and $wgUseDatabaseMessages) 00167 MessageCache::destroyInstance(); 00168 00169 parent::tearDown(); 00170 00171 MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache 00172 $wgContLang->resetNamespaces(); # reset namespace cache 00173 } 00174 00175 public static function tearDownAfterClass() { 00176 ParserTest::tearDownInterwikis(); 00177 parent::tearDownAfterClass(); 00178 } 00179 00180 function addDBData() { 00181 $this->tablesUsed[] = 'site_stats'; 00182 # disabled for performance 00183 #$this->tablesUsed[] = 'image'; 00184 00185 # Update certain things in site_stats 00186 $this->db->insert( 'site_stats', 00187 array( 'ss_row_id' => 1, 'ss_images' => 2, 'ss_good_articles' => 1 ), 00188 __METHOD__ 00189 ); 00190 00191 $user = User::newFromId( 0 ); 00192 LinkCache::singleton()->clear(); # Avoids the odd failure at creating the nullRevision 00193 00194 # Upload DB table entries for files. 00195 # We will upload the actual files later. Note that if anything causes LocalFile::load() 00196 # to be triggered before then, it will break via maybeUpgrade() setting the fileExists 00197 # member to false and storing it in cache. 00198 # note that the size/width/height/bits/etc of the file 00199 # are actually set by inspecting the file itself; the arguments 00200 # to recordUpload2 have no effect. That said, we try to make things 00201 # match up so it is less confusing to readers of the code & tests. 00202 $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Foobar.jpg' ) ); 00203 if ( !$this->db->selectField( 'image', '1', array( 'img_name' => $image->getName() ) ) ) { 00204 $image->recordUpload2( 00205 '', // archive name 00206 'Upload of some lame file', 00207 'Some lame file', 00208 array( 00209 'size' => 7881, 00210 'width' => 1941, 00211 'height' => 220, 00212 'bits' => 8, 00213 'media_type' => MEDIATYPE_BITMAP, 00214 'mime' => 'image/jpeg', 00215 'metadata' => serialize( array() ), 00216 'sha1' => wfBaseConvert( '1', 16, 36, 31 ), 00217 'fileExists' => true ), 00218 $this->db->timestamp( '20010115123500' ), $user 00219 ); 00220 } 00221 00222 $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Thumb.png' ) ); 00223 if ( !$this->db->selectField( 'image', '1', array( 'img_name' => $image->getName() ) ) ) { 00224 $image->recordUpload2( 00225 '', // archive name 00226 'Upload of some lame thumbnail', 00227 'Some lame thumbnail', 00228 array( 00229 'size' => 22589, 00230 'width' => 135, 00231 'height' => 135, 00232 'bits' => 8, 00233 'media_type' => MEDIATYPE_BITMAP, 00234 'mime' => 'image/png', 00235 'metadata' => serialize( array() ), 00236 'sha1' => wfBaseConvert( '2', 16, 36, 31 ), 00237 'fileExists' => true ), 00238 $this->db->timestamp( '20130225203040' ), $user 00239 ); 00240 } 00241 00242 # This image will be blacklisted in [[MediaWiki:Bad image list]] 00243 $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Bad.jpg' ) ); 00244 if ( !$this->db->selectField( 'image', '1', array( 'img_name' => $image->getName() ) ) ) { 00245 $image->recordUpload2( 00246 '', // archive name 00247 'zomgnotcensored', 00248 'Borderline image', 00249 array( 00250 'size' => 12345, 00251 'width' => 320, 00252 'height' => 240, 00253 'bits' => 24, 00254 'media_type' => MEDIATYPE_BITMAP, 00255 'mime' => 'image/jpeg', 00256 'metadata' => serialize( array() ), 00257 'sha1' => wfBaseConvert( '3', 16, 36, 31 ), 00258 'fileExists' => true ), 00259 $this->db->timestamp( '20010115123500' ), $user 00260 ); 00261 } 00262 $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Foobar.svg' ) ); 00263 if ( !$this->db->selectField( 'image', '1', array( 'img_name' => $image->getName() ) ) ) { 00264 $image->recordUpload2( '', 'Upload of some lame SVG', 'Some lame SVG', array( 00265 'size' => 12345, 00266 'width' => 240, 00267 'height' => 180, 00268 'bits' => 24, 00269 'media_type' => MEDIATYPE_DRAWING, 00270 'mime' => 'image/svg+xml', 00271 'metadata' => serialize( array() ), 00272 'sha1' => wfBaseConvert( '', 16, 36, 31 ), 00273 'fileExists' => true 00274 ), $this->db->timestamp( '20010115123500' ), $user ); 00275 } 00276 } 00277 00278 //ParserTest setup/teardown functions 00279 00284 protected function setupGlobals( $opts = array(), $config = '' ) { 00285 global $wgFileBackends; 00286 # Find out values for some special options. 00287 $lang = 00288 self::getOptionValue( 'language', $opts, 'en' ); 00289 $variant = 00290 self::getOptionValue( 'variant', $opts, false ); 00291 $maxtoclevel = 00292 self::getOptionValue( 'wgMaxTocLevel', $opts, 999 ); 00293 $linkHolderBatchSize = 00294 self::getOptionValue( 'wgLinkHolderBatchSize', $opts, 1000 ); 00295 00296 $uploadDir = $this->getUploadDir(); 00297 if ( $this->getCliArg( 'use-filebackend=' ) ) { 00298 if ( self::$backendToUse ) { 00299 $backend = self::$backendToUse; 00300 } else { 00301 $name = $this->getCliArg( 'use-filebackend=' ); 00302 $useConfig = array(); 00303 foreach ( $wgFileBackends as $conf ) { 00304 if ( $conf['name'] == $name ) { 00305 $useConfig = $conf; 00306 } 00307 } 00308 $useConfig['name'] = 'local-backend'; // swap name 00309 unset( $useConfig['lockManager'] ); 00310 unset( $useConfig['fileJournal'] ); 00311 $class = $useConfig['class']; 00312 self::$backendToUse = new $class( $useConfig ); 00313 $backend = self::$backendToUse; 00314 } 00315 } else { 00316 # Replace with a mock. We do not care about generating real 00317 # files on the filesystem, just need to expose the file 00318 # informations. 00319 $backend = new MockFileBackend( array( 00320 'name' => 'local-backend', 00321 'wikiId' => wfWikiId() 00322 ) ); 00323 } 00324 00325 $settings = array( 00326 'wgLocalFileRepo' => array( 00327 'class' => 'LocalRepo', 00328 'name' => 'local', 00329 'url' => 'http://example.com/images', 00330 'hashLevels' => 2, 00331 'transformVia404' => false, 00332 'backend' => $backend 00333 ), 00334 'wgEnableUploads' => self::getOptionValue( 'wgEnableUploads', $opts, true ), 00335 'wgLanguageCode' => $lang, 00336 'wgDBprefix' => $this->db->getType() != 'oracle' ? 'unittest_' : 'ut_', 00337 'wgRawHtml' => self::getOptionValue( 'wgRawHtml', $opts, false ), 00338 'wgNamespacesWithSubpages' => array( NS_MAIN => isset( $opts['subpage'] ) ), 00339 'wgAllowExternalImages' => self::getOptionValue( 'wgAllowExternalImages', $opts, true ), 00340 'wgThumbLimits' => array( self::getOptionValue( 'thumbsize', $opts, 180 ) ), 00341 'wgMaxTocLevel' => $maxtoclevel, 00342 'wgUseTeX' => isset( $opts['math'] ) || isset( $opts['texvc'] ), 00343 'wgMathDirectory' => $uploadDir . '/math', 00344 'wgDefaultLanguageVariant' => $variant, 00345 'wgLinkHolderBatchSize' => $linkHolderBatchSize, 00346 ); 00347 00348 if ( $config ) { 00349 $configLines = explode( "\n", $config ); 00350 00351 foreach ( $configLines as $line ) { 00352 list( $var, $value ) = explode( '=', $line, 2 ); 00353 00354 $settings[$var] = eval( "return $value;" ); //??? 00355 } 00356 } 00357 00358 $this->savedGlobals = array(); 00359 00361 wfRunHooks( 'ParserTestGlobals', array( &$settings ) ); 00362 00363 $langObj = Language::factory( $lang ); 00364 $settings['wgContLang'] = $langObj; 00365 $settings['wgLang'] = $langObj; 00366 00367 $context = new RequestContext(); 00368 $settings['wgOut'] = $context->getOutput(); 00369 $settings['wgUser'] = $context->getUser(); 00370 $settings['wgRequest'] = $context->getRequest(); 00371 00372 // We (re)set $wgThumbLimits to a single-element array above. 00373 $context->getUser()->setOption( 'thumbsize', 0 ); 00374 00375 foreach ( $settings as $var => $val ) { 00376 if ( array_key_exists( $var, $GLOBALS ) ) { 00377 $this->savedGlobals[$var] = $GLOBALS[$var]; 00378 } 00379 00380 $GLOBALS[$var] = $val; 00381 } 00382 00383 MagicWord::clearCache(); 00384 00385 # The entries saved into RepoGroup cache with previous globals will be wrong. 00386 RepoGroup::destroySingleton(); 00387 FileBackendGroup::destroySingleton(); 00388 00389 # Create dummy files in storage 00390 $this->setupUploads(); 00391 00392 # Publish the articles after we have the final language set 00393 $this->publishTestArticles(); 00394 00395 MessageCache::destroyInstance(); 00396 00397 return $context; 00398 } 00399 00405 protected function getUploadDir() { 00406 if ( $this->keepUploads ) { 00407 $dir = wfTempDir() . '/mwParser-images'; 00408 00409 if ( is_dir( $dir ) ) { 00410 return $dir; 00411 } 00412 } else { 00413 $dir = wfTempDir() . "/mwParser-" . mt_rand() . "-images"; 00414 } 00415 00416 // wfDebug( "Creating upload directory $dir\n" ); 00417 if ( file_exists( $dir ) ) { 00418 wfDebug( "Already exists!\n" ); 00419 00420 return $dir; 00421 } 00422 00423 return $dir; 00424 } 00425 00432 protected function setupUploads() { 00433 global $IP; 00434 00435 $base = $this->getBaseDir(); 00436 $backend = RepoGroup::singleton()->getLocalRepo()->getBackend(); 00437 $backend->prepare( array( 'dir' => "$base/local-public/3/3a" ) ); 00438 $backend->store( array( 00439 'src' => "$IP/skins/monobook/headbg.jpg", 'dst' => "$base/local-public/3/3a/Foobar.jpg" 00440 ) ); 00441 $backend->prepare( array( 'dir' => "$base/local-public/e/ea" ) ); 00442 $backend->store( array( 00443 'src' => "$IP/skins/monobook/wiki.png", 'dst' => "$base/local-public/e/ea/Thumb.png" 00444 ) ); 00445 $backend->prepare( array( 'dir' => "$base/local-public/0/09" ) ); 00446 $backend->store( array( 00447 'src' => "$IP/skins/monobook/headbg.jpg", 'dst' => "$base/local-public/0/09/Bad.jpg" 00448 ) ); 00449 00450 // No helpful SVG file to copy, so make one ourselves 00451 $data = '<?xml version="1.0" encoding="utf-8"?>' . 00452 '<svg xmlns="http://www.w3.org/2000/svg"' . 00453 ' version="1.1" width="240" height="180"/>'; 00454 00455 $backend->prepare( array( 'dir' => "$base/local-public/f/ff" ) ); 00456 $backend->quickCreate( array( 00457 'content' => $data, 'dst' => "$base/local-public/f/ff/Foobar.svg" 00458 ) ); 00459 } 00460 00465 protected function teardownGlobals() { 00466 $this->teardownUploads(); 00467 00468 foreach ( $this->savedGlobals as $var => $val ) { 00469 $GLOBALS[$var] = $val; 00470 } 00471 } 00472 00476 private function teardownUploads() { 00477 if ( $this->keepUploads ) { 00478 return; 00479 } 00480 00481 $backend = RepoGroup::singleton()->getLocalRepo()->getBackend(); 00482 if ( $backend instanceof MockFileBackend ) { 00483 # In memory backend, so dont bother cleaning them up. 00484 return; 00485 } 00486 00487 $base = $this->getBaseDir(); 00488 // delete the files first, then the dirs. 00489 self::deleteFiles( 00490 array( 00491 "$base/local-public/3/3a/Foobar.jpg", 00492 "$base/local-thumb/3/3a/Foobar.jpg/1000px-Foobar.jpg", 00493 "$base/local-thumb/3/3a/Foobar.jpg/100px-Foobar.jpg", 00494 "$base/local-thumb/3/3a/Foobar.jpg/120px-Foobar.jpg", 00495 "$base/local-thumb/3/3a/Foobar.jpg/1280px-Foobar.jpg", 00496 "$base/local-thumb/3/3a/Foobar.jpg/137px-Foobar.jpg", 00497 "$base/local-thumb/3/3a/Foobar.jpg/1500px-Foobar.jpg", 00498 "$base/local-thumb/3/3a/Foobar.jpg/177px-Foobar.jpg", 00499 "$base/local-thumb/3/3a/Foobar.jpg/180px-Foobar.jpg", 00500 "$base/local-thumb/3/3a/Foobar.jpg/200px-Foobar.jpg", 00501 "$base/local-thumb/3/3a/Foobar.jpg/206px-Foobar.jpg", 00502 "$base/local-thumb/3/3a/Foobar.jpg/20px-Foobar.jpg", 00503 "$base/local-thumb/3/3a/Foobar.jpg/220px-Foobar.jpg", 00504 "$base/local-thumb/3/3a/Foobar.jpg/265px-Foobar.jpg", 00505 "$base/local-thumb/3/3a/Foobar.jpg/270px-Foobar.jpg", 00506 "$base/local-thumb/3/3a/Foobar.jpg/274px-Foobar.jpg", 00507 "$base/local-thumb/3/3a/Foobar.jpg/300px-Foobar.jpg", 00508 "$base/local-thumb/3/3a/Foobar.jpg/30px-Foobar.jpg", 00509 "$base/local-thumb/3/3a/Foobar.jpg/330px-Foobar.jpg", 00510 "$base/local-thumb/3/3a/Foobar.jpg/353px-Foobar.jpg", 00511 "$base/local-thumb/3/3a/Foobar.jpg/360px-Foobar.jpg", 00512 "$base/local-thumb/3/3a/Foobar.jpg/400px-Foobar.jpg", 00513 "$base/local-thumb/3/3a/Foobar.jpg/40px-Foobar.jpg", 00514 "$base/local-thumb/3/3a/Foobar.jpg/440px-Foobar.jpg", 00515 "$base/local-thumb/3/3a/Foobar.jpg/442px-Foobar.jpg", 00516 "$base/local-thumb/3/3a/Foobar.jpg/450px-Foobar.jpg", 00517 "$base/local-thumb/3/3a/Foobar.jpg/50px-Foobar.jpg", 00518 "$base/local-thumb/3/3a/Foobar.jpg/600px-Foobar.jpg", 00519 "$base/local-thumb/3/3a/Foobar.jpg/640px-Foobar.jpg", 00520 "$base/local-thumb/3/3a/Foobar.jpg/70px-Foobar.jpg", 00521 "$base/local-thumb/3/3a/Foobar.jpg/75px-Foobar.jpg", 00522 "$base/local-thumb/3/3a/Foobar.jpg/960px-Foobar.jpg", 00523 00524 "$base/local-public/e/ea/Thumb.png", 00525 00526 "$base/local-public/0/09/Bad.jpg", 00527 00528 "$base/local-public/f/ff/Foobar.svg", 00529 "$base/local-thumb/f/ff/Foobar.svg/180px-Foobar.svg.png", 00530 "$base/local-thumb/f/ff/Foobar.svg/2000px-Foobar.svg.png", 00531 "$base/local-thumb/f/ff/Foobar.svg/270px-Foobar.svg.png", 00532 "$base/local-thumb/f/ff/Foobar.svg/3000px-Foobar.svg.png", 00533 "$base/local-thumb/f/ff/Foobar.svg/360px-Foobar.svg.png", 00534 "$base/local-thumb/f/ff/Foobar.svg/4000px-Foobar.svg.png", 00535 "$base/local-thumb/f/ff/Foobar.svg/langde-180px-Foobar.svg.png", 00536 "$base/local-thumb/f/ff/Foobar.svg/langde-270px-Foobar.svg.png", 00537 "$base/local-thumb/f/ff/Foobar.svg/langde-360px-Foobar.svg.png", 00538 00539 "$base/local-public/math/f/a/5/fa50b8b616463173474302ca3e63586b.png", 00540 ) 00541 ); 00542 } 00543 00548 private static function deleteFiles( $files ) { 00549 $backend = RepoGroup::singleton()->getLocalRepo()->getBackend(); 00550 foreach ( $files as $file ) { 00551 $backend->delete( array( 'src' => $file ), array( 'force' => 1 ) ); 00552 } 00553 foreach ( $files as $file ) { 00554 $tmp = $file; 00555 while ( $tmp = FileBackend::parentStoragePath( $tmp ) ) { 00556 if ( !$backend->clean( array( 'dir' => $tmp ) )->isOK() ) { 00557 break; 00558 } 00559 } 00560 } 00561 } 00562 00563 protected function getBaseDir() { 00564 return 'mwstore://local-backend'; 00565 } 00566 00567 public function parserTestProvider() { 00568 if ( $this->file === false ) { 00569 global $wgParserTestFiles; 00570 $this->file = $wgParserTestFiles[0]; 00571 } 00572 00573 return new TestFileIterator( $this->file, $this ); 00574 } 00575 00579 public function setParserTestFile( $filename ) { 00580 $this->file = $filename; 00581 } 00582 00587 public function testParserTest( $desc, $input, $result, $opts, $config ) { 00588 if ( $this->regex != '' && !preg_match( '/' . $this->regex . '/', $desc ) ) { 00589 $this->assertTrue( true ); // XXX: don't flood output with "test made no assertions" 00590 //$this->markTestSkipped( 'Filtered out by the user' ); 00591 return; 00592 } 00593 00594 if ( !$this->isWikitextNS( NS_MAIN ) ) { 00595 // parser tests frequently assume that the main namespace contains wikitext. 00596 // @todo When setting up pages, force the content model. Only skip if 00597 // $wgtContentModelUseDB is false. 00598 $this->markTestSkipped( "Main namespace does not support wikitext," 00599 . "skipping parser test: $desc" ); 00600 } 00601 00602 wfDebug( "Running parser test: $desc\n" ); 00603 00604 $opts = $this->parseOptions( $opts ); 00605 $context = $this->setupGlobals( $opts, $config ); 00606 00607 $user = $context->getUser(); 00608 $options = ParserOptions::newFromContext( $context ); 00609 00610 if ( isset( $opts['title'] ) ) { 00611 $titleText = $opts['title']; 00612 } else { 00613 $titleText = 'Parser test'; 00614 } 00615 00616 $local = isset( $opts['local'] ); 00617 $preprocessor = isset( $opts['preprocessor'] ) ? $opts['preprocessor'] : null; 00618 $parser = $this->getParser( $preprocessor ); 00619 00620 $title = Title::newFromText( $titleText ); 00621 00622 # Parser test requiring math. Make sure texvc is executable 00623 # or just skip such tests. 00624 if ( isset( $opts['math'] ) || isset( $opts['texvc'] ) ) { 00625 global $wgTexvc; 00626 00627 if ( !isset( $wgTexvc ) ) { 00628 $this->markTestSkipped( "SKIPPED: \$wgTexvc is not set" ); 00629 } elseif ( !is_executable( $wgTexvc ) ) { 00630 $this->markTestSkipped( "SKIPPED: texvc binary does not exist" 00631 . " or is not executable.\n" 00632 . "Current configuration is:\n\$wgTexvc = '$wgTexvc'" ); 00633 } 00634 } 00635 00636 if ( isset( $opts['pst'] ) ) { 00637 $out = $parser->preSaveTransform( $input, $title, $user, $options ); 00638 } elseif ( isset( $opts['msg'] ) ) { 00639 $out = $parser->transformMsg( $input, $options, $title ); 00640 } elseif ( isset( $opts['section'] ) ) { 00641 $section = $opts['section']; 00642 $out = $parser->getSection( $input, $section ); 00643 } elseif ( isset( $opts['replace'] ) ) { 00644 $section = $opts['replace'][0]; 00645 $replace = $opts['replace'][1]; 00646 $out = $parser->replaceSection( $input, $section, $replace ); 00647 } elseif ( isset( $opts['comment'] ) ) { 00648 $out = Linker::formatComment( $input, $title, $local ); 00649 } elseif ( isset( $opts['preload'] ) ) { 00650 $out = $parser->getPreloadText( $input, $title, $options ); 00651 } else { 00652 $output = $parser->parse( $input, $title, $options, true, true, 1337 ); 00653 $output->setTOCEnabled( !isset( $opts['notoc'] ) ); 00654 $out = $output->getText(); 00655 00656 if ( isset( $opts['showtitle'] ) ) { 00657 if ( $output->getTitleText() ) { 00658 $title = $output->getTitleText(); 00659 } 00660 00661 $out = "$title\n$out"; 00662 } 00663 00664 if ( isset( $opts['ill'] ) ) { 00665 $out = $this->tidy( implode( ' ', $output->getLanguageLinks() ) ); 00666 } elseif ( isset( $opts['cat'] ) ) { 00667 $outputPage = $context->getOutput(); 00668 $outputPage->addCategoryLinks( $output->getCategories() ); 00669 $cats = $outputPage->getCategoryLinks(); 00670 00671 if ( isset( $cats['normal'] ) ) { 00672 $out = $this->tidy( implode( ' ', $cats['normal'] ) ); 00673 } else { 00674 $out = ''; 00675 } 00676 } 00677 $parser->mPreprocessor = null; 00678 00679 $result = $this->tidy( $result ); 00680 } 00681 00682 $this->teardownGlobals(); 00683 00684 $this->assertEquals( $result, $out, $desc ); 00685 } 00686 00695 public function testFuzzTests() { 00696 global $wgParserTestFiles; 00697 00698 $files = $wgParserTestFiles; 00699 00700 if ( $this->getCliArg( 'file=' ) ) { 00701 $files = array( $this->getCliArg( 'file=' ) ); 00702 } 00703 00704 $dict = $this->getFuzzInput( $files ); 00705 $dictSize = strlen( $dict ); 00706 $logMaxLength = log( $this->maxFuzzTestLength ); 00707 00708 ini_set( 'memory_limit', $this->memoryLimit * 1048576 ); 00709 00710 $user = new User; 00711 $opts = ParserOptions::newFromUser( $user ); 00712 $title = Title::makeTitle( NS_MAIN, 'Parser_test' ); 00713 00714 $id = 1; 00715 00716 while ( true ) { 00717 00718 // Generate test input 00719 mt_srand( ++$this->fuzzSeed ); 00720 $totalLength = mt_rand( 1, $this->maxFuzzTestLength ); 00721 $input = ''; 00722 00723 while ( strlen( $input ) < $totalLength ) { 00724 $logHairLength = mt_rand( 0, 1000000 ) / 1000000 * $logMaxLength; 00725 $hairLength = min( intval( exp( $logHairLength ) ), $dictSize ); 00726 $offset = mt_rand( 0, $dictSize - $hairLength ); 00727 $input .= substr( $dict, $offset, $hairLength ); 00728 } 00729 00730 $this->setupGlobals(); 00731 $parser = $this->getParser(); 00732 00733 // Run the test 00734 try { 00735 $parser->parse( $input, $title, $opts ); 00736 $this->assertTrue( true, "Test $id, fuzz seed {$this->fuzzSeed}" ); 00737 } catch ( Exception $exception ) { 00738 $input_dump = sprintf( "string(%d) \"%s\"\n", strlen( $input ), $input ); 00739 00740 $this->assertTrue( false, "Test $id, fuzz seed {$this->fuzzSeed}. \n\n" . 00741 "Input: $input_dump\n\nError: {$exception->getMessage()}\n\n" . 00742 "Backtrace: {$exception->getTraceAsString()}" ); 00743 } 00744 00745 $this->teardownGlobals(); 00746 $parser->__destruct(); 00747 00748 if ( $id % 100 == 0 ) { 00749 $usage = intval( memory_get_usage( true ) / $this->memoryLimit / 1048576 * 100 ); 00750 //echo "{$this->fuzzSeed}: $numSuccess/$numTotal (mem: $usage%)\n"; 00751 if ( $usage > 90 ) { 00752 $ret = "Out of memory:\n"; 00753 $memStats = $this->getMemoryBreakdown(); 00754 00755 foreach ( $memStats as $name => $usage ) { 00756 $ret .= "$name: $usage\n"; 00757 } 00758 00759 throw new MWException( $ret ); 00760 } 00761 } 00762 00763 $id++; 00764 } 00765 } 00766 00767 //Various getter functions 00768 00772 function getFuzzInput( $filenames ) { 00773 $dict = ''; 00774 00775 foreach ( $filenames as $filename ) { 00776 $contents = file_get_contents( $filename ); 00777 preg_match_all( '/!!\s*input\n(.*?)\n!!\s*result/s', $contents, $matches ); 00778 00779 foreach ( $matches[1] as $match ) { 00780 $dict .= $match . "\n"; 00781 } 00782 } 00783 00784 return $dict; 00785 } 00786 00790 function getMemoryBreakdown() { 00791 $memStats = array(); 00792 00793 foreach ( $GLOBALS as $name => $value ) { 00794 $memStats['$' . $name] = strlen( serialize( $value ) ); 00795 } 00796 00797 $classes = get_declared_classes(); 00798 00799 foreach ( $classes as $class ) { 00800 $rc = new ReflectionClass( $class ); 00801 $props = $rc->getStaticProperties(); 00802 $memStats[$class] = strlen( serialize( $props ) ); 00803 $methods = $rc->getMethods(); 00804 00805 foreach ( $methods as $method ) { 00806 $memStats[$class] += strlen( serialize( $method->getStaticVariables() ) ); 00807 } 00808 } 00809 00810 $functions = get_defined_functions(); 00811 00812 foreach ( $functions['user'] as $function ) { 00813 $rf = new ReflectionFunction( $function ); 00814 $memStats["$function()"] = strlen( serialize( $rf->getStaticVariables() ) ); 00815 } 00816 00817 asort( $memStats ); 00818 00819 return $memStats; 00820 } 00821 00825 function getParser( $preprocessor = null ) { 00826 global $wgParserConf; 00827 00828 $class = $wgParserConf['class']; 00829 $parser = new $class( array( 'preprocessorClass' => $preprocessor ) + $wgParserConf ); 00830 00831 wfRunHooks( 'ParserTestParser', array( &$parser ) ); 00832 00833 return $parser; 00834 } 00835 00836 //Various action functions 00837 00838 public function addArticle( $name, $text, $line ) { 00839 self::$articles[$name] = array( $text, $line ); 00840 } 00841 00842 public function publishTestArticles() { 00843 if ( empty( self::$articles ) ) { 00844 return; 00845 } 00846 00847 foreach ( self::$articles as $name => $info ) { 00848 list( $text, $line ) = $info; 00849 ParserTest::addArticle( $name, $text, $line, 'ignoreduplicate' ); 00850 } 00851 } 00852 00861 public function requireHook( $name ) { 00862 global $wgParser; 00863 $wgParser->firstCallInit(); // make sure hooks are loaded. 00864 return isset( $wgParser->mTagHooks[$name] ); 00865 } 00866 00867 public function requireFunctionHook( $name ) { 00868 global $wgParser; 00869 $wgParser->firstCallInit(); // make sure hooks are loaded. 00870 return isset( $wgParser->mFunctionHooks[$name] ); 00871 } 00872 00873 //Various "cleanup" functions 00874 00882 protected function tidy( $text ) { 00883 global $wgUseTidy; 00884 00885 if ( $wgUseTidy ) { 00886 $text = MWTidy::tidy( $text ); 00887 } 00888 00889 return $text; 00890 } 00891 00895 public function removeEndingNewline( $s ) { 00896 if ( substr( $s, -1 ) === "\n" ) { 00897 return substr( $s, 0, -1 ); 00898 } else { 00899 return $s; 00900 } 00901 } 00902 00903 //Test options parser functions 00904 00905 protected function parseOptions( $instring ) { 00906 $opts = array(); 00907 // foo 00908 // foo=bar 00909 // foo="bar baz" 00910 // foo=[[bar baz]] 00911 // foo=bar,"baz quux" 00912 $regex = '/\b 00913 ([\w-]+) # Key 00914 \b 00915 (?:\s* 00916 = # First sub-value 00917 \s* 00918 ( 00919 " 00920 [^"]* # Quoted val 00921 " 00922 | 00923 \[\[ 00924 [^]]* # Link target 00925 \]\] 00926 | 00927 [\w-]+ # Plain word 00928 ) 00929 (?:\s* 00930 , # Sub-vals 1..N 00931 \s* 00932 ( 00933 "[^"]*" # Quoted val 00934 | 00935 \[\[[^]]*\]\] # Link target 00936 | 00937 [\w-]+ # Plain word 00938 ) 00939 )* 00940 )? 00941 /x'; 00942 00943 if ( preg_match_all( $regex, $instring, $matches, PREG_SET_ORDER ) ) { 00944 foreach ( $matches as $bits ) { 00945 array_shift( $bits ); 00946 $key = strtolower( array_shift( $bits ) ); 00947 if ( count( $bits ) == 0 ) { 00948 $opts[$key] = true; 00949 } elseif ( count( $bits ) == 1 ) { 00950 $opts[$key] = $this->cleanupOption( array_shift( $bits ) ); 00951 } else { 00952 // Array! 00953 $opts[$key] = array_map( array( $this, 'cleanupOption' ), $bits ); 00954 } 00955 } 00956 } 00957 00958 return $opts; 00959 } 00960 00961 protected function cleanupOption( $opt ) { 00962 if ( substr( $opt, 0, 1 ) == '"' ) { 00963 return substr( $opt, 1, -1 ); 00964 } 00965 00966 if ( substr( $opt, 0, 2 ) == '[[' ) { 00967 return substr( $opt, 2, -2 ); 00968 } 00969 00970 return $opt; 00971 } 00972 00979 protected static function getOptionValue( $key, $opts, $default ) { 00980 $key = strtolower( $key ); 00981 00982 if ( isset( $opts[$key] ) ) { 00983 return $opts[$key]; 00984 } else { 00985 return $default; 00986 } 00987 } 00988 }