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