30 use Wikimedia\ScopedCallback;
35 class ParserTestRunner {
39 private $useTemporaryTables =
true;
44 private $setupDone = [
45 'staticSetup' =>
false,
46 'perTestSetup' =>
false,
47 'setupDatabase' =>
false,
48 'setDatabase' =>
false,
49 'setupUploads' =>
false,
72 private $tidyDriver =
null;
84 private $uploadDir =
null;
90 private $fileBackendName;
103 private $normalizationFunctions = [];
110 $this->recorder = $recorder;
114 if ( in_array( $func, [
'removeTbody',
'trimWhitespace' ] ) ) {
115 $this->normalizationFunctions[] = $func;
118 "Warning: unknown normalization option \"$func\"\n" );
130 $this->keepUploads = !empty(
$options[
'keep-uploads'] );
132 $this->fileBackendName = isset(
$options[
'file-backend'] ) ?
135 $this->runDisabled = !empty(
$options[
'run-disabled'] );
136 $this->runParsoid = !empty(
$options[
'run-parsoid'] );
139 if ( !$this->tidySupport->isEnabled() ) {
140 $this->recorder->warning(
141 "Warning: tidy is not installed, skipping some tests\n" );
144 if ( isset(
$options[
'upload-dir'] ) ) {
145 $this->uploadDir =
$options[
'upload-dir'];
149 public function getRecorder() {
150 return $this->recorder;
172 public function staticSetup( $nextTeardown =
null ) {
199 $teardown[] = $this->markSetupDone(
'staticSetup' );
202 $setup[
'wgSitename'] =
'MediaWiki';
203 $setup[
'wgServer'] =
'http://example.org';
204 $setup[
'wgServerName'] =
'example.org';
205 $setup[
'wgScriptPath'] =
'';
206 $setup[
'wgScript'] =
'/index.php';
207 $setup[
'wgResourceBasePath'] =
'';
208 $setup[
'wgStylePath'] =
'/skins';
209 $setup[
'wgExtensionAssetsPath'] =
'/extensions';
210 $setup[
'wgArticlePath'] =
'/wiki/$1';
211 $setup[
'wgActionPaths'] = [];
212 $setup[
'wgVariantArticlePath'] =
false;
213 $setup[
'wgUploadNavigationUrl'] =
false;
214 $setup[
'wgCapitalLinks'] =
true;
215 $setup[
'wgNoFollowLinks'] =
true;
216 $setup[
'wgNoFollowDomainExceptions'] = [
'no-nofollow.org' ];
217 $setup[
'wgExternalLinkTarget'] =
false;
218 $setup[
'wgExperimentalHtmlIds'] =
false;
219 $setup[
'wgLocaltimezone'] =
'UTC';
220 $setup[
'wgHtml5'] =
true;
221 $setup[
'wgDisableLangConversion'] =
false;
222 $setup[
'wgDisableTitleConversion'] =
false;
226 $setup[
'wgExtraInterlanguageLinkPrefixes'] = [
'mul' ];
231 $teardown[] =
function () {
236 $setup[
'wgLockManagers'] = [ [
237 'name' =>
'fsLockManager',
238 'class' =>
'NullLockManager',
240 'name' =>
'nullLockManager',
241 'class' =>
'NullLockManager',
243 $reset =
function() {
247 $teardown[] = $reset;
250 $setup[
'wgDefaultExternalStore'] =
false;
253 $setup[
'wgAdaptiveMessageCache'] =
true;
256 $setup[
'wgUseDatabaseMessages'] =
true;
257 $reset =
function () {
261 $teardown[] = $reset;
264 $setup[
'wgSVGConverter'] =
'null';
265 $setup[
'wgSVGConverters'] = [
'null' =>
'echo "1">$output' ];
268 Hooks::register(
'ParserGetVariableValueTs',
'ParserTestRunner::getFakeTimestamp' );
269 $teardown[] =
function () {
273 $this->appendNamespaceSetup( $setup, $teardown );
276 $teardown[] = $this->setupInterwikis();
280 $setup[
'wgLocalInterwikis'] = [
'local',
'mi' ];
281 $reset =
function () {
282 $this->resetTitleServices();
285 $teardown[] = $reset;
288 MediaWikiServices::getInstance()->disableService(
'MediaHandlerFactory' );
289 MediaWikiServices::getInstance()->redefineService(
290 'MediaHandlerFactory',
295 $teardown[] =
function () {
296 MediaWikiServices::getInstance()->resetServiceForTesting(
'MediaHandlerFactory' );
307 $teardown[] =
function ()
use ( $savedCache ) {
312 $teardown[] = $this->executeSetupSnippets( $setup );
315 return $this->createTeardownObject( $teardown, $nextTeardown );
318 private function appendNamespaceSetup( &$setup, &$teardown ) {
321 $setup[
'wgExtraNamespaces'] = [
322 100 =>
'MemoryAlpha',
323 101 =>
'MemoryAlpha_talk'
327 $reset =
function () {
329 $GLOBALS[
'wgContLang']->resetNamespaces();
332 $teardown[] = $reset;
339 protected function createRepoGroup() {
340 if ( $this->uploadDir ) {
341 if ( $this->fileBackendName ) {
342 throw new MWException(
'You cannot specify both use-filebackend and upload-dir' );
345 'name' =>
'local-backend',
347 'basePath' => $this->uploadDir,
350 } elseif ( $this->fileBackendName ) {
352 $name = $this->fileBackendName;
355 if ( $conf[
'name'] ===
$name ) {
359 if ( $useConfig ===
false ) {
360 throw new MWException(
"Unable to find file backend \"$name\"" );
362 $useConfig[
'name'] =
'local-backend';
363 unset( $useConfig[
'lockManager'] );
364 unset( $useConfig[
'fileJournal'] );
365 $class = $useConfig[
'class'];
366 $backend =
new $class( $useConfig );
368 # Replace with a mock. We do not care about generating real
369 # files on the filesystem, just need to expose the file
372 'name' =>
'local-backend',
379 'class' =>
'MockLocalRepo',
381 'url' =>
'http://example.com/images',
383 'transformVia404' =>
false,
384 'backend' => $backend
403 protected function executeSetupSnippets( $setup ) {
406 if ( is_int(
$name ) ) {
413 return function ()
use ( $saved ) {
414 $this->executeSetupSnippets( $saved );
430 protected function createTeardownObject( $teardown, $nextTeardown =
null ) {
431 return new ScopedCallback(
function()
use ( $teardown, $nextTeardown ) {
433 $teardown = array_reverse( $teardown );
435 $this->executeSetupSnippets( $teardown );
436 if ( $nextTeardown ) {
437 ScopedCallback::consume( $nextTeardown );
449 protected function markSetupDone( $funcName ) {
450 if ( $this->setupDone[$funcName] ) {
451 throw new MWException(
"$funcName is already done" );
453 $this->setupDone[$funcName] =
true;
454 return function ()
use ( $funcName ) {
455 $this->setupDone[$funcName] =
false;
463 protected function checkSetupDone( $funcName, $funcName2 =
null ) {
464 if ( !$this->setupDone[$funcName]
465 && ( $funcName ===
null || !$this->setupDone[$funcName2] )
467 throw new MWException(
"$funcName must be called before calling " .
478 public function isSetupDone( $funcName ) {
479 return isset( $this->setupDone[$funcName] ) ? $this->setupDone[$funcName] :
false;
493 private function setupInterwikis() {
494 # Hack: insert a few Wikipedia in-project interwiki prefixes,
495 # for testing inter-language links
496 Hooks::register(
'InterwikiLoadPrefix',
function ( $prefix, &$iwData ) {
497 static $testInterwikis = [
499 'iw_url' =>
'http://doesnt.matter.org/$1',
504 'iw_url' =>
'http://en.wikipedia.org/wiki/$1',
509 'iw_url' =>
'http://www.usemod.com/cgi-bin/mb.pl?$1',
514 'iw_url' =>
'http://www.memory-alpha.org/en/index.php/$1',
519 'iw_url' =>
'http://zh.wikipedia.org/wiki/$1',
524 'iw_url' =>
'http://es.wikipedia.org/wiki/$1',
529 'iw_url' =>
'http://fr.wikipedia.org/wiki/$1',
534 'iw_url' =>
'http://ru.wikipedia.org/wiki/$1',
539 'iw_url' =>
'http://mi.wikipedia.org/wiki/$1',
544 'iw_url' =>
'http://wikisource.org/wiki/$1',
549 if ( array_key_exists( $prefix, $testInterwikis ) ) {
550 $iwData = $testInterwikis[$prefix];
567 private function resetTitleServices() {
568 $services = MediaWikiServices::getInstance();
569 $services->resetServiceForTesting(
'TitleFormatter' );
570 $services->resetServiceForTesting(
'TitleParser' );
571 $services->resetServiceForTesting(
'_MediaWikiTitleCodec' );
572 $services->resetServiceForTesting(
'LinkRenderer' );
573 $services->resetServiceForTesting(
'LinkRendererFactory' );
582 public static function chomp(
$s ) {
583 if ( substr(
$s, -1 ) ===
"\n" ) {
584 return substr(
$s, 0, -1 );
603 public function runTestsFromFiles( $filenames ) {
606 $teardownGuard = $this->staticSetup();
607 $teardownGuard = $this->setupDatabase( $teardownGuard );
608 $teardownGuard = $this->setupUploads( $teardownGuard );
610 $this->recorder->start();
614 foreach ( $filenames
as $filename ) {
616 'runDisabled' => $this->runDisabled,
617 'runParsoid' => $this->runParsoid,
618 'regex' => $this->regex ] );
621 if ( !$testFileInfo[
'tests'] ) {
625 $this->recorder->startSuite( $filename );
626 $ok = $this->runTests( $testFileInfo ) && $ok;
627 $this->recorder->endSuite( $filename );
630 $this->recorder->report();
631 }
catch ( DBError
$e ) {
632 $this->recorder->warning(
$e->getMessage() );
634 $this->recorder->end();
636 ScopedCallback::consume( $teardownGuard );
645 public function meetsRequirements( $requirements ) {
646 foreach ( $requirements
as $requirement ) {
647 switch ( $requirement[
'type'] ) {
649 $ok = $this->requireHook( $requirement[
'name'] );
652 $ok = $this->requireFunctionHook( $requirement[
'name'] );
654 case 'transparentHook':
655 $ok = $this->requireTransparentHook( $requirement[
'name'] );
672 public function runTests( $testFileInfo ) {
675 $this->checkSetupDone(
'staticSetup' );
678 if ( !$testFileInfo[
'tests'] ) {
683 if ( !$this->meetsRequirements( $testFileInfo[
'requirements'] ) ) {
684 foreach ( $testFileInfo[
'tests']
as $test ) {
685 $this->recorder->startTest( $test );
686 $this->recorder->skipped( $test,
'required extension not enabled' );
692 $this->addArticles( $testFileInfo[
'articles'] );
695 foreach ( $testFileInfo[
'tests']
as $test ) {
696 $this->recorder->startTest( $test );
698 $this->runTest( $test );
700 $ok = $ok &&
$result->isSuccess();
701 $this->recorder->record( $test,
$result );
714 function getParser( $preprocessor =
null ) {
717 $class = $wgParserConf[
'class'];
718 $parser =
new $class( [
'preprocessorClass' => $preprocessor ] + $wgParserConf );
741 public function runTest( $test ) {
742 wfDebug( __METHOD__.
": running {$test['desc']}" );
743 $opts = $this->parseOptions( $test[
'options'] );
744 $teardownGuard = $this->perTestSetup( $test );
750 if ( isset( $opts[
'tidy'] ) ) {
751 if ( !$this->tidySupport->isEnabled() ) {
752 $this->recorder->skipped( $test,
'tidy extension is not installed' );
759 if ( isset( $opts[
'title'] ) ) {
760 $titleText = $opts[
'title'];
762 $titleText =
'Parser test';
765 $local = isset( $opts[
'local'] );
766 $preprocessor = isset( $opts[
'preprocessor'] ) ? $opts[
'preprocessor'] :
null;
767 $parser = $this->getParser( $preprocessor );
770 if ( isset( $opts[
'pst'] ) ) {
772 } elseif ( isset( $opts[
'msg'] ) ) {
774 } elseif ( isset( $opts[
'section'] ) ) {
777 } elseif ( isset( $opts[
'replace'] ) ) {
779 $replace = $opts[
'replace'][1];
781 } elseif ( isset( $opts[
'comment'] ) ) {
783 } elseif ( isset( $opts[
'preload'] ) ) {
787 $output->setTOCEnabled( !isset( $opts[
'notoc'] ) );
789 if ( isset( $opts[
'tidy'] ) ) {
790 $out = preg_replace(
'/\s+$/',
'',
$out );
793 if ( isset( $opts[
'showtitle'] ) ) {
794 if (
$output->getTitleText() ) {
798 $out =
"$title\n$out";
801 if ( isset( $opts[
'showindicators'] ) ) {
804 $indicators .=
"$id=$content\n";
809 if ( isset( $opts[
'ill'] ) ) {
810 $out = implode(
' ',
$output->getLanguageLinks() );
811 } elseif ( isset( $opts[
'cat'] ) ) {
817 $out .=
"cat=$name sort=$sortkey";
822 ScopedCallback::consume( $teardownGuard );
824 $expected = $test[
'result'];
825 if (
count( $this->normalizationFunctions ) ) {
827 $test[
'expected'], $this->normalizationFunctions );
842 private static function getOptionValue( $key, $opts, $default ) {
843 $key = strtolower( $key );
845 if ( isset( $opts[$key] ) ) {
859 private function parseOptions( $instring ) {
868 (?<qstr> # Quoted string
870 (?:[^\\\\"] | \\\\.)*
876 [^"{}] | # Not a quoted string or object, or
877 (?&qstr) | # A quoted string, or
878 (?&json) # A json object (recursively)
884 (?&qstr) # Quoted val
892 (?&json) # JSON object
896 $regex =
'/' . $defs .
'\b
912 $valueregex =
'/' . $defs .
'(?&value)/x';
914 if ( preg_match_all( $regex, $instring,
$matches, PREG_SET_ORDER ) ) {
916 $key = strtolower( $bits[
'k'] );
917 if ( !isset( $bits[
'v'] ) ) {
920 preg_match_all( $valueregex, $bits[
'v'], $vmatches );
921 $opts[$key] = array_map( [ $this,
'cleanupOption' ], $vmatches[0] );
922 if (
count( $opts[$key] ) == 1 ) {
923 $opts[$key] = $opts[$key][0];
931 private function cleanupOption(
$opt ) {
932 if ( substr(
$opt, 0, 1 ) ==
'"' ) {
933 return stripcslashes( substr(
$opt, 1, -1 ) );
936 if ( substr(
$opt, 0, 2 ) ==
'[[' ) {
937 return substr(
$opt, 2, -2 );
940 if ( substr(
$opt, 0, 1 ) ==
'{' ) {
955 public function perTestSetup( $test, $nextTeardown =
null ) {
958 $this->checkSetupDone(
'setupDatabase',
'setDatabase' );
959 $teardown[] = $this->markSetupDone(
'perTestSetup' );
961 $opts = $this->parseOptions( $test[
'options'] );
962 $config = $test[
'config'];
966 self::getOptionValue(
'language', $opts,
'en' );
968 self::getOptionValue(
'variant', $opts,
false );
970 self::getOptionValue(
'wgMaxTocLevel', $opts, 999 );
971 $linkHolderBatchSize =
972 self::getOptionValue(
'wgLinkHolderBatchSize', $opts, 1000 );
975 'wgEnableUploads' => self::getOptionValue(
'wgEnableUploads', $opts,
true ),
976 'wgLanguageCode' => $langCode,
977 'wgRawHtml' => self::getOptionValue(
'wgRawHtml', $opts,
false ),
978 'wgNamespacesWithSubpages' => array_fill_keys(
981 'wgMaxTocLevel' => $maxtoclevel,
982 'wgAllowExternalImages' => self::getOptionValue(
'wgAllowExternalImages', $opts,
true ),
983 'wgThumbLimits' => [ self::getOptionValue(
'thumbsize', $opts, 180 ) ],
984 'wgDefaultLanguageVariant' => $variant,
985 'wgLinkHolderBatchSize' => $linkHolderBatchSize,
988 'wgEnableMagicLinks' => self::getOptionValue(
'wgEnableMagicLinks', $opts, [] )
989 + [
'ISBN' =>
true,
'PMID' =>
true,
'RFC' =>
true ],
993 $configLines = explode(
"\n", $config );
995 foreach ( $configLines
as $line ) {
997 $setup[$var] = eval(
"return $value;" );
1002 Hooks::run(
'ParserTestGlobals', [ &$setup ] );
1005 if ( isset( $opts[
'tidy'] ) ) {
1007 if ( $this->tidyDriver ===
null ) {
1008 $this->tidyDriver =
MWTidy::factory( $this->tidySupport->getConfig() );
1010 $tidy = $this->tidyDriver;
1015 $teardown[] =
function () {
1021 $setup[
'wgContLang'] =
$lang;
1022 $reset =
function () {
1024 $this->resetTitleServices();
1027 $teardown[] = $reset;
1031 $user->setOption(
'language', $langCode );
1032 $setup[
'wgLang'] =
$lang;
1035 $user->setOption(
'thumbsize', 0 );
1037 $setup[
'wgUser'] =
$user;
1049 $teardown[] = $this->executeSetupSnippets( $setup );
1051 return $this->createTeardownObject( $teardown, $nextTeardown );
1059 private function listTables() {
1060 $tables = [
'user',
'user_properties',
'user_former_groups',
'page',
'page_restrictions',
1061 'protected_titles',
'revision',
'text',
'pagelinks',
'imagelinks',
1062 'categorylinks',
'templatelinks',
'externallinks',
'langlinks',
'iwlinks',
1063 'site_stats',
'ipblocks',
'image',
'oldimage',
1064 'recentchanges',
'watchlist',
'interwiki',
'logging',
'log_search',
1065 'querycache',
'objectcache',
'job',
'l10n_cache',
'redirect',
'querycachetwo',
1066 'archive',
'user_groups',
'page_props',
'category'
1069 if ( in_array( $this->db->getType(), [
'mysql',
'sqlite',
'oracle' ] ) ) {
1070 array_push(
$tables,
'searchindex' );
1081 public function setDatabase(
IDatabase $db ) {
1083 $this->setupDone[
'setDatabase'] =
true;
1103 public function setupDatabase( $nextTeardown =
null ) {
1107 $dbType = $this->db->getType();
1109 if ( $dbType ==
'oracle' ) {
1114 if ( in_array(
$wgDBprefix, $suspiciousPrefixes ) ) {
1115 throw new MWException(
"\$wgDBprefix=$wgDBprefix suggests DB setup is already done" );
1120 $teardown[] = $this->markSetupDone(
'setupDatabase' );
1122 # CREATE TEMPORARY TABLE breaks if there is more than one server
1123 if (
wfGetLB()->getServerCount() != 1 ) {
1124 $this->useTemporaryTables =
false;
1127 $temporary = $this->useTemporaryTables || $dbType ==
'postgres';
1128 $prefix = $dbType !=
'oracle' ?
'parsertest_' :
'pt_';
1130 $this->dbClone =
new CloneDatabase( $this->db, $this->listTables(), $prefix );
1131 $this->dbClone->useTemporaryTables( $temporary );
1132 $this->dbClone->cloneTableStructure();
1134 if ( $dbType ==
'oracle' ) {
1135 $this->db->query(
'BEGIN FILL_WIKI_INFO; END;' );
1136 # Insert 0 user to prevent FK violations
1139 $this->db->insert(
'user', [
1141 'user_name' =>
'Anonymous' ] );
1144 $teardown[] =
function () {
1145 $this->teardownDatabase();
1149 $reset =
function () {
1156 $teardown[] = $reset;
1157 return $this->createTeardownObject( $teardown, $nextTeardown );
1168 public function setupUploads( $nextTeardown =
null ) {
1171 $this->checkSetupDone(
'setupDatabase',
'setDatabase' );
1172 $teardown[] = $this->markSetupDone(
'setupUploads' );
1176 $teardown[] = $this->setupUploadBackend();
1184 # note that the size/width/height/bits/etc of the file
1185 # are actually set by inspecting the file itself; the arguments
1186 # to recordUpload2 have no effect. That said, we try to make things
1187 # match up so it is less confusing to readers of the code & tests.
1188 $image->recordUpload2(
'',
'Upload of some lame file',
'Some lame file', [
1194 'mime' =>
'image/jpeg',
1196 'sha1' =>
Wikimedia\base_convert(
'1', 16, 36, 31 ),
1197 'fileExists' =>
true
1198 ], $this->db->timestamp(
'20010115123500' ),
$user );
1201 # again, note that size/width/height below are ignored; see above.
1202 $image->recordUpload2(
'',
'Upload of some lame thumbnail',
'Some lame thumbnail', [
1208 'mime' =>
'image/png',
1210 'sha1' =>
Wikimedia\base_convert(
'2', 16, 36, 31 ),
1211 'fileExists' =>
true
1212 ], $this->db->timestamp(
'20130225203040' ),
$user );
1215 $image->recordUpload2(
'',
'Upload of some lame SVG',
'Some lame SVG', [
1221 'mime' =>
'image/svg+xml',
1223 'sha1' =>
Wikimedia\base_convert(
'', 16, 36, 31 ),
1224 'fileExists' =>
true
1225 ], $this->db->timestamp(
'20010115123500' ),
$user );
1227 # This image will be blacklisted in [[MediaWiki:Bad image list]]
1229 $image->recordUpload2(
'',
'zomgnotcensored',
'Borderline image', [
1235 'mime' =>
'image/jpeg',
1237 'sha1' =>
Wikimedia\base_convert(
'3', 16, 36, 31 ),
1238 'fileExists' =>
true
1239 ], $this->db->timestamp(
'20010115123500' ),
$user );
1242 $image->recordUpload2(
'',
'A pretty movie',
'Will it play', [
1248 'mime' =>
'application/ogg',
1250 'sha1' =>
Wikimedia\base_convert(
'', 16, 36, 31 ),
1251 'fileExists' =>
true
1252 ], $this->db->timestamp(
'20010115123500' ),
$user );
1255 $image->recordUpload2(
'',
'An awesome hitsong',
'Will it play', [
1261 'mime' =>
'application/ogg',
1263 'sha1' =>
Wikimedia\base_convert(
'', 16, 36, 31 ),
1264 'fileExists' =>
true
1265 ], $this->db->timestamp(
'20010115123500' ),
$user );
1269 $image->recordUpload2(
'',
'Upload a DjVu',
'A DjVu', [
1275 'mime' =>
'image/vnd.djvu',
1276 'metadata' =>
'<?xml version="1.0" ?>
1277 <!DOCTYPE DjVuXML PUBLIC "-//W3C//DTD DjVuXML 1.1//EN" "pubtext/DjVuXML-s.dtd">
1280 <BODY><OBJECT height="3508" width="2480">
1281 <PARAM name="DPI" value="300" />
1282 <PARAM name="GAMMA" value="2.2" />
1284 <OBJECT height="3508" width="2480">
1285 <PARAM name="DPI" value="300" />
1286 <PARAM name="GAMMA" value="2.2" />
1288 <OBJECT height="3508" width="2480">
1289 <PARAM name="DPI" value="300" />
1290 <PARAM name="GAMMA" value="2.2" />
1292 <OBJECT height="3508" width="2480">
1293 <PARAM name="DPI" value="300" />
1294 <PARAM name="GAMMA" value="2.2" />
1296 <OBJECT height="3508" width="2480">
1297 <PARAM name="DPI" value="300" />
1298 <PARAM name="GAMMA" value="2.2" />
1302 'sha1' =>
Wikimedia\base_convert(
'', 16, 36, 31 ),
1303 'fileExists' =>
true
1304 ], $this->db->timestamp(
'20010115123600' ),
$user );
1306 return $this->createTeardownObject( $teardown, $nextTeardown );
1315 private function teardownDatabase() {
1316 $this->checkSetupDone(
'setupDatabase' );
1318 $this->dbClone->destroy();
1319 $this->databaseSetupDone =
false;
1321 if ( $this->useTemporaryTables ) {
1322 if ( $this->db->getType() ==
'sqlite' ) {
1323 # Under SQLite the searchindex table is virtual and need
1324 # to be explicitly destroyed. See T31912
1325 # See also MediaWikiTestCase::destroyDB()
1326 wfDebug( __METHOD__ .
" explicitly destroying sqlite virtual table parsertest_searchindex\n" );
1327 $this->db->query(
"DROP TABLE `parsertest_searchindex`" );
1329 # Don't need to do anything
1333 $tables = $this->listTables();
1336 if ( $this->db->getType() ==
'oracle' ) {
1337 $this->db->query(
"DROP TABLE pt_$table DROP CONSTRAINTS" );
1339 $this->db->query(
"DROP TABLE `parsertest_$table`" );
1343 if ( $this->db->getType() ==
'oracle' ) {
1344 $this->db->query(
'BEGIN FILL_WIKI_INFO; END;' );
1353 private function setupUploadBackend() {
1357 $base = $repo->getZonePath(
'public' );
1358 $backend = $repo->getBackend();
1359 $backend->prepare( [
'dir' =>
"$base/3/3a" ] );
1361 'src' =>
"$IP/tests/phpunit/data/parser/headbg.jpg",
1362 'dst' =>
"$base/3/3a/Foobar.jpg"
1364 $backend->prepare( [
'dir' =>
"$base/e/ea" ] );
1366 'src' =>
"$IP/tests/phpunit/data/parser/wiki.png",
1367 'dst' =>
"$base/e/ea/Thumb.png"
1369 $backend->prepare( [
'dir' =>
"$base/0/09" ] );
1371 'src' =>
"$IP/tests/phpunit/data/parser/headbg.jpg",
1372 'dst' =>
"$base/0/09/Bad.jpg"
1374 $backend->prepare( [
'dir' =>
"$base/5/5f" ] );
1376 'src' =>
"$IP/tests/phpunit/data/parser/LoremIpsum.djvu",
1377 'dst' =>
"$base/5/5f/LoremIpsum.djvu"
1381 $data =
'<?xml version="1.0" encoding="utf-8"?>' .
1382 '<svg xmlns="http://www.w3.org/2000/svg"' .
1383 ' version="1.1" width="240" height="180"/>';
1385 $backend->prepare( [
'dir' =>
"$base/f/ff" ] );
1386 $backend->quickCreate( [
1387 'content' => $data,
'dst' =>
"$base/f/ff/Foobar.svg"
1390 return function ()
use ( $backend ) {
1395 $this->teardownUploadBackend();
1402 private function teardownUploadBackend() {
1403 if ( $this->keepUploads ) {
1408 $public = $repo->getZonePath(
'public' );
1412 "$public/3/3a/Foobar.jpg",
1413 "$public/e/ea/Thumb.png",
1414 "$public/0/09/Bad.jpg",
1415 "$public/5/5f/LoremIpsum.djvu",
1416 "$public/f/ff/Foobar.svg",
1417 "$public/0/00/Video.ogv",
1418 "$public/4/41/Audio.oga",
1427 private function deleteFiles( $files ) {
1430 foreach ( $files
as $file ) {
1431 $backend->delete( [
'src' => $file ], [
'force' => 1 ] );
1435 foreach ( $files
as $file ) {
1438 if ( !$backend->clean( [
'dir' => $tmp ] )->isOK() ) {
1451 public function addArticles( $articles ) {
1459 $setup[
'wgLanguageCode'] =
'en';
1464 $this->appendNamespaceSetup( $setup, $teardown );
1467 $setup[
'wgCapitalLinks'] =
true;
1469 $teardown[] = $this->executeSetupSnippets( $setup );
1471 foreach ( $articles
as $info ) {
1472 $this->addArticle( $info[
'name'], $info[
'text'], $info[
'file'], $info[
'line'] );
1479 $this->executeSetupSnippets( $teardown );
1491 private function addArticle(
$name, $text, $file,
$line ) {
1492 $text = self::chomp( $text );
1496 wfDebug( __METHOD__ .
": adding $name" );
1498 if ( is_null(
$title ) ) {
1499 throw new MWException(
"invalid title '$name' at $file:$line\n" );
1503 $page->loadPageData(
'fromdbmaster' );
1505 if (
$page->exists() ) {
1506 throw new MWException(
"duplicate article '$name' at $file:$line\n" );
1537 public function requireHook(
$name ) {
1544 $this->recorder->warning(
" This test suite requires the '$name' hook " .
1545 "extension, skipping." );
1556 public function requireFunctionHook(
$name ) {
1564 $this->recorder->warning(
" This test suite requires the '$name' function " .
1565 "hook extension, skipping." );
1576 public function requireTransparentHook(
$name ) {
1584 $this->recorder->warning(
" This test suite requires the '$name' transparent " .
1585 "hook extension, skipping.\n" );
1594 static function getFakeTimestamp( &
$parser, &$ts ) {