3 use PHPUnit\Framework\TestSuite
;
4 use Wikimedia\ScopedCallback
;
7 * The UnitTest must be either a class that inherits from MediaWikiIntegrationTestCase
8 * or a class that provides a public static suite() method which returns
9 * an PHPUnit\Framework\Test object
15 class ParserTestTopLevelSuite
extends TestSuite
{
18 /** @var PhpunitTestRecorder */
21 /** @var ParserTestRunner */
24 /** @var ScopedCallback */
25 private $ptTeardownScope;
28 * @defgroup filtering_constants Filtering constants
30 * Limit inclusion of parser tests files coming from MediaWiki core
35 * Include files shipped with MediaWiki core
37 public const CORE_ONLY
= 1;
38 /** Include non core files returned by
39 * ParserTestRunner::getParserTestFiles() (that is, parser tests belonging
42 public const NO_CORE
= 2;
43 /** Include anything returned by ParserTestRunner::getParserTestFiles(),
44 * both core and extensions.
46 public const WITH_ALL
= self
::CORE_ONLY | self
::NO_CORE
;
51 * Get a PHPUnit test suite of parser tests. Optionally filtered with
55 * Get a suite of parser tests shipped by MediaWiki core:
57 * ParserTestTopLevelSuite::suite( ParserTestTopLevelSuite::CORE_ONLY );
59 * Get a suite of various parser tests, like extensions:
61 * ParserTestTopLevelSuite::suite( ParserTestTopLevelSuite::NO_CORE );
63 * Get any test returned by ParserTestRunner::getParserTestFiles():
65 * ParserTestTopLevelSuite::suite( ParserTestTopLevelSuite::WITH_ALL );
68 * @param int $flags Bitwise flag to filter out the test files that
69 * will be included. Default: ParserTestTopLevelSuite::CORE_ONLY
73 public static function suite( $flags = self
::CORE_ONLY
) {
74 return new self( $flags );
77 public function __construct( $flags, ?
array $parserTestFlags = null ) {
78 parent
::__construct();
80 $this->ptRecorder
= new PhpunitTestRecorder
;
81 $runnerOpts = $parserTestFlags ??
json_decode( getenv( "PARSERTEST_FLAGS" ) ?
: "[]", true );
82 // PHPUnit test runners requires all tests to be pregenerated.
83 // But, generating Parsoid selser edit trees requires the DOM.
84 // So, we cannot pregenerate Parsoid selser auto-edit tests.
85 // They have to be generated dynamically. So, set this to 0.
86 // We will handle auto-edit selser tests as a composite test.
87 $runnerOpts['numchanges'] = 0;
88 $this->ptRunner
= new ParserTestRunner(
89 $this->ptRecorder
, $runnerOpts
92 if ( is_string( $flags ) ) {
93 $flags = self
::CORE_ONLY
;
97 $mwTestDir = $IP . '/tests/';
99 # Human friendly helpers
100 $wantsCore = ( $flags & self
::CORE_ONLY
);
101 $wantsRest = ( $flags & self
::NO_CORE
);
103 # Will hold the .txt parser test files we will include
106 # Filter out .txt files
107 $files = ParserTestRunner
::getParserTestFiles();
108 foreach ( $files as $extName => $parserTestFile ) {
109 $isCore = str_starts_with( $parserTestFile, $mwTestDir );
111 if ( $isCore && $wantsCore ) {
112 self
::debug( "included core parser tests: $parserTestFile" );
113 $filesToTest[$extName] = $parserTestFile;
114 } elseif ( !$isCore && $wantsRest ) {
115 self
::debug( "included non core parser tests: $parserTestFile" );
116 $filesToTest[$extName] = $parserTestFile;
118 self
::debug( "skipped parser tests: $parserTestFile" );
121 self
::debug( 'parser tests files: '
122 . implode( ' ', $filesToTest ) );
126 foreach ( $filesToTest as $extensionName => $fileName ) {
127 $isCore = str_starts_with( $fileName, $mwTestDir );
128 if ( is_int( $extensionName ) ) {
129 // If there's no extension name because this is coming
130 // from the legacy global, then assume the next level directory
131 // is the extension name (e.g. extensions/FooBar/parserTests.txt).
132 $extensionName = basename( dirname( $fileName ) );
134 $testsName = $extensionName . '__' . basename( $fileName, '.txt' );
135 $parserTestClassName = ucfirst( $testsName );
137 // Official spec for class names: https://www.php.net/manual/en/language.oop5.basic.php
138 // Prepend 'ParserTest_' to be paranoid about it not starting with a number
139 $parserTestClassName = 'ParserTest_' .
140 preg_replace( '/[^a-zA-Z0-9_\x7f-\xff]/', '_', $parserTestClassName );
142 $originalClassName = $parserTestClassName;
143 while ( isset( $testList[$parserTestClassName] ) ) {
144 // If there is a conflict, append a number.
146 $parserTestClassName = $originalClassName . '_' . $counter;
148 $testList[$parserTestClassName] = true;
150 // Previously we actually created a class here, with eval(). We now
151 // just override the name.
153 self
::debug( "Adding test class $parserTestClassName" );
155 $this->addTest( new ParserTestFileSuite(
156 $this->ptRunner
, "Legacy$parserTestClassName", $fileName ) );
157 // Parsoid (only run this on extensions for now, since Parsoid
158 // has its own copy of core's parser tests which it runs in its
161 $this->addTest( new ParsoidTestFileSuite(
162 $this->ptRunner
, "Parsoid$parserTestClassName", $fileName
168 protected function setUp(): void
{
169 // MediaWikiIntegrationTestCase leaves its test DB hanging around.
170 // we want to make sure we have a clean instance, so tear down any
171 // existing test DB. This has no effect if no test DB exists.
172 MediaWikiIntegrationTestCase
::teardownTestDB();
173 // Similarly, make sure we don't reuse Test users from other tests
174 TestUserRegistry
::clear();
176 $teardown = $this->ptRunner
->setupDatabase( null );
177 $teardown = $this->ptRunner
->staticSetup( $teardown );
178 $teardown = $this->ptRunner
->setupUploads( $teardown );
179 $this->ptTeardownScope
= $teardown;
182 protected function tearDown(): void
{
183 if ( $this->ptTeardownScope
) {
184 ScopedCallback
::consume( $this->ptTeardownScope
);
186 TestUserRegistry
::clear();
190 * Write $msg under log group 'tests-parser'
191 * @param string $msg Message to log
193 protected static function debug( $msg ) {
194 wfDebugLog( 'tests-parser', wfGetCaller() . ' ' . $msg );