wiki.techinc.nl/tests/phpunit/includes/json/FormatJsonTest.php
Bryan Davis 8fea9c619d FormatJson::stripComments
Add stripComments method that can be used to remove single line and
multiline comments from an otherwise valid JSON string. Inspired by the
comment removal code in redisJobRunnerService and discussions on irc
about the Extension registration RFC.

Change-Id: Ie743957bfbb7b1fca8cb78ad48c1efd953362fde
2014-10-12 12:34:22 -06:00

337 lines
8.7 KiB
PHP

<?php
/**
* @covers FormatJson
*/
class FormatJsonTest extends MediaWikiTestCase {
public static function provideEncoderPrettyPrinting() {
return array(
// Four spaces
array( true, ' ' ),
array( ' ', ' ' ),
// Two spaces
array( ' ', ' ' ),
// One tab
array( "\t", "\t" ),
);
}
/**
* @dataProvider provideEncoderPrettyPrinting
*/
public function testEncoderPrettyPrinting( $pretty, $expectedIndent ) {
$obj = array(
'emptyObject' => new stdClass,
'emptyArray' => array(),
'string' => 'foobar\\',
'filledArray' => array(
array(
123,
456,
),
// Nested json works without problems
'"7":["8",{"9":"10"}]',
// Whitespace clean up doesn't touch strings that look alike
"{\n\t\"emptyObject\": {\n\t},\n\t\"emptyArray\": [ ]\n}",
),
);
// No trailing whitespace, no trailing linefeed
$json = '{
"emptyObject": {},
"emptyArray": [],
"string": "foobar\\\\",
"filledArray": [
[
123,
456
],
"\"7\":[\"8\",{\"9\":\"10\"}]",
"{\n\t\"emptyObject\": {\n\t},\n\t\"emptyArray\": [ ]\n}"
]
}';
$json = str_replace( "\r", '', $json ); // Windows compat
$json = str_replace( "\t", $expectedIndent, $json );
$this->assertSame( $json, FormatJson::encode( $obj, $pretty ) );
}
public static function provideEncodeDefault() {
return self::getEncodeTestCases( array() );
}
/**
* @dataProvider provideEncodeDefault
*/
public function testEncodeDefault( $from, $to ) {
$this->assertSame( $to, FormatJson::encode( $from ) );
}
public static function provideEncodeUtf8() {
return self::getEncodeTestCases( array( 'unicode' ) );
}
/**
* @dataProvider provideEncodeUtf8
*/
public function testEncodeUtf8( $from, $to ) {
$this->assertSame( $to, FormatJson::encode( $from, false, FormatJson::UTF8_OK ) );
}
public static function provideEncodeXmlMeta() {
return self::getEncodeTestCases( array( 'xmlmeta' ) );
}
/**
* @dataProvider provideEncodeXmlMeta
*/
public function testEncodeXmlMeta( $from, $to ) {
$this->assertSame( $to, FormatJson::encode( $from, false, FormatJson::XMLMETA_OK ) );
}
public static function provideEncodeAllOk() {
return self::getEncodeTestCases( array( 'unicode', 'xmlmeta' ) );
}
/**
* @dataProvider provideEncodeAllOk
*/
public function testEncodeAllOk( $from, $to ) {
$this->assertSame( $to, FormatJson::encode( $from, false, FormatJson::ALL_OK ) );
}
public function testEncodePhpBug46944() {
$this->assertNotEquals(
'\ud840\udc00',
strtolower( FormatJson::encode( "\xf0\xa0\x80\x80" ) ),
'Test encoding an broken json_encode character (U+20000)'
);
}
public function testDecodeReturnType() {
$this->assertInternalType(
'object',
FormatJson::decode( '{"Name": "Cheeso", "Rank": 7}' ),
'Default to object'
);
$this->assertInternalType(
'array',
FormatJson::decode( '{"Name": "Cheeso", "Rank": 7}', true ),
'Optional array'
);
}
public static function provideParse() {
return array(
array( null ),
array( true ),
array( false ),
array( 0 ),
array( 1 ),
array( 1.2 ),
array( '' ),
array( 'str' ),
array( array( 0, 1, 2 ) ),
array( array( 'a' => 'b' ) ),
array( array( 'a' => 'b' ) ),
array( array( 'a' => 'b', 'x' => array( 'c' => 'd' ) ) ),
);
}
/**
* Recursively convert arrays into stdClass
* @param array|string|bool|int|float|null $value
* @return stdClass|string|bool|int|float|null
*/
public static function toObject( $value ) {
return !is_array( $value ) ? $value : (object) array_map( __METHOD__, $value );
}
/**
* @dataProvider provideParse
* @param mixed $value
*/
public function testParse( $value ) {
$expected = self::toObject( $value );
$json = FormatJson::encode( $expected, false, FormatJson::ALL_OK );
$this->assertJson( $json );
$st = FormatJson::parse( $json );
$this->assertType( 'Status', $st );
$this->assertTrue( $st->isGood() );
$this->assertEquals( $expected, $st->getValue() );
$st = FormatJson::parse( $json, FormatJson::FORCE_ASSOC );
$this->assertType( 'Status', $st );
$this->assertTrue( $st->isGood() );
$this->assertEquals( $value, $st->getValue() );
}
public static function provideParseTryFixing() {
return array(
array( "[,]", '[]' ),
array( "[ , ]", '[]' ),
array( "[ , }", false ),
array( '[1],', false ),
array( "[1,]", '[1]' ),
array( "[1\n,]", '[1]' ),
array( "[1,\n]", '[1]' ),
array( "[1,]\n", '[1]' ),
array( "[1\n,\n]\n", '[1]' ),
array( '["a,",]', '["a,"]' ),
array( "[[1,]\n,[2,\n],[3\n,]]", '[[1],[2],[3]]' ),
array( '[[1,],[2,],[3,]]', false ), // I wish we could parse this, but would need quote parsing
array( '[1,,]', false ),
);
}
/**
* @dataProvider provideParseTryFixing
* @param string $value
* @param string|bool $expected
*/
public function testParseTryFixing( $value, $expected ) {
$st = FormatJson::parse( $value, FormatJson::TRY_FIXING );
$this->assertType( 'Status', $st );
if ( $expected === false ) {
$this->assertFalse( $st->isOK() );
} else {
$this->assertFalse( $st->isGood() );
$this->assertTrue( $st->isOK() );
$val = FormatJson::encode( $st->getValue(), false, FormatJson::ALL_OK );
$this->assertEquals( $expected, $val );
}
}
public static function provideParseErrors() {
return array(
array( 'aaa' ),
array( '{"j": 1 ] }' ),
);
}
/**
* @dataProvider provideParseErrors
* @param mixed $value
*/
public function testParseErrors( $value ) {
$st = FormatJson::parse( $value );
$this->assertType( 'Status', $st );
$this->assertFalse( $st->isOK() );
}
public function provideStripComments() {
return array(
array( '{"a":"b"}', '{"a":"b"}' ),
array( "{\"a\":\"b\"}\n", "{\"a\":\"b\"}\n" ),
array( '/*c*/{"c":"b"}', '{"c":"b"}' ),
array( '{"a":"c"}/*c*/', '{"a":"c"}' ),
array( '/*c//d*/{"c":"b"}', '{"c":"b"}' ),
array( '{/*c*/"c":"b"}', '{"c":"b"}' ),
array( "/*\nc\r\n*/{\"c\":\"b\"}", '{"c":"b"}' ),
array( "//c\n{\"c\":\"b\"}", '{"c":"b"}' ),
array( "//c\r\n{\"c\":\"b\"}", '{"c":"b"}' ),
array( '{"a":"c"}//c', '{"a":"c"}' ),
array( "{\"a-c\"://c\n\"b\"}", '{"a-c":"b"}' ),
array( '{"/*a":"b"}', '{"/*a":"b"}' ),
array( '{"a":"//b"}', '{"a":"//b"}' ),
array( '{"a":"b/*c*/"}', '{"a":"b/*c*/"}' ),
array( "{\"\\\"/*a\":\"b\"}", "{\"\\\"/*a\":\"b\"}" ),
array( '', '' ),
array( '/*c', '' ),
array( '//c', '' ),
array( '"http://example.com"', '"http://example.com"' ),
array( "\0", "\0" ),
array( '"Blåbærsyltetøy"', '"Blåbærsyltetøy"' ),
);
}
/**
* @covers FormatJson::stripComments
* @dataProvider provideStripComments
* @param string $json
* @param string $expect
*/
public function testStripComments( $json, $expect ) {
$this->assertSame( $expect, FormatJson::stripComments( $json ) );
}
public function provideParseStripComments() {
return array(
array( '/* blah */true', true ),
array( "// blah \ntrue", true ),
array( '[ "a" , /* blah */ "b" ]', array( 'a', 'b' ) ),
);
}
/**
* @covers FormatJson::parse
* @covers FormatJson::stripComments
* @dataProvider provideParseStripComments
* @param string $json
* @param mixed $expect
*/
public function testParseStripComments( $json, $expect ) {
$st = FormatJson::parse( $json, FormatJson::STRIP_COMMENTS );
$this->assertType( 'Status', $st );
$this->assertTrue( $st->isGood() );
$this->assertEquals( $expect, $st->getValue() );
}
/**
* Generate a set of test cases for a particular combination of encoder options.
*
* @param array $unescapedGroups List of character groups to leave unescaped
* @return array Arrays of unencoded strings and corresponding encoded strings
*/
private static function getEncodeTestCases( array $unescapedGroups ) {
$groups = array(
'always' => array(
// Forward slash (always unescaped)
'/' => '/',
// Control characters
"\0" => '\u0000',
"\x08" => '\b',
"\t" => '\t',
"\n" => '\n',
"\r" => '\r',
"\f" => '\f',
"\x1f" => '\u001f', // representative example
// Double quotes
'"' => '\"',
// Backslashes
'\\' => '\\\\',
'\\\\' => '\\\\\\\\',
'\\u00e9' => '\\\u00e9', // security check for Unicode unescaping
// Line terminators
"\xe2\x80\xa8" => '\u2028',
"\xe2\x80\xa9" => '\u2029',
),
'unicode' => array(
"\xc3\xa9" => '\u00e9',
"\xf0\x9d\x92\x9e" => '\ud835\udc9e', // U+1D49E, outside the BMP
),
'xmlmeta' => array(
'<' => '\u003C', // JSON_HEX_TAG uses uppercase hex digits
'>' => '\u003E',
'&' => '\u0026',
),
);
$cases = array();
foreach ( $groups as $name => $rules ) {
$leaveUnescaped = in_array( $name, $unescapedGroups );
foreach ( $rules as $from => $to ) {
$cases[] = array( $from, '"' . ( $leaveUnescaped ? $from : $to ) . '"' );
}
}
return $cases;
}
}