Merge "Remove Revision::getRevisionText from migrateArchiveText"
[lhc/web/wiklou.git] / tests / phpunit / maintenance / DumpTestCase.php
1 <?php
2
3 namespace MediaWiki\Tests\Maintenance;
4
5 use ContentHandler;
6 use DOMDocument;
7 use ExecutableFinder;
8 use MediaWikiLangTestCase;
9 use User;
10 use WikiExporter;
11 use WikiPage;
12 use MWException;
13
14 /**
15 * Base TestCase for dumps
16 */
17 abstract class DumpTestCase extends MediaWikiLangTestCase {
18
19 /**
20 * exception to be rethrown once in sound PHPUnit surrounding
21 *
22 * As the current MediaWikiTestCase::run is not robust enough to recover
23 * from thrown exceptions directly, we cannot throw frow within
24 * self::addDBData, although it would be appropriate. Hence, we catch the
25 * exception and store it until we are in setUp and may finally rethrow
26 * the exception without crashing the test suite.
27 *
28 * @var \Exception|null
29 */
30 protected $exceptionFromAddDBData = null;
31
32 /** @var bool|null Whether the 'gzip' utility is available */
33 protected static $hasGzip = null;
34
35 /**
36 * Skip the test if 'gzip' is not in $PATH.
37 *
38 * @return bool
39 */
40 protected function checkHasGzip() {
41 if ( self::$hasGzip === null ) {
42 self::$hasGzip = ( ExecutableFinder::findInDefaultPaths( 'gzip' ) !== false );
43 }
44
45 if ( !self::$hasGzip ) {
46 $this->markTestSkipped( "Skip test, requires the gzip utility in PATH" );
47 }
48
49 return self::$hasGzip;
50 }
51
52 /**
53 * Adds a revision to a page, while returning the resuting revision's id
54 *
55 * @param WikiPage $page Page to add the revision to
56 * @param string $text Revisions text
57 * @param string $summary Revisions summary
58 * @param string $model The model ID (defaults to wikitext)
59 *
60 * @throws MWException
61 * @return array
62 */
63 protected function addRevision(
64 WikiPage $page,
65 $text,
66 $summary,
67 $model = CONTENT_MODEL_WIKITEXT
68 ) {
69 $status = $page->doEditContent(
70 ContentHandler::makeContent( $text, $page->getTitle(), $model ),
71 $summary, 0, false, $this->getTestUser()->getUser()
72 );
73
74 if ( $status->isGood() ) {
75 $value = $status->getValue();
76 $revision = $value['revision'];
77 $revision_id = $revision->getId();
78 $text_id = $revision->getTextId();
79
80 if ( ( $revision_id > 0 ) && ( $text_id > 0 ) ) {
81 return [ $revision_id, $text_id ];
82 }
83 }
84
85 throw new MWException( "Could not determine revision id ("
86 . $status->getWikiText( false, false, 'en' ) . ")" );
87 }
88
89 /**
90 * gunzips the given file and stores the result in the original file name
91 *
92 * @param string $fname Filename to read the gzipped data from and stored
93 * the gunzipped data into
94 */
95 protected function gunzip( $fname ) {
96 $gzipped_contents = file_get_contents( $fname );
97 if ( $gzipped_contents === false ) {
98 $this->fail( "Could not get contents of $fname" );
99 }
100
101 $contents = gzdecode( $gzipped_contents );
102
103 $this->assertEquals(
104 strlen( $contents ),
105 file_put_contents( $fname, $contents ),
106 '# bytes written'
107 );
108 }
109
110 public static function setUpBeforeClass() {
111 parent::setUpBeforeClass();
112
113 if ( !function_exists( 'libxml_set_external_entity_loader' ) ) {
114 return;
115 }
116
117 // The W3C is intentionally slow about returning schema files,
118 // see <https://www.w3.org/Help/Webmaster#slowdtd>.
119 // To work around that, we keep our own copies of the relevant schema files.
120 libxml_set_external_entity_loader(
121 function ( $public, $system, $context ) {
122 switch ( $system ) {
123 // if more schema files are needed, add them here.
124 case 'http://www.w3.org/2001/xml.xsd':
125 $file = __DIR__ . '/xml.xsd';
126 break;
127 default:
128 if ( is_file( $system ) ) {
129 $file = $system;
130 } else {
131 return null;
132 }
133 }
134
135 return $file;
136 }
137 );
138 }
139
140 /**
141 * Default set up function.
142 *
143 * Clears $wgUser, and reports errors from addDBData to PHPUnit
144 */
145 protected function setUp() {
146 parent::setUp();
147
148 // Check if any Exception is stored for rethrowing from addDBData
149 // @see self::exceptionFromAddDBData
150 if ( $this->exceptionFromAddDBData !== null ) {
151 throw $this->exceptionFromAddDBData;
152 }
153
154 $this->setMwGlobals( 'wgUser', new User() );
155 }
156
157 /**
158 * Returns the path to the XML schema file for the given schema version.
159 *
160 * @param string|null $schemaVersion
161 *
162 * @return string
163 */
164 protected function getXmlSchemaPath( $schemaVersion = null ) {
165 global $IP, $wgXmlDumpSchemaVersion;
166
167 $schemaVersion = $schemaVersion ?: $wgXmlDumpSchemaVersion;
168
169 return "$IP/docs/export-$schemaVersion.xsd";
170 }
171
172 /**
173 * Checks for test output consisting only of lines containing ETA announcements
174 */
175 function expectETAOutput() {
176 // Newer PHPUnits require assertion about the output using PHPUnit's own
177 // expectOutput[...] functions. However, the PHPUnit shipped prediactes
178 // do not allow to check /each/ line of the output using /readable/ REs.
179 // So we ...
180
181 // 1. ... add a dummy output checking to make PHPUnit not complain
182 // about unchecked test output
183 $this->expectOutputRegex( '//' );
184
185 // 2. Do the real output checking on our own.
186 $lines = explode( "\n", $this->getActualOutput() );
187 $this->assertGreaterThan( 1, count( $lines ), "Minimal lines of produced output" );
188 $this->assertSame( '', array_pop( $lines ), "Output ends in LF" );
189 $timestamp_re = "[0-9]{4}-[01][0-9]-[0-3][0-9] [0-2][0-9]:[0-5][0-9]:[0-6][0-9]";
190 foreach ( $lines as $line ) {
191 $this->assertRegExp(
192 "/$timestamp_re: .* \(ID [0-9]+\) [0-9]* pages .*, [0-9]* revs .*, ETA/",
193 $line
194 );
195 }
196 }
197
198 /**
199 * @param null|string $schemaVersion
200 *
201 * @return DumpAsserter
202 */
203 protected function getDumpAsserter( $schemaVersion = null ) {
204 $schemaVersion = $schemaVersion ?: WikiExporter::schemaVersion();
205 return new DumpAsserter( $schemaVersion );
206 }
207
208 /**
209 * Checks an XML file against an XSD schema.
210 */
211 protected function assertDumpSchema( $fname, $schemaFile ) {
212 if ( !function_exists( 'libxml_use_internal_errors' ) ) {
213 // Would be nice to leave a warning somehow.
214 // We don't want to skip all of the test case that calls this, though.
215 $this->markAsRisky();
216 return;
217 }
218 if ( defined( 'HHVM_VERSION' ) ) {
219 // In HHVM, loading a schema from a file is disabled per default.
220 // This is controlled by hhvm.libxml.ext_entity_whitelist which
221 // cannot be read with ini_get(), see
222 // <https://docs.hhvm.com/hhvm/configuration/INI-settings#xml>.
223 // Would be nice to leave a warning somehow.
224 // We don't want to skip all of the test case that calls this, though.
225 $this->markAsRisky();
226 return;
227 }
228
229 $xml = new DOMDocument();
230 $this->assertTrue( $xml->load( $fname ),
231 "Opening temporary file $fname via DOMDocument failed" );
232
233 // Don't throw
234 $oldLibXmlInternalErrors = libxml_use_internal_errors( true );
235
236 // NOTE: if this reports "Invalid Schema", the schema may be referencing an external
237 // entity (typically, another schema) that needs to be mapped in the
238 // libxml_set_external_entity_loader callback defined in setUpBeforeClass() above!
239 // Or $schemaFile doesn't point to a schema file, or the schema is indeed just broken.
240 if ( !$xml->schemaValidate( $schemaFile ) ) {
241 $errorText = '';
242
243 foreach ( libxml_get_errors() as $error ) {
244 $errorText .= "\nline {$error->line}: {$error->message}";
245 }
246
247 libxml_clear_errors();
248
249 $this->fail(
250 "Failed asserting that $fname conforms to the schema in $schemaFile:\n$errorText"
251 );
252 }
253
254 libxml_use_internal_errors( $oldLibXmlInternalErrors );
255 }
256
257 }