Page Menu
Home
WickedGov Phorge
Search
Configure Global Search
Log In
Files
F4064842
TestRunner.php
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
39 KB
Referenced Files
None
Subscribers
None
TestRunner.php
View Options
<?php
declare
(
strict_types
=
1
);
namespace
Wikimedia\Parsoid\ParserTests
;
use
Closure
;
use
Psr\Log\LoggerInterface
;
use
Wikimedia\Assert\Assert
;
use
Wikimedia\Bcp47Code\Bcp47CodeValue
;
use
Wikimedia\Parsoid\Config\Api\DataAccess
;
use
Wikimedia\Parsoid\Config\Api\PageConfig
;
use
Wikimedia\Parsoid\Config\Env
;
use
Wikimedia\Parsoid\Config\StubMetadataCollector
;
use
Wikimedia\Parsoid\Core\SelectiveUpdateData
;
use
Wikimedia\Parsoid\DOM\Document
;
use
Wikimedia\Parsoid\DOM\Element
;
use
Wikimedia\Parsoid\Ext\ParsoidExtensionAPI
;
use
Wikimedia\Parsoid\Mocks\MockPageConfig
;
use
Wikimedia\Parsoid\Mocks\MockPageContent
;
use
Wikimedia\Parsoid\Utils\ContentUtils
;
use
Wikimedia\Parsoid\Utils\DOMCompat
;
use
Wikimedia\Parsoid\Utils\DOMDataUtils
;
use
Wikimedia\Parsoid\Utils\ScriptUtils
;
use
Wikimedia\Parsoid\Utils\Title
;
use
Wikimedia\Parsoid\Utils\Utils
;
use
Wikimedia\Parsoid\Wt2Html\PageConfigFrame
;
/**
* Test runner for parser tests
*/
class
TestRunner
{
// Hard-code some interwiki prefixes, as is done
// in ParserTestRunner::appendInterwikiSetup() in core
// Note that ApiQuerySiteInfo will always expand the URL to include a
// protocol, but will set 'protorel' to indicate whether its internal
// form included a protocol or not. So in this file 'url' will always
// have a protocol and we'll include an explicit 'protorel' field; but
// in core there is no 'protorel' field and 'url' will not always have
// a protocol.
private
const
PARSER_TESTS_IWPS
=
[
[
'prefix'
=>
'wikinvest'
,
'local'
=>
true
,
// This url doesn't have a $1 to exercise the fix in
// ConfigUtils::computeInterwikiMap
'url'
=>
'https://meta.wikimedia.org/wiki/Interwiki_map/discontinued#Wikinvest'
,
'protorel'
=>
false
],
[
'prefix'
=>
'local'
,
'url'
=>
'http://example.org/wiki/$1'
,
'local'
=>
true
,
'localinterwiki'
=>
true
],
[
// Local interwiki that matches a namespace name (T228616)
'prefix'
=>
'project'
,
'url'
=>
'http://example.org/wiki/$1'
,
'local'
=>
true
,
'localinterwiki'
=>
true
],
[
'prefix'
=>
'wikipedia'
,
'url'
=>
'http://en.wikipedia.org/wiki/$1'
],
[
'prefix'
=>
'meatball'
,
// this has been updated in the live wikis, but the parser tests
// expect the old value (as set in parserTest.inc:setupInterwikis())
'url'
=>
'http://www.usemod.com/cgi-bin/mb.pl?$1'
],
[
'prefix'
=>
'memoryalpha'
,
'url'
=>
'http://www.memory-alpha.org/en/index.php/$1'
],
[
'prefix'
=>
'zh'
,
'url'
=>
'http://zh.wikipedia.org/wiki/$1'
,
'language'
=>
"中文"
,
'local'
=>
true
],
[
'prefix'
=>
'es'
,
'url'
=>
'http://es.wikipedia.org/wiki/$1'
,
'language'
=>
"español"
,
'local'
=>
true
],
[
'prefix'
=>
'fr'
,
'url'
=>
'http://fr.wikipedia.org/wiki/$1'
,
'language'
=>
"français"
,
'local'
=>
true
],
[
'prefix'
=>
'ru'
,
'url'
=>
'http://ru.wikipedia.org/wiki/$1'
,
'language'
=>
"русский"
,
'local'
=>
true
],
[
'prefix'
=>
'mi'
,
'url'
=>
'http://example.org/wiki/$1'
,
// better for testing if one of the
// localinterwiki prefixes is also a language
'language'
=>
'Test'
,
'local'
=>
true
,
'localinterwiki'
=>
true
],
[
'prefix'
=>
'mul'
,
'url'
=>
'http://wikisource.org/wiki/$1'
,
'extralanglink'
=>
true
,
'linktext'
=>
'Multilingual'
,
'sitename'
=>
'WikiSource'
,
'local'
=>
true
],
// added to core's ParserTestRunner::appendInterwikiSetup() to support
// Parsoid tests [T254181]
[
'prefix'
=>
'en'
,
'url'
=>
'http://en.wikipedia.org/wiki/$1'
,
'language'
=>
'English'
,
'local'
=>
true
,
'protorel'
=>
true
],
[
'prefix'
=>
'stats'
,
'local'
=>
true
,
'url'
=>
'https://stats.wikimedia.org/$1'
],
[
'prefix'
=>
'gerrit'
,
'local'
=>
true
,
'url'
=>
'https://gerrit.wikimedia.org/$1'
],
];
/** @var bool */
private
$runDisabled
;
/** @var bool */
private
$runPHP
;
/** @var string */
private
$offsetType
;
/** @var string */
private
$testFileName
;
/** @var string */
private
$testFilePath
;
/** @var ?string */
private
$knownFailuresInfix
;
/** @var string */
private
$knownFailuresPath
;
/** @var array */
private
$articles
;
/** @var LoggerInterface */
private
$defaultLogger
;
/**
* Sets one of 'regex' or 'string' properties
* - $testFilter['raw'] is the value of the filter
* - if $testFilter['regex'] is true, $testFilter['raw'] is used as a regex filter.
* - If $testFilter['string'] is true, $testFilter['raw'] is used as a plain string filter.
* @var ?array
*/
private
$testFilter
;
/** @var Test[] */
private
$testCases
;
/** @var Stats */
private
$stats
;
/** @var MockApiHelper */
private
$mockApi
;
/** @var SiteConfig */
private
$siteConfig
;
/** @var DataAccess */
private
$dataAccess
;
/**
* Global cross-test env object only to be used for title processing while
* reading the parserTests file.
*
* Every test constructs its own private $env object.
*
* @var Env
*/
private
$dummyEnv
;
/**
* Options needed to construct the per-test private $env object
* @var array
*/
private
$envOptions
;
/**
* @param string $testFilePath
* @param ?string $knownFailuresInfix
* @param string[] $modes
*/
public
function
__construct
(
string
$testFilePath
,
?
string
$knownFailuresInfix
,
array
$modes
)
{
$this
->
testFilePath
=
$testFilePath
;
$this
->
knownFailuresInfix
=
$knownFailuresInfix
;
$testFilePathInfo
=
pathinfo
(
$testFilePath
);
$this
->
testFileName
=
$testFilePathInfo
[
'basename'
];
$newModes
=
[];
$modes
[]
=
'metadata'
;
foreach
(
$modes
as
$mode
)
{
$newModes
[
$mode
]
=
new
Stats
();
$newModes
[
$mode
]->
failList
=
[];
$newModes
[
$mode
]->
result
=
''
;
// XML reporter uses this.
}
$this
->
stats
=
new
Stats
();
$this
->
stats
->
modes
=
$newModes
;
$this
->
mockApi
=
new
MockApiHelper
(
null
,
fn
(
$title
)=>
$this
->
normalizeTitleKey
(
$title
)
);
$this
->
siteConfig
=
new
SiteConfig
(
$this
->
mockApi
,
[]
);
$this
->
dataAccess
=
new
DataAccess
(
$this
->
mockApi
,
$this
->
siteConfig
,
[
'stripProto'
=>
false
]
);
$this
->
dummyEnv
=
new
Env
(
$this
->
siteConfig
,
// Unused; needed to satisfy Env signature requirements
new
MockPageConfig
(
$this
->
siteConfig
,
[],
new
MockPageContent
(
[
'main'
=>
''
]
)
),
// Unused; needed to satisfy Env signature requirements
$this
->
dataAccess
,
// Unused; needed to satisfy Env signature requirements
new
StubMetadataCollector
(
$this
->
siteConfig
)
);
// Init interwiki map to parser tests info.
// This suppresses interwiki info from cached configs.
$this
->
siteConfig
->
setupInterwikiMap
(
self
::
PARSER_TESTS_IWPS
);
$this
->
siteConfig
->
reset
();
}
private
function
newEnv
(
Test
$test
,
string
$wikitext
):
Env
{
$title
=
$this
->
dummyEnv
->
makeTitleFromURLDecodedStr
(
$test
->
pageName
()
);
$opts
=
[
'title'
=>
$title
,
'pageContent'
=>
$wikitext
,
'pageLanguage'
=>
$this
->
siteConfig
->
langBcp47
(),
'pageLanguagedir'
=>
$this
->
siteConfig
->
rtl
()
?
'rtl'
:
'ltr'
];
$pageConfig
=
new
PageConfig
(
null
,
$this
->
siteConfig
,
$opts
);
$env
=
new
Env
(
$this
->
siteConfig
,
$pageConfig
,
$this
->
dataAccess
,
new
StubMetadataCollector
(
$this
->
siteConfig
),
$this
->
envOptions
);
$env
->
pageCache
=
$this
->
articles
;
// Set parsing resource limits.
// $env->setResourceLimits();
return
$env
;
}
private
function
normalizeTitleKey
(
string
$title
):
string
{
return
$this
->
dummyEnv
->
normalizedTitleKey
(
$title
,
false
,
true
);
}
private
function
addArticle
(
Article
$art
):
array
{
$key
=
$this
->
normalizeTitleKey
(
$art
->
title
);
$oldVal
=
$this
->
articles
[
$key
]
??
null
;
$this
->
articles
[
$key
]
=
$art
->
text
;
$teardown
=
[
function
()
use
(
$key
,
$oldVal
)
{
$this
->
articles
[
$key
]
=
$oldVal
;
},
$this
->
mockApi
->
addArticle
(
$key
,
$art
),
];
return
$teardown
;
}
/**
* Parse the test file and set up articles and test cases
* @param array $options
*/
private
function
buildTests
(
array
$options
):
void
{
// Startup by loading .txt test file
$warnFunc
=
static
function
(
string
$warnMsg
):
void
{
error_log
(
$warnMsg
);
};
$normFunc
=
function
(
string
$title
):
string
{
return
$this
->
normalizeTitleKey
(
$title
);
};
$testReader
=
TestFileReader
::
read
(
$this
->
testFilePath
,
$warnFunc
,
$normFunc
,
$this
->
knownFailuresInfix
);
$this
->
knownFailuresPath
=
$testReader
->
knownFailuresPath
;
$this
->
testCases
=
$testReader
->
testCases
;
$this
->
articles
=
[];
foreach
(
$testReader
->
articles
as
$art
)
{
$this
->
addArticle
(
$art
);
}
if
(
!
ScriptUtils
::
booleanOption
(
$options
[
'quieter'
]
??
''
)
)
{
if
(
$this
->
knownFailuresPath
)
{
error_log
(
'Loaded known failures from '
.
$this
->
knownFailuresPath
);
}
else
{
error_log
(
'No known failures found.'
);
}
}
}
/**
* Convert a wikitext string to an HTML Node
*
* @param Env $env
* @param Test $test
* @param string $mode
* @param string $wikitext
* @return Document
*/
private
function
convertWt2Html
(
Env
$env
,
Test
$test
,
string
$mode
,
string
$wikitext
):
Document
{
// FIXME: Ugly! Maybe we should switch to using the entrypoint to
// the library for parserTests instead of reusing the environment
// and touching these internals.
$content
=
$env
->
getPageConfig
()->
getRevisionContent
();
// @phan-suppress-next-line PhanUndeclaredProperty
$content
->
data
[
'main'
][
'content'
]
=
$wikitext
;
$env
->
topFrame
=
new
PageConfigFrame
(
$env
,
$env
->
getPageConfig
(),
$env
->
getSiteConfig
()
);
if
(
$mode
===
'html2html'
)
{
// Since this was set when serializing we need to setup a new doc
$env
->
setupTopLevelDoc
();
}
$handler
=
$env
->
getContentHandler
();
$extApi
=
new
ParsoidExtensionAPI
(
$env
);
$doc
=
$handler
->
toDOM
(
$extApi
);
return
$doc
;
}
/**
* Convert a DOM to Wikitext.
*
* @param Env $env
* @param Test $test
* @param string $mode
* @param Document $doc
* @return string
*/
private
function
convertHtml2Wt
(
Env
$env
,
Test
$test
,
string
$mode
,
Document
$doc
):
string
{
$startsAtWikitext
=
$mode
===
'wt2wt'
||
$mode
===
'wt2html'
||
$mode
===
'selser'
;
if
(
$mode
===
'selser'
)
{
$selserData
=
new
SelectiveUpdateData
(
$test
->
wikitext
,
$test
->
cachedBODYstr
);
}
else
{
$selserData
=
null
;
}
$env
->
topLevelDoc
=
$doc
;
$extApi
=
new
ParsoidExtensionAPI
(
$env
);
return
$env
->
getContentHandler
()->
fromDOM
(
$extApi
,
$selserData
);
}
/**
* Run test in the requested mode
* @param Test $test
* @param string $mode
* @param array $options
*/
private
function
runTest
(
Test
$test
,
string
$mode
,
array
$options
):
void
{
$test
->
time
=
[];
$testOpts
=
$test
->
options
;
// These changes are for environment options that change between runs of
// different modes. See `processTest` for changes per test.
// Page language matches "wiki language" (which is set by
// the item 'language' option).
// Variant conversion is disabled by default
$this
->
envOptions
[
'wtVariantLanguage'
]
=
null
;
$this
->
envOptions
[
'htmlVariantLanguage'
]
=
null
;
// The test can explicitly opt-in to variant conversion with the
// 'langconv' option.
if
(
$testOpts
[
'langconv'
]
??
null
)
{
// These test option names are deprecated:
// (Note that test options names are lowercased by the reader.)
if
(
$testOpts
[
'sourcevariant'
]
??
false
)
{
$this
->
envOptions
[
'wtVariantLanguage'
]
=
Utils
::
mwCodeToBcp47
(
$testOpts
[
'sourcevariant'
],
true
,
$this
->
siteConfig
->
getLogger
()
);
}
if
(
$testOpts
[
'variant'
]
??
false
)
{
$this
->
envOptions
[
'htmlVariantLanguage'
]
=
Utils
::
mwCodeToBcp47
(
$testOpts
[
'variant'
],
true
,
$this
->
siteConfig
->
getLogger
()
);
}
// Preferred option names, which are also specified in bcp-47 codes
// (Note that test options names are lowercased by the reader.)
if
(
$testOpts
[
'wtvariantlanguage'
]
??
false
)
{
$this
->
envOptions
[
'wtVariantLanguage'
]
=
new
Bcp47CodeValue
(
$testOpts
[
'wtvariantlanguage'
]
);
}
if
(
$testOpts
[
'htmlvariantlanguage'
]
??
false
)
{
$this
->
envOptions
[
'htmlVariantLanguage'
]
=
new
Bcp47CodeValue
(
$testOpts
[
'htmlvariantlanguage'
]
);
}
}
$env
=
$this
->
newEnv
(
$test
,
$test
->
wikitext
??
''
);
// Some useful booleans
$startsAtHtml
=
$mode
===
'html2html'
||
$mode
===
'html2wt'
;
$endsAtHtml
=
$mode
===
'wt2html'
||
$mode
===
'html2html'
;
$parsoidOnly
=
isset
(
$test
->
sections
[
'html/parsoid'
]
)
||
isset
(
$test
->
sections
[
'html/parsoid+standalone'
]
)
||
(
!
empty
(
$testOpts
[
'parsoid'
]
)
&&
!
isset
(
$testOpts
[
'parsoid'
][
'normalizePhp'
]
)
);
$test
->
time
[
'start'
]
=
microtime
(
true
);
$doc
=
null
;
$wt
=
null
;
if
(
isset
(
$test
->
sections
[
'html/parsoid+standalone'
]
)
)
{
$test
->
parsoidHtml
=
$test
->
sections
[
'html/parsoid+standalone'
];
}
// Source preparation
if
(
$startsAtHtml
)
{
$html
=
$test
->
parsoidHtml
??
''
;
if
(
!
$parsoidOnly
)
{
// Strip some php output that has no wikitext representation
// (like .mw-editsection) and won't html2html roundtrip and
// therefore causes false failures.
$html
=
TestUtils
::
normalizePhpOutput
(
$html
);
}
$doc
=
ContentUtils
::
createDocument
(
$html
);
$wt
=
$this
->
convertHtml2Wt
(
$env
,
$test
,
$mode
,
$doc
);
}
else
{
// startsAtWikitext
// Always serialize DOM to string and reparse before passing to wt2wt
if
(
$test
->
cachedBODYstr
===
null
)
{
$doc
=
$this
->
convertWt2Html
(
$env
,
$test
,
$mode
,
$test
->
wikitext
);
// Cache parsed HTML
$test
->
cachedBODYstr
=
ContentUtils
::
toXML
(
DOMCompat
::
getBody
(
$doc
)
);
// - In wt2html mode, pass through original DOM
// so that it is serialized just once.
// - In wt2wt and selser modes, pass through serialized and
// reparsed DOM so that fostering/normalization effects
// are reproduced.
if
(
$mode
===
'wt2html'
)
{
// no-op
}
else
{
$doc
=
ContentUtils
::
createDocument
(
$test
->
cachedBODYstr
);
}
}
else
{
$doc
=
ContentUtils
::
createDocument
(
$test
->
cachedBODYstr
);
}
}
// Generate and make changes for the selser test mode
$testManualChanges
=
$testOpts
[
'parsoid'
][
'changes'
]
??
null
;
if
(
$mode
===
'selser'
)
{
if
(
$testManualChanges
&&
$test
->
changetree
===
[
'manual'
]
)
{
$test
->
applyManualChanges
(
$doc
);
}
else
{
$changetree
=
isset
(
$options
[
'changetree'
]
)
?
json_decode
(
$options
[
'changetree'
]
)
:
$test
->
changetree
;
if
(
!
$changetree
)
{
$changetree
=
$test
->
generateChanges
(
$doc
);
}
$dumpOpts
=
[
'dom:post-changes'
=>
$env
->
hasDumpFlag
(
'dom:post-changes'
),
'logger'
=>
$env
->
getSiteConfig
()->
getLogger
()
];
$test
->
applyChanges
(
$dumpOpts
,
$doc
,
$changetree
);
}
// Save the modified DOM so we can re-test it later.
// Always serialize to string and reparse before passing to selser/wt2wt.
$test
->
changedHTMLStr
=
ContentUtils
::
toXML
(
DOMCompat
::
getBody
(
$doc
)
);
$doc
=
ContentUtils
::
createDocument
(
$test
->
changedHTMLStr
);
}
elseif
(
$mode
===
'wt2wt'
)
{
// Handle a 'changes' option if present.
if
(
$testManualChanges
)
{
$test
->
applyManualChanges
(
$doc
);
}
}
// Roundtrip stage
if
(
$mode
===
'wt2wt'
||
$mode
===
'selser'
)
{
$wt
=
$this
->
convertHtml2Wt
(
$env
,
$test
,
$mode
,
$doc
);
}
elseif
(
$mode
===
'html2html'
)
{
$doc
=
$this
->
convertWt2Html
(
$env
,
$test
,
$mode
,
$wt
);
}
// Result verification stage
if
(
$endsAtHtml
)
{
$this
->
processParsedHTML
(
$env
,
$test
,
$options
,
$mode
,
$doc
);
}
else
{
$this
->
processSerializedWT
(
$env
,
$test
,
$options
,
$mode
,
$wt
);
}
}
/**
* Process test options that impact output.
* These are almost always only pertinent in wt2html test modes.
* Returns:
* - null if there are no applicable output options.
* - true if the output matches expected output for the requested option(s).
* - false otherwise
*
* @param Env $env
* @param Test $test
* @param array $options
* @param string $mode
* @param Document $doc
* @param ?string $metadataExpected A metadata section from the test,
* or null if none present. If a metadata section is not present,
* the metadata output is added to $doc, otherwise it is returned
* in $metadataActual
* @param ?string &$metadataActual The "actual" metadata output for
* this test.
*/
private
function
addParserOutputInfo
(
Env
$env
,
Test
$test
,
array
$options
,
string
$mode
,
Document
$doc
,
?
string
$metadataExpected
,
?
string
&
$metadataActual
):
void
{
$output
=
$env
->
getMetadata
();
$opts
=
$test
->
options
;
'@phan-var StubMetadataCollector $output'
;
// @var StubMetadataCollector $metadata
// See ParserTestRunner::addParserOutputInfo() in core.
$before
=
[];
$after
=
[];
// 'showtitle' not yet supported
// unlike other link types, this dumps the 'sort' property as well
if
(
isset
(
$opts
[
'cat'
]
)
)
{
$defaultSortKey
=
$output
->
getPageProperty
(
'defaultsort'
)
??
''
;
foreach
(
$output
->
getLinkList
(
StubMetadataCollector
::
LINKTYPE_CATEGORY
)
as
[
'link'
=>
$link
,
'sort'
=>
$sort
]
)
{
$sortkey
=
$sort
?:
$defaultSortKey
;
$name
=
$link
->
getDBkey
();
$after
[]
=
"cat=$name sort=$sortkey"
;
}
}
if
(
isset
(
$opts
[
'extlinks'
]
)
)
{
foreach
(
$output
->
getExternalLinks
()
as
$url
=>
$ignore
)
{
$after
[]
=
"extlink=$url"
;
}
}
// Unlike other link types, this is stored as text, not dbkey
if
(
isset
(
$opts
[
'ill'
]
)
)
{
foreach
(
$output
->
getLinkList
(
StubMetadataCollector
::
LINKTYPE_LANGUAGE
)
as
[
'link'
=>
$ll
]
)
{
$after
[]
=
"ill="
.
Title
::
newFromLinkTarget
(
$ll
,
$this
->
siteConfig
)->
getFullText
();
}
}
$linkoptions
=
[
[
'iwl'
,
'iwl='
,
StubMetadataCollector
::
LINKTYPE_INTERWIKI
],
[
'links'
,
'link='
,
StubMetadataCollector
::
LINKTYPE_LOCAL
],
[
'special'
,
'special='
,
StubMetadataCollector
::
LINKTYPE_SPECIAL
],
[
'templates'
,
'template='
,
StubMetadataCollector
::
LINKTYPE_TEMPLATE
],
];
foreach
(
$linkoptions
as
[
$optName
,
$prefix
,
$type
]
)
{
if
(
isset
(
$opts
[
$optName
]
)
)
{
foreach
(
$output
->
getLinkList
(
$type
)
as
[
'link'
=>
$ll
]
)
{
$after
[]
=
$prefix
.
Title
::
newFromLinkTarget
(
$ll
,
$this
->
siteConfig
)->
getPrefixedDBkey
();
}
}
}
if
(
isset
(
$opts
[
'extension'
]
)
)
{
$extList
=
$opts
[
'extension'
];
if
(
!
is_array
(
$extList
)
)
{
$extList
=
[
$extList
];
}
foreach
(
$extList
as
$ext
)
{
$after
[]
=
"extension[$ext]="
.
// XXX should use JsonCodec
json_encode
(
$output
->
getExtensionData
(
$ext
),
JSON_UNESCAPED_SLASHES
|
JSON_UNESCAPED_UNICODE
|
JSON_PRETTY_PRINT
);
}
}
if
(
isset
(
$opts
[
'property'
]
)
)
{
$propList
=
$opts
[
'property'
];
if
(
!
is_array
(
$propList
)
)
{
$propList
=
[
$propList
];
}
foreach
(
$propList
as
$prop
)
{
$after
[]
=
"property[$prop]="
.
(
$output
->
getPageProperty
(
$prop
)
??
''
);
}
}
if
(
isset
(
$opts
[
'showflags'
]
)
)
{
$actualFlags
=
$output
->
getOutputFlags
();
sort
(
$actualFlags
);
$after
[]
=
"flags="
.
implode
(
', '
,
$actualFlags
);
}
if
(
isset
(
$opts
[
'showtocdata'
]
)
)
{
$tocData
=
$output
->
getTOCData
();
if
(
$tocData
!==
null
)
{
$after
[]
=
$tocData
->
prettyPrint
();
}
}
if
(
isset
(
$opts
[
'showindicators'
]
)
)
{
foreach
(
$output
->
getIndicators
()
as
$name
=>
$content
)
{
$after
[]
=
"$name=$content"
;
}
}
if
(
isset
(
$opts
[
'showmedia'
]
)
)
{
$images
=
array_map
(
fn
(
$item
)
=>
$item
[
'link'
]->
getDBkey
(),
$output
->
getLinkList
(
StubMetadataCollector
::
LINKTYPE_MEDIA
)
);
$after
[]
=
'images='
.
implode
(
', '
,
$images
);
}
if
(
$metadataExpected
===
null
)
{
// legacy format, add $before and $after to $doc
$body
=
DOMCompat
::
getBody
(
$doc
);
if
(
count
(
$before
)
)
{
$before
=
$doc
->
createTextNode
(
implode
(
"
\n
"
,
$before
)
);
$body
->
insertBefore
(
$before
,
$body
->
firstChild
);
}
if
(
count
(
$after
)
)
{
$after
=
$doc
->
createTextNode
(
implode
(
"
\n
"
,
$after
)
);
$body
->
appendChild
(
$after
);
}
}
else
{
$metadataActual
=
implode
(
"
\n
"
,
array_merge
(
$before
,
$after
)
);
}
}
/**
* Return the appropriate metadata section for this test, given that
* we are running in parsoid "standalone" mode, or 'null' if none is
* present.
* @param Test $test
* @return ?string The expected metadata for this test
*/
public
static
function
getStandaloneMetadataSection
(
Test
$test
):
?
string
{
return
// specific results for parsoid standalone mode
$test
->
sections
[
'metadata/parsoid+standalone'
]
??
// specific results for parsoid
$test
->
sections
[
'metadata/parsoid'
]
??
// generic for all parsers (even standalone)
$test
->
sections
[
'metadata'
]
??
// missing (== use legacy combined output format)
null
;
}
/**
* Check the given HTML result against the expected result,
* and throw an exception if necessary.
*
* @param Env $env
* @param Test $test
* @param array $options
* @param string $mode
* @param Document $doc
*/
private
function
processParsedHTML
(
Env
$env
,
Test
$test
,
array
$options
,
string
$mode
,
Document
$doc
):
void
{
$modeObj
=
new
TestMode
(
$mode
);
$test
->
time
[
'end'
]
=
microtime
(
true
);
$metadataExpected
=
self
::
getStandaloneMetadataSection
(
$test
);
$metadataActual
=
null
;
if
(
isset
(
$test
->
options
[
'nohtml'
]
)
)
{
$body
=
DOMCompat
::
getBody
(
$doc
);
while
(
$body
->
hasChildNodes
()
)
{
$body
->
removeChild
(
$body
->
firstChild
);
}
}
$this
->
addParserOutputInfo
(
$env
,
$test
,
$options
,
$mode
,
$doc
,
$metadataExpected
,
$metadataActual
);
if
(
$test
->
parsoidHtml
!==
null
)
{
$checkPassed
=
$this
->
checkHTML
(
$test
,
DOMCompat
::
getBody
(
$doc
),
$options
,
$mode
);
}
else
{
// Running the test for metadata, presumably.
$checkPassed
=
true
;
}
// We could also check metadata in the html2html or wt2wt
// modes, but (a) we'd need a separate key for known failures
// to avoid overwriting the wt2html metadata results, and (b)
// any failures would probably be redundant with html2wt
// failures and not indicative of a "real" root cause bug.
if
(
$metadataExpected
!==
null
&&
!
$modeObj
->
isCachingMode
()
&&
$mode
===
'wt2html'
)
{
$metadataResult
=
$this
->
checkMetadata
(
$test
,
$metadataExpected
,
$metadataActual
??
''
,
$options
);
$checkPassed
=
$checkPassed
&&
$metadataResult
;
}
// Only throw an error if --exit-unexpected was set and there was an error
// Otherwise, continue running tests
if
(
$options
[
'exit-unexpected'
]
&&
!
$checkPassed
)
{
throw
new
UnexpectedException
;
}
}
/**
* Check the given wikitext result against the expected result,
* and throw an exception if necessary.
*
* @param Env $env
* @param Test $test
* @param array $options
* @param string $mode
* @param string $wikitext
*/
private
function
processSerializedWT
(
Env
$env
,
Test
$test
,
array
$options
,
string
$mode
,
string
$wikitext
):
void
{
$test
->
time
[
'end'
]
=
microtime
(
true
);
if
(
$mode
===
'selser'
&&
$options
[
'selser'
]
!==
'noauto'
)
{
if
(
$test
->
changetree
===
[
5
]
)
{
$test
->
resultWT
=
$test
->
wikitext
;
}
else
{
$doc
=
ContentUtils
::
createDocument
(
$test
->
changedHTMLStr
);
$test
->
resultWT
=
$this
->
convertHtml2Wt
(
$env
,
$test
,
'wt2wt'
,
$doc
);
}
}
$checkPassed
=
$this
->
checkWikitext
(
$test
,
$wikitext
,
$options
,
$mode
);
// Only throw an error if --exit-unexpected was set and there was an error
// Otherwise, continue running tests
if
(
$options
[
'exit-unexpected'
]
&&
!
$checkPassed
)
{
throw
new
UnexpectedException
;
}
}
private
function
checkHTML
(
Test
$test
,
Element
$out
,
array
$options
,
string
$mode
):
bool
{
[
$normOut
,
$normExpected
]
=
$test
->
normalizeHTML
(
$out
,
$test
->
cachedNormalizedHTML
);
$expected
=
[
'normal'
=>
$normExpected
,
'raw'
=>
$test
->
parsoidHtml
];
$actual
=
[
'normal'
=>
$normOut
,
'raw'
=>
ContentUtils
::
toXML
(
$out
,
[
'innerXML'
=>
true
]
),
'input'
=>
(
$mode
===
'html2html'
)
?
$test
->
parsoidHtml
:
$test
->
wikitext
];
return
$options
[
'reportResult'
](
$this
->
stats
,
$test
,
$options
,
$mode
,
$expected
,
$actual
);
}
private
function
checkMetadata
(
Test
$test
,
string
$metadataExpected
,
string
$metadataActual
,
array
$options
):
bool
{
$expected
=
[
'normal'
=>
$metadataExpected
,
'raw'
=>
$metadataExpected
];
$actual
=
[
'normal'
=>
$metadataActual
,
'raw'
=>
$metadataActual
,
'input'
=>
$test
->
wikitext
,
];
$mode
=
'metadata'
;
return
$options
[
'reportResult'
](
$this
->
stats
,
$test
,
$options
,
$mode
,
$expected
,
$actual
);
}
/**
* Removes DSR from data-parsoid for test normalization of a complet document. If
* data-parsoid gets subsequently empty, removes it too.
* @param string $raw
* @return string
*/
private
function
filterDsr
(
string
$raw
):
string
{
$doc
=
ContentUtils
::
createAndLoadDocument
(
$raw
);
foreach
(
$doc
->
childNodes
as
$child
)
{
if
(
$child
instanceof
Element
)
{
$this
->
filterNodeDsr
(
$child
);
}
}
DOMDataUtils
::
visitAndStoreDataAttribs
(
$doc
);
$ret
=
ContentUtils
::
toXML
(
DOMCompat
::
getBody
(
$doc
),
[
'innerXML'
=>
true
]
);
$ret
=
preg_replace
(
'/
\s
data-parsoid="{}"/'
,
''
,
$ret
);
return
$ret
;
}
/**
* Removes DSR from data-parsoid for test normalization of an element.
*/
private
function
filterNodeDsr
(
Element
$el
)
{
$dp
=
DOMDataUtils
::
getDataParsoid
(
$el
);
unset
(
$dp
->
dsr
);
foreach
(
$el
->
childNodes
as
$child
)
{
if
(
$child
instanceof
Element
)
{
$this
->
filterNodeDsr
(
$child
);
}
}
}
private
function
checkWikitext
(
Test
$test
,
string
$out
,
array
$options
,
string
$mode
):
bool
{
if
(
$mode
===
'html2wt'
)
{
$input
=
$test
->
parsoidHtml
;
$testWikitext
=
$test
->
wikitext
;
}
elseif
(
$mode
===
'wt2wt'
)
{
if
(
isset
(
$test
->
options
[
'parsoid'
][
'changes'
]
)
)
{
$input
=
$test
->
wikitext
;
$testWikitext
=
$test
->
sections
[
'wikitext/edited'
];
}
else
{
$input
=
$testWikitext
=
$test
->
wikitext
;
}
}
else
{
/* selser */
if
(
$test
->
changetree
===
[
5
]
)
{
/* selser with oracle */
$input
=
$test
->
changedHTMLStr
;
$testWikitext
=
$test
->
wikitext
;
$out
=
preg_replace
(
'/<!--'
.
Test
::
STATIC_RANDOM_STRING
.
'-->/'
,
''
,
$out
);
}
elseif
(
$test
->
changetree
===
[
'manual'
]
&&
isset
(
$test
->
options
[
'parsoid'
][
'changes'
]
)
)
{
/* manual changes */
$input
=
$test
->
wikitext
;
$testWikitext
=
$test
->
sections
[
'wikitext/edited'
];
}
else
{
/* automated selser changes, no oracle */
$input
=
$test
->
changedHTMLStr
;
$testWikitext
=
$test
->
resultWT
;
}
}
[
$normalizedOut
,
$normalizedExpected
]
=
$test
->
normalizeWT
(
$out
,
$testWikitext
);
$expected
=
[
'normal'
=>
$normalizedExpected
,
'raw'
=>
$testWikitext
];
$actual
=
[
'normal'
=>
$normalizedOut
,
'raw'
=>
$out
,
'input'
=>
$input
];
return
$options
[
'reportResult'
](
$this
->
stats
,
$test
,
$options
,
$mode
,
$expected
,
$actual
);
}
private
function
updateKnownFailures
(
array
$options
):
array
{
// Check in case any tests were removed but we didn't update
// the knownFailures
$knownFailuresChanged
=
false
;
$allModes
=
$options
[
'wt2html'
]
&&
$options
[
'wt2wt'
]
&&
$options
[
'html2wt'
]
&&
$options
[
'html2html'
]
&&
isset
(
$options
[
'selser'
]
)
&&
!(
isset
(
$options
[
'filter'
]
)
||
isset
(
$options
[
'regex'
]
)
||
isset
(
$options
[
'maxtests'
]
)
);
$offsetType
=
$options
[
'offsetType'
]
??
'byte'
;
// Update knownFailures, if requested
if
(
$allModes
||
ScriptUtils
::
booleanOption
(
$options
[
'updateKnownFailures'
]
??
null
)
)
{
if
(
$this
->
knownFailuresPath
!==
null
)
{
$old
=
file_get_contents
(
$this
->
knownFailuresPath
);
}
else
{
// If file doesn't exist, use the JSON representation of an
// empty array, so it compares equal in the case that we
// end up with an empty array of known failures below.
$old
=
'{}'
;
}
$testKnownFailures
=
[];
$kfModes
=
array_merge
(
$options
[
'modes'
],
[
'metadata'
]
);
foreach
(
$kfModes
as
$mode
)
{
foreach
(
$this
->
stats
->
modes
[
$mode
]->
failList
as
$fail
)
{
$testKnownFailures
[
$fail
[
'testName'
]]
??=
[];
Assert
::
invariant
(
!
isset
(
$testKnownFailures
[
$fail
[
'testName'
]][
$mode
.
$fail
[
'suffix'
]]
),
"Overwriting known failures result for "
.
$fail
[
'testName'
]
.
" "
.
$mode
.
$fail
[
'suffix'
]
);
$testKnownFailures
[
$fail
[
'testName'
]][
$mode
.
$fail
[
'suffix'
]]
=
$fail
[
'raw'
];
}
}
// Sort, otherwise, titles get added above based on the first
// failing mode, which can make diffs harder to verify when
// failing modes change.
ksort
(
$testKnownFailures
);
$contents
=
json_encode
(
$testKnownFailures
,
JSON_PRETTY_PRINT
|
JSON_UNESCAPED_SLASHES
|
JSON_FORCE_OBJECT
|
JSON_UNESCAPED_UNICODE
)
.
"
\n
"
;
if
(
ScriptUtils
::
booleanOption
(
$options
[
'updateKnownFailures'
]
??
null
)
)
{
if
(
$this
->
knownFailuresPath
!==
null
)
{
file_put_contents
(
$this
->
knownFailuresPath
,
$contents
);
}
else
{
// To be safe, we don't try to write a file that doesn't
// (yet) exist. Create an empty file if you need to, and
// then we'll happily update it for you.
throw
new
\RuntimeException
(
"Known failures file for {$this->testFileName} does not exist, "
.
"and so won't be updated."
);
}
}
elseif
(
$allModes
&&
$offsetType
===
'byte'
)
{
$knownFailuresChanged
=
$contents
!==
$old
;
}
}
// Write updated tests from failed ones
if
(
ScriptUtils
::
booleanOption
(
$options
[
'update-tests'
]
??
null
)
||
ScriptUtils
::
booleanOption
(
$options
[
'update-unexpected'
]
??
null
)
)
{
$updateFormat
=
$options
[
'update-format'
];
if
(
$updateFormat
!==
'raw'
&&
$updateFormat
!==
'actualNormalized'
)
{
$updateFormat
=
'noDsr'
;
}
$fileContent
=
file_get_contents
(
$this
->
testFilePath
);
foreach
(
[
'wt2html'
,
'metadata'
]
as
$mode
)
{
foreach
(
$this
->
stats
->
modes
[
$mode
]->
failList
as
$fail
)
{
if
(
$options
[
'update-tests'
]
||
$fail
[
'unexpected'
]
)
{
$exp
=
'/(!!
\s
*test
\s
*'
.
preg_quote
(
$fail
[
'testName'
],
'/'
)
.
'(?:(?!!!
\s
*end)[
\s\S
])*'
.
')('
.
preg_quote
(
$fail
[
'expected'
],
'/'
)
.
')/m'
;
$fail
[
'noDsr'
]
=
$fail
[
'raw'
];
if
(
$updateFormat
===
'noDsr'
&&
$mode
!==
'metadata'
)
{
$fail
[
'noDsr'
]
=
$this
->
filterDsr
(
$fail
[
'noDsr'
]
);
}
$fileContent
=
preg_replace_callback
(
$exp
,
static
function
(
array
$matches
)
use
(
$fail
,
$updateFormat
)
{
return
$matches
[
1
]
.
$fail
[
$updateFormat
];
},
$fileContent
);
}
}
}
file_put_contents
(
$this
->
testFilePath
,
$fileContent
);
}
// print out the summary
$options
[
'reportSummary'
](
$options
[
'modes'
],
$this
->
stats
,
$this
->
testFileName
,
$this
->
testFilter
,
$knownFailuresChanged
,
$options
);
// we're done!
// exit status 1 == uncaught exception
$failures
=
$this
->
stats
->
allFailures
();
$exitCode
=
(
$failures
>
0
||
$knownFailuresChanged
)
?
2
:
0
;
if
(
ScriptUtils
::
booleanOption
(
$options
[
'exit-zero'
]
??
null
)
)
{
$exitCode
=
0
;
}
return
[
'exitCode'
=>
$exitCode
,
'stats'
=>
$this
->
stats
,
'file'
=>
$this
->
testFileName
,
'knownFailuresChanged'
=>
$knownFailuresChanged
];
}
/**
* Run the test in all requested modes.
*
* @param Test $test
* @param array $options
*/
private
function
processTest
(
Test
$test
,
array
$options
):
void
{
if
(
!
$test
->
options
)
{
$test
->
options
=
[];
}
$testOpts
=
$test
->
options
;
// ensure that test is not skipped if it has a wikitext/edited or
// html/parsoid+langconv section (but not a parsoid html section)
$haveHtml
=
(
$test
->
parsoidHtml
!==
null
)
||
isset
(
$test
->
sections
[
'wikitext/edited'
]
)
||
isset
(
$test
->
sections
[
'html/parsoid+standalone'
]
)
||
isset
(
$test
->
sections
[
'html/parsoid+langconv'
]
)
||
self
::
getStandaloneMetadataSection
(
$test
)
!==
null
;
$hasHtmlParsoid
=
isset
(
$test
->
sections
[
'html/parsoid'
]
)
||
isset
(
$test
->
sections
[
'html/parsoid+standalone'
]
);
// Skip test whose title does not match --filter
// or which is disabled or php-only
if
(
$test
->
wikitext
===
null
||
!
$haveHtml
||
(
isset
(
$testOpts
[
'disabled'
]
)
&&
!
$this
->
runDisabled
)
||
(
isset
(
$testOpts
[
'php'
]
)
&&
!(
$hasHtmlParsoid
||
$this
->
runPHP
)
)
||
!
$test
->
matchesFilter
(
$this
->
testFilter
)
)
{
return
;
}
$suppressErrors
=
!
empty
(
$testOpts
[
'parsoid'
][
'suppressErrors'
]
);
$this
->
siteConfig
->
setLogger
(
$suppressErrors
?
$this
->
siteConfig
->
suppressLogger
:
$this
->
defaultLogger
);
$targetModes
=
$test
->
computeTestModes
(
$options
[
'modes'
]
);
// Filter out html2* tests if we don't have an HTML section
// (Most likely there's either a metadata section or a html/php
// section but not html/parsoid section.)
if
(
$test
->
parsoidHtml
===
null
&&
!
isset
(
$test
->
sections
[
'html/parsoid+standalone'
]
)
)
{
$targetModes
=
array_diff
(
$targetModes
,
[
'html2wt'
,
'html2html'
]
);
}
if
(
!
count
(
$targetModes
)
)
{
return
;
}
// Honor language option
$prefix
=
$testOpts
[
'language'
]
??
'enwiki'
;
if
(
!
str_contains
(
$prefix
,
'wiki'
)
)
{
// Convert to our enwiki.. format
$prefix
.=
'wiki'
;
}
// Switch to requested wiki
$this
->
mockApi
->
setApiPrefix
(
$prefix
);
$this
->
siteConfig
->
reset
();
// Add the title associated with the current test as a known title to
// be consistent with the test runner in the core repo.
$teardown
=
$this
->
addArticle
(
new
Article
(
[
'title'
=>
$test
->
pageName
(),
'text'
=>
$test
->
wikitext
??
''
,
// Fake it
'type'
=>
'article'
,
'filename'
=>
'fake'
,
'lineNumStart'
=>
0
,
'lineNumEnd'
=>
0
,
]
)
);
// We don't do any sanity checking or type casting on $test->config
// values here: if you set a bogus value in a parser test it *should*
// blow things up, so that you fix your test case.
// Update $wgInterwikiMagic flag
// default (undefined) setting is true
$this
->
siteConfig
->
setInterwikiMagic
(
$test
->
config
[
'wgInterwikiMagic'
]
??
true
);
// Update $wgEnableMagicLinks flag
// default (undefined) setting is true for all types
foreach
(
[
"RFC"
,
"ISBN"
,
"PMID"
]
as
$v
)
{
$this
->
siteConfig
->
setMagicLinkEnabled
(
$v
,
(
$test
->
config
[
'wgEnableMagicLinks'
]
??
[]
)[
$v
]
??
true
);
}
if
(
isset
(
$testOpts
[
'pmid-interwiki'
]
)
)
{
$this
->
siteConfig
->
setupInterwikiMap
(
array_merge
(
self
::
PARSER_TESTS_IWPS
,
[
// Added to support T145590#8608455
[
'prefix'
=>
'pmid'
,
'local'
=>
true
,
'url'
=>
'//www.ncbi.nlm.nih.gov/pubmed/$1?dopt=Abstract'
,
]
]
)
);
$teardown
[]
=
fn
()
=>
$this
->
siteConfig
->
setupInterwikiMap
(
self
::
PARSER_TESTS_IWPS
);
}
// FIXME: Cite-specific hack
$this
->
siteConfig
->
responsiveReferences
=
[
'enabled'
=>
$test
->
config
[
'wgCiteResponsiveReferences'
]
??
$this
->
siteConfig
->
responsiveReferences
[
'enabled'
],
'threshold'
=>
$test
->
config
[
'wgCiteResponsiveReferencesThreshold'
]
??
$this
->
siteConfig
->
responsiveReferences
[
'threshold'
],
];
if
(
isset
(
$test
->
config
[
'wgNoFollowLinks'
]
)
)
{
$this
->
siteConfig
->
setNoFollowConfig
(
'nofollow'
,
$test
->
config
[
'wgNoFollowLinks'
]
);
}
if
(
isset
(
$test
->
config
[
'wgNoFollowDomainExceptions'
]
)
)
{
$this
->
siteConfig
->
setNoFollowConfig
(
'domainexceptions'
,
$test
->
config
[
'wgNoFollowDomainExceptions'
]
);
}
// FIXME: Redundant with $testOpts['externallinktarget'] below
if
(
isset
(
$test
->
config
[
'wgExternalLinkTarget'
]
)
)
{
$this
->
siteConfig
->
setExternalLinkTarget
(
$test
->
config
[
'wgExternalLinkTarget'
]
);
}
// Process test-specific options
if
(
$testOpts
)
{
Assert
::
invariant
(
!
isset
(
$testOpts
[
'extensions'
]
),
'Cannot configure extensions in tests'
);
$availableParsoidTestOpts
=
[
'wrapSections'
];
foreach
(
$availableParsoidTestOpts
as
$opt
)
{
if
(
isset
(
$testOpts
[
'parsoid'
][
$opt
]
)
)
{
$this
->
envOptions
[
$opt
]
=
$testOpts
[
'parsoid'
][
$opt
];
}
}
$this
->
siteConfig
->
disableSubpagesForNS
(
0
);
if
(
isset
(
$testOpts
[
'subpage'
]
)
)
{
$this
->
siteConfig
->
enableSubpagesForNS
(
0
);
}
$allowedPrefixes
=
[
''
];
// all allowed
if
(
isset
(
$testOpts
[
'wgallowexternalimages'
]
)
&&
!
preg_match
(
'/^(1|true|)$/D'
,
$testOpts
[
'wgallowexternalimages'
]
)
)
{
$allowedPrefixes
=
[];
}
$this
->
siteConfig
->
allowedExternalImagePrefixes
=
$allowedPrefixes
;
// Emulate PHP parser's tag hook to tunnel content past the sanitizer
if
(
isset
(
$testOpts
[
'styletag'
]
)
)
{
$this
->
siteConfig
->
registerParserTestExtension
(
new
StyleTag
()
);
}
if
(
(
$testOpts
[
'wgrawhtml'
]
??
null
)
===
'1'
)
{
$this
->
siteConfig
->
registerParserTestExtension
(
new
RawHTML
()
);
}
if
(
isset
(
$testOpts
[
'thumbsize'
]
)
)
{
$this
->
siteConfig
->
thumbsize
=
(
int
)
$testOpts
[
'thumbsize'
];
}
if
(
isset
(
$testOpts
[
'annotations'
]
)
)
{
$this
->
siteConfig
->
registerParserTestExtension
(
new
DummyAnnotation
()
);
}
if
(
isset
(
$testOpts
[
'i18next'
]
)
)
{
$this
->
siteConfig
->
registerParserTestExtension
(
new
I18nTag
()
);
}
if
(
isset
(
$testOpts
[
'externallinktarget'
]
)
)
{
$this
->
siteConfig
->
setExternalLinkTarget
(
$testOpts
[
'externallinktarget'
]
);
}
}
// Ensure ParserHook is always registered!
$this
->
siteConfig
->
registerParserTestExtension
(
new
ParserHook
()
);
$runner
=
$this
;
$test
->
testAllModes
(
$targetModes
,
$options
,
Closure
::
fromCallable
(
[
$this
,
'runTest'
]
)
);
foreach
(
$teardown
as
$t
)
{
$t
();
}
}
/**
* Run parser tests for the file with the provided options
*
* @param array $options
* @return array
*/
public
function
run
(
array
$options
):
array
{
$this
->
runDisabled
=
ScriptUtils
::
booleanOption
(
$options
[
'run-disabled'
]
??
null
);
$this
->
runPHP
=
ScriptUtils
::
booleanOption
(
$options
[
'run-php'
]
??
null
);
$this
->
offsetType
=
$options
[
'offsetType'
]
??
'byte'
;
// Test case filtering
$this
->
testFilter
=
null
;
if
(
isset
(
$options
[
'filter'
]
)
||
isset
(
$options
[
'regex'
]
)
)
{
$this
->
testFilter
=
[
'raw'
=>
$options
[
'regex'
]
??
$options
[
'filter'
],
'regex'
=>
isset
(
$options
[
'regex'
]
),
'string'
=>
isset
(
$options
[
'filter'
]
)
];
}
$this
->
buildTests
(
$options
);
// Trim test cases to the desired amount
if
(
isset
(
$options
[
'maxtests'
]
)
)
{
$n
=
$options
[
'maxtests'
];
if
(
$n
>
0
)
{
$this
->
testCases
=
array_slice
(
$this
->
testCases
,
0
,
$n
);
}
}
$defaultOpts
=
[
'wrapSections'
=>
false
,
'nativeTemplateExpansion'
=>
true
,
'offsetType'
=>
$this
->
offsetType
,
];
ScriptUtils
::
setDebuggingFlags
(
$defaultOpts
,
$options
);
ScriptUtils
::
setTemplatingAndProcessingFlags
(
$defaultOpts
,
$options
);
if
(
ScriptUtils
::
booleanOption
(
$options
[
'quiet'
]
??
null
)
||
ScriptUtils
::
booleanOption
(
$options
[
'quieter'
]
??
null
)
)
{
$defaultOpts
[
'logLevels'
]
=
[
'fatal'
,
'error'
];
}
// Save default logger so we can be reset it after temporarily
// switching to the suppressLogger to suppress expected error messages.
$this
->
defaultLogger
=
$this
->
siteConfig
->
getLogger
();
/**
* PORT-FIXME(T238722)
* // Enable sampling to assert it's working while testing.
* $parsoidConfig->loggerSampling = [ [ '/^warn(\/|$)/', 100 ] ];
*
* // Override env's `setLogger` to record if we see `fatal` or `error`
* // while running parser tests. (Keep it clean, folks! Use
* // "suppressError" option on the test if error is expected.)
* $env->setLogger = ( ( function ( $parserTests, $superSetLogger ) {
* return function ( $_logger ) use ( &$parserTests ) {
* call_user_func( 'superSetLogger', $_logger );
* $this->log = function ( $level ) use ( &$_logger, &$parserTests ) {
* if ( $_logger !== $parserTests->suppressLogger &&
* preg_match( '/^(fatal|error)\b/', $level )
* ) {
* $parserTests->stats->loggedErrorCount++;
* }
* return call_user_func_array( [ $_logger, 'log' ], $arguments );
* };
* };
* } ) );
*/
$options
[
'reportStart'
]();
// Run tests
foreach
(
$this
->
testCases
as
$test
)
{
try
{
$this
->
envOptions
=
$defaultOpts
;
$this
->
processTest
(
$test
,
$options
);
}
catch
(
UnexpectedException
$e
)
{
// Exit unexpected
break
;
}
}
// Update knownFailures
return
$this
->
updateKnownFailures
(
$options
);
}
}
File Metadata
Details
Attached
Mime Type
text/x-php
Expires
Aug 16 2026, 14:28 (11 w, 6 d ago)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
8c/d4/e84b9795d9ed2438188b0980e46f
Default Alt Text
TestRunner.php (39 KB)
Attached To
Mode
rMWPROD MediaWiki Production
Attached
Detach File
Event Timeline
Log In to Comment