MediaWiki  REL1_22
NewParserTest.php
Go to the documentation of this file.
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'       => 200,
00267                     'height'      => 200,
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                 $class = $conf['class'];
00310                 self::$backendToUse = new $class( $useConfig );
00311                 $backend = self::$backendToUse;
00312             }
00313         } else {
00314             # Replace with a mock. We do not care about generating real
00315             # files on the filesystem, just need to expose the file
00316             # informations.
00317             $backend = new MockFileBackend( array(
00318                 'name' => 'local-backend',
00319                 'lockManager' => 'nullLockManager',
00320                 'containerPaths' => array(
00321                     'local-public' => "$uploadDir",
00322                     'local-thumb' => "$uploadDir/thumb",
00323                 )
00324             ) );
00325         }
00326 
00327         $settings = array(
00328             'wgLocalFileRepo' => array(
00329                 'class' => 'LocalRepo',
00330                 'name' => 'local',
00331                 'url' => 'http://example.com/images',
00332                 'hashLevels' => 2,
00333                 'transformVia404' => false,
00334                 'backend' => $backend
00335             ),
00336             'wgEnableUploads' => self::getOptionValue( 'wgEnableUploads', $opts, true ),
00337             'wgLanguageCode' => $lang,
00338             'wgDBprefix' => $this->db->getType() != 'oracle' ? 'unittest_' : 'ut_',
00339             'wgRawHtml' => self::getOptionValue( 'wgRawHtml', $opts, false ),
00340             'wgNamespacesWithSubpages' => array( NS_MAIN => isset( $opts['subpage'] ) ),
00341             'wgAllowExternalImages' => self::getOptionValue( 'wgAllowExternalImages', $opts, true ),
00342             'wgMaxTocLevel' => $maxtoclevel,
00343             'wgUseTeX' => isset( $opts['math'] ) || isset( $opts['texvc'] ),
00344             'wgMathDirectory' => $uploadDir . '/math',
00345             'wgDefaultLanguageVariant' => $variant,
00346             'wgLinkHolderBatchSize' => $linkHolderBatchSize,
00347         );
00348 
00349         if ( $config ) {
00350             $configLines = explode( "\n", $config );
00351 
00352             foreach ( $configLines as $line ) {
00353                 list( $var, $value ) = explode( '=', $line, 2 );
00354 
00355                 $settings[$var] = eval( "return $value;" ); //???
00356             }
00357         }
00358 
00359         $this->savedGlobals = array();
00360 
00362         wfRunHooks( 'ParserTestGlobals', array( &$settings ) );
00363 
00364         $langObj = Language::factory( $lang );
00365         $settings['wgContLang'] = $langObj;
00366         $settings['wgLang'] = $langObj;
00367 
00368         $context = new RequestContext();
00369         $settings['wgOut'] = $context->getOutput();
00370         $settings['wgUser'] = $context->getUser();
00371         $settings['wgRequest'] = $context->getRequest();
00372 
00373         foreach ( $settings as $var => $val ) {
00374             if ( array_key_exists( $var, $GLOBALS ) ) {
00375                 $this->savedGlobals[$var] = $GLOBALS[$var];
00376             }
00377 
00378             $GLOBALS[$var] = $val;
00379         }
00380 
00381         MagicWord::clearCache();
00382 
00383         # The entries saved into RepoGroup cache with previous globals will be wrong.
00384         RepoGroup::destroySingleton();
00385         FileBackendGroup::destroySingleton();
00386 
00387         # Create dummy files in storage
00388         $this->setupUploads();
00389 
00390         # Publish the articles after we have the final language set
00391         $this->publishTestArticles();
00392 
00393         MessageCache::destroyInstance();
00394 
00395         return $context;
00396     }
00397 
00403     protected function getUploadDir() {
00404         if ( $this->keepUploads ) {
00405             $dir = wfTempDir() . '/mwParser-images';
00406 
00407             if ( is_dir( $dir ) ) {
00408                 return $dir;
00409             }
00410         } else {
00411             $dir = wfTempDir() . "/mwParser-" . mt_rand() . "-images";
00412         }
00413 
00414         // wfDebug( "Creating upload directory $dir\n" );
00415         if ( file_exists( $dir ) ) {
00416             wfDebug( "Already exists!\n" );
00417 
00418             return $dir;
00419         }
00420 
00421         return $dir;
00422     }
00423 
00430     protected function setupUploads() {
00431         global $IP;
00432 
00433         $base = $this->getBaseDir();
00434         $backend = RepoGroup::singleton()->getLocalRepo()->getBackend();
00435         $backend->prepare( array( 'dir' => "$base/local-public/3/3a" ) );
00436         $backend->store( array(
00437             'src' => "$IP/skins/monobook/headbg.jpg", 'dst' => "$base/local-public/3/3a/Foobar.jpg"
00438         ) );
00439         $backend->prepare( array( 'dir' => "$base/local-public/e/ea" ) );
00440         $backend->store( array(
00441             'src' => "$IP/skins/monobook/wiki.png", 'dst' => "$base/local-public/e/ea/Thumb.png"
00442         ) );
00443         $backend->prepare( array( 'dir' => "$base/local-public/0/09" ) );
00444         $backend->store( array(
00445             'src' => "$IP/skins/monobook/headbg.jpg", 'dst' => "$base/local-public/0/09/Bad.jpg"
00446         ) );
00447 
00448         // No helpful SVG file to copy, so make one ourselves
00449         $tmpDir = wfTempDir();
00450         $tempFsFile = new TempFSFile( "$tmpDir/Foobar.svg" );
00451         $tempFsFile->autocollect(); // destroy file when $tempFsFile leaves scope
00452         file_put_contents( "$tmpDir/Foobar.svg",
00453             '<?xml version="1.0" encoding="utf-8"?>' .
00454             '<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200"><text>Foo</text></svg>' );
00455 
00456         $backend->prepare( array( 'dir' => "$base/local-public/f/ff" ) );
00457         $backend->quickStore( array(
00458             'src' => "$tmpDir/Foobar.svg", 'dst' => "$base/local-public/f/ff/Foobar.svg"
00459         ) );
00460     }
00461 
00466     protected function teardownGlobals() {
00467         $this->teardownUploads();
00468 
00469         foreach ( $this->savedGlobals as $var => $val ) {
00470             $GLOBALS[$var] = $val;
00471         }
00472     }
00473 
00477     private function teardownUploads() {
00478         if ( $this->keepUploads ) {
00479             return;
00480         }
00481 
00482         $backend = RepoGroup::singleton()->getLocalRepo()->getBackend();
00483         if ( $backend instanceof MockFileBackend ) {
00484             # In memory backend, so dont bother cleaning them up.
00485             return;
00486         }
00487 
00488         $base = $this->getBaseDir();
00489         // delete the files first, then the dirs.
00490         self::deleteFiles(
00491             array(
00492                 "$base/local-public/3/3a/Foobar.jpg",
00493                 "$base/local-thumb/3/3a/Foobar.jpg/180px-Foobar.jpg",
00494                 "$base/local-thumb/3/3a/Foobar.jpg/200px-Foobar.jpg",
00495                 "$base/local-thumb/3/3a/Foobar.jpg/640px-Foobar.jpg",
00496                 "$base/local-thumb/3/3a/Foobar.jpg/120px-Foobar.jpg",
00497                 "$base/local-thumb/3/3a/Foobar.jpg/1280px-Foobar.jpg",
00498                 "$base/local-thumb/3/3a/Foobar.jpg/20px-Foobar.jpg",
00499                 "$base/local-thumb/3/3a/Foobar.jpg/270px-Foobar.jpg",
00500                 "$base/local-thumb/3/3a/Foobar.jpg/300px-Foobar.jpg",
00501                 "$base/local-thumb/3/3a/Foobar.jpg/30px-Foobar.jpg",
00502                 "$base/local-thumb/3/3a/Foobar.jpg/360px-Foobar.jpg",
00503                 "$base/local-thumb/3/3a/Foobar.jpg/400px-Foobar.jpg",
00504                 "$base/local-thumb/3/3a/Foobar.jpg/40px-Foobar.jpg",
00505                 "$base/local-thumb/3/3a/Foobar.jpg/70px-Foobar.jpg",
00506                 "$base/local-thumb/3/3a/Foobar.jpg/960px-Foobar.jpg",
00507 
00508                 "$base/local-public/e/ea/Thumb.png",
00509 
00510                 "$base/local-public/0/09/Bad.jpg",
00511 
00512                 "$base/local-public/f/ff/Foobar.svg",
00513                 "$base/local-thumb/f/ff/Foobar.svg/180px-Foobar.svg.jpg",
00514                 "$base/local-thumb/f/ff/Foobar.svg/270px-Foobar.svg.jpg",
00515                 "$base/local-thumb/f/ff/Foobar.svg/360px-Foobar.svg.jpg",
00516                 "$base/local-thumb/f/ff/Foobar.svg/langde-180px-Foobar.svg.jpg",
00517                 "$base/local-thumb/f/ff/Foobar.svg/langde-270px-Foobar.svg.jpg",
00518                 "$base/local-thumb/f/ff/Foobar.svg/langde-360px-Foobar.svg.jpg",
00519 
00520                 "$base/local-public/math/f/a/5/fa50b8b616463173474302ca3e63586b.png",
00521             )
00522         );
00523     }
00524 
00529     private static function deleteFiles( $files ) {
00530         $backend = RepoGroup::singleton()->getLocalRepo()->getBackend();
00531         foreach ( $files as $file ) {
00532             $backend->delete( array( 'src' => $file ), array( 'force' => 1 ) );
00533         }
00534         foreach ( $files as $file ) {
00535             $tmp = $file;
00536             while ( $tmp = FileBackend::parentStoragePath( $tmp ) ) {
00537                 if ( !$backend->clean( array( 'dir' => $tmp ) )->isOK() ) {
00538                     break;
00539                 }
00540             }
00541         }
00542     }
00543 
00544     protected function getBaseDir() {
00545         return 'mwstore://local-backend';
00546     }
00547 
00548     public function parserTestProvider() {
00549         if ( $this->file === false ) {
00550             global $wgParserTestFiles;
00551             $this->file = $wgParserTestFiles[0];
00552         }
00553 
00554         return new TestFileIterator( $this->file, $this );
00555     }
00556 
00560     public function setParserTestFile( $filename ) {
00561         $this->file = $filename;
00562     }
00563 
00568     public function testParserTest( $desc, $input, $result, $opts, $config ) {
00569         if ( $this->regex != '' && !preg_match( '/' . $this->regex . '/', $desc ) ) {
00570             $this->assertTrue( true ); // XXX: don't flood output with "test made no assertions"
00571             //$this->markTestSkipped( 'Filtered out by the user' );
00572             return;
00573         }
00574 
00575         if ( !$this->isWikitextNS( NS_MAIN ) ) {
00576             // parser tests frequently assume that the main namespace contains wikitext.
00577             // @todo When setting up pages, force the content model. Only skip if
00578             //        $wgtContentModelUseDB is false.
00579             $this->markTestSkipped( "Main namespace does not support wikitext,"
00580                 . "skipping parser test: $desc" );
00581         }
00582 
00583         wfDebug( "Running parser test: $desc\n" );
00584 
00585         $opts = $this->parseOptions( $opts );
00586         $context = $this->setupGlobals( $opts, $config );
00587 
00588         $user = $context->getUser();
00589         $options = ParserOptions::newFromContext( $context );
00590 
00591         if ( isset( $opts['title'] ) ) {
00592             $titleText = $opts['title'];
00593         } else {
00594             $titleText = 'Parser test';
00595         }
00596 
00597         $local = isset( $opts['local'] );
00598         $preprocessor = isset( $opts['preprocessor'] ) ? $opts['preprocessor'] : null;
00599         $parser = $this->getParser( $preprocessor );
00600 
00601         $title = Title::newFromText( $titleText );
00602 
00603         # Parser test requiring math. Make sure texvc is executable
00604         # or just skip such tests.
00605         if ( isset( $opts['math'] ) || isset( $opts['texvc'] ) ) {
00606             global $wgTexvc;
00607 
00608             if ( !isset( $wgTexvc ) ) {
00609                 $this->markTestSkipped( "SKIPPED: \$wgTexvc is not set" );
00610             } elseif ( !is_executable( $wgTexvc ) ) {
00611                 $this->markTestSkipped( "SKIPPED: texvc binary does not exist"
00612                     . " or is not executable.\n"
00613                     . "Current configuration is:\n\$wgTexvc = '$wgTexvc'" );
00614             }
00615         }
00616 
00617         if ( isset( $opts['pst'] ) ) {
00618             $out = $parser->preSaveTransform( $input, $title, $user, $options );
00619         } elseif ( isset( $opts['msg'] ) ) {
00620             $out = $parser->transformMsg( $input, $options, $title );
00621         } elseif ( isset( $opts['section'] ) ) {
00622             $section = $opts['section'];
00623             $out = $parser->getSection( $input, $section );
00624         } elseif ( isset( $opts['replace'] ) ) {
00625             $section = $opts['replace'][0];
00626             $replace = $opts['replace'][1];
00627             $out = $parser->replaceSection( $input, $section, $replace );
00628         } elseif ( isset( $opts['comment'] ) ) {
00629             $out = Linker::formatComment( $input, $title, $local );
00630         } elseif ( isset( $opts['preload'] ) ) {
00631             $out = $parser->getPreloadText( $input, $title, $options );
00632         } else {
00633             $output = $parser->parse( $input, $title, $options, true, true, 1337 );
00634             $output->setTOCEnabled( !isset( $opts['notoc'] ) );
00635             $out = $output->getText();
00636 
00637             if ( isset( $opts['showtitle'] ) ) {
00638                 if ( $output->getTitleText() ) {
00639                     $title = $output->getTitleText();
00640                 }
00641 
00642                 $out = "$title\n$out";
00643             }
00644 
00645             if ( isset( $opts['ill'] ) ) {
00646                 $out = $this->tidy( implode( ' ', $output->getLanguageLinks() ) );
00647             } elseif ( isset( $opts['cat'] ) ) {
00648                 $outputPage = $context->getOutput();
00649                 $outputPage->addCategoryLinks( $output->getCategories() );
00650                 $cats = $outputPage->getCategoryLinks();
00651 
00652                 if ( isset( $cats['normal'] ) ) {
00653                     $out = $this->tidy( implode( ' ', $cats['normal'] ) );
00654                 } else {
00655                     $out = '';
00656                 }
00657             }
00658             $parser->mPreprocessor = null;
00659 
00660             $result = $this->tidy( $result );
00661         }
00662 
00663         $this->teardownGlobals();
00664 
00665         $this->assertEquals( $result, $out, $desc );
00666     }
00667 
00676     public function testFuzzTests() {
00677         global $wgParserTestFiles;
00678 
00679         $files = $wgParserTestFiles;
00680 
00681         if ( $this->getCliArg( 'file=' ) ) {
00682             $files = array( $this->getCliArg( 'file=' ) );
00683         }
00684 
00685         $dict = $this->getFuzzInput( $files );
00686         $dictSize = strlen( $dict );
00687         $logMaxLength = log( $this->maxFuzzTestLength );
00688 
00689         ini_set( 'memory_limit', $this->memoryLimit * 1048576 );
00690 
00691         $user = new User;
00692         $opts = ParserOptions::newFromUser( $user );
00693         $title = Title::makeTitle( NS_MAIN, 'Parser_test' );
00694 
00695         $id = 1;
00696 
00697         while ( true ) {
00698 
00699             // Generate test input
00700             mt_srand( ++$this->fuzzSeed );
00701             $totalLength = mt_rand( 1, $this->maxFuzzTestLength );
00702             $input = '';
00703 
00704             while ( strlen( $input ) < $totalLength ) {
00705                 $logHairLength = mt_rand( 0, 1000000 ) / 1000000 * $logMaxLength;
00706                 $hairLength = min( intval( exp( $logHairLength ) ), $dictSize );
00707                 $offset = mt_rand( 0, $dictSize - $hairLength );
00708                 $input .= substr( $dict, $offset, $hairLength );
00709             }
00710 
00711             $this->setupGlobals();
00712             $parser = $this->getParser();
00713 
00714             // Run the test
00715             try {
00716                 $parser->parse( $input, $title, $opts );
00717                 $this->assertTrue( true, "Test $id, fuzz seed {$this->fuzzSeed}" );
00718             } catch ( Exception $exception ) {
00719                 $input_dump = sprintf( "string(%d) \"%s\"\n", strlen( $input ), $input );
00720 
00721                 $this->assertTrue( false, "Test $id, fuzz seed {$this->fuzzSeed}. \n\n" .
00722                     "Input: $input_dump\n\nError: {$exception->getMessage()}\n\n" .
00723                     "Backtrace: {$exception->getTraceAsString()}" );
00724             }
00725 
00726             $this->teardownGlobals();
00727             $parser->__destruct();
00728 
00729             if ( $id % 100 == 0 ) {
00730                 $usage = intval( memory_get_usage( true ) / $this->memoryLimit / 1048576 * 100 );
00731                 //echo "{$this->fuzzSeed}: $numSuccess/$numTotal (mem: $usage%)\n";
00732                 if ( $usage > 90 ) {
00733                     $ret = "Out of memory:\n";
00734                     $memStats = $this->getMemoryBreakdown();
00735 
00736                     foreach ( $memStats as $name => $usage ) {
00737                         $ret .= "$name: $usage\n";
00738                     }
00739 
00740                     throw new MWException( $ret );
00741                 }
00742             }
00743 
00744             $id++;
00745         }
00746     }
00747 
00748     //Various getter functions
00749 
00753     function getFuzzInput( $filenames ) {
00754         $dict = '';
00755 
00756         foreach ( $filenames as $filename ) {
00757             $contents = file_get_contents( $filename );
00758             preg_match_all( '/!!\s*input\n(.*?)\n!!\s*result/s', $contents, $matches );
00759 
00760             foreach ( $matches[1] as $match ) {
00761                 $dict .= $match . "\n";
00762             }
00763         }
00764 
00765         return $dict;
00766     }
00767 
00771     function getMemoryBreakdown() {
00772         $memStats = array();
00773 
00774         foreach ( $GLOBALS as $name => $value ) {
00775             $memStats['$' . $name] = strlen( serialize( $value ) );
00776         }
00777 
00778         $classes = get_declared_classes();
00779 
00780         foreach ( $classes as $class ) {
00781             $rc = new ReflectionClass( $class );
00782             $props = $rc->getStaticProperties();
00783             $memStats[$class] = strlen( serialize( $props ) );
00784             $methods = $rc->getMethods();
00785 
00786             foreach ( $methods as $method ) {
00787                 $memStats[$class] += strlen( serialize( $method->getStaticVariables() ) );
00788             }
00789         }
00790 
00791         $functions = get_defined_functions();
00792 
00793         foreach ( $functions['user'] as $function ) {
00794             $rf = new ReflectionFunction( $function );
00795             $memStats["$function()"] = strlen( serialize( $rf->getStaticVariables() ) );
00796         }
00797 
00798         asort( $memStats );
00799 
00800         return $memStats;
00801     }
00802 
00806     function getParser( $preprocessor = null ) {
00807         global $wgParserConf;
00808 
00809         $class = $wgParserConf['class'];
00810         $parser = new $class( array( 'preprocessorClass' => $preprocessor ) + $wgParserConf );
00811 
00812         wfRunHooks( 'ParserTestParser', array( &$parser ) );
00813 
00814         return $parser;
00815     }
00816 
00817     //Various action functions
00818 
00819     public function addArticle( $name, $text, $line ) {
00820         self::$articles[$name] = array( $text, $line );
00821     }
00822 
00823     public function publishTestArticles() {
00824         if ( empty( self::$articles ) ) {
00825             return;
00826         }
00827 
00828         foreach ( self::$articles as $name => $info ) {
00829             list( $text, $line ) = $info;
00830             ParserTest::addArticle( $name, $text, $line, 'ignoreduplicate' );
00831         }
00832     }
00833 
00842     public function requireHook( $name ) {
00843         global $wgParser;
00844         $wgParser->firstCallInit(); // make sure hooks are loaded.
00845         return isset( $wgParser->mTagHooks[$name] );
00846     }
00847 
00848     public function requireFunctionHook( $name ) {
00849         global $wgParser;
00850         $wgParser->firstCallInit(); // make sure hooks are loaded.
00851         return isset( $wgParser->mFunctionHooks[$name] );
00852     }
00853 
00854     //Various "cleanup" functions
00855 
00863     protected function tidy( $text ) {
00864         global $wgUseTidy;
00865 
00866         if ( $wgUseTidy ) {
00867             $text = MWTidy::tidy( $text );
00868         }
00869 
00870         return $text;
00871     }
00872 
00876     public function removeEndingNewline( $s ) {
00877         if ( substr( $s, -1 ) === "\n" ) {
00878             return substr( $s, 0, -1 );
00879         } else {
00880             return $s;
00881         }
00882     }
00883 
00884     //Test options parser functions
00885 
00886     protected function parseOptions( $instring ) {
00887         $opts = array();
00888         // foo
00889         // foo=bar
00890         // foo="bar baz"
00891         // foo=[[bar baz]]
00892         // foo=bar,"baz quux"
00893         $regex = '/\b
00894             ([\w-]+)                        # Key
00895             \b
00896             (?:\s*
00897                 =                       # First sub-value
00898                 \s*
00899                 (
00900                     "
00901                         [^"]*           # Quoted val
00902                     "
00903                 |
00904                     \[\[
00905                         [^]]*           # Link target
00906                     \]\]
00907                 |
00908                     [\w-]+              # Plain word
00909                 )
00910                 (?:\s*
00911                     ,                   # Sub-vals 1..N
00912                     \s*
00913                     (
00914                         "[^"]*"         # Quoted val
00915                     |
00916                         \[\[[^]]*\]\]   # Link target
00917                     |
00918                         [\w-]+          # Plain word
00919                     )
00920                 )*
00921             )?
00922             /x';
00923 
00924         if ( preg_match_all( $regex, $instring, $matches, PREG_SET_ORDER ) ) {
00925             foreach ( $matches as $bits ) {
00926                 array_shift( $bits );
00927                 $key = strtolower( array_shift( $bits ) );
00928                 if ( count( $bits ) == 0 ) {
00929                     $opts[$key] = true;
00930                 } elseif ( count( $bits ) == 1 ) {
00931                     $opts[$key] = $this->cleanupOption( array_shift( $bits ) );
00932                 } else {
00933                     // Array!
00934                     $opts[$key] = array_map( array( $this, 'cleanupOption' ), $bits );
00935                 }
00936             }
00937         }
00938 
00939         return $opts;
00940     }
00941 
00942     protected function cleanupOption( $opt ) {
00943         if ( substr( $opt, 0, 1 ) == '"' ) {
00944             return substr( $opt, 1, -1 );
00945         }
00946 
00947         if ( substr( $opt, 0, 2 ) == '[[' ) {
00948             return substr( $opt, 2, -2 );
00949         }
00950 
00951         return $opt;
00952     }
00953 
00960     protected static function getOptionValue( $key, $opts, $default ) {
00961         $key = strtolower( $key );
00962 
00963         if ( isset( $opts[$key] ) ) {
00964             return $opts[$key];
00965         } else {
00966             return $default;
00967         }
00968     }
00969 }