Localisation updates from https://translatewiki.net.
[mediawiki.git] / tests / phpunit / maintenance / DumpTestCase.php
blobbd12b501cc9f9b0292aa69534c1099e0caa47ef3
1 <?php
3 namespace MediaWiki\Tests\Maintenance;
5 use DOMDocument;
6 use ExecutableFinder;
7 use MediaWiki\CommentStore\CommentStoreComment;
8 use MediaWiki\Content\Content;
9 use MediaWiki\MediaWikiServices;
10 use MediaWiki\Revision\RevisionAccessException;
11 use MediaWiki\Revision\RevisionRecord;
12 use MediaWiki\Revision\SlotRecord;
13 use MediaWikiLangTestCase;
14 use RuntimeException;
15 use WikiExporter;
16 use WikiPage;
18 /**
19 * Base TestCase for dumps
21 abstract class DumpTestCase extends MediaWikiLangTestCase {
23 /**
24 * exception to be rethrown once in sound PHPUnit surrounding
26 * As the current MediaWikiIntegrationTestCase::run is not robust enough to recover
27 * from thrown exceptions directly, we cannot throw frow within
28 * self::addDBData, although it would be appropriate. Hence, we catch the
29 * exception and store it until we are in setUp and may finally rethrow
30 * the exception without crashing the test suite.
32 * @var \Exception|null
34 protected $exceptionFromAddDBData = null;
36 /** @var bool|null Whether the 'gzip' utility is available */
37 protected static $hasGzip = null;
39 /**
40 * Skip the test if 'gzip' is not in $PATH.
42 * @return bool
44 protected function checkHasGzip() {
45 if ( self::$hasGzip === null ) {
46 self::$hasGzip = ( ExecutableFinder::findInDefaultPaths( 'gzip' ) !== false );
49 if ( !self::$hasGzip ) {
50 $this->markTestSkipped( "Skip test, requires the gzip utility in PATH" );
53 return self::$hasGzip;
56 /**
57 * Adds a revision to a page, while returning the resuting revision's id text id.
59 * @param WikiPage $page Page to add the revision to
60 * @param string $text Revisions text
61 * @param string $summary Revisions summary
62 * @param string $model The model ID (defaults to wikitext)
63 * @return array
65 protected function addRevision(
66 WikiPage $page,
67 $text,
68 $summary,
69 $model = CONTENT_MODEL_WIKITEXT
70 ) {
71 $contentHandler = $this->getServiceContainer()
72 ->getContentHandlerFactory()->getContentHandler( $model );
74 $content = $contentHandler->unserializeContent( $text );
76 $rev = $this->addMultiSlotRevision( $page, [ SlotRecord::MAIN => $content ], $summary );
78 if ( !$rev ) {
79 throw new RuntimeException( "Could not create revision" );
82 $text_id = $this->getSlotTextId( $rev->getSlot( SlotRecord::MAIN ) );
83 return [ $rev->getId(), $text_id, $rev ];
86 /**
87 * @param SlotRecord $slot
89 * @return string|null
91 protected function getSlotText( SlotRecord $slot ) {
92 try {
93 return $slot->getContent()->serialize();
94 } catch ( RevisionAccessException $ex ) {
95 return null;
99 /**
100 * @param SlotRecord $slot
102 * @return int
104 protected function getSlotTextId( SlotRecord $slot ) {
105 return (int)preg_replace( '/^tt:/', '', $slot->getAddress() );
109 * @param SlotRecord $slot
111 * @return string
113 protected function getSlotFormat( SlotRecord $slot ) {
114 $contentHandler = $this->getServiceContainer()
115 ->getContentHandlerFactory()->getContentHandler( $slot->getModel() );
117 return $contentHandler->getDefaultFormat();
121 * Adds a revision to a page, while returning the resulting revision's id and text id.
123 * @param WikiPage $page Page to add the revision to
124 * @param Content[] $slots A mapping of slot names to Content objects
125 * @param string $summary Revisions summary
126 * @return RevisionRecord
128 protected function addMultiSlotRevision(
129 WikiPage $page,
130 array $slots,
131 $summary
133 $slotRoleRegistry = MediaWikiServices::getInstance()->getSlotRoleRegistry();
135 $updater = $page->newPageUpdater( $this->getTestUser()->getUser() );
137 foreach ( $slots as $role => $content ) {
138 if ( !$slotRoleRegistry->isDefinedRole( $role ) ) {
139 $slotRoleRegistry->defineRoleWithModel( $role, $content->getModel() );
142 $updater->setContent( $role, $content );
145 $updater->saveRevision( CommentStoreComment::newUnsavedComment( trim( $summary ) ) );
146 return $updater->getNewRevision();
150 * gunzips the given file and stores the result in the original file name
152 * @param string $fname Filename to read the gzipped data from and stored
153 * the gunzipped data into
155 protected function gunzip( $fname ) {
156 $gzipped_contents = file_get_contents( $fname );
157 if ( $gzipped_contents === false ) {
158 $this->fail( "Could not get contents of $fname" );
161 $contents = gzdecode( $gzipped_contents );
163 $this->assertEquals(
164 strlen( $contents ),
165 file_put_contents( $fname, $contents ),
166 '# bytes written'
170 public static function setUpBeforeClass(): void {
171 parent::setUpBeforeClass();
173 if ( !function_exists( 'libxml_set_external_entity_loader' ) ) {
174 return;
177 // The W3C is intentionally slow about returning schema files,
178 // see <https://www.w3.org/Help/Webmaster#slowdtd>.
179 // To work around that, we keep our own copies of the relevant schema files.
180 libxml_set_external_entity_loader(
181 static function ( $public, $system, $context ) {
182 switch ( $system ) {
183 // if more schema files are needed, add them here.
184 case 'http://www.w3.org/2001/xml.xsd':
185 $file = __DIR__ . '/xml.xsd';
186 break;
187 default:
188 if ( is_file( $system ) ) {
189 $file = $system;
190 } else {
191 return null;
195 return $file;
201 * Default set up function.
203 * Reports errors from addDBData to PHPUnit
205 protected function setUp(): void {
206 parent::setUp();
208 // Check if any Exception is stored for rethrowing from addDBData
209 // @see self::exceptionFromAddDBData
210 if ( $this->exceptionFromAddDBData !== null ) {
211 throw $this->exceptionFromAddDBData;
216 * Returns the path to the XML schema file for the given schema version.
218 * @param string|null $schemaVersion
220 * @return string
222 protected function getXmlSchemaPath( $schemaVersion = null ) {
223 global $IP, $wgXmlDumpSchemaVersion;
225 $schemaVersion = $schemaVersion ?: $wgXmlDumpSchemaVersion;
227 return "$IP/docs/export-$schemaVersion.xsd";
231 * Checks for test output consisting only of lines containing ETA announcements
233 protected function expectETAOutput() {
234 // Newer PHPUnits require assertion about the output using PHPUnit's own
235 // expectOutput[...] functions. However, the PHPUnit shipped prediactes
236 // do not allow to check /each/ line of the output using /readable/ REs.
237 // So we ...
239 // 1. ... add a dummy output checking to make PHPUnit not complain
240 // about unchecked test output
241 $this->expectOutputRegex( '//' );
243 // 2. Do the real output checking on our own.
244 $lines = explode( "\n", $this->getActualOutput() );
245 $this->assertGreaterThan( 1, count( $lines ), "Minimal lines of produced output" );
246 $this->assertSame( '', array_pop( $lines ), "Output ends in LF" );
247 $timestamp_re = "[0-9]{4}-[01][0-9]-[0-3][0-9] [0-2][0-9]:[0-5][0-9]:[0-6][0-9]";
248 foreach ( $lines as $line ) {
249 $this->assertMatchesRegularExpression(
250 "/$timestamp_re: .* \(ID [0-9]+\) [0-9]* pages .*, [0-9]* revs .*, ETA/",
251 $line
257 * @param null|string $schemaVersion
259 * @return DumpAsserter
261 protected function getDumpAsserter( $schemaVersion = null ) {
262 $schemaVersion = $schemaVersion ?: WikiExporter::schemaVersion();
263 return new DumpAsserter( $schemaVersion );
267 * Checks an XML file against an XSD schema.
268 * @param string $fname
269 * @param string $schemaFile
271 protected function assertDumpSchema( $fname, $schemaFile ) {
272 if ( !function_exists( 'libxml_use_internal_errors' ) ) {
273 // Would be nice to leave a warning somehow.
274 // We don't want to skip all of the test case that calls this, though.
275 $this->markAsRisky();
276 return;
278 $xml = new DOMDocument();
279 $this->assertTrue( $xml->load( $fname ),
280 "Opening temporary file $fname via DOMDocument failed" );
282 // Don't throw
283 $oldLibXmlInternalErrors = libxml_use_internal_errors( true );
285 // NOTE: if this reports "Invalid Schema", the schema may be referencing an external
286 // entity (typically, another schema) that needs to be mapped in the
287 // libxml_set_external_entity_loader callback defined in setUpBeforeClass() above!
288 // Or $schemaFile doesn't point to a schema file, or the schema is indeed just broken.
289 if ( !$xml->schemaValidate( $schemaFile ) ) {
290 $errorText = '';
292 foreach ( libxml_get_errors() as $error ) {
293 $errorText .= "\nline {$error->line}: {$error->message}";
296 libxml_clear_errors();
298 $this->fail(
299 "Failed asserting that $fname conforms to the schema in $schemaFile:\n$errorText"
303 libxml_use_internal_errors( $oldLibXmlInternalErrors );