30 use Wikimedia\ScopedCallback;
31 use Wikimedia\TestingAccessWrapper;
36 class ParserTestRunner {
44 private static $coreTestFiles = [
46 'extraParserTests.txt',
52 private $useTemporaryTables =
true;
57 private $setupDone = [
58 'staticSetup' =>
false,
59 'perTestSetup' =>
false,
60 'setupDatabase' =>
false,
61 'setDatabase' =>
false,
62 'setupUploads' =>
false,
85 private $tidyDriver =
null;
97 private $uploadDir =
null;
103 private $fileBackendName;
116 private $normalizationFunctions = [];
123 $this->recorder = $recorder;
127 if ( in_array( $func, [
'removeTbody',
'trimWhitespace' ] ) ) {
128 $this->normalizationFunctions[] = $func;
131 "Warning: unknown normalization option \"$func\"\n" );
143 $this->keepUploads = !empty(
$options[
'keep-uploads'] );
145 $this->fileBackendName = isset(
$options[
'file-backend'] ) ?
148 $this->runDisabled = !empty(
$options[
'run-disabled'] );
149 $this->runParsoid = !empty(
$options[
'run-parsoid'] );
152 if ( !$this->tidySupport->isEnabled() ) {
153 $this->recorder->warning(
154 "Warning: tidy is not installed, skipping some tests\n" );
157 if ( isset(
$options[
'upload-dir'] ) ) {
158 $this->uploadDir =
$options[
'upload-dir'];
167 public static function getParserTestFiles() {
171 $files = array_map(
function ( $item ) {
172 return __DIR__ .
"/$item";
173 }, self::$coreTestFiles );
180 foreach ( $registry->getAllThings()
as $info ) {
181 $dir = dirname( $info[
'path'] ) .
'/tests/parser';
182 if ( !file_exists(
$dir ) ) {
186 $dirIterator =
new RecursiveIteratorIterator(
187 new RecursiveDirectoryIterator(
$dir )
189 foreach ( $dirIterator
as $fileInfo ) {
191 if ( substr( $fileInfo->getFilename(), -4 ) ===
'.txt' ) {
192 $name = $info[
'name'] . $counter;
193 while ( isset( $files[
$name] ) ) {
194 $name = $info[
'name'] .
'_' . $counter++;
196 $files[
$name] = $fileInfo->getPathname();
201 return array_unique( $files );
204 public function getRecorder() {
205 return $this->recorder;
227 public function staticSetup( $nextTeardown =
null ) {
254 $teardown[] = $this->markSetupDone(
'staticSetup' );
257 $setup[
'wgSitename'] =
'MediaWiki';
258 $setup[
'wgServer'] =
'http://example.org';
259 $setup[
'wgServerName'] =
'example.org';
260 $setup[
'wgScriptPath'] =
'';
261 $setup[
'wgScript'] =
'/index.php';
262 $setup[
'wgResourceBasePath'] =
'';
263 $setup[
'wgStylePath'] =
'/skins';
264 $setup[
'wgExtensionAssetsPath'] =
'/extensions';
265 $setup[
'wgArticlePath'] =
'/wiki/$1';
266 $setup[
'wgActionPaths'] = [];
267 $setup[
'wgVariantArticlePath'] =
false;
268 $setup[
'wgUploadNavigationUrl'] =
false;
269 $setup[
'wgCapitalLinks'] =
true;
270 $setup[
'wgNoFollowLinks'] =
true;
271 $setup[
'wgNoFollowDomainExceptions'] = [
'no-nofollow.org' ];
272 $setup[
'wgExternalLinkTarget'] =
false;
273 $setup[
'wgExperimentalHtmlIds'] =
false;
274 $setup[
'wgLocaltimezone'] =
'UTC';
275 $setup[
'wgHtml5'] =
true;
276 $setup[
'wgDisableLangConversion'] =
false;
277 $setup[
'wgDisableTitleConversion'] =
false;
281 $setup[
'wgExtraInterlanguageLinkPrefixes'] = [
'mul' ];
286 $teardown[] =
function () {
291 $setup[
'wgLockManagers'] = [ [
292 'name' =>
'fsLockManager',
293 'class' =>
'NullLockManager',
295 'name' =>
'nullLockManager',
296 'class' =>
'NullLockManager',
298 $reset =
function () {
302 $teardown[] = $reset;
305 $setup[
'wgDefaultExternalStore'] =
false;
308 $setup[
'wgAdaptiveMessageCache'] =
true;
311 $setup[
'wgUseDatabaseMessages'] =
true;
312 $reset =
function () {
316 $teardown[] = $reset;
319 $setup[
'wgSVGConverter'] =
'null';
320 $setup[
'wgSVGConverters'] = [
'null' =>
'echo "1">$output' ];
324 $ts = $this->getFakeTimestamp();
327 $teardown[] =
function () {
331 $this->appendNamespaceSetup( $setup, $teardown );
334 $teardown[] = $this->setupInterwikis();
338 $setup[
'wgLocalInterwikis'] = [
'local',
'mi' ];
339 $reset =
function () {
340 $this->resetTitleServices();
343 $teardown[] = $reset;
346 MediaWikiServices::getInstance()->disableService(
'MediaHandlerFactory' );
347 MediaWikiServices::getInstance()->redefineService(
348 'MediaHandlerFactory',
350 $handlers =
$services->getMainConfig()->get(
'ParserTestMediaHandlers' );
354 $teardown[] =
function () {
355 MediaWikiServices::getInstance()->resetServiceForTesting(
'MediaHandlerFactory' );
366 $teardown[] =
function ()
use ( $savedCache ) {
371 $teardown[] = $this->executeSetupSnippets( $setup );
374 return $this->createTeardownObject( $teardown, $nextTeardown );
377 private function appendNamespaceSetup( &$setup, &$teardown ) {
380 $setup[
'wgExtraNamespaces'] = [
381 100 =>
'MemoryAlpha',
382 101 =>
'MemoryAlpha_talk'
386 $reset =
function () {
388 $GLOBALS[
'wgContLang']->resetNamespaces();
391 $teardown[] = $reset;
398 protected function createRepoGroup() {
399 if ( $this->uploadDir ) {
400 if ( $this->fileBackendName ) {
401 throw new MWException(
'You cannot specify both use-filebackend and upload-dir' );
404 'name' =>
'local-backend',
406 'basePath' => $this->uploadDir,
409 } elseif ( $this->fileBackendName ) {
411 $name = $this->fileBackendName;
414 if ( $conf[
'name'] ===
$name ) {
418 if ( $useConfig ===
false ) {
419 throw new MWException(
"Unable to find file backend \"$name\"" );
421 $useConfig[
'name'] =
'local-backend';
422 unset( $useConfig[
'lockManager'] );
423 unset( $useConfig[
'fileJournal'] );
424 $class = $useConfig[
'class'];
425 $backend =
new $class( $useConfig );
427 # Replace with a mock. We do not care about generating real
428 # files on the filesystem, just need to expose the file
431 'name' =>
'local-backend',
438 'class' =>
'MockLocalRepo',
440 'url' =>
'http://example.com/images',
442 'transformVia404' =>
false,
443 'backend' => $backend
462 protected function executeSetupSnippets( $setup ) {
465 if ( is_int(
$name ) ) {
472 return function ()
use ( $saved ) {
473 $this->executeSetupSnippets( $saved );
489 protected function createTeardownObject( $teardown, $nextTeardown =
null ) {
490 return new ScopedCallback(
function ()
use ( $teardown, $nextTeardown ) {
492 $teardown = array_reverse( $teardown );
494 $this->executeSetupSnippets( $teardown );
495 if ( $nextTeardown ) {
496 ScopedCallback::consume( $nextTeardown );
508 protected function markSetupDone( $funcName ) {
509 if ( $this->setupDone[$funcName] ) {
510 throw new MWException(
"$funcName is already done" );
512 $this->setupDone[$funcName] =
true;
513 return function ()
use ( $funcName ) {
514 $this->setupDone[$funcName] =
false;
524 protected function checkSetupDone( $funcName, $funcName2 =
null ) {
525 if ( !$this->setupDone[$funcName]
526 && ( $funcName ===
null || !$this->setupDone[$funcName2] )
528 throw new MWException(
"$funcName must be called before calling " .
539 public function isSetupDone( $funcName ) {
540 return isset( $this->setupDone[$funcName] ) ? $this->setupDone[$funcName] :
false;
554 private function setupInterwikis() {
555 # Hack: insert a few Wikipedia in-project interwiki prefixes,
556 # for testing inter-language links
557 Hooks::register(
'InterwikiLoadPrefix',
function ( $prefix, &$iwData ) {
558 static $testInterwikis = [
560 'iw_url' =>
'http://doesnt.matter.org/$1',
565 'iw_url' =>
'http://en.wikipedia.org/wiki/$1',
570 'iw_url' =>
'http://www.usemod.com/cgi-bin/mb.pl?$1',
575 'iw_url' =>
'http://www.memory-alpha.org/en/index.php/$1',
580 'iw_url' =>
'http://zh.wikipedia.org/wiki/$1',
585 'iw_url' =>
'http://es.wikipedia.org/wiki/$1',
590 'iw_url' =>
'http://fr.wikipedia.org/wiki/$1',
595 'iw_url' =>
'http://ru.wikipedia.org/wiki/$1',
600 'iw_url' =>
'http://mi.wikipedia.org/wiki/$1',
605 'iw_url' =>
'http://wikisource.org/wiki/$1',
610 if ( array_key_exists( $prefix, $testInterwikis ) ) {
611 $iwData = $testInterwikis[$prefix];
628 private function resetTitleServices() {
629 $services = MediaWikiServices::getInstance();
630 $services->resetServiceForTesting(
'TitleFormatter' );
631 $services->resetServiceForTesting(
'TitleParser' );
632 $services->resetServiceForTesting(
'_MediaWikiTitleCodec' );
633 $services->resetServiceForTesting(
'LinkRenderer' );
634 $services->resetServiceForTesting(
'LinkRendererFactory' );
643 public static function chomp(
$s ) {
644 if ( substr(
$s, -1 ) ===
"\n" ) {
645 return substr(
$s, 0, -1 );
664 public function runTestsFromFiles( $filenames ) {
667 $teardownGuard = $this->staticSetup();
668 $teardownGuard = $this->setupDatabase( $teardownGuard );
669 $teardownGuard = $this->setupUploads( $teardownGuard );
671 $this->recorder->start();
675 foreach ( $filenames
as $filename ) {
677 'runDisabled' => $this->runDisabled,
678 'runParsoid' => $this->runParsoid,
679 'regex' => $this->regex ] );
682 if ( !$testFileInfo[
'tests'] ) {
686 $this->recorder->startSuite( $filename );
687 $ok = $this->runTests( $testFileInfo ) && $ok;
688 $this->recorder->endSuite( $filename );
691 $this->recorder->report();
692 }
catch ( DBError
$e ) {
693 $this->recorder->warning(
$e->getMessage() );
695 $this->recorder->end();
697 ScopedCallback::consume( $teardownGuard );
708 public function meetsRequirements( $requirements ) {
709 foreach ( $requirements
as $requirement ) {
710 switch ( $requirement[
'type'] ) {
712 $ok = $this->requireHook( $requirement[
'name'] );
715 $ok = $this->requireFunctionHook( $requirement[
'name'] );
717 case 'transparentHook':
718 $ok = $this->requireTransparentHook( $requirement[
'name'] );
735 public function runTests( $testFileInfo ) {
738 $this->checkSetupDone(
'staticSetup' );
741 if ( !$testFileInfo[
'tests'] ) {
746 if ( !$this->meetsRequirements( $testFileInfo[
'requirements'] ) ) {
747 foreach ( $testFileInfo[
'tests']
as $test ) {
748 $this->recorder->startTest( $test );
749 $this->recorder->skipped( $test,
'required extension not enabled' );
755 $this->addArticles( $testFileInfo[
'articles'] );
758 foreach ( $testFileInfo[
'tests']
as $test ) {
759 $this->recorder->startTest( $test );
761 $this->runTest( $test );
763 $ok = $ok &&
$result->isSuccess();
764 $this->recorder->record( $test,
$result );
777 function getParser( $preprocessor =
null ) {
804 public function runTest( $test ) {
805 wfDebug( __METHOD__.
": running {$test['desc']}" );
806 $opts = $this->parseOptions( $test[
'options'] );
807 $teardownGuard = $this->perTestSetup( $test );
812 $options->setTimestamp( $this->getFakeTimestamp() );
814 if ( !isset( $opts[
'wrap'] ) ) {
815 $options->setWrapOutputClass(
false );
818 if ( isset( $opts[
'tidy'] ) ) {
819 if ( !$this->tidySupport->isEnabled() ) {
820 $this->recorder->skipped( $test,
'tidy extension is not installed' );
827 if ( isset( $opts[
'title'] ) ) {
828 $titleText = $opts[
'title'];
830 $titleText =
'Parser test';
833 $local = isset( $opts[
'local'] );
834 $preprocessor = isset( $opts[
'preprocessor'] ) ? $opts[
'preprocessor'] :
null;
835 $parser = $this->getParser( $preprocessor );
838 if ( isset( $opts[
'pst'] ) ) {
841 } elseif ( isset( $opts[
'msg'] ) ) {
843 } elseif ( isset( $opts[
'section'] ) ) {
846 } elseif ( isset( $opts[
'replace'] ) ) {
848 $replace = $opts[
'replace'][1];
850 } elseif ( isset( $opts[
'comment'] ) ) {
852 } elseif ( isset( $opts[
'preload'] ) ) {
856 $output->setTOCEnabled( !isset( $opts[
'notoc'] ) );
858 if ( isset( $opts[
'tidy'] ) ) {
859 $out = preg_replace(
'/\s+$/',
'',
$out );
862 if ( isset( $opts[
'showtitle'] ) ) {
863 if (
$output->getTitleText() ) {
867 $out =
"$title\n$out";
870 if ( isset( $opts[
'showindicators'] ) ) {
872 foreach (
$output->getIndicators()
as $id => $content ) {
873 $indicators .=
"$id=$content\n";
878 if ( isset( $opts[
'ill'] ) ) {
879 $out = implode(
' ',
$output->getLanguageLinks() );
880 } elseif ( isset( $opts[
'cat'] ) ) {
886 $out .=
"cat=$name sort=$sortkey";
891 if ( isset(
$output ) && isset( $opts[
'showflags'] ) ) {
892 $actualFlags = array_keys( TestingAccessWrapper::newFromObject(
$output )->mFlags );
893 sort( $actualFlags );
894 $out .=
"\nflags=" . join(
', ', $actualFlags );
897 ScopedCallback::consume( $teardownGuard );
899 $expected = $test[
'result'];
900 if (
count( $this->normalizationFunctions ) ) {
902 $test[
'expected'], $this->normalizationFunctions );
917 private static function getOptionValue( $key, $opts, $default ) {
918 $key = strtolower( $key );
920 if ( isset( $opts[$key] ) ) {
934 private function parseOptions( $instring ) {
943 (?<qstr> # Quoted string
945 (?:[^\\\\"] | \\\\.)*
951 [^"{}] | # Not a quoted string or object, or
952 (?&qstr) | # A quoted string, or
953 (?&json) # A json object (recursively)
959 (?&qstr) # Quoted val
967 (?&json) # JSON object
971 $regex =
'/' . $defs .
'\b
987 $valueregex =
'/' . $defs .
'(?&value)/x';
989 if ( preg_match_all( $regex, $instring,
$matches, PREG_SET_ORDER ) ) {
991 $key = strtolower( $bits[
'k'] );
992 if ( !isset( $bits[
'v'] ) ) {
995 preg_match_all( $valueregex, $bits[
'v'], $vmatches );
996 $opts[$key] = array_map( [ $this,
'cleanupOption' ], $vmatches[0] );
997 if (
count( $opts[$key] ) == 1 ) {
998 $opts[$key] = $opts[$key][0];
1006 private function cleanupOption(
$opt ) {
1007 if ( substr(
$opt, 0, 1 ) ==
'"' ) {
1008 return stripcslashes( substr(
$opt, 1, -1 ) );
1011 if ( substr(
$opt, 0, 2 ) ==
'[[' ) {
1012 return substr(
$opt, 2, -2 );
1015 if ( substr(
$opt, 0, 1 ) ==
'{' ) {
1030 public function perTestSetup( $test, $nextTeardown =
null ) {
1033 $this->checkSetupDone(
'setupDatabase',
'setDatabase' );
1034 $teardown[] = $this->markSetupDone(
'perTestSetup' );
1036 $opts = $this->parseOptions( $test[
'options'] );
1037 $config = $test[
'config'];
1041 self::getOptionValue(
'language', $opts,
'en' );
1043 self::getOptionValue(
'variant', $opts,
false );
1045 self::getOptionValue(
'wgMaxTocLevel', $opts, 999 );
1046 $linkHolderBatchSize =
1047 self::getOptionValue(
'wgLinkHolderBatchSize', $opts, 1000 );
1050 $skin = self::getOptionValue(
'skin', $opts,
'fallback' );
1053 'wgEnableUploads' => self::getOptionValue(
'wgEnableUploads', $opts,
true ),
1054 'wgLanguageCode' => $langCode,
1055 'wgRawHtml' => self::getOptionValue(
'wgRawHtml', $opts,
false ),
1056 'wgNamespacesWithSubpages' => array_fill_keys(
1059 'wgMaxTocLevel' => $maxtoclevel,
1060 'wgAllowExternalImages' => self::getOptionValue(
'wgAllowExternalImages', $opts,
true ),
1061 'wgThumbLimits' => [ self::getOptionValue(
'thumbsize', $opts, 180 ) ],
1062 'wgDefaultLanguageVariant' => $variant,
1063 'wgLinkHolderBatchSize' => $linkHolderBatchSize,
1066 'wgEnableMagicLinks' => self::getOptionValue(
'wgEnableMagicLinks', $opts, [] )
1067 + [
'ISBN' =>
true,
'PMID' =>
true,
'RFC' =>
true ],
1069 'wgFragmentMode' => [
'legacy' ],
1073 $configLines = explode(
"\n", $config );
1075 foreach ( $configLines
as $line ) {
1077 $setup[$var] = eval(
"return $value;" );
1082 Hooks::run(
'ParserTestGlobals', [ &$setup ] );
1085 if ( isset( $opts[
'tidy'] ) ) {
1087 if ( $this->tidyDriver ===
null ) {
1088 $this->tidyDriver =
MWTidy::factory( $this->tidySupport->getConfig() );
1090 $tidy = $this->tidyDriver;
1095 $teardown[] =
function () {
1101 $setup[
'wgContLang'] =
$lang;
1102 $reset =
function () {
1104 $this->resetTitleServices();
1107 $teardown[] = $reset;
1111 $user->setOption(
'language', $langCode );
1112 $setup[
'wgLang'] =
$lang;
1115 $user->setOption(
'thumbsize', 0 );
1117 $setup[
'wgUser'] =
$user;
1125 $skinFactory = MediaWikiServices::getInstance()->getSkinFactory();
1128 $setup[
'wgOut'] =
$context->getOutput();
1129 $teardown[] =
function ()
use (
$context, $oldSkin ) {
1131 $wrapper = TestingAccessWrapper::newFromObject(
1132 $context->getLanguage()->getConverter()
1134 $wrapper->reloadTables();
1142 $teardown[] = $this->executeSetupSnippets( $setup );
1144 return $this->createTeardownObject( $teardown, $nextTeardown );
1152 private function listTables() {
1153 $tables = [
'user',
'user_properties',
'user_former_groups',
'page',
'page_restrictions',
1154 'protected_titles',
'revision',
'ip_changes',
'text',
'pagelinks',
'imagelinks',
1155 'categorylinks',
'templatelinks',
'externallinks',
'langlinks',
'iwlinks',
1156 'site_stats',
'ipblocks',
'image',
'oldimage',
1157 'recentchanges',
'watchlist',
'interwiki',
'logging',
'log_search',
1158 'querycache',
'objectcache',
'job',
'l10n_cache',
'redirect',
'querycachetwo',
1159 'archive',
'user_groups',
'page_props',
'category'
1162 if ( in_array( $this->db->getType(), [
'mysql',
'sqlite',
'oracle' ] ) ) {
1163 array_push(
$tables,
'searchindex' );
1174 public function setDatabase(
IDatabase $db ) {
1176 $this->setupDone[
'setDatabase'] =
true;
1196 public function setupDatabase( $nextTeardown =
null ) {
1200 $dbType = $this->db->getType();
1202 if ( $dbType ==
'oracle' ) {
1207 if ( in_array(
$wgDBprefix, $suspiciousPrefixes ) ) {
1208 throw new MWException(
"\$wgDBprefix=$wgDBprefix suggests DB setup is already done" );
1213 $teardown[] = $this->markSetupDone(
'setupDatabase' );
1215 # CREATE TEMPORARY TABLE breaks if there is more than one server
1216 if (
wfGetLB()->getServerCount() != 1 ) {
1217 $this->useTemporaryTables =
false;
1220 $temporary = $this->useTemporaryTables || $dbType ==
'postgres';
1221 $prefix = $dbType !=
'oracle' ?
'parsertest_' :
'pt_';
1223 $this->dbClone =
new CloneDatabase( $this->db, $this->listTables(), $prefix );
1224 $this->dbClone->useTemporaryTables( $temporary );
1225 $this->dbClone->cloneTableStructure();
1227 if ( $dbType ==
'oracle' ) {
1228 $this->db->query(
'BEGIN FILL_WIKI_INFO; END;' );
1229 # Insert 0 user to prevent FK violations
1232 $this->db->insert(
'user', [
1234 'user_name' =>
'Anonymous' ] );
1237 $teardown[] =
function () {
1238 $this->teardownDatabase();
1242 $reset =
function () {
1249 $teardown[] = $reset;
1250 return $this->createTeardownObject( $teardown, $nextTeardown );
1261 public function setupUploads( $nextTeardown =
null ) {
1264 $this->checkSetupDone(
'setupDatabase',
'setDatabase' );
1265 $teardown[] = $this->markSetupDone(
'setupUploads' );
1269 $teardown[] = $this->setupUploadBackend();
1277 # note that the size/width/height/bits/etc of the file
1278 # are actually set by inspecting the file itself; the arguments
1279 # to recordUpload2 have no effect. That said, we try to make things
1280 # match up so it is less confusing to readers of the code & tests.
1281 $image->recordUpload2(
'',
'Upload of some lame file',
'Some lame file', [
1287 'mime' =>
'image/jpeg',
1289 'sha1' =>
Wikimedia\base_convert(
'1', 16, 36, 31 ),
1290 'fileExists' =>
true
1291 ], $this->db->timestamp(
'20010115123500' ),
$user );
1294 # again, note that size/width/height below are ignored; see above.
1295 $image->recordUpload2(
'',
'Upload of some lame thumbnail',
'Some lame thumbnail', [
1301 'mime' =>
'image/png',
1303 'sha1' =>
Wikimedia\base_convert(
'2', 16, 36, 31 ),
1304 'fileExists' =>
true
1305 ], $this->db->timestamp(
'20130225203040' ),
$user );
1308 $image->recordUpload2(
'',
'Upload of some lame SVG',
'Some lame SVG', [
1314 'mime' =>
'image/svg+xml',
1316 'sha1' =>
Wikimedia\base_convert(
'', 16, 36, 31 ),
1317 'fileExists' =>
true
1318 ], $this->db->timestamp(
'20010115123500' ),
$user );
1320 # This image will be blacklisted in [[MediaWiki:Bad image list]]
1322 $image->recordUpload2(
'',
'zomgnotcensored',
'Borderline image', [
1328 'mime' =>
'image/jpeg',
1330 'sha1' =>
Wikimedia\base_convert(
'3', 16, 36, 31 ),
1331 'fileExists' =>
true
1332 ], $this->db->timestamp(
'20010115123500' ),
$user );
1335 $image->recordUpload2(
'',
'A pretty movie',
'Will it play', [
1341 'mime' =>
'application/ogg',
1343 'sha1' =>
Wikimedia\base_convert(
'', 16, 36, 31 ),
1344 'fileExists' =>
true
1345 ], $this->db->timestamp(
'20010115123500' ),
$user );
1348 $image->recordUpload2(
'',
'An awesome hitsong',
'Will it play', [
1354 'mime' =>
'application/ogg',
1356 'sha1' =>
Wikimedia\base_convert(
'', 16, 36, 31 ),
1357 'fileExists' =>
true
1358 ], $this->db->timestamp(
'20010115123500' ),
$user );
1362 $image->recordUpload2(
'',
'Upload a DjVu',
'A DjVu', [
1368 'mime' =>
'image/vnd.djvu',
1369 'metadata' =>
'<?xml version="1.0" ?>
1370 <!DOCTYPE DjVuXML PUBLIC "-//W3C//DTD DjVuXML 1.1//EN" "pubtext/DjVuXML-s.dtd">
1373 <BODY><OBJECT height="3508" width="2480">
1374 <PARAM name="DPI" value="300" />
1375 <PARAM name="GAMMA" value="2.2" />
1377 <OBJECT height="3508" width="2480">
1378 <PARAM name="DPI" value="300" />
1379 <PARAM name="GAMMA" value="2.2" />
1381 <OBJECT height="3508" width="2480">
1382 <PARAM name="DPI" value="300" />
1383 <PARAM name="GAMMA" value="2.2" />
1385 <OBJECT height="3508" width="2480">
1386 <PARAM name="DPI" value="300" />
1387 <PARAM name="GAMMA" value="2.2" />
1389 <OBJECT height="3508" width="2480">
1390 <PARAM name="DPI" value="300" />
1391 <PARAM name="GAMMA" value="2.2" />
1395 'sha1' =>
Wikimedia\base_convert(
'', 16, 36, 31 ),
1396 'fileExists' =>
true
1397 ], $this->db->timestamp(
'20010115123600' ),
$user );
1399 return $this->createTeardownObject( $teardown, $nextTeardown );
1408 private function teardownDatabase() {
1409 $this->checkSetupDone(
'setupDatabase' );
1411 $this->dbClone->destroy();
1412 $this->databaseSetupDone =
false;
1414 if ( $this->useTemporaryTables ) {
1415 if ( $this->db->getType() ==
'sqlite' ) {
1416 # Under SQLite the searchindex table is virtual and need
1417 # to be explicitly destroyed. See T31912
1418 # See also MediaWikiTestCase::destroyDB()
1419 wfDebug( __METHOD__ .
" explicitly destroying sqlite virtual table parsertest_searchindex\n" );
1420 $this->db->query(
"DROP TABLE `parsertest_searchindex`" );
1422 # Don't need to do anything
1426 $tables = $this->listTables();
1429 if ( $this->db->getType() ==
'oracle' ) {
1430 $this->db->query(
"DROP TABLE pt_$table DROP CONSTRAINTS" );
1432 $this->db->query(
"DROP TABLE `parsertest_$table`" );
1436 if ( $this->db->getType() ==
'oracle' ) {
1437 $this->db->query(
'BEGIN FILL_WIKI_INFO; END;' );
1446 private function setupUploadBackend() {
1450 $base = $repo->getZonePath(
'public' );
1451 $backend = $repo->getBackend();
1452 $backend->prepare( [
'dir' =>
"$base/3/3a" ] );
1454 'src' =>
"$IP/tests/phpunit/data/parser/headbg.jpg",
1455 'dst' =>
"$base/3/3a/Foobar.jpg"
1457 $backend->prepare( [
'dir' =>
"$base/e/ea" ] );
1459 'src' =>
"$IP/tests/phpunit/data/parser/wiki.png",
1460 'dst' =>
"$base/e/ea/Thumb.png"
1462 $backend->prepare( [
'dir' =>
"$base/0/09" ] );
1464 'src' =>
"$IP/tests/phpunit/data/parser/headbg.jpg",
1465 'dst' =>
"$base/0/09/Bad.jpg"
1467 $backend->prepare( [
'dir' =>
"$base/5/5f" ] );
1469 'src' =>
"$IP/tests/phpunit/data/parser/LoremIpsum.djvu",
1470 'dst' =>
"$base/5/5f/LoremIpsum.djvu"
1474 $data =
'<?xml version="1.0" encoding="utf-8"?>' .
1475 '<svg xmlns="http://www.w3.org/2000/svg"' .
1476 ' version="1.1" width="240" height="180"/>';
1478 $backend->prepare( [
'dir' =>
"$base/f/ff" ] );
1479 $backend->quickCreate( [
1480 'content' => $data,
'dst' =>
"$base/f/ff/Foobar.svg"
1483 return function ()
use ( $backend ) {
1488 $this->teardownUploadBackend();
1495 private function teardownUploadBackend() {
1496 if ( $this->keepUploads ) {
1501 $public = $repo->getZonePath(
'public' );
1505 "$public/3/3a/Foobar.jpg",
1506 "$public/e/ea/Thumb.png",
1507 "$public/0/09/Bad.jpg",
1508 "$public/5/5f/LoremIpsum.djvu",
1509 "$public/f/ff/Foobar.svg",
1510 "$public/0/00/Video.ogv",
1511 "$public/4/41/Audio.oga",
1520 private function deleteFiles( $files ) {
1523 foreach ( $files
as $file ) {
1524 $backend->delete( [
'src' => $file ], [
'force' => 1 ] );
1528 foreach ( $files
as $file ) {
1531 if ( !$backend->clean( [
'dir' => $tmp ] )->isOK() ) {
1544 public function addArticles( $articles ) {
1552 $setup[
'wgLanguageCode'] =
'en';
1557 $this->appendNamespaceSetup( $setup, $teardown );
1560 $setup[
'wgCapitalLinks'] =
true;
1562 $teardown[] = $this->executeSetupSnippets( $setup );
1564 foreach ( $articles
as $info ) {
1565 $this->addArticle( $info[
'name'], $info[
'text'], $info[
'file'], $info[
'line'] );
1572 $this->executeSetupSnippets( $teardown );
1584 private function addArticle(
$name, $text, $file,
$line ) {
1585 $text = self::chomp( $text );
1589 wfDebug( __METHOD__ .
": adding $name" );
1591 if ( is_null(
$title ) ) {
1592 throw new MWException(
"invalid title '$name' at $file:$line\n" );
1596 $page->loadPageData(
'fromdbmaster' );
1598 if ( $page->exists() ) {
1599 throw new MWException(
"duplicate article '$name' at $file:$line\n" );
1608 $status = $page->doEditContent(
1633 public function requireHook(
$name ) {
1640 $this->recorder->warning(
" This test suite requires the '$name' hook " .
1641 "extension, skipping." );
1652 public function requireFunctionHook(
$name ) {
1660 $this->recorder->warning(
" This test suite requires the '$name' function " .
1661 "hook extension, skipping." );
1672 public function requireTransparentHook(
$name ) {
1680 $this->recorder->warning(
" This test suite requires the '$name' transparent " .
1681 "hook extension, skipping.\n" );
1693 private function getFakeTimestamp() {