From 39703e93187bc0aa8059fbfa666b3605424b90f3 Mon Sep 17 00:00:00 2001 From: Brad Jorsch Date: Wed, 17 Dec 2014 16:48:03 -0500 Subject: [PATCH] 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 --- includes/api/ApiFormatWddx.php | 54 ++++++++-- .../includes/api/format/ApiFormatDbgTest.php | 38 +++++++ .../includes/api/format/ApiFormatDumpTest.php | 46 ++++++++ .../includes/api/format/ApiFormatJsonTest.php | 40 +++++-- .../includes/api/format/ApiFormatNoneTest.php | 28 ++++- .../includes/api/format/ApiFormatPhpTest.php | 72 +++++++++++-- .../includes/api/format/ApiFormatTestBase.php | 63 ++++++++--- .../includes/api/format/ApiFormatTxtTest.php | 38 +++++++ .../includes/api/format/ApiFormatWddxTest.php | 63 ++++++++--- .../includes/api/format/ApiFormatXmlTest.php | 101 ++++++++++++++++++ 10 files changed, 481 insertions(+), 62 deletions(-) create mode 100644 tests/phpunit/includes/api/format/ApiFormatDbgTest.php create mode 100644 tests/phpunit/includes/api/format/ApiFormatDumpTest.php create mode 100644 tests/phpunit/includes/api/format/ApiFormatTxtTest.php create mode 100644 tests/phpunit/includes/api/format/ApiFormatXmlTest.php 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' ), + ); + } + +} -- 2.20.1