From: Brad Jorsch Date: Wed, 17 Dec 2014 21:48:03 +0000 (-0500) Subject: Improve testing for ApiFormatBase subclasses X-Git-Tag: 1.31.0-rc.0~12868^2 X-Git-Url: http://git.heureux-cyclage.org/?a=commitdiff_plain;ds=sidebyside;h=39703e93187bc0aa8059fbfa666b3605424b90f3;p=lhc%2Fweb%2Fwiklou.git Improve testing for ApiFormatBase subclasses I7b37295e is going to be changing around how ApiResult works, which is going to need corresponding changes in the formatters. So it would probably be a good idea to have a decent starting point to catch any breakage. The non-backwards-compatible changes to ApiFormatTestBase shouldn't be a concern, as no extensions in Gerrit reference this class or any /ApiFormat.*Test/ class. This also fixes two small bugs in ApiFormatWddx (null handling and spacing for non-fm slow path) discovered during testing, and works around some HHVM wddx extension bugs. Bug: T85236 Change-Id: I9cdf896e7070ed51e42625d61609ad9ef91cd567 --- diff --git a/includes/api/ApiFormatWddx.php b/includes/api/ApiFormatWddx.php index e2d4d612db..8662a64b8d 100644 --- a/includes/api/ApiFormatWddx.php +++ b/includes/api/ApiFormatWddx.php @@ -38,24 +38,16 @@ class ApiFormatWddx extends ApiFormatBase { public function execute() { $this->markDeprecated(); - // Some versions of PHP have a broken wddx_serialize_value, see - // PHP bug 45314. Test encoding an affected character (U+00A0) - // to avoid this. - $expected = - "
\xc2\xa0"; - if ( function_exists( 'wddx_serialize_value' ) - && !$this->getIsHtml() - && wddx_serialize_value( "\xc2\xa0" ) == $expected - ) { + if ( !$this->getIsHtml() && !static::useSlowPrinter() ) { $this->printText( wddx_serialize_value( $this->getResultData() ) ); } else { // Don't do newlines and indentation if we weren't asked // for pretty output $nl = ( $this->getIsHtml() ? "\n" : '' ); - $indstr = ' '; + $indstr = ( $this->getIsHtml() ? ' ' : '' ); $this->printText( "$nl" ); $this->printText( "$nl" ); - $this->printText( "$indstr
$nl" ); + $this->printText( "$indstr
$nl" ); $this->printText( "$indstr$nl" ); $this->slowWddxPrinter( $this->getResultData(), 4 ); $this->printText( "$indstr$nl" ); @@ -63,6 +55,44 @@ class ApiFormatWddx extends ApiFormatBase { } } + public static function useSlowPrinter() { + if ( !function_exists( 'wddx_serialize_value' ) ) { + return true; + } + + // Some versions of PHP have a broken wddx_serialize_value, see + // PHP bug 45314. Test encoding an affected character (U+00A0) + // to avoid this. + $expected = + "
\xc2\xa0"; + if ( wddx_serialize_value( "\xc2\xa0" ) !== $expected ) { + return true; + } + + // Some versions of HHVM don't correctly encode ampersands. + $expected = + "
&"; + if ( wddx_serialize_value( '&' ) !== $expected ) { + return true; + } + + // Some versions of HHVM don't correctly encode empty arrays as subvalues. + $expected = + "
"; + if ( wddx_serialize_value( array( array() ) ) !== $expected ) { + return true; + } + + // Some versions of HHVM don't correctly encode associative arrays with numeric keys. + $expected = + "
1"; + if ( wddx_serialize_value( array( 2 => 1 ) ) !== $expected ) { + return true; + } + + return false; + } + /** * Recursively go through the object and output its data in WDDX format. * @param mixed $elemValue @@ -104,6 +134,8 @@ class ApiFormatWddx extends ApiFormatBase { $this->printText( $indstr . Xml::element( 'boolean', array( 'value' => $elemValue ? 'true' : 'false' ) ) . $nl ); + } elseif ( $elemValue === null ) { + $this->printText( $indstr . Xml::element( 'null', array() ) . $nl ); } else { ApiBase::dieDebug( __METHOD__, 'Unknown type ' . gettype( $elemValue ) ); } diff --git a/tests/phpunit/includes/api/format/ApiFormatDbgTest.php b/tests/phpunit/includes/api/format/ApiFormatDbgTest.php new file mode 100644 index 0000000000..1e4ea53ccc --- /dev/null +++ b/tests/phpunit/includes/api/format/ApiFormatDbgTest.php @@ -0,0 +1,38 @@ + \n array (\n 'dbg' => \n array (\n" . + " '*' => 'format=dbg has been deprecated. Please use format=json instead.',\n" . + " ),\n ),"; + + return array( + // Basic types + array( array( null ), "array ({$warning}\n 0 => NULL,\n)" ), + array( array( true ), "array ({$warning}\n 0 => true,\n)" ), + array( array( false ), "array ({$warning}\n 0 => false,\n)" ), + array( array( 42 ), "array ({$warning}\n 0 => 42,\n)" ), + array( array( 42.5 ), "array ({$warning}\n 0 => 42.5,\n)" ), + array( array( 1e42 ), "array ({$warning}\n 0 => 1.0E+42,\n)" ), + array( array( 'foo' ), "array ({$warning}\n 0 => 'foo',\n)" ), + array( array( 'fóo' ), "array ({$warning}\n 0 => 'fóo',\n)" ), + + // Arrays and objects + array( array( array() ), "array ({$warning}\n 0 => \n array (\n ),\n)" ), + array( array( array( 1 ) ), "array ({$warning}\n 0 => \n array (\n 0 => 1,\n ),\n)" ), + array( array( array( 'x' => 1 ) ), "array ({$warning}\n 0 => \n array (\n 'x' => 1,\n ),\n)" ), + array( array( array( 2 => 1 ) ), "array ({$warning}\n 0 => \n array (\n 2 => 1,\n ),\n)" ), + + // Content + array( array( '*' => 'foo' ), "array ({$warning}\n '*' => 'foo',\n)" ), + ); + } + +} diff --git a/tests/phpunit/includes/api/format/ApiFormatDumpTest.php b/tests/phpunit/includes/api/format/ApiFormatDumpTest.php new file mode 100644 index 0000000000..2800d2dfb6 --- /dev/null +++ b/tests/phpunit/includes/api/format/ApiFormatDumpTest.php @@ -0,0 +1,46 @@ + true ) ), + ); + } + + $warning = "\n [\"warnings\"]=>\n array(1) {\n [\"dump\"]=>\n array(1) {\n [\"*\"]=>\n" . + " string(64) \"format=dump has been deprecated. Please use format=json instead.\"\n" . + " }\n }"; + + return array( + // Basic types + array( array( null ), "array(2) {{$warning}\n [0]=>\n NULL\n}\n" ), + array( array( true ), "array(2) {{$warning}\n [0]=>\n bool(true)\n}\n" ), + array( array( false ), "array(2) {{$warning}\n [0]=>\n bool(false)\n}\n" ), + array( array( 42 ), "array(2) {{$warning}\n [0]=>\n int(42)\n}\n" ), + array( array( 42.5 ), "array(2) {{$warning}\n [0]=>\n float(42.5)\n}\n" ), + array( array( 1e42 ), "array(2) {{$warning}\n [0]=>\n float(1.0E+42)\n}\n" ), + array( array( 'foo' ), "array(2) {{$warning}\n [0]=>\n string(3) \"foo\"\n}\n" ), + array( array( 'fóo' ), "array(2) {{$warning}\n [0]=>\n string(4) \"fóo\"\n}\n" ), + + // Arrays + array( array( array() ), "array(2) {{$warning}\n [0]=>\n array(0) {\n }\n}\n" ), + array( array( array( 1 ) ), "array(2) {{$warning}\n [0]=>\n array(1) {\n [0]=>\n int(1)\n }\n}\n" ), + array( array( array( 'x' => 1 ) ), "array(2) {{$warning}\n [0]=>\n array(1) {\n [\"x\"]=>\n int(1)\n }\n}\n" ), + array( array( array( 2 => 1 ) ), "array(2) {{$warning}\n [0]=>\n array(1) {\n [2]=>\n int(1)\n }\n}\n" ), + + // Content + array( array( '*' => 'foo' ), "array(2) {{$warning}\n [\"*\"]=>\n string(3) \"foo\"\n}\n" ), + ); + } + +} diff --git a/tests/phpunit/includes/api/format/ApiFormatJsonTest.php b/tests/phpunit/includes/api/format/ApiFormatJsonTest.php index fc1f90217a..bdf3f13947 100644 --- a/tests/phpunit/includes/api/format/ApiFormatJsonTest.php +++ b/tests/phpunit/includes/api/format/ApiFormatJsonTest.php @@ -2,21 +2,41 @@ /** * @group API - * @group Database - * @group medium * @covers ApiFormatJson */ class ApiFormatJsonTest extends ApiFormatTestBase { - public function testValidSyntax( ) { - $data = $this->apiRequest( 'json', array( 'action' => 'query', 'meta' => 'siteinfo' ) ); + protected $printerName = 'json'; - $this->assertInternalType( 'array', json_decode( $data, true ) ); - $this->assertGreaterThan( 0, count( (array)$data ) ); - } + public static function provideGeneralEncoding() { + return array( + // Basic types + array( array( null ), '[null]' ), + array( array( true ), '[true]' ), + array( array( false ), '[false]' ), + array( array( 42 ), '[42]' ), + array( array( 42.5 ), '[42.5]' ), + array( array( 1e42 ), '[1.0e+42]' ), + array( array( 'foo' ), '["foo"]' ), + array( array( 'fóo' ), '["f\u00f3o"]' ), + array( array( 'fóo' ), '["fóo"]', array( 'utf8' => 1 ) ), + + // Arrays and objects + array( array( array() ), '[[]]' ), + array( array( array( 1 ) ), '[[1]]' ), + array( array( array( 'x' => 1 ) ), '[{"x":1}]' ), + array( array( array( 2 => 1 ) ), '[{"2":1}]' ), + array( array( (object)array() ), '[{}]' ), + + // Content + array( array( '*' => 'foo' ), '{"*":"foo"}' ), - public function testJsonpInjection( ) { - $data = $this->apiRequest( 'json', array( 'action' => 'query', 'meta' => 'siteinfo', 'callback' => 'myCallback' ) ); - $this->assertEquals( '/**/myCallback(', substr( $data, 0, 15 ) ); + // Callbacks + array( array( 1 ), '/**/myCallback([1])', array( 'callback' => 'myCallback' ) ), + + // Cross-domain mangling + array( array( '< Cross-Domain-Policy >' ), '["\u003C Cross-Domain-Policy \u003E"]' ), + ); } + } diff --git a/tests/phpunit/includes/api/format/ApiFormatNoneTest.php b/tests/phpunit/includes/api/format/ApiFormatNoneTest.php index cabd750b4b..1487ad0ddb 100644 --- a/tests/phpunit/includes/api/format/ApiFormatNoneTest.php +++ b/tests/phpunit/includes/api/format/ApiFormatNoneTest.php @@ -2,15 +2,33 @@ /** * @group API - * @group Database - * @group medium * @covers ApiFormatNone */ class ApiFormatNoneTest extends ApiFormatTestBase { - public function testValidSyntax( ) { - $data = $this->apiRequest( 'none', array( 'action' => 'query', 'meta' => 'siteinfo' ) ); + protected $printerName = 'none'; - $this->assertEquals( '', $data ); // No output! + public static function provideGeneralEncoding() { + return array( + // Basic types + array( array( null ), '' ), + array( array( true ), '' ), + array( array( false ), '' ), + array( array( 42 ), '' ), + array( array( 42.5 ), '' ), + array( array( 1e42 ), '' ), + array( array( 'foo' ), '' ), + array( array( 'fóo' ), '' ), + + // Arrays and objects + array( array( array() ), '' ), + array( array( array( 1 ) ), '' ), + array( array( array( 'x' => 1 ) ), '' ), + array( array( array( 2 => 1 ) ), '' ), + + // Content + array( array( '*' => 'foo' ), '' ), + ); } + } diff --git a/tests/phpunit/includes/api/format/ApiFormatPhpTest.php b/tests/phpunit/includes/api/format/ApiFormatPhpTest.php index 54f447a92a..469346c8a7 100644 --- a/tests/phpunit/includes/api/format/ApiFormatPhpTest.php +++ b/tests/phpunit/includes/api/format/ApiFormatPhpTest.php @@ -2,16 +2,76 @@ /** * @group API - * @group Database - * @group medium * @covers ApiFormatPhp */ class ApiFormatPhpTest extends ApiFormatTestBase { - public function testValidSyntax( ) { - $data = $this->apiRequest( 'php', array( 'action' => 'query', 'meta' => 'siteinfo' ) ); + protected $printerName = 'php'; - $this->assertInternalType( 'array', unserialize( $data ) ); - $this->assertGreaterThan( 0, count( (array)$data ) ); + public static function provideGeneralEncoding() { + return array( + // Basic types + array( array( null ), 'a:1:{i:0;N;}' ), + array( array( true ), 'a:1:{i:0;b:1;}' ), + array( array( false ), 'a:1:{i:0;b:0;}' ), + array( array( 42 ), 'a:1:{i:0;i:42;}' ), + array( array( 42.5 ), 'a:1:{i:0;d:42.5;}' ), + array( array( 1e42 ), 'a:1:{i:0;d:1.0E+42;}' ), + array( array( 'foo' ), 'a:1:{i:0;s:3:"foo";}' ), + array( array( 'fóo' ), 'a:1:{i:0;s:4:"fóo";}' ), + + // Arrays and objects + array( array( array() ), 'a:1:{i:0;a:0:{}}' ), + array( array( array( 1 ) ), 'a:1:{i:0;a:1:{i:0;i:1;}}' ), + array( array( array( 'x' => 1 ) ), 'a:1:{i:0;a:1:{s:1:"x";i:1;}}' ), + array( array( array( 2 => 1 ) ), 'a:1:{i:0;a:1:{i:2;i:1;}}' ), + + // Content + array( array( '*' => 'foo' ), 'a:1:{s:1:"*";s:3:"foo";}' ), + ); + } + + public function testCrossDomainMangling() { + $config = new HashConfig( array( 'MangleFlashPolicy' => false ) ); + $context = new RequestContext; + $context->setConfig( new MultiConfig( array( + $config, + $context->getConfig(), + ) ) ); + $main = new ApiMain( $context ); + $main->getResult()->addValue( null, null, '< Cross-Domain-Policy >' ); + + if ( !function_exists( 'wfOutputHandler' ) ) { + function wfOutputHandler( $s ) { + return $s; + } + } + + $printer = $main->createPrinterByName( 'php' ); + ob_start( 'wfOutputHandler' ); + $printer->initPrinter(); + $printer->execute(); + $printer->closePrinter(); + $ret = ob_get_clean(); + $this->assertSame( 'a:1:{i:0;s:23:"< Cross-Domain-Policy >";}', $ret ); + + $config->set( 'MangleFlashPolicy', true ); + $printer = $main->createPrinterByName( 'php' ); + ob_start( 'wfOutputHandler' ); + try { + $printer->initPrinter(); + $printer->execute(); + $printer->closePrinter(); + ob_end_clean(); + $this->fail( 'Expected exception not thrown' ); + } catch ( UsageException $ex ) { + ob_end_clean(); + $this->assertSame( + 'This response cannot be represented using format=php. See https://bugzilla.wikimedia.org/show_bug.cgi?id=66776', + $ex->getMessage(), + 'Expected exception' + ); + } } + } diff --git a/tests/phpunit/includes/api/format/ApiFormatTestBase.php b/tests/phpunit/includes/api/format/ApiFormatTestBase.php index af775708cd..8134c50975 100644 --- a/tests/phpunit/includes/api/format/ApiFormatTestBase.php +++ b/tests/phpunit/includes/api/format/ApiFormatTestBase.php @@ -1,29 +1,60 @@ createPrinterByName( $format ); + /** + * Get the formatter output for the given input data + * @param array $params Query parameters + * @param array $data Data to encode + * @param string $class Printer class to use instead of the normal one + */ + protected function encodeData( array $params, array $data, $class = null ) { + $context = new RequestContext; + $context->setRequest( new FauxRequest( $params, true ) ); + $main = new ApiMain( $context ); + if ( $class !== null ) { + $main->getModuleManager()->addModule( $this->printerName, 'format', $class ); + } + $result = $main->getResult(); + foreach ( $data as $k => $v ) { + $result->addValue( null, $k, $v ); + } - ob_start(); - $printer->initPrinter( false ); + $printer = $main->createPrinterByName( $this->printerName ); + $printer->initPrinter(); $printer->execute(); - $printer->closePrinter(); - $out = ob_get_clean(); + ob_start(); + try { + $printer->closePrinter(); + return ob_get_clean(); + } catch ( Exception $ex ) { + ob_end_clean(); + throw $ex; + } + } - return $out; + /** + * @dataProvider provideGeneralEncoding + */ + public function testGeneralEncoding( array $data, $expect, array $params = array() ) { + if ( isset( $params['SKIP'] ) ) { + $this->markTestSkipped( $expect ); + } + $this->assertSame( $expect, $this->encodeData( $params, $data ) ); } } diff --git a/tests/phpunit/includes/api/format/ApiFormatTxtTest.php b/tests/phpunit/includes/api/format/ApiFormatTxtTest.php new file mode 100644 index 0000000000..06e9204f5b --- /dev/null +++ b/tests/phpunit/includes/api/format/ApiFormatTxtTest.php @@ -0,0 +1,38 @@ + Array\n (\n [txt] => Array\n (\n" . + " [*] => format=txt has been deprecated. Please use format=json instead.\n" . + " )\n\n )\n"; + + return array( + // Basic types + array( array( null ), "Array\n({$warning}\n [0] => \n)\n" ), + array( array( true ), "Array\n({$warning}\n [0] => 1\n)\n" ), + array( array( false ), "Array\n({$warning}\n [0] => \n)\n" ), + array( array( 42 ), "Array\n({$warning}\n [0] => 42\n)\n" ), + array( array( 42.5 ), "Array\n({$warning}\n [0] => 42.5\n)\n" ), + array( array( 1e42 ), "Array\n({$warning}\n [0] => 1.0E+42\n)\n" ), + array( array( 'foo' ), "Array\n({$warning}\n [0] => foo\n)\n" ), + array( array( 'fóo' ), "Array\n({$warning}\n [0] => fóo\n)\n" ), + + // Arrays and objects + array( array( array() ), "Array\n({$warning}\n [0] => Array\n (\n )\n\n)\n" ), + array( array( array( 1 ) ), "Array\n({$warning}\n [0] => Array\n (\n [0] => 1\n )\n\n)\n" ), + array( array( array( 'x' => 1 ) ), "Array\n({$warning}\n [0] => Array\n (\n [x] => 1\n )\n\n)\n" ), + array( array( array( 2 => 1 ) ), "Array\n({$warning}\n [0] => Array\n (\n [2] => 1\n )\n\n)\n" ), + + // Content + array( array( '*' => 'foo' ), "Array\n({$warning}\n [*] => foo\n)\n" ), + ); + } + +} diff --git a/tests/phpunit/includes/api/format/ApiFormatWddxTest.php b/tests/phpunit/includes/api/format/ApiFormatWddxTest.php index c00545f8eb..81676e0bf0 100644 --- a/tests/phpunit/includes/api/format/ApiFormatWddxTest.php +++ b/tests/phpunit/includes/api/format/ApiFormatWddxTest.php @@ -2,27 +2,62 @@ /** * @group API - * @group Database - * @group medium * @covers ApiFormatWddx */ class ApiFormatWddxTest extends ApiFormatTestBase { - public function testValidSyntax( ) { - if ( !function_exists( 'wddx_deserialize' ) ) { - $this->markTestSkipped( "Function 'wddx_deserialize' not exist, skipping." ); - } + protected $printerName = 'wddx'; - if ( wfIsHHVM() && false === strpos( wddx_serialize_value( "Test for &" ), '&' ) ) { - # Some version of HHVM fails to escape the ampersand - # - # https://phabricator.wikimedia.org/T75531 - $this->markTestSkipped( "wddx_deserialize is bugged under this version of HHVM" ); + public static function provideGeneralEncoding() { + if ( ApiFormatWddx::useSlowPrinter() ) { + return array( + array( array(), 'Fast Wddx printer is unavailable', array( 'SKIP' => true ) ) + ); } + return self::provideEncoding(); + } + + public static function provideEncoding() { + $p = '
format=wddx has been deprecated. Please use format=json instead.'; + $s = ''; + + return array( + // Basic types + array( array( null ), "{$p}{$s}" ), + array( array( true ), "{$p}{$s}" ), + array( array( false ), "{$p}{$s}" ), + array( array( 42 ), "{$p}42{$s}" ), + array( array( 42.5 ), "{$p}42.5{$s}" ), + array( array( 1e42 ), "{$p}1.0E+42{$s}" ), + array( array( 'foo' ), "{$p}foo{$s}" ), + array( array( 'fóo' ), "{$p}fóo{$s}" ), + + // Arrays and objects + array( array( array() ), "{$p}{$s}" ), + array( array( array( 1 ) ), "{$p}1{$s}" ), + array( array( array( 'x' => 1 ) ), "{$p}1{$s}" ), + array( array( array( 2 => 1 ) ), "{$p}1{$s}" ), - $data = $this->apiRequest( 'wddx', array( 'action' => 'query', 'meta' => 'siteinfo' ) ); + // Content + array( array( '*' => 'foo' ), "{$p}foo{$s}" ), + ); + } + + /** + * @dataProvider provideEncoding + */ + public function testSlowEncoding( array $data, $expect, array $params = array() ) { + // Adjust expectation for differences between fast and slow printers. + $expect = str_replace( '\'', '"', $expect ); + $expect = str_replace( '/>', ' />', $expect ); + $expect = '' . $expect; + + $this->assertSame( $expect, $this->encodeData( $params, $data, 'ApiFormatWddxTest_SlowWddx' ) ); + } +} - $this->assertInternalType( 'array', wddx_deserialize( $data ) ); - $this->assertGreaterThan( 0, count( (array)$data ) ); +class ApiFormatWddxTest_SlowWddx extends ApiFormatWddx { + public static function useSlowPrinter() { + return true; } } diff --git a/tests/phpunit/includes/api/format/ApiFormatXmlTest.php b/tests/phpunit/includes/api/format/ApiFormatXmlTest.php new file mode 100644 index 0000000000..afb47e7134 --- /dev/null +++ b/tests/phpunit/includes/api/format/ApiFormatXmlTest.php @@ -0,0 +1,101 @@ +doEditContent( new WikitextContent( + '' + ), 'Summary' ); + $page = WikiPage::factory( Title::newFromText( 'MediaWiki:ApiFormatXmlTest' ) ); + $page->doEditContent( new WikitextContent( 'Bogus' ), 'Summary' ); + $page = WikiPage::factory( Title::newFromText( 'ApiFormatXmlTest' ) ); + $page->doEditContent( new WikitextContent( 'Bogus' ), 'Summary' ); + } + + public static function provideGeneralEncoding() { + $tests = array( + // Basic types + array( array( null ), '' ), + array( array( true, 'a' => true ), '1' ), + array( array( false, 'a' => false ), '' ), + array( array( 42, 'a' => 42 ), '42' ), + array( array( 42.5, 'a' => 42.5 ), '42.5' ), + array( array( 1e42, 'a' => 1e42 ), '1.0E+42' ), + array( array( 'foo', 'a' => 'foo' ), 'foo' ), + array( array( 'fóo', 'a' => 'fóo' ), 'fóo' ), + + // Arrays and objects + array( array( array() ), '' ), + array( array( array( 'x' => 1 ) ), '' ), + array( array( array( 2 => 1, '_element' => 'x' ) ), '1' ), + + // Content + array( array( '*' => 'foo' ), 'foo' ), + + // Subelements + array( array( 'a' => 1, 's' => 1, '_subelements' => array( 's' ) ), + '1' ), + + // includenamespace param + array( array( 'x' => 'foo' ), '', + array( 'includexmlnamespace' => 1 ) ), + + // xslt param + array( array(), 'Invalid or non-existent stylesheet specified', + array( 'xslt' => 'DoesNotExist' ) ), + array( array(), 'Stylesheet should be in the MediaWiki namespace.', + array( 'xslt' => 'ApiFormatXmlTest' ) ), + array( array(), 'Stylesheet should have .xsl extension.', + array( 'xslt' => 'MediaWiki:ApiFormatXmlTest' ) ), + array( array(), + 'getLocalURL( 'action=raw' ) ) . + '" type="text/xsl" ?>', + array( 'xslt' => 'MediaWiki:ApiFormatXmlTest.xsl' ) ), + ); + + // Add in the needed "_element" for all indexed arrays + $ret = array(); + foreach ( $tests as $v ) { + $v[0] += array( '_element' => 'x' ); + $ret[] = $v; + } + return $ret; + } + + /** + * @dataProvider provideXmlFail + */ + public function testXmlFail( array $data, $expect, array $params = array() ) { + try { + echo $this->encodeData( $params, $data ) . "\n"; + $this->fail( "Expected exception not thrown" ); + } catch ( MWException $ex ) { + $this->assertSame( $expect, $ex->getMessage(), 'Expected exception' ); + } + } + + public static function provideXmlFail() { + return array( + // Array without _element + array( array( 1 ), 'Internal error in ApiFormatXml::recXmlPrint: (api, ...) has integer keys without _element value. Use ApiResult::setIndexedTagName().' ), + // Content and subelement + array( array( 1, 's' => array(), '*' => 2, '_element' => 'x' ), 'Internal error in ApiFormatXml::recXmlPrint: (api, ...) has content and subelements' ), + array( array( 1, 's' => 1, '*' => 2, '_element' => 'x', '_subelements' => array( 's' ) ), 'Internal error in ApiFormatXml::recXmlPrint: (api, ...) has content and subelements' ), + // These should fail but don't because of a long-standing bug (see T57371#639713) + //array( array( 1, '*' => 2, '_element' => 'x' ), 'Internal error in ApiFormatXml::recXmlPrint: (api, ...) has content and subelements' ), + //array( array( 's' => array(), '*' => 2 ), 'Internal error in ApiFormatXml::recXmlPrint: (api, ...) has content and subelements' ), + //array( array( 's' => 1, '*' => 2, '_subelements' => array( 's' ) ), 'Internal error in ApiFormatXml::recXmlPrint: (api, ...) has content and subelements' ), + ); + } + +}