From 94d559b1497475ca58961b8d1bb62ca58409ebf1 Mon Sep 17 00:00:00 2001 From: daniel Date: Fri, 14 Dec 2018 12:06:45 +0100 Subject: [PATCH] Validate the output of the dump scripts. This introduces XML schema validation into the unit tests for the backup dump scripts. This is intended to ensure that the output keeps conforming to the schema when updating XmlDumpWriter and friends to support newer schema versions. Bug: T174031 Change-Id: Iafa8cb8ba5b3684a08172e92974edaf0482f19b7 --- docs/export-0.10.xsd | 2 +- tests/common/TestsAutoLoader.php | 1 + tests/phpunit/PHPUnit4And6Compat.php | 14 + tests/phpunit/maintenance/DumpAsserter.php | 347 ++++++++++++ tests/phpunit/maintenance/DumpTestCase.php | 350 ++++-------- .../maintenance/backupTextPassTest.php | 157 ++++-- tests/phpunit/maintenance/backup_LogTest.php | 74 +-- tests/phpunit/maintenance/backup_PageTest.php | 520 +++++++++++++----- tests/phpunit/maintenance/xml.xsd | 287 ++++++++++ 9 files changed, 1256 insertions(+), 496 deletions(-) create mode 100644 tests/phpunit/maintenance/DumpAsserter.php create mode 100644 tests/phpunit/maintenance/xml.xsd diff --git a/docs/export-0.10.xsd b/docs/export-0.10.xsd index 9d5d49e08e..6291bfcd28 100644 --- a/docs/export-0.10.xsd +++ b/docs/export-0.10.xsd @@ -224,7 +224,7 @@ - + diff --git a/tests/common/TestsAutoLoader.php b/tests/common/TestsAutoLoader.php index f742a1bae8..8db31b5a52 100644 --- a/tests/common/TestsAutoLoader.php +++ b/tests/common/TestsAutoLoader.php @@ -178,6 +178,7 @@ $wgAutoloadClasses += [ 'GenericArrayObjectTest' => "$testDir/phpunit/includes/libs/GenericArrayObjectTest.php", # tests/phpunit/maintenance + 'MediaWiki\Tests\Maintenance\DumpAsserter' => "$testDir/phpunit/maintenance/DumpAsserter.php", 'MediaWiki\Tests\Maintenance\DumpTestCase' => "$testDir/phpunit/maintenance/DumpTestCase.php", 'MediaWiki\Tests\Maintenance\MaintenanceBaseTestCase' => "$testDir/phpunit/maintenance/MaintenanceBaseTestCase.php", diff --git a/tests/phpunit/PHPUnit4And6Compat.php b/tests/phpunit/PHPUnit4And6Compat.php index 79ce634fab..1ef0c91683 100644 --- a/tests/phpunit/PHPUnit4And6Compat.php +++ b/tests/phpunit/PHPUnit4And6Compat.php @@ -130,4 +130,18 @@ trait PHPUnit4And6Compat { // ->disallowMockingUnknownTypes() ->getMock(); } + + /** + * Marks the current test as risky. This + * is a forward port of the markAsRisky function that + * was introduced in PHPUnit 5.7.6. + */ + public function markAsRisky() { + if ( is_callable( 'parent::markAsRisky' ) ) { + return parent::markAsRisky(); + } + + // "risky" tests are not supported in phpunit 4, so just ignore + } + } diff --git a/tests/phpunit/maintenance/DumpAsserter.php b/tests/phpunit/maintenance/DumpAsserter.php new file mode 100644 index 0000000000..5b4c6efd50 --- /dev/null +++ b/tests/phpunit/maintenance/DumpAsserter.php @@ -0,0 +1,347 @@ +schemaVersion = $schemaVersion; + } + + /** + * Step the current XML reader until node end of given name is found. + * + * @param string $name Name of the closing element to look for + * (e.g.: "mediawiki" when looking for ) + * + * @return bool True if the end node could be found. false otherwise. + */ + public function skipToNodeEnd( $name ) { + while ( $this->xml->read() ) { + if ( $this->xml->nodeType == XMLReader::END_ELEMENT && + $this->xml->name == $name + ) { + return true; + } + } + + return false; + } + + /** + * Step the current XML reader to the first element start after the node + * end of a given name. + * + * @param string $name Name of the closing element to look for + * (e.g.: "mediawiki" when looking for ) + * + * @return bool True if new element after the closing of $name could be + * found. false otherwise. + */ + public function skipPastNodeEnd( $name ) { + Assert::assertTrue( $this->skipToNodeEnd( $name ), + "Skipping to end of $name" ); + while ( $this->xml->read() ) { + if ( $this->xml->nodeType == XMLReader::ELEMENT ) { + return true; + } + } + + return false; + } + + /** + * Opens an XML file to analyze and optionally skips past siteinfo. + * + * @param string $fname Name of file to analyze + * @param bool $skip_siteinfo (optional) If true, step the xml reader + * to the first element after + */ + public function assertDumpStart( $fname, $skip_siteinfo = true ) { + $this->xml = new XMLReader(); + + Assert::assertTrue( $this->xml->open( $fname ), + "Opening temporary file $fname via XMLReader failed" ); + if ( $skip_siteinfo ) { + Assert::assertTrue( $this->skipPastNodeEnd( "siteinfo" ), + "Skipping past end of siteinfo" ); + } + } + + /** + * Asserts that the xml reader is at the final closing tag of an xml file and + * closes the reader. + * + * @param string $name (optional) the name of the final tag + * (e.g.: "mediawiki" for ) + */ + public function assertDumpEnd( $name = "mediawiki" ) { + $this->assertNodeEnd( $name, false ); + if ( $this->xml->read() ) { + $this->skipWhitespace(); + } + Assert::assertEquals( $this->xml->nodeType, XMLReader::NONE, + "No proper entity left to parse" ); + $this->xml->close(); + } + + /** + * Steps the xml reader over white space + */ + public function skipWhitespace() { + $cont = true; + while ( $cont && ( ( $this->xml->nodeType == XMLReader::WHITESPACE ) + || ( $this->xml->nodeType == XMLReader::SIGNIFICANT_WHITESPACE ) ) ) { + $cont = $this->xml->read(); + } + } + + /** + * Asserts that the xml reader is at an element of given name, and optionally + * skips past it. + * + * @param string $name The name of the element to check for + * (e.g.: "mediawiki" for ) + * @param bool $skip (optional) if true, skip past the found element + */ + public function assertNodeStart( $name, $skip = true ) { + Assert::assertEquals( $name, $this->xml->name, "Node name" ); + Assert::assertEquals( XMLReader::ELEMENT, $this->xml->nodeType, "Node type" ); + if ( $skip ) { + Assert::assertTrue( $this->xml->read(), "Skipping past start tag" ); + } + } + + /** + * Asserts that the xml reader is at an closing element of given name, and optionally + * skips past it. + * + * @param string $name The name of the closing element to check for + * (e.g.: "mediawiki" for ) + * @param bool $skip (optional) if true, skip past the found element + */ + public function assertNodeEnd( $name, $skip = true ) { + Assert::assertEquals( $name, $this->xml->name, "Node name" ); + Assert::assertEquals( XMLReader::END_ELEMENT, $this->xml->nodeType, "Node type" ); + if ( $skip ) { + Assert::assertTrue( $this->xml->read(), "Skipping past end tag" ); + } + } + + /** + * Asserts that the xml reader is at an element of given tag that contains a given text, + * and skips over the element. + * + * @param string $name The name of the element to check for + * (e.g.: "mediawiki" for ...) + * @param string|bool $text If string, check if it equals the elements text. + * If false, ignore the element's text + * @param bool $skip_ws (optional) if true, skip past white spaces that trail the + * closing element. + */ + public function assertTextNode( $name, $text, $skip_ws = true ) { + $this->assertNodeStart( $name ); + + if ( $text !== false ) { + Assert::assertEquals( $text, $this->xml->value, "Text of node " . $name ); + } + Assert::assertTrue( $this->xml->read(), "Skipping past processed text of " . $name ); + $this->assertNodeEnd( $name ); + + if ( $skip_ws ) { + $this->skipWhitespace(); + } + } + + /** + * Asserts that the xml reader is at the start of a page element and skips over the first + * tags, after checking them. + * + * Besides the opening page element, this function also checks for and skips over the + * title, ns, and id tags. Hence after this function, the xml reader is at the first + * revision of the current page. + * + * @param int $id Id of the page to assert + * @param int $ns Number of namespage to assert + * @param string $name Title of the current page + */ + public function assertPageStart( $id, $ns, $name ) { + $this->assertNodeStart( "page" ); + $this->skipWhitespace(); + + $this->assertTextNode( "title", $name ); + $this->assertTextNode( "ns", $ns ); + $this->assertTextNode( "id", $id ); + } + + /** + * Asserts that the xml reader is at the page's closing element and skips to the next + * element. + */ + public function assertPageEnd() { + $this->assertNodeEnd( "page" ); + $this->skipWhitespace(); + } + + /** + * Asserts that the xml reader is at a revision and checks its representation before + * skipping over it. + * + * @param int $id Id of the revision + * @param string $summary Summary of the revision + * @param int $text_id Id of the revision's text + * @param int $text_bytes Number of bytes in the revision's text + * @param string $text_sha1 The base36 SHA-1 of the revision's text + * @param string|bool $text (optional) The revision's string, or false to check for a + * revision stub + * @param int|bool $parentid (optional) id of the parent revision + * @param string $model The expected content model id (default: CONTENT_MODEL_WIKITEXT) + * @param string $format The expected format model id (default: CONTENT_FORMAT_WIKITEXT) + */ + public function assertRevision( $id, $summary, $text_id, $text_bytes, + $text_sha1, $text = false, $parentid = false, + $model = CONTENT_MODEL_WIKITEXT, $format = CONTENT_FORMAT_WIKITEXT + ) { + $this->assertNodeStart( "revision" ); + $this->skipWhitespace(); + + $this->assertTextNode( "id", $id ); + if ( $parentid !== false ) { + $this->assertTextNode( "parentid", $parentid ); + } + $this->assertTextNode( "timestamp", false ); + + $this->assertNodeStart( "contributor" ); + $this->skipWhitespace(); + $this->assertTextNode( "ip", false ); + $this->assertNodeEnd( "contributor" ); + $this->skipWhitespace(); + + $this->assertTextNode( "comment", $summary ); + $this->skipWhitespace(); + + $this->assertTextNode( "model", $model ); + $this->skipWhitespace(); + + $this->assertTextNode( "format", $format ); + $this->skipWhitespace(); + + if ( $this->xml->name == "text" ) { + // note: tag may occur here or at the very end. + $text_found = true; + $this->assertText( $id, $text_id, $text_bytes, $text ); + } else { + $text_found = false; + } + + $this->assertTextNode( "sha1", $text_sha1 ); + + if ( !$text_found ) { + $this->assertText( $id, $text_id, $text_bytes, $text ); + } + + $this->assertNodeEnd( "revision" ); + $this->skipWhitespace(); + } + + public function assertText( $id, $text_id, $text_bytes, $text ) { + $this->assertNodeStart( "text", false ); + if ( $text_bytes !== false ) { + Assert::assertEquals( $this->xml->getAttribute( "bytes" ), $text_bytes, + "Attribute 'bytes' of revision " . $id ); + } + + if ( $text === false ) { + // Testing for a stub + Assert::assertEquals( $this->xml->getAttribute( "id" ), $text_id, + "Text id of revision " . $id ); + Assert::assertFalse( $this->xml->hasValue, "Revision has text" ); + Assert::assertTrue( $this->xml->read(), "Skipping text start tag" ); + if ( ( $this->xml->nodeType == XMLReader::END_ELEMENT ) + && ( $this->xml->name == "text" ) + ) { + $this->xml->read(); + } + $this->skipWhitespace(); + } else { + // Testing for a real dump + Assert::assertTrue( $this->xml->read(), "Skipping text start tag" ); + Assert::assertEquals( $text, $this->xml->value, "Text of revision " . $id ); + Assert::assertTrue( $this->xml->read(), "Skipping past text" ); + $this->assertNodeEnd( "text" ); + $this->skipWhitespace(); + } + } + + /** + * asserts that the xml reader is at the beginning of a log entry and skips over + * it while analyzing it. + * + * @param int $id Id of the log entry + * @param string $user_name User name of the log entry's performer + * @param int $user_id User id of the log entry 's performer + * @param string|null $comment Comment of the log entry. If null, the comment text is ignored. + * @param string $type Type of the log entry + * @param string $subtype Subtype of the log entry + * @param string $title Title of the log entry's target + * @param array $parameters (optional) unserialized data accompanying the log entry + */ + public function assertLogItem( $id, $user_name, $user_id, $comment, $type, + $subtype, $title, $parameters = [] + ) { + $this->assertNodeStart( "logitem" ); + $this->skipWhitespace(); + + $this->assertTextNode( "id", $id ); + $this->assertTextNode( "timestamp", false ); + + $this->assertNodeStart( "contributor" ); + $this->skipWhitespace(); + $this->assertTextNode( "username", $user_name ); + $this->assertTextNode( "id", $user_id ); + $this->assertNodeEnd( "contributor" ); + $this->skipWhitespace(); + + if ( $comment !== null ) { + $this->assertTextNode( "comment", $comment ); + } + $this->assertTextNode( "type", $type ); + $this->assertTextNode( "action", $subtype ); + $this->assertTextNode( "logtitle", $title ); + + $this->assertNodeStart( "params" ); + $parameters_xml = unserialize( $this->xml->value ); + Assert::assertEquals( $parameters, $parameters_xml ); + Assert::assertTrue( $this->xml->read(), "Skipping past processed text of params" ); + $this->assertNodeEnd( "params" ); + $this->skipWhitespace(); + + $this->assertNodeEnd( "logitem" ); + $this->skipWhitespace(); + } +} diff --git a/tests/phpunit/maintenance/DumpTestCase.php b/tests/phpunit/maintenance/DumpTestCase.php index 4b7a7eb3d4..eebc201ae2 100644 --- a/tests/phpunit/maintenance/DumpTestCase.php +++ b/tests/phpunit/maintenance/DumpTestCase.php @@ -3,11 +3,12 @@ namespace MediaWiki\Tests\Maintenance; use ContentHandler; +use DOMDocument; use ExecutableFinder; use MediaWikiLangTestCase; -use Page; use User; -use XMLReader; +use WikiExporter; +use WikiPage; use MWException; /** @@ -28,13 +29,6 @@ abstract class DumpTestCase extends MediaWikiLangTestCase { */ protected $exceptionFromAddDBData = null; - /** - * Holds the XMLReader used for analyzing an XML dump - * - * @var XMLReader|null - */ - protected $xml = null; - /** @var bool|null Whether the 'gzip' utility is available */ protected static $hasGzip = null; @@ -58,7 +52,7 @@ abstract class DumpTestCase extends MediaWikiLangTestCase { /** * Adds a revision to a page, while returning the resuting revision's id * - * @param Page $page Page to add the revision to + * @param WikiPage $page Page to add the revision to * @param string $text Revisions text * @param string $summary Revisions summary * @param string $model The model ID (defaults to wikitext) @@ -66,7 +60,12 @@ abstract class DumpTestCase extends MediaWikiLangTestCase { * @throws MWException * @return array */ - protected function addRevision( Page $page, $text, $summary, $model = CONTENT_MODEL_WIKITEXT ) { + protected function addRevision( + WikiPage $page, + $text, + $summary, + $model = CONTENT_MODEL_WIKITEXT + ) { $status = $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle(), $model ), $summary @@ -108,6 +107,36 @@ abstract class DumpTestCase extends MediaWikiLangTestCase { ); } + public static function setUpBeforeClass() { + parent::setUpBeforeClass(); + + if ( !function_exists( 'libxml_set_external_entity_loader' ) ) { + return; + } + + // The W3C is intentionally slow about returning schema files, + // see . + // To work around that, we keep our own copies of the relevant schema files. + libxml_set_external_entity_loader( + function ( $public, $system, $context ) { + switch ( $system ) { + // if more schema files are needed, add them here. + case 'http://www.w3.org/2001/xml.xsd': + $file = __DIR__ . '/xml.xsd'; + break; + default: + if ( is_file( $system ) ) { + $file = $system; + } else { + return null; + } + } + + return $file; + } + ); + } + /** * Default set up function. * @@ -125,6 +154,21 @@ abstract class DumpTestCase extends MediaWikiLangTestCase { $this->setMwGlobals( 'wgUser', new User() ); } + /** + * Returns the path to the XML schema file for the given schema version. + * + * @param string|null $schemaVersion + * + * @return string + */ + protected function getXmlSchemaPath( $schemaVersion = null ) { + global $IP; + + $schemaVersion = $schemaVersion ?: '0.10'; + + return "$IP/docs/export-$schemaVersion.xsd"; + } + /** * Checks for test output consisting only of lines containing ETA announcements */ @@ -152,266 +196,62 @@ abstract class DumpTestCase extends MediaWikiLangTestCase { } /** - * Step the current XML reader until node end of given name is found. - * - * @param string $name Name of the closing element to look for - * (e.g.: "mediawiki" when looking for ) + * @param null|string $schemaVersion * - * @return bool True if the end node could be found. false otherwise. + * @return DumpAsserter */ - protected function skipToNodeEnd( $name ) { - while ( $this->xml->read() ) { - if ( $this->xml->nodeType == XMLReader::END_ELEMENT && - $this->xml->name == $name - ) { - return true; - } - } - - return false; + protected function getDumpAsserter( $schemaVersion = null ) { + $schemaVersion = $schemaVersion ?: WikiExporter::schemaVersion(); + return new DumpAsserter( $schemaVersion ); } /** - * Step the current XML reader to the first element start after the node - * end of a given name. - * - * @param string $name Name of the closing element to look for - * (e.g.: "mediawiki" when looking for ) - * - * @return bool True if new element after the closing of $name could be - * found. false otherwise. + * Checks an XML file against an XSD schema. */ - protected function skipPastNodeEnd( $name ) { - $this->assertTrue( $this->skipToNodeEnd( $name ), - "Skipping to end of $name" ); - while ( $this->xml->read() ) { - if ( $this->xml->nodeType == XMLReader::ELEMENT ) { - return true; - } + protected function assertDumpSchema( $fname, $schemaFile ) { + if ( !function_exists( 'libxml_use_internal_errors' ) ) { + // Would be nice to leave a warning somehow. + // We don't want to skip all of the test case that calls this, though. + $this->markAsRisky(); + return; } - - return false; - } - - /** - * Opens an XML file to analyze and optionally skips past siteinfo. - * - * @param string $fname Name of file to analyze - * @param bool $skip_siteinfo (optional) If true, step the xml reader - * to the first element after - */ - protected function assertDumpStart( $fname, $skip_siteinfo = true ) { - $this->xml = new XMLReader(); - $this->assertTrue( $this->xml->open( $fname ), - "Opening temporary file $fname via XMLReader failed" ); - if ( $skip_siteinfo ) { - $this->assertTrue( $this->skipPastNodeEnd( "siteinfo" ), - "Skipping past end of siteinfo" ); + if ( defined( 'HHVM_VERSION' ) ) { + // In HHVM, loading a schema from a file is disabled per default. + // This is controlled by hhvm.libxml.ext_entity_whitelist which + // cannot be read with ini_get(), see + // . + // Would be nice to leave a warning somehow. + // We don't want to skip all of the test case that calls this, though. + $this->markAsRisky(); + return; } - } - /** - * Asserts that the xml reader is at the final closing tag of an xml file and - * closes the reader. - * - * @param string $name (optional) the name of the final tag - * (e.g.: "mediawiki" for ) - */ - protected function assertDumpEnd( $name = "mediawiki" ) { - $this->assertNodeEnd( $name, false ); - if ( $this->xml->read() ) { - $this->skipWhitespace(); - } - $this->assertEquals( $this->xml->nodeType, XMLReader::NONE, - "No proper entity left to parse" ); - $this->xml->close(); - } + $xml = new DOMDocument(); + $this->assertTrue( $xml->load( $fname ), + "Opening temporary file $fname via DOMDocument failed" ); - /** - * Steps the xml reader over white space - */ - protected function skipWhitespace() { - $cont = true; - while ( $cont && ( ( $this->xml->nodeType == XMLReader::WHITESPACE ) - || ( $this->xml->nodeType == XMLReader::SIGNIFICANT_WHITESPACE ) ) ) { - $cont = $this->xml->read(); - } - } + // Don't throw + $oldLibXmlInternalErrors = libxml_use_internal_errors( true ); - /** - * Asserts that the xml reader is at an element of given name, and optionally - * skips past it. - * - * @param string $name The name of the element to check for - * (e.g.: "mediawiki" for ) - * @param bool $skip (optional) if true, skip past the found element - */ - protected function assertNodeStart( $name, $skip = true ) { - $this->assertEquals( $name, $this->xml->name, "Node name" ); - $this->assertEquals( XMLReader::ELEMENT, $this->xml->nodeType, "Node type" ); - if ( $skip ) { - $this->assertTrue( $this->xml->read(), "Skipping past start tag" ); - } - } - - /** - * Asserts that the xml reader is at an closing element of given name, and optionally - * skips past it. - * - * @param string $name The name of the closing element to check for - * (e.g.: "mediawiki" for ) - * @param bool $skip (optional) if true, skip past the found element - */ - protected function assertNodeEnd( $name, $skip = true ) { - $this->assertEquals( $name, $this->xml->name, "Node name" ); - $this->assertEquals( XMLReader::END_ELEMENT, $this->xml->nodeType, "Node type" ); - if ( $skip ) { - $this->assertTrue( $this->xml->read(), "Skipping past end tag" ); - } - } - - /** - * Asserts that the xml reader is at an element of given tag that contains a given text, - * and skips over the element. - * - * @param string $name The name of the element to check for - * (e.g.: "mediawiki" for ...) - * @param string|bool $text If string, check if it equals the elements text. - * If false, ignore the element's text - * @param bool $skip_ws (optional) if true, skip past white spaces that trail the - * closing element. - */ - protected function assertTextNode( $name, $text, $skip_ws = true ) { - $this->assertNodeStart( $name ); - - if ( $text !== false ) { - $this->assertEquals( $text, $this->xml->value, "Text of node " . $name ); - } - $this->assertTrue( $this->xml->read(), "Skipping past processed text of " . $name ); - $this->assertNodeEnd( $name ); - - if ( $skip_ws ) { - $this->skipWhitespace(); - } - } - - /** - * Asserts that the xml reader is at the start of a page element and skips over the first - * tags, after checking them. - * - * Besides the opening page element, this function also checks for and skips over the - * title, ns, and id tags. Hence after this function, the xml reader is at the first - * revision of the current page. - * - * @param int $id Id of the page to assert - * @param int $ns Number of namespage to assert - * @param string $name Title of the current page - */ - protected function assertPageStart( $id, $ns, $name ) { - $this->assertNodeStart( "page" ); - $this->skipWhitespace(); - - $this->assertTextNode( "title", $name ); - $this->assertTextNode( "ns", $ns ); - $this->assertTextNode( "id", $id ); - } + // NOTE: if this reports "Invalid Schema", the schema may be referencing an external + // entity (typically, another schema) that needs to be mapped in the + // libxml_set_external_entity_loader callback defined in setUpBeforeClass() above! + // Or $schemaFile doesn't point to a schema file, or the schema is indeed just broken. + if ( !$xml->schemaValidate( $schemaFile ) ) { + $errorText = ''; - /** - * Asserts that the xml reader is at the page's closing element and skips to the next - * element. - */ - protected function assertPageEnd() { - $this->assertNodeEnd( "page" ); - $this->skipWhitespace(); - } - - /** - * Asserts that the xml reader is at a revision and checks its representation before - * skipping over it. - * - * @param int $id Id of the revision - * @param string $summary Summary of the revision - * @param int $text_id Id of the revision's text - * @param int $text_bytes Number of bytes in the revision's text - * @param string $text_sha1 The base36 SHA-1 of the revision's text - * @param string|bool $text (optional) The revision's string, or false to check for a - * revision stub - * @param int|bool $parentid (optional) id of the parent revision - * @param string $model The expected content model id (default: CONTENT_MODEL_WIKITEXT) - * @param string $format The expected format model id (default: CONTENT_FORMAT_WIKITEXT) - */ - protected function assertRevision( $id, $summary, $text_id, $text_bytes, - $text_sha1, $text = false, $parentid = false, - $model = CONTENT_MODEL_WIKITEXT, $format = CONTENT_FORMAT_WIKITEXT - ) { - $this->assertNodeStart( "revision" ); - $this->skipWhitespace(); - - $this->assertTextNode( "id", $id ); - if ( $parentid !== false ) { - $this->assertTextNode( "parentid", $parentid ); - } - $this->assertTextNode( "timestamp", false ); - - $this->assertNodeStart( "contributor" ); - $this->skipWhitespace(); - $this->assertTextNode( "ip", false ); - $this->assertNodeEnd( "contributor" ); - $this->skipWhitespace(); - - $this->assertTextNode( "comment", $summary ); - $this->skipWhitespace(); - - $this->assertTextNode( "model", $model ); - $this->skipWhitespace(); - - $this->assertTextNode( "format", $format ); - $this->skipWhitespace(); - - if ( $this->xml->name == "text" ) { - // note: tag may occur here or at the very end. - $text_found = true; - $this->assertText( $id, $text_id, $text_bytes, $text ); - } else { - $text_found = false; - } + foreach ( libxml_get_errors() as $error ) { + $errorText .= "\nline {$error->line}: {$error->message}"; + } - $this->assertTextNode( "sha1", $text_sha1 ); + libxml_clear_errors(); - if ( !$text_found ) { - $this->assertText( $id, $text_id, $text_bytes, $text ); + $this->fail( + "Failed asserting that $fname conforms to the schema in $schemaFile:\n$errorText" + ); } - $this->assertNodeEnd( "revision" ); - $this->skipWhitespace(); + libxml_use_internal_errors( $oldLibXmlInternalErrors ); } - protected function assertText( $id, $text_id, $text_bytes, $text ) { - $this->assertNodeStart( "text", false ); - if ( $text_bytes !== false ) { - $this->assertEquals( $this->xml->getAttribute( "bytes" ), $text_bytes, - "Attribute 'bytes' of revision " . $id ); - } - - if ( $text === false ) { - // Testing for a stub - $this->assertEquals( $this->xml->getAttribute( "id" ), $text_id, - "Text id of revision " . $id ); - $this->assertFalse( $this->xml->hasValue, "Revision has text" ); - $this->assertTrue( $this->xml->read(), "Skipping text start tag" ); - if ( ( $this->xml->nodeType == XMLReader::END_ELEMENT ) - && ( $this->xml->name == "text" ) - ) { - $this->xml->read(); - } - $this->skipWhitespace(); - } else { - // Testing for a real dump - $this->assertTrue( $this->xml->read(), "Skipping text start tag" ); - $this->assertEquals( $text, $this->xml->value, "Text of revision " . $id ); - $this->assertTrue( $this->xml->read(), "Skipping past text" ); - $this->assertNodeEnd( "text" ); - $this->skipWhitespace(); - } - } } diff --git a/tests/phpunit/maintenance/backupTextPassTest.php b/tests/phpunit/maintenance/backupTextPassTest.php index 38a513e279..0d4bc56cb0 100644 --- a/tests/phpunit/maintenance/backupTextPassTest.php +++ b/tests/phpunit/maintenance/backupTextPassTest.php @@ -130,45 +130,46 @@ class TextPassDumperDatabaseTest extends DumpTestCase { $dumper->dump( WikiExporter::FULL, WikiExporter::TEXT ); // Checking for correctness of the dumped data - $this->assertDumpStart( $nameFull ); + $asserter = $this->getDumpAsserter(); + $asserter->assertDumpStart( $nameFull ); // Page 1 - $this->assertPageStart( $this->pageId1, NS_MAIN, "BackupDumperTestP1" ); - $this->assertRevision( $this->revId1_1, "BackupDumperTestP1Summary1", + $asserter->assertPageStart( $this->pageId1, NS_MAIN, "BackupDumperTestP1" ); + $asserter->assertRevision( $this->revId1_1, "BackupDumperTestP1Summary1", $this->textId1_1, false, "0bolhl6ol7i6x0e7yq91gxgaan39j87", "BackupDumperTestP1Text1" ); - $this->assertPageEnd(); + $asserter->assertPageEnd(); // Page 2 - $this->assertPageStart( $this->pageId2, NS_MAIN, "BackupDumperTestP2" ); - $this->assertRevision( $this->revId2_1, "BackupDumperTestP2Summary1", + $asserter->assertPageStart( $this->pageId2, NS_MAIN, "BackupDumperTestP2" ); + $asserter->assertRevision( $this->revId2_1, "BackupDumperTestP2Summary1", $this->textId2_1, false, "jprywrymfhysqllua29tj3sc7z39dl2", "BackupDumperTestP2Text1" ); - $this->assertRevision( $this->revId2_2, "BackupDumperTestP2Summary2", + $asserter->assertRevision( $this->revId2_2, "BackupDumperTestP2Summary2", $this->textId2_2, false, "b7vj5ks32po5m1z1t1br4o7scdwwy95", "BackupDumperTestP2Text2", $this->revId2_1 ); - $this->assertRevision( $this->revId2_3, "BackupDumperTestP2Summary3", + $asserter->assertRevision( $this->revId2_3, "BackupDumperTestP2Summary3", $this->textId2_3, false, "jfunqmh1ssfb8rs43r19w98k28gg56r", "BackupDumperTestP2Text3", $this->revId2_2 ); - $this->assertRevision( $this->revId2_4, "BackupDumperTestP2Summary4 extra", + $asserter->assertRevision( $this->revId2_4, "BackupDumperTestP2Summary4 extra", $this->textId2_4, false, "6o1ciaxa6pybnqprmungwofc4lv00wv", "BackupDumperTestP2Text4 some additional Text", $this->revId2_3 ); - $this->assertPageEnd(); + $asserter->assertPageEnd(); // Page 3 // -> Page is marked deleted. Hence not visible // Page 4 - $this->assertPageStart( $this->pageId4, NS_TALK, "Talk:BackupDumperTestP1" ); - $this->assertRevision( $this->revId4_1, "Talk BackupDumperTestP1 Summary1", + $asserter->assertPageStart( $this->pageId4, NS_TALK, "Talk:BackupDumperTestP1" ); + $asserter->assertRevision( $this->revId4_1, "Talk BackupDumperTestP1 Summary1", $this->textId4_1, false, "nktofwzd0tl192k3zfepmlzxoax1lpe", "TALK ABOUT BACKUPDUMPERTESTP1 TEXT1", false, "BackupTextPassTestModel", "text/plain" ); - $this->assertPageEnd(); + $asserter->assertPageEnd(); - $this->assertDumpEnd(); + $asserter->assertDumpEnd(); } function testPrefetchPlain() { @@ -202,49 +203,50 @@ class TextPassDumperDatabaseTest extends DumpTestCase { $dumper->dump( WikiExporter::FULL, WikiExporter::TEXT ); // Checking for correctness of the dumped data - $this->assertDumpStart( $nameFull ); + $asserter = $this->getDumpAsserter(); + $asserter->assertDumpStart( $nameFull ); // Page 1 - $this->assertPageStart( $this->pageId1, NS_MAIN, "BackupDumperTestP1" ); + $asserter->assertPageStart( $this->pageId1, NS_MAIN, "BackupDumperTestP1" ); // Prefetch kicks in. This is still the SHA-1 of the original text, // But the actual text (with different SHA-1) comes from prefetch. - $this->assertRevision( $this->revId1_1, "BackupDumperTestP1Summary1", + $asserter->assertRevision( $this->revId1_1, "BackupDumperTestP1Summary1", $this->textId1_1, false, "0bolhl6ol7i6x0e7yq91gxgaan39j87", "Prefetch_________1Text1" ); - $this->assertPageEnd(); + $asserter->assertPageEnd(); // Page 2 - $this->assertPageStart( $this->pageId2, NS_MAIN, "BackupDumperTestP2" ); - $this->assertRevision( $this->revId2_1, "BackupDumperTestP2Summary1", + $asserter->assertPageStart( $this->pageId2, NS_MAIN, "BackupDumperTestP2" ); + $asserter->assertRevision( $this->revId2_1, "BackupDumperTestP2Summary1", $this->textId2_1, false, "jprywrymfhysqllua29tj3sc7z39dl2", "BackupDumperTestP2Text1" ); - $this->assertRevision( $this->revId2_2, "BackupDumperTestP2Summary2", + $asserter->assertRevision( $this->revId2_2, "BackupDumperTestP2Summary2", $this->textId2_2, false, "b7vj5ks32po5m1z1t1br4o7scdwwy95", "BackupDumperTestP2Text2", $this->revId2_1 ); // Prefetch kicks in. This is still the SHA-1 of the original text, // But the actual text (with different SHA-1) comes from prefetch. - $this->assertRevision( $this->revId2_3, "BackupDumperTestP2Summary3", + $asserter->assertRevision( $this->revId2_3, "BackupDumperTestP2Summary3", $this->textId2_3, false, "jfunqmh1ssfb8rs43r19w98k28gg56r", "Prefetch_________2Text3", $this->revId2_2 ); - $this->assertRevision( $this->revId2_4, "BackupDumperTestP2Summary4 extra", + $asserter->assertRevision( $this->revId2_4, "BackupDumperTestP2Summary4 extra", $this->textId2_4, false, "6o1ciaxa6pybnqprmungwofc4lv00wv", "BackupDumperTestP2Text4 some additional Text", $this->revId2_3 ); - $this->assertPageEnd(); + $asserter->assertPageEnd(); // Page 3 // -> Page is marked deleted. Hence not visible // Page 4 - $this->assertPageStart( $this->pageId4, NS_TALK, "Talk:BackupDumperTestP1" ); - $this->assertRevision( $this->revId4_1, "Talk BackupDumperTestP1 Summary1", + $asserter->assertPageStart( $this->pageId4, NS_TALK, "Talk:BackupDumperTestP1" ); + $asserter->assertRevision( $this->revId4_1, "Talk BackupDumperTestP1 Summary1", $this->textId4_1, false, "nktofwzd0tl192k3zfepmlzxoax1lpe", "TALK ABOUT BACKUPDUMPERTESTP1 TEXT1", false, "BackupTextPassTestModel", "text/plain" ); - $this->assertPageEnd(); + $asserter->assertPageEnd(); - $this->assertDumpEnd(); + $asserter->assertDumpEnd(); } /** @@ -329,6 +331,8 @@ class TextPassDumperDatabaseTest extends DumpTestCase { $lookingForPage = 1; $checkpointFiles = 0; + $asserter = $this->getDumpAsserter(); + // Each run of the following loop body tries to handle exactly 1 /page/ (not // iteration of stub content). $i is only increased after having treated page 4. for ( $i = 0; $i < $iterations; ) { @@ -346,7 +350,7 @@ class TextPassDumperDatabaseTest extends DumpTestCase { if ( $checkpointFormat == "gzip" ) { $this->gunzip( $nameOutputDir . "/" . $fname ); } - $this->assertDumpStart( $nameOutputDir . "/" . $fname ); + $asserter->assertDumpStart( $nameOutputDir . "/" . $fname ); $fileOpened = true; $checkpointFiles++; } @@ -355,51 +359,90 @@ class TextPassDumperDatabaseTest extends DumpTestCase { switch ( $lookingForPage ) { case 1: // Page 1 - $this->assertPageStart( $this->pageId1 + $i * self::$numOfPages, NS_MAIN, - "BackupDumperTestP1" ); - $this->assertRevision( $this->revId1_1 + $i * self::$numOfRevs, "BackupDumperTestP1Summary1", - $this->textId1_1, false, "0bolhl6ol7i6x0e7yq91gxgaan39j87", - "BackupDumperTestP1Text1" ); - $this->assertPageEnd(); + $asserter->assertPageStart( + $this->pageId1 + $i * self::$numOfPages, + NS_MAIN, + "BackupDumperTestP1" + ); + $asserter->assertRevision( + $this->revId1_1 + $i * self::$numOfRevs, + "BackupDumperTestP1Summary1", + $this->textId1_1, + false, + "0bolhl6ol7i6x0e7yq91gxgaan39j87", + "BackupDumperTestP1Text1" + ); + $asserter->assertPageEnd(); $lookingForPage = 2; break; case 2: // Page 2 - $this->assertPageStart( $this->pageId2 + $i * self::$numOfPages, NS_MAIN, - "BackupDumperTestP2" ); - $this->assertRevision( $this->revId2_1 + $i * self::$numOfRevs, "BackupDumperTestP2Summary1", - $this->textId2_1, false, "jprywrymfhysqllua29tj3sc7z39dl2", - "BackupDumperTestP2Text1" ); - $this->assertRevision( $this->revId2_2 + $i * self::$numOfRevs, "BackupDumperTestP2Summary2", - $this->textId2_2, false, "b7vj5ks32po5m1z1t1br4o7scdwwy95", - "BackupDumperTestP2Text2", $this->revId2_1 + $i * self::$numOfRevs ); - $this->assertRevision( $this->revId2_3 + $i * self::$numOfRevs, "BackupDumperTestP2Summary3", - $this->textId2_3, false, "jfunqmh1ssfb8rs43r19w98k28gg56r", - "BackupDumperTestP2Text3", $this->revId2_2 + $i * self::$numOfRevs ); - $this->assertRevision( $this->revId2_4 + $i * self::$numOfRevs, + $asserter->assertPageStart( + $this->pageId2 + $i * self::$numOfPages, + NS_MAIN, + "BackupDumperTestP2" + ); + $asserter->assertRevision( + $this->revId2_1 + $i * self::$numOfRevs, + "BackupDumperTestP2Summary1", + $this->textId2_1, + false, + "jprywrymfhysqllua29tj3sc7z39dl2", + "BackupDumperTestP2Text1" + ); + $asserter->assertRevision( + $this->revId2_2 + $i * self::$numOfRevs, + "BackupDumperTestP2Summary2", + $this->textId2_2, + false, + "b7vj5ks32po5m1z1t1br4o7scdwwy95", + "BackupDumperTestP2Text2", + $this->revId2_1 + $i * self::$numOfRevs + ); + $asserter->assertRevision( + $this->revId2_3 + $i * self::$numOfRevs, + "BackupDumperTestP2Summary3", + $this->textId2_3, + false, + "jfunqmh1ssfb8rs43r19w98k28gg56r", + "BackupDumperTestP2Text3", + $this->revId2_2 + $i * self::$numOfRevs + ); + $asserter->assertRevision( + $this->revId2_4 + $i * self::$numOfRevs, "BackupDumperTestP2Summary4 extra", - $this->textId2_4, false, "6o1ciaxa6pybnqprmungwofc4lv00wv", + $this->textId2_4, + false, + "6o1ciaxa6pybnqprmungwofc4lv00wv", "BackupDumperTestP2Text4 some additional Text", - $this->revId2_3 + $i * self::$numOfRevs ); - $this->assertPageEnd(); + $this->revId2_3 + $i * self::$numOfRevs + ); + $asserter->assertPageEnd(); $lookingForPage = 4; break; case 4: // Page 4 - $this->assertPageStart( $this->pageId4 + $i * self::$numOfPages, NS_TALK, - "Talk:BackupDumperTestP1" ); - $this->assertRevision( $this->revId4_1 + $i * self::$numOfRevs, + $asserter->assertPageStart( + $this->pageId4 + $i * self::$numOfPages, + NS_TALK, + "Talk:BackupDumperTestP1" + ); + $asserter->assertRevision( + $this->revId4_1 + $i * self::$numOfRevs, "Talk BackupDumperTestP1 Summary1", - $this->textId4_1, false, "nktofwzd0tl192k3zfepmlzxoax1lpe", + $this->textId4_1, + false, + "nktofwzd0tl192k3zfepmlzxoax1lpe", "TALK ABOUT BACKUPDUMPERTESTP1 TEXT1", false, "BackupTextPassTestModel", - "text/plain" ); - $this->assertPageEnd(); + "text/plain" + ); + $asserter->assertPageEnd(); $lookingForPage = 1; @@ -415,7 +458,7 @@ class TextPassDumperDatabaseTest extends DumpTestCase { if ( $this->xml->nodeType == XMLReader::END_ELEMENT && $this->xml->name == "mediawiki" ) { - $this->assertDumpEnd(); + $asserter->assertDumpEnd(); $fileOpened = false; } } diff --git a/tests/phpunit/maintenance/backup_LogTest.php b/tests/phpunit/maintenance/backup_LogTest.php index 9357451481..811f1ee501 100644 --- a/tests/phpunit/maintenance/backup_LogTest.php +++ b/tests/phpunit/maintenance/backup_LogTest.php @@ -2,6 +2,7 @@ namespace MediaWiki\Tests\Maintenance; +use Exception; use MediaWiki\MediaWikiServices; use DumpBackup; use ManualLogEntry; @@ -98,53 +99,6 @@ class BackupDumperLoggerTest extends DumpTestCase { } } - /** - * asserts that the xml reader is at the beginning of a log entry and skips over - * it while analyzing it. - * - * @param int $id Id of the log entry - * @param string $user_name User name of the log entry's performer - * @param int $user_id User id of the log entry 's performer - * @param string|null $comment Comment of the log entry. If null, the comment text is ignored. - * @param string $type Type of the log entry - * @param string $subtype Subtype of the log entry - * @param string $title Title of the log entry's target - * @param array $parameters (optional) unserialized data accompanying the log entry - */ - private function assertLogItem( $id, $user_name, $user_id, $comment, $type, - $subtype, $title, $parameters = [] - ) { - $this->assertNodeStart( "logitem" ); - $this->skipWhitespace(); - - $this->assertTextNode( "id", $id ); - $this->assertTextNode( "timestamp", false ); - - $this->assertNodeStart( "contributor" ); - $this->skipWhitespace(); - $this->assertTextNode( "username", $user_name ); - $this->assertTextNode( "id", $user_id ); - $this->assertNodeEnd( "contributor" ); - $this->skipWhitespace(); - - if ( $comment !== null ) { - $this->assertTextNode( "comment", $comment ); - } - $this->assertTextNode( "type", $type ); - $this->assertTextNode( "action", $subtype ); - $this->assertTextNode( "logtitle", $title ); - - $this->assertNodeStart( "params" ); - $parameters_xml = unserialize( $this->xml->value ); - $this->assertEquals( $parameters, $parameters_xml ); - $this->assertTrue( $this->xml->read(), "Skipping past processed text of params" ); - $this->assertNodeEnd( "params" ); - $this->skipWhitespace(); - - $this->assertNodeEnd( "logitem" ); - $this->skipWhitespace(); - } - function testPlain() { // Preparing the dump $fname = $this->getNewTempFile(); @@ -159,9 +113,12 @@ class BackupDumperLoggerTest extends DumpTestCase { $dumper->dump( WikiExporter::LOGS, WikiExporter::TEXT ); // Analyzing the dumped data - $this->assertDumpStart( $fname ); + $this->assertDumpSchema( $fname, $this->getXmlSchemaPath() ); + + $asserter = $this->getDumpAsserter(); + $asserter->assertDumpStart( $fname ); - $this->assertLogItem( $this->logId1, "BackupDumperLogUserA", + $asserter->assertLogItem( $this->logId1, "BackupDumperLogUserA", $this->userId1, null, "type", "subtype", "PageA" ); $contLang = MediaWikiServices::getInstance()->getContentLanguage(); @@ -169,15 +126,15 @@ class BackupDumperLoggerTest extends DumpTestCase { $namespace = $contLang->getNsText( NS_TALK ); $this->assertInternalType( 'string', $namespace ); $this->assertGreaterThan( 0, strlen( $namespace ) ); - $this->assertLogItem( $this->logId2, "BackupDumperLogUserB", + $asserter->assertLogItem( $this->logId2, "BackupDumperLogUserB", $this->userId2, "SomeComment", "supress", "delete", $namespace . ":PageB" ); - $this->assertLogItem( $this->logId3, "BackupDumperLogUserB", + $asserter->assertLogItem( $this->logId3, "BackupDumperLogUserB", $this->userId2, "SomeOtherComment", "move", "delete", "PageA", [ 'key1' => 1, 3 => 'value3' ] ); - $this->assertDumpEnd(); + $asserter->assertDumpEnd(); } function testXmlDumpsBackupUseCaseLogging() { @@ -211,9 +168,12 @@ class BackupDumperLoggerTest extends DumpTestCase { // Analyzing the dumped data $this->gunzip( $fname ); - $this->assertDumpStart( $fname ); + $this->assertDumpSchema( $fname, $this->getXmlSchemaPath() ); + + $asserter = $this->getDumpAsserter(); + $asserter->assertDumpStart( $fname ); - $this->assertLogItem( $this->logId1, "BackupDumperLogUserA", + $asserter->assertLogItem( $this->logId1, "BackupDumperLogUserA", $this->userId1, null, "type", "subtype", "PageA" ); $contLang = MediaWikiServices::getInstance()->getContentLanguage(); @@ -221,15 +181,15 @@ class BackupDumperLoggerTest extends DumpTestCase { $namespace = $contLang->getNsText( NS_TALK ); $this->assertInternalType( 'string', $namespace ); $this->assertGreaterThan( 0, strlen( $namespace ) ); - $this->assertLogItem( $this->logId2, "BackupDumperLogUserB", + $asserter->assertLogItem( $this->logId2, "BackupDumperLogUserB", $this->userId2, "SomeComment", "supress", "delete", $namespace . ":PageB" ); - $this->assertLogItem( $this->logId3, "BackupDumperLogUserB", + $asserter->assertLogItem( $this->logId3, "BackupDumperLogUserB", $this->userId2, "SomeOtherComment", "move", "delete", "PageA", [ 'key1' => 1, 3 => 'value3' ] ); - $this->assertDumpEnd(); + $asserter->assertDumpEnd(); // Currently, no reporting is implemented. Alert via failure, once // this changes. diff --git a/tests/phpunit/maintenance/backup_PageTest.php b/tests/phpunit/maintenance/backup_PageTest.php index c37be4e3ab..afe8c4b11c 100644 --- a/tests/phpunit/maintenance/backup_PageTest.php +++ b/tests/phpunit/maintenance/backup_PageTest.php @@ -3,6 +3,7 @@ namespace MediaWiki\Tests\Maintenance; use DumpBackup; +use Exception; use MediaWiki\MediaWikiServices; use MediaWikiTestCase; use MWException; @@ -169,12 +170,19 @@ class BackupDumperPageTest extends DumpTestCase { return $dumper; } - function testFullTextPlain() { + public function schemaVersionProvider() { + yield [ '0.10' ]; + } + + /** + * @dataProvider schemaVersionProvider + */ + function testFullTextPlain( $schemaVersion ) { // Preparing the dump $fname = $this->getNewTempFile(); $dumper = $this->newDumpBackup( - [ '--full', '--quiet', '--output', 'file:' . $fname ], + [ '--full', '--quiet', '--output', 'file:' . $fname, '--schema-version', $schemaVersion ], $this->pageId1, $this->pageId4 + 1 ); @@ -183,54 +191,114 @@ class BackupDumperPageTest extends DumpTestCase { $dumper->execute(); // Checking the dumped data - $this->assertDumpStart( $fname ); + $this->assertDumpSchema( $fname, $this->getXmlSchemaPath( $schemaVersion ) ); + $asserter = $this->getDumpAsserter( $schemaVersion ); + + $asserter->assertDumpStart( $fname ); // Page 1 - $this->assertPageStart( $this->pageId1, $this->namespace, $this->pageTitle1->getPrefixedText() ); - $this->assertRevision( $this->revId1_1, "BackupDumperTestP1Summary1", - $this->textId1_1, 23, "0bolhl6ol7i6x0e7yq91gxgaan39j87", - "BackupDumperTestP1Text1" ); - $this->assertPageEnd(); + $asserter->assertPageStart( + $this->pageId1, + $this->namespace, + $this->pageTitle1->getPrefixedText() + ); + $asserter->assertRevision( + $this->revId1_1, + "BackupDumperTestP1Summary1", + $this->textId1_1, + 23, + "0bolhl6ol7i6x0e7yq91gxgaan39j87", + "BackupDumperTestP1Text1" + ); + $asserter->assertPageEnd(); // Page 2 - $this->assertPageStart( $this->pageId2, $this->namespace, $this->pageTitle2->getPrefixedText() ); - $this->assertRevision( $this->revId2_1, "BackupDumperTestP2Summary1", - $this->textId2_1, 23, "jprywrymfhysqllua29tj3sc7z39dl2", - "BackupDumperTestP2Text1" ); - $this->assertRevision( $this->revId2_2, "BackupDumperTestP2Summary2", - $this->textId2_2, 23, "b7vj5ks32po5m1z1t1br4o7scdwwy95", - "BackupDumperTestP2Text2", $this->revId2_1 ); - $this->assertRevision( $this->revId2_3, "BackupDumperTestP2Summary3", - $this->textId2_3, 23, "jfunqmh1ssfb8rs43r19w98k28gg56r", - "BackupDumperTestP2Text3", $this->revId2_2 ); - $this->assertRevision( $this->revId2_4, "BackupDumperTestP2Summary4 extra", - $this->textId2_4, 44, "6o1ciaxa6pybnqprmungwofc4lv00wv", - "BackupDumperTestP2Text4 some additional Text", $this->revId2_3 ); - $this->assertPageEnd(); + $asserter->assertPageStart( + $this->pageId2, + $this->namespace, + $this->pageTitle2->getPrefixedText() + ); + $asserter->assertRevision( + $this->revId2_1, + "BackupDumperTestP2Summary1", + $this->textId2_1, + 23, + "jprywrymfhysqllua29tj3sc7z39dl2", + "BackupDumperTestP2Text1" + ); + $asserter->assertRevision( + $this->revId2_2, + "BackupDumperTestP2Summary2", + $this->textId2_2, + 23, + "b7vj5ks32po5m1z1t1br4o7scdwwy95", + "BackupDumperTestP2Text2", + $this->revId2_1 + ); + $asserter->assertRevision( + $this->revId2_3, + "BackupDumperTestP2Summary3", + $this->textId2_3, + 23, + "jfunqmh1ssfb8rs43r19w98k28gg56r", + "BackupDumperTestP2Text3", + $this->revId2_2 + ); + $asserter->assertRevision( + $this->revId2_4, + "BackupDumperTestP2Summary4 extra", + $this->textId2_4, + 44, + "6o1ciaxa6pybnqprmungwofc4lv00wv", + "BackupDumperTestP2Text4 some additional Text", + $this->revId2_3 + ); + $asserter->assertPageEnd(); // Page 3 // -> Page is marked deleted. Hence not visible // Page 4 - $this->assertPageStart( + $asserter->assertPageStart( $this->pageId4, $this->talk_namespace, $this->pageTitle4->getPrefixedText() ); - $this->assertRevision( $this->revId4_1, "Talk BackupDumperTestP1 Summary1", - $this->textId4_1, 35, "nktofwzd0tl192k3zfepmlzxoax1lpe", - "Talk about BackupDumperTestP1 Text1" ); - $this->assertPageEnd(); + $asserter->assertRevision( + $this->revId4_1, + "Talk BackupDumperTestP1 Summary1", + $this->textId4_1, + 35, + "nktofwzd0tl192k3zfepmlzxoax1lpe", + "Talk about BackupDumperTestP1 Text1", + false, + CONTENT_MODEL_WIKITEXT, + CONTENT_FORMAT_WIKITEXT, + $schemaVersion + ); + $asserter->assertPageEnd(); - $this->assertDumpEnd(); + $asserter->assertDumpEnd(); + + // FIXME: add multi-slot test case! } - function testFullStubPlain() { + /** + * @dataProvider schemaVersionProvider + */ + function testFullStubPlain( $schemaVersion ) { // Preparing the dump $fname = $this->getNewTempFile(); $dumper = $this->newDumpBackup( - [ '--full', '--quiet', '--output', 'file:' . $fname, '--stub' ], + [ + '--full', + '--quiet', + '--output', + 'file:' . $fname, + '--stub', + '--schema-version', $schemaVersion, + ], $this->pageId1, $this->pageId4 + 1 ); @@ -239,48 +307,98 @@ class BackupDumperPageTest extends DumpTestCase { $dumper->execute(); // Checking the dumped data - $this->assertDumpStart( $fname ); + $this->assertDumpSchema( $fname, $this->getXmlSchemaPath( $schemaVersion ) ); + $asserter = $this->getDumpAsserter( $schemaVersion ); + + $asserter->assertDumpStart( $fname ); // Page 1 - $this->assertPageStart( $this->pageId1, $this->namespace, $this->pageTitle1->getPrefixedText() ); - $this->assertRevision( $this->revId1_1, "BackupDumperTestP1Summary1", - $this->textId1_1, 23, "0bolhl6ol7i6x0e7yq91gxgaan39j87" ); - $this->assertPageEnd(); + $asserter->assertPageStart( + $this->pageId1, + $this->namespace, + $this->pageTitle1->getPrefixedText() + ); + $asserter->assertRevision( + $this->revId1_1, + "BackupDumperTestP1Summary1", + $this->textId1_1, + 23, + "0bolhl6ol7i6x0e7yq91gxgaan39j87" + ); + $asserter->assertPageEnd(); // Page 2 - $this->assertPageStart( $this->pageId2, $this->namespace, $this->pageTitle2->getPrefixedText() ); - $this->assertRevision( $this->revId2_1, "BackupDumperTestP2Summary1", - $this->textId2_1, 23, "jprywrymfhysqllua29tj3sc7z39dl2" ); - $this->assertRevision( $this->revId2_2, "BackupDumperTestP2Summary2", - $this->textId2_2, 23, "b7vj5ks32po5m1z1t1br4o7scdwwy95", false, $this->revId2_1 ); - $this->assertRevision( $this->revId2_3, "BackupDumperTestP2Summary3", - $this->textId2_3, 23, "jfunqmh1ssfb8rs43r19w98k28gg56r", false, $this->revId2_2 ); - $this->assertRevision( $this->revId2_4, "BackupDumperTestP2Summary4 extra", - $this->textId2_4, 44, "6o1ciaxa6pybnqprmungwofc4lv00wv", false, $this->revId2_3 ); - $this->assertPageEnd(); + $asserter->assertPageStart( + $this->pageId2, + $this->namespace, + $this->pageTitle2->getPrefixedText() + ); + $asserter->assertRevision( + $this->revId2_1, + "BackupDumperTestP2Summary1", + $this->textId2_1, + 23, + "jprywrymfhysqllua29tj3sc7z39dl2" + ); + $asserter->assertRevision( + $this->revId2_2, + "BackupDumperTestP2Summary2", + $this->textId2_2, + 23, + "b7vj5ks32po5m1z1t1br4o7scdwwy95", + false, + $this->revId2_1 + ); + $asserter->assertRevision( + $this->revId2_3, + "BackupDumperTestP2Summary3", + $this->textId2_3, + 23, + "jfunqmh1ssfb8rs43r19w98k28gg56r", + false, + $this->revId2_2 + ); + $asserter->assertRevision( + $this->revId2_4, + "BackupDumperTestP2Summary4 extra", + $this->textId2_4, + 44, + "6o1ciaxa6pybnqprmungwofc4lv00wv", + false, + $this->revId2_3 + ); + $asserter->assertPageEnd(); // Page 3 // -> Page is marked deleted. Hence not visible // Page 4 - $this->assertPageStart( + $asserter->assertPageStart( $this->pageId4, $this->talk_namespace, $this->pageTitle4->getPrefixedText() ); - $this->assertRevision( $this->revId4_1, "Talk BackupDumperTestP1 Summary1", - $this->textId4_1, 35, "nktofwzd0tl192k3zfepmlzxoax1lpe" ); - $this->assertPageEnd(); + $asserter->assertRevision( + $this->revId4_1, + "Talk BackupDumperTestP1 Summary1", + $this->textId4_1, + 35, + "nktofwzd0tl192k3zfepmlzxoax1lpe" + ); + $asserter->assertPageEnd(); - $this->assertDumpEnd(); + $asserter->assertDumpEnd(); } - function testCurrentStubPlain() { + /** + * @dataProvider schemaVersionProvider + */ + function testCurrentStubPlain( $schemaVersion ) { // Preparing the dump $fname = $this->getNewTempFile(); $dumper = $this->newDumpBackup( - [ '--output', 'file:' . $fname ], + [ '--output', 'file:' . $fname, '--schema-version', $schemaVersion ], $this->pageId1, $this->pageId4 + 1 ); @@ -289,34 +407,62 @@ class BackupDumperPageTest extends DumpTestCase { $dumper->dump( WikiExporter::CURRENT, WikiExporter::STUB ); // Checking the dumped data - $this->assertDumpStart( $fname ); + $this->assertDumpSchema( $fname, $this->getXmlSchemaPath( $schemaVersion ) ); + + $asserter = $this->getDumpAsserter( $schemaVersion ); + $asserter->assertDumpStart( $fname ); // Page 1 - $this->assertPageStart( $this->pageId1, $this->namespace, $this->pageTitle1->getPrefixedText() ); - $this->assertRevision( $this->revId1_1, "BackupDumperTestP1Summary1", - $this->textId1_1, 23, "0bolhl6ol7i6x0e7yq91gxgaan39j87" ); - $this->assertPageEnd(); + $asserter->assertPageStart( + $this->pageId1, + $this->namespace, + $this->pageTitle1->getPrefixedText() + ); + $asserter->assertRevision( + $this->revId1_1, + "BackupDumperTestP1Summary1", + $this->textId1_1, + 23, + "0bolhl6ol7i6x0e7yq91gxgaan39j87" + ); + $asserter->assertPageEnd(); // Page 2 - $this->assertPageStart( $this->pageId2, $this->namespace, $this->pageTitle2->getPrefixedText() ); - $this->assertRevision( $this->revId2_4, "BackupDumperTestP2Summary4 extra", - $this->textId2_4, 44, "6o1ciaxa6pybnqprmungwofc4lv00wv", false, $this->revId2_3 ); - $this->assertPageEnd(); + $asserter->assertPageStart( + $this->pageId2, + $this->namespace, + $this->pageTitle2->getPrefixedText() + ); + $asserter->assertRevision( + $this->revId2_4, + "BackupDumperTestP2Summary4 extra", + $this->textId2_4, + 44, + "6o1ciaxa6pybnqprmungwofc4lv00wv", + false, + $this->revId2_3 + ); + $asserter->assertPageEnd(); // Page 3 // -> Page is marked deleted. Hence not visible // Page 4 - $this->assertPageStart( + $asserter->assertPageStart( $this->pageId4, $this->talk_namespace, $this->pageTitle4->getPrefixedText() ); - $this->assertRevision( $this->revId4_1, "Talk BackupDumperTestP1 Summary1", - $this->textId4_1, 35, "nktofwzd0tl192k3zfepmlzxoax1lpe" ); - $this->assertPageEnd(); + $asserter->assertRevision( + $this->revId4_1, + "Talk BackupDumperTestP1 Summary1", + $this->textId4_1, + 35, + "nktofwzd0tl192k3zfepmlzxoax1lpe" + ); + $asserter->assertPageEnd(); - $this->assertDumpEnd(); + $asserter->assertDumpEnd(); } function testCurrentStubGzip() { @@ -336,34 +482,56 @@ class BackupDumperPageTest extends DumpTestCase { // Checking the dumped data $this->gunzip( $fname ); - $this->assertDumpStart( $fname ); + + $asserter = $this->getDumpAsserter(); + $asserter->assertDumpStart( $fname ); // Page 1 - $this->assertPageStart( $this->pageId1, $this->namespace, $this->pageTitle1->getPrefixedText() ); - $this->assertRevision( $this->revId1_1, "BackupDumperTestP1Summary1", - $this->textId1_1, 23, "0bolhl6ol7i6x0e7yq91gxgaan39j87" ); - $this->assertPageEnd(); + $asserter->assertPageStart( + $this->pageId1, + $this->namespace, + $this->pageTitle1->getPrefixedText() + ); + $asserter->assertRevision( + $this->revId1_1, + "BackupDumperTestP1Summary1", + $this->textId1_1, + 23, + "0bolhl6ol7i6x0e7yq91gxgaan39j87" + ); + $asserter->assertPageEnd(); // Page 2 - $this->assertPageStart( $this->pageId2, $this->namespace, $this->pageTitle2->getPrefixedText() ); - $this->assertRevision( $this->revId2_4, "BackupDumperTestP2Summary4 extra", - $this->textId2_4, 44, "6o1ciaxa6pybnqprmungwofc4lv00wv", false, $this->revId2_3 ); - $this->assertPageEnd(); + $asserter->assertPageStart( + $this->pageId2, + $this->namespace, + $this->pageTitle2->getPrefixedText() + ); + $asserter->assertRevision( + $this->revId2_4, + "BackupDumperTestP2Summary4 extra", + $this->textId2_4, + 44, + "6o1ciaxa6pybnqprmungwofc4lv00wv", + false, + $this->revId2_3 + ); + $asserter->assertPageEnd(); // Page 3 // -> Page is marked deleted. Hence not visible // Page 4 - $this->assertPageStart( + $asserter->assertPageStart( $this->pageId4, $this->talk_namespace, $this->pageTitle4->getPrefixedText() ); - $this->assertRevision( $this->revId4_1, "Talk BackupDumperTestP1 Summary1", + $asserter->assertRevision( $this->revId4_1, "Talk BackupDumperTestP1 Summary1", $this->textId4_1, 35, "nktofwzd0tl192k3zfepmlzxoax1lpe" ); - $this->assertPageEnd(); + $asserter->assertPageEnd(); - $this->assertDumpEnd(); + $asserter->assertDumpEnd(); } /** @@ -376,8 +544,10 @@ class BackupDumperPageTest extends DumpTestCase { * * We reproduce such a setup with our mini fixture, although we omit * chunks, and all the other gimmicks of xmldumps-backup. + * + * @dataProvider schemaVersionProvider */ - function testXmlDumpsBackupUseCase() { + function testXmlDumpsBackupUseCase( $schemaVersion ) { $this->checkHasGzip(); $fnameMetaHistory = $this->getNewTempFile(); @@ -389,7 +559,7 @@ class BackupDumperPageTest extends DumpTestCase { "--output=gzip:" . $fnameMetaCurrent, "--filter=latest", "--output=gzip:" . $fnameArticles, "--filter=latest", "--filter=notalk", "--filter=namespace:!NS_USER", - "--reporting=1000" + "--reporting=1000", '--schema-version', $schemaVersion ], $this->pageId1, $this->pageId4 + 1 @@ -413,89 +583,187 @@ class BackupDumperPageTest extends DumpTestCase { // Checking meta-history ------------------------------------------------- $this->gunzip( $fnameMetaHistory ); - $this->assertDumpStart( $fnameMetaHistory ); + $this->assertDumpSchema( $fnameMetaHistory, $this->getXmlSchemaPath( $schemaVersion ) ); + + $asserter = $this->getDumpAsserter( $schemaVersion ); + $asserter->assertDumpStart( $fnameMetaHistory ); // Page 1 - $this->assertPageStart( $this->pageId1, $this->namespace, $this->pageTitle1->getPrefixedText() ); - $this->assertRevision( $this->revId1_1, "BackupDumperTestP1Summary1", - $this->textId1_1, 23, "0bolhl6ol7i6x0e7yq91gxgaan39j87" ); - $this->assertPageEnd(); + $asserter->assertPageStart( + $this->pageId1, + $this->namespace, + $this->pageTitle1->getPrefixedText() + ); + $asserter->assertRevision( + $this->revId1_1, + "BackupDumperTestP1Summary1", + $this->textId1_1, + 23, + "0bolhl6ol7i6x0e7yq91gxgaan39j87" + ); + $asserter->assertPageEnd(); // Page 2 - $this->assertPageStart( $this->pageId2, $this->namespace, $this->pageTitle2->getPrefixedText() ); - $this->assertRevision( $this->revId2_1, "BackupDumperTestP2Summary1", - $this->textId2_1, 23, "jprywrymfhysqllua29tj3sc7z39dl2" ); - $this->assertRevision( $this->revId2_2, "BackupDumperTestP2Summary2", - $this->textId2_2, 23, "b7vj5ks32po5m1z1t1br4o7scdwwy95", false, $this->revId2_1 ); - $this->assertRevision( $this->revId2_3, "BackupDumperTestP2Summary3", - $this->textId2_3, 23, "jfunqmh1ssfb8rs43r19w98k28gg56r", false, $this->revId2_2 ); - $this->assertRevision( $this->revId2_4, "BackupDumperTestP2Summary4 extra", - $this->textId2_4, 44, "6o1ciaxa6pybnqprmungwofc4lv00wv", false, $this->revId2_3 ); - $this->assertPageEnd(); + $asserter->assertPageStart( + $this->pageId2, + $this->namespace, + $this->pageTitle2->getPrefixedText() + ); + $asserter->assertRevision( + $this->revId2_1, + "BackupDumperTestP2Summary1", + $this->textId2_1, + 23, + "jprywrymfhysqllua29tj3sc7z39dl2" + ); + $asserter->assertRevision( + $this->revId2_2, + "BackupDumperTestP2Summary2", + $this->textId2_2, + 23, + "b7vj5ks32po5m1z1t1br4o7scdwwy95", + false, + $this->revId2_1 + ); + $asserter->assertRevision( + $this->revId2_3, + "BackupDumperTestP2Summary3", + $this->textId2_3, + 23, + "jfunqmh1ssfb8rs43r19w98k28gg56r", + false, + $this->revId2_2 + ); + $asserter->assertRevision( + $this->revId2_4, + "BackupDumperTestP2Summary4 extra", + $this->textId2_4, + 44, + "6o1ciaxa6pybnqprmungwofc4lv00wv", + false, + $this->revId2_3 + ); + $asserter->assertPageEnd(); // Page 3 // -> Page is marked deleted. Hence not visible // Page 4 - $this->assertPageStart( + $asserter->assertPageStart( $this->pageId4, $this->talk_namespace, - $this->pageTitle4->getPrefixedText() + $this->pageTitle4->getPrefixedText( $schemaVersion ) ); - $this->assertRevision( $this->revId4_1, "Talk BackupDumperTestP1 Summary1", - $this->textId4_1, 35, "nktofwzd0tl192k3zfepmlzxoax1lpe" ); - $this->assertPageEnd(); + $asserter->assertRevision( + $this->revId4_1, + "Talk BackupDumperTestP1 Summary1", + $this->textId4_1, + 35, + "nktofwzd0tl192k3zfepmlzxoax1lpe" + ); + $asserter->assertPageEnd(); - $this->assertDumpEnd(); + $asserter->assertDumpEnd(); // Checking meta-current ------------------------------------------------- $this->gunzip( $fnameMetaCurrent ); - $this->assertDumpStart( $fnameMetaCurrent ); + $this->assertDumpSchema( $fnameMetaCurrent, $this->getXmlSchemaPath( $schemaVersion ) ); + + $asserter = $this->getDumpAsserter( $schemaVersion ); + $asserter->assertDumpStart( $fnameMetaCurrent ); // Page 1 - $this->assertPageStart( $this->pageId1, $this->namespace, $this->pageTitle1->getPrefixedText() ); - $this->assertRevision( $this->revId1_1, "BackupDumperTestP1Summary1", - $this->textId1_1, 23, "0bolhl6ol7i6x0e7yq91gxgaan39j87" ); - $this->assertPageEnd(); + $asserter->assertPageStart( + $this->pageId1, + $this->namespace, + $this->pageTitle1->getPrefixedText() + ); + $asserter->assertRevision( + $this->revId1_1, + "BackupDumperTestP1Summary1", + $this->textId1_1, + 23, + "0bolhl6ol7i6x0e7yq91gxgaan39j87" + ); + $asserter->assertPageEnd(); // Page 2 - $this->assertPageStart( $this->pageId2, $this->namespace, $this->pageTitle2->getPrefixedText() ); - $this->assertRevision( $this->revId2_4, "BackupDumperTestP2Summary4 extra", - $this->textId2_4, 44, "6o1ciaxa6pybnqprmungwofc4lv00wv", false, $this->revId2_3 ); - $this->assertPageEnd(); + $asserter->assertPageStart( + $this->pageId2, + $this->namespace, + $this->pageTitle2->getPrefixedText() + ); + $asserter->assertRevision( + $this->revId2_4, + "BackupDumperTestP2Summary4 extra", + $this->textId2_4, + 44, + "6o1ciaxa6pybnqprmungwofc4lv00wv", + false, + $this->revId2_3 + ); + $asserter->assertPageEnd(); // Page 3 // -> Page is marked deleted. Hence not visible // Page 4 - $this->assertPageStart( + $asserter->assertPageStart( $this->pageId4, $this->talk_namespace, $this->pageTitle4->getPrefixedText() ); - $this->assertRevision( $this->revId4_1, "Talk BackupDumperTestP1 Summary1", - $this->textId4_1, 35, "nktofwzd0tl192k3zfepmlzxoax1lpe" ); - $this->assertPageEnd(); + $asserter->assertRevision( + $this->revId4_1, + "Talk BackupDumperTestP1 Summary1", + $this->textId4_1, + 35, + "nktofwzd0tl192k3zfepmlzxoax1lpe" + ); + $asserter->assertPageEnd(); - $this->assertDumpEnd(); + $asserter->assertDumpEnd(); // Checking articles ------------------------------------------------- $this->gunzip( $fnameArticles ); - $this->assertDumpStart( $fnameArticles ); + $this->assertDumpSchema( $fnameArticles, $this->getXmlSchemaPath( $schemaVersion ) ); + + $asserter = $this->getDumpAsserter( $schemaVersion ); + $asserter->assertDumpStart( $fnameArticles ); // Page 1 - $this->assertPageStart( $this->pageId1, $this->namespace, $this->pageTitle1->getPrefixedText() ); - $this->assertRevision( $this->revId1_1, "BackupDumperTestP1Summary1", - $this->textId1_1, 23, "0bolhl6ol7i6x0e7yq91gxgaan39j87" ); - $this->assertPageEnd(); + $asserter->assertPageStart( + $this->pageId1, + $this->namespace, + $this->pageTitle1->getPrefixedText() + ); + $asserter->assertRevision( + $this->revId1_1, + "BackupDumperTestP1Summary1", + $this->textId1_1, + 23, + "0bolhl6ol7i6x0e7yq91gxgaan39j87" + ); + $asserter->assertPageEnd(); // Page 2 - $this->assertPageStart( $this->pageId2, $this->namespace, $this->pageTitle2->getPrefixedText() ); - $this->assertRevision( $this->revId2_4, "BackupDumperTestP2Summary4 extra", - $this->textId2_4, 44, "6o1ciaxa6pybnqprmungwofc4lv00wv", false, $this->revId2_3 ); - $this->assertPageEnd(); + $asserter->assertPageStart( + $this->pageId2, + $this->namespace, + $this->pageTitle2->getPrefixedText() + ); + $asserter->assertRevision( + $this->revId2_4, + "BackupDumperTestP2Summary4 extra", + $this->textId2_4, + 44, + "6o1ciaxa6pybnqprmungwofc4lv00wv", + false, + $this->revId2_3 + ); + $asserter->assertPageEnd(); // Page 3 // -> Page is marked deleted. Hence not visible @@ -503,7 +771,7 @@ class BackupDumperPageTest extends DumpTestCase { // Page 4 // -> Page is not in $this->namespace. Hence not visible - $this->assertDumpEnd(); + $asserter->assertDumpEnd(); $this->expectETAOutput(); } diff --git a/tests/phpunit/maintenance/xml.xsd b/tests/phpunit/maintenance/xml.xsd new file mode 100644 index 0000000000..aea7d0db0a --- /dev/null +++ b/tests/phpunit/maintenance/xml.xsd @@ -0,0 +1,287 @@ + + + + + + +
+

About the XML namespace

+ +
+

+ This schema document describes the XML namespace, in a form + suitable for import by other schema documents. +

+

+ See + http://www.w3.org/XML/1998/namespace.html and + + http://www.w3.org/TR/REC-xml for information + about this namespace. +

+

+ Note that local names in this namespace are intended to be + defined only by the World Wide Web Consortium or its subgroups. + The names currently defined in this namespace are listed below. + They should not be used with conflicting semantics by any Working + Group, specification, or document instance. +

+

+ See further below in this document for more information about how to refer to this schema document from your own + XSD schema documents and about the + namespace-versioning policy governing this schema document. +

+
+
+
+
+ + + + +
+ +

lang (as an attribute name)

+

+ denotes an attribute whose value + is a language code for the natural language of the content of + any element; its value is inherited. This name is reserved + by virtue of its definition in the XML specification.

+ +
+
+

Notes

+

+ Attempting to install the relevant ISO 2- and 3-letter + codes as the enumerated possible values is probably never + going to be a realistic possibility. +

+

+ See BCP 47 at + http://www.rfc-editor.org/rfc/bcp/bcp47.txt + and the IANA language subtag registry at + + http://www.iana.org/assignments/language-subtag-registry + for further information. +

+

+ The union allows for the 'un-declaration' of xml:lang with + the empty string. +

+
+
+
+ + + + + + + + + +
+ + + + +
+ +

space (as an attribute name)

+

+ denotes an attribute whose + value is a keyword indicating what whitespace processing + discipline is intended for the content of the element; its + value is inherited. This name is reserved by virtue of its + definition in the XML specification.

+ +
+
+
+ + + + + + +
+ + + +
+ +

base (as an attribute name)

+

+ denotes an attribute whose value + provides a URI to be used as the base for interpreting any + relative URIs in the scope of the element on which it + appears; its value is inherited. This name is reserved + by virtue of its definition in the XML Base specification.

+ +

+ See http://www.w3.org/TR/xmlbase/ + for information about this attribute. +

+
+
+
+
+ + + + +
+ +

id (as an attribute name)

+

+ denotes an attribute whose value + should be interpreted as if declared to be of type ID. + This name is reserved by virtue of its definition in the + xml:id specification.

+ +

+ See http://www.w3.org/TR/xml-id/ + for information about this attribute. +

+
+
+
+
+ + + + + + + + + + +
+ +

Father (in any context at all)

+ +
+

+ denotes Jon Bosak, the chair of + the original XML Working Group. This name is reserved by + the following decision of the W3C XML Plenary and + XML Coordination groups: +

+
+

+ In appreciation for his vision, leadership and + dedication the W3C XML Plenary on this 10th day of + February, 2000, reserves for Jon Bosak in perpetuity + the XML name "xml:Father". +

+
+
+
+
+
+ + + +
+

About this schema document

+ +
+

+ This schema defines attributes and an attribute group suitable + for use by schemas wishing to allow xml:base, + xml:lang, xml:space or + xml:id attributes on elements they define. +

+

+ To enable this, such a schema must import this schema for + the XML namespace, e.g. as follows: +

+
+          <schema . . .>
+           . . .
+           <import namespace="http://www.w3.org/XML/1998/namespace"
+                      schemaLocation="http://www.w3.org/2001/xml.xsd"/>
+     
+

+ or +

+
+           <import namespace="http://www.w3.org/XML/1998/namespace"
+                      schemaLocation="http://www.w3.org/2009/01/xml.xsd"/>
+     
+

+ Subsequently, qualified reference to any of the attributes or the + group defined below will have the desired effect, e.g. +

+
+          <type . . .>
+           . . .
+           <attributeGroup ref="xml:specialAttrs"/>
+     
+

+ will define a type which will schema-validate an instance element + with any of those attributes. +

+
+
+
+
+ + + +
+

Versioning policy for this schema document

+
+

+ In keeping with the XML Schema WG's standard versioning + policy, this schema document will persist at + + http://www.w3.org/2009/01/xml.xsd. +

+

+ At the date of issue it can also be found at + + http://www.w3.org/2001/xml.xsd. +

+

+ The schema document at that URI may however change in the future, + in order to remain compatible with the latest version of XML + Schema itself, or with the XML namespace itself. In other words, + if the XML Schema or XML namespaces change, the version of this + document at + http://www.w3.org/2001/xml.xsd + + will change accordingly; the version at + + http://www.w3.org/2009/01/xml.xsd + + will not change. +

+

+ Previous dated (and unchanging) versions of this schema + document are at: +

+ +
+
+
+
+ +
+ -- 2.20.1