PageRenderTime 25ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/tests/phpunit/includes/json/FormatJsonTest.php

https://gitlab.com/link233/bootmw
PHP | 375 lines | 247 code | 38 blank | 90 comment | 3 complexity | 1854009795f57ffb5897cc12efdb8879 MD5 | raw file
  1. <?php
  2. /**
  3. * @covers FormatJson
  4. */
  5. class FormatJsonTest extends MediaWikiTestCase {
  6. public static function provideEncoderPrettyPrinting() {
  7. return [
  8. // Four spaces
  9. [ true, ' ' ],
  10. [ ' ', ' ' ],
  11. // Two spaces
  12. [ ' ', ' ' ],
  13. // One tab
  14. [ "\t", "\t" ],
  15. ];
  16. }
  17. /**
  18. * @dataProvider provideEncoderPrettyPrinting
  19. */
  20. public function testEncoderPrettyPrinting( $pretty, $expectedIndent ) {
  21. $obj = [
  22. 'emptyObject' => new stdClass,
  23. 'emptyArray' => [],
  24. 'string' => 'foobar\\',
  25. 'filledArray' => [
  26. [
  27. 123,
  28. 456,
  29. ],
  30. // Nested json works without problems
  31. '"7":["8",{"9":"10"}]',
  32. // Whitespace clean up doesn't touch strings that look alike
  33. "{\n\t\"emptyObject\": {\n\t},\n\t\"emptyArray\": [ ]\n}",
  34. ],
  35. ];
  36. // No trailing whitespace, no trailing linefeed
  37. $json = '{
  38. "emptyObject": {},
  39. "emptyArray": [],
  40. "string": "foobar\\\\",
  41. "filledArray": [
  42. [
  43. 123,
  44. 456
  45. ],
  46. "\"7\":[\"8\",{\"9\":\"10\"}]",
  47. "{\n\t\"emptyObject\": {\n\t},\n\t\"emptyArray\": [ ]\n}"
  48. ]
  49. }';
  50. $json = str_replace( "\r", '', $json ); // Windows compat
  51. $json = str_replace( "\t", $expectedIndent, $json );
  52. $this->assertSame( $json, FormatJson::encode( $obj, $pretty ) );
  53. }
  54. public static function provideEncodeDefault() {
  55. return self::getEncodeTestCases( [] );
  56. }
  57. /**
  58. * @dataProvider provideEncodeDefault
  59. */
  60. public function testEncodeDefault( $from, $to ) {
  61. $this->assertSame( $to, FormatJson::encode( $from ) );
  62. }
  63. public static function provideEncodeUtf8() {
  64. return self::getEncodeTestCases( [ 'unicode' ] );
  65. }
  66. /**
  67. * @dataProvider provideEncodeUtf8
  68. */
  69. public function testEncodeUtf8( $from, $to ) {
  70. $this->assertSame( $to, FormatJson::encode( $from, false, FormatJson::UTF8_OK ) );
  71. }
  72. public static function provideEncodeXmlMeta() {
  73. return self::getEncodeTestCases( [ 'xmlmeta' ] );
  74. }
  75. /**
  76. * @dataProvider provideEncodeXmlMeta
  77. */
  78. public function testEncodeXmlMeta( $from, $to ) {
  79. $this->assertSame( $to, FormatJson::encode( $from, false, FormatJson::XMLMETA_OK ) );
  80. }
  81. public static function provideEncodeAllOk() {
  82. return self::getEncodeTestCases( [ 'unicode', 'xmlmeta' ] );
  83. }
  84. /**
  85. * @dataProvider provideEncodeAllOk
  86. */
  87. public function testEncodeAllOk( $from, $to ) {
  88. $this->assertSame( $to, FormatJson::encode( $from, false, FormatJson::ALL_OK ) );
  89. }
  90. public function testEncodePhpBug46944() {
  91. $this->assertNotEquals(
  92. '\ud840\udc00',
  93. strtolower( FormatJson::encode( "\xf0\xa0\x80\x80" ) ),
  94. 'Test encoding an broken json_encode character (U+20000)'
  95. );
  96. }
  97. public function testDecodeReturnType() {
  98. $this->assertInternalType(
  99. 'object',
  100. FormatJson::decode( '{"Name": "Cheeso", "Rank": 7}' ),
  101. 'Default to object'
  102. );
  103. $this->assertInternalType(
  104. 'array',
  105. FormatJson::decode( '{"Name": "Cheeso", "Rank": 7}', true ),
  106. 'Optional array'
  107. );
  108. }
  109. public static function provideParse() {
  110. return [
  111. [ null ],
  112. [ true ],
  113. [ false ],
  114. [ 0 ],
  115. [ 1 ],
  116. [ 1.2 ],
  117. [ '' ],
  118. [ 'str' ],
  119. [ [ 0, 1, 2 ] ],
  120. [ [ 'a' => 'b' ] ],
  121. [ [ 'a' => 'b' ] ],
  122. [ [ 'a' => 'b', 'x' => [ 'c' => 'd' ] ] ],
  123. ];
  124. }
  125. /**
  126. * Recursively convert arrays into stdClass
  127. * @param array|string|bool|int|float|null $value
  128. * @return stdClass|string|bool|int|float|null
  129. */
  130. public static function toObject( $value ) {
  131. return !is_array( $value ) ? $value : (object) array_map( __METHOD__, $value );
  132. }
  133. /**
  134. * @dataProvider provideParse
  135. * @param mixed $value
  136. */
  137. public function testParse( $value ) {
  138. $expected = self::toObject( $value );
  139. $json = FormatJson::encode( $expected, false, FormatJson::ALL_OK );
  140. $this->assertJson( $json );
  141. $st = FormatJson::parse( $json );
  142. $this->assertInstanceOf( 'Status', $st );
  143. $this->assertTrue( $st->isGood() );
  144. $this->assertEquals( $expected, $st->getValue() );
  145. $st = FormatJson::parse( $json, FormatJson::FORCE_ASSOC );
  146. $this->assertInstanceOf( 'Status', $st );
  147. $this->assertTrue( $st->isGood() );
  148. $this->assertEquals( $value, $st->getValue() );
  149. }
  150. /**
  151. * Test data for testParseTryFixing.
  152. *
  153. * Some PHP interpreters use json-c rather than the JSON.org cannonical
  154. * parser to avoid being encumbered by the "shall be used for Good, not
  155. * Evil" clause of the JSON.org parser's license. By default, json-c
  156. * parses in a non-strict mode which allows trailing commas for array and
  157. * object delarations among other things, so our JSON_ERROR_SYNTAX rescue
  158. * block is not always triggered. It however isn't lenient in exactly the
  159. * same ways as our TRY_FIXING mode, so the assertions in this test are
  160. * a bit more complicated than they ideally would be:
  161. *
  162. * Optional third argument: true if json-c parses the value without
  163. * intervention, false otherwise. Defaults to true.
  164. *
  165. * Optional fourth argument: expected cannonical JSON serialization of
  166. * json-c parsed result. Defaults to the second argument's value.
  167. */
  168. public static function provideParseTryFixing() {
  169. return [
  170. [ "[,]", '[]', false ],
  171. [ "[ , ]", '[]', false ],
  172. [ "[ , }", false ],
  173. [ '[1],', false, true, '[1]' ],
  174. [ "[1,]", '[1]' ],
  175. [ "[1\n,]", '[1]' ],
  176. [ "[1,\n]", '[1]' ],
  177. [ "[1,]\n", '[1]' ],
  178. [ "[1\n,\n]\n", '[1]' ],
  179. [ '["a,",]', '["a,"]' ],
  180. [ "[[1,]\n,[2,\n],[3\n,]]", '[[1],[2],[3]]' ],
  181. // I wish we could parse this, but would need quote parsing
  182. [ '[[1,],[2,],[3,]]', false, true, '[[1],[2],[3]]' ],
  183. [ '[1,,]', false, false, '[1]' ],
  184. ];
  185. }
  186. /**
  187. * @dataProvider provideParseTryFixing
  188. * @param string $value
  189. * @param string|bool $expected Expected result with strict parser
  190. * @param bool $jsoncParses Will json-c parse this value without TRY_FIXING?
  191. * @param string|bool $expectedJsonc Expected result with lenient parser
  192. * if different from the strict expectation
  193. */
  194. public function testParseTryFixing(
  195. $value, $expected,
  196. $jsoncParses = true, $expectedJsonc = null
  197. ) {
  198. // PHP5 results are always expected to have isGood() === false
  199. $expectedGoodStatus = false;
  200. // Check to see if json parser allows trailing commas
  201. if ( json_decode( '[1,]' ) !== null ) {
  202. // Use json-c specific expected result if provided
  203. $expected = ( $expectedJsonc === null ) ? $expected : $expectedJsonc;
  204. // If json-c parses the value natively, expect isGood() === true
  205. $expectedGoodStatus = $jsoncParses;
  206. }
  207. $st = FormatJson::parse( $value, FormatJson::TRY_FIXING );
  208. $this->assertInstanceOf( 'Status', $st );
  209. if ( $expected === false ) {
  210. $this->assertFalse( $st->isOK(), 'Expected isOK() == false' );
  211. } else {
  212. $this->assertSame( $expectedGoodStatus, $st->isGood(),
  213. 'Expected isGood() == ' . ( $expectedGoodStatus ? 'true' : 'false' )
  214. );
  215. $this->assertTrue( $st->isOK(), 'Expected isOK == true' );
  216. $val = FormatJson::encode( $st->getValue(), false, FormatJson::ALL_OK );
  217. $this->assertEquals( $expected, $val );
  218. }
  219. }
  220. public static function provideParseErrors() {
  221. return [
  222. [ 'aaa' ],
  223. [ '{"j": 1 ] }' ],
  224. ];
  225. }
  226. /**
  227. * @dataProvider provideParseErrors
  228. * @param mixed $value
  229. */
  230. public function testParseErrors( $value ) {
  231. $st = FormatJson::parse( $value );
  232. $this->assertInstanceOf( 'Status', $st );
  233. $this->assertFalse( $st->isOK() );
  234. }
  235. public function provideStripComments() {
  236. return [
  237. [ '{"a":"b"}', '{"a":"b"}' ],
  238. [ "{\"a\":\"b\"}\n", "{\"a\":\"b\"}\n" ],
  239. [ '/*c*/{"c":"b"}', '{"c":"b"}' ],
  240. [ '{"a":"c"}/*c*/', '{"a":"c"}' ],
  241. [ '/*c//d*/{"c":"b"}', '{"c":"b"}' ],
  242. [ '{/*c*/"c":"b"}', '{"c":"b"}' ],
  243. [ "/*\nc\r\n*/{\"c\":\"b\"}", '{"c":"b"}' ],
  244. [ "//c\n{\"c\":\"b\"}", '{"c":"b"}' ],
  245. [ "//c\r\n{\"c\":\"b\"}", '{"c":"b"}' ],
  246. [ '{"a":"c"}//c', '{"a":"c"}' ],
  247. [ "{\"a-c\"://c\n\"b\"}", '{"a-c":"b"}' ],
  248. [ '{"/*a":"b"}', '{"/*a":"b"}' ],
  249. [ '{"a":"//b"}', '{"a":"//b"}' ],
  250. [ '{"a":"b/*c*/"}', '{"a":"b/*c*/"}' ],
  251. [ "{\"\\\"/*a\":\"b\"}", "{\"\\\"/*a\":\"b\"}" ],
  252. [ '', '' ],
  253. [ '/*c', '' ],
  254. [ '//c', '' ],
  255. [ '"http://example.com"', '"http://example.com"' ],
  256. [ "\0", "\0" ],
  257. [ '"Blåbærsyltetøy"', '"Blåbærsyltetøy"' ],
  258. ];
  259. }
  260. /**
  261. * @covers FormatJson::stripComments
  262. * @dataProvider provideStripComments
  263. * @param string $json
  264. * @param string $expect
  265. */
  266. public function testStripComments( $json, $expect ) {
  267. $this->assertSame( $expect, FormatJson::stripComments( $json ) );
  268. }
  269. public function provideParseStripComments() {
  270. return [
  271. [ '/* blah */true', true ],
  272. [ "// blah \ntrue", true ],
  273. [ '[ "a" , /* blah */ "b" ]', [ 'a', 'b' ] ],
  274. ];
  275. }
  276. /**
  277. * @covers FormatJson::parse
  278. * @covers FormatJson::stripComments
  279. * @dataProvider provideParseStripComments
  280. * @param string $json
  281. * @param mixed $expect
  282. */
  283. public function testParseStripComments( $json, $expect ) {
  284. $st = FormatJson::parse( $json, FormatJson::STRIP_COMMENTS );
  285. $this->assertInstanceOf( 'Status', $st );
  286. $this->assertTrue( $st->isGood() );
  287. $this->assertEquals( $expect, $st->getValue() );
  288. }
  289. /**
  290. * Generate a set of test cases for a particular combination of encoder options.
  291. *
  292. * @param array $unescapedGroups List of character groups to leave unescaped
  293. * @return array Arrays of unencoded strings and corresponding encoded strings
  294. */
  295. private static function getEncodeTestCases( array $unescapedGroups ) {
  296. $groups = [
  297. 'always' => [
  298. // Forward slash (always unescaped)
  299. '/' => '/',
  300. // Control characters
  301. "\0" => '\u0000',
  302. "\x08" => '\b',
  303. "\t" => '\t',
  304. "\n" => '\n',
  305. "\r" => '\r',
  306. "\f" => '\f',
  307. "\x1f" => '\u001f', // representative example
  308. // Double quotes
  309. '"' => '\"',
  310. // Backslashes
  311. '\\' => '\\\\',
  312. '\\\\' => '\\\\\\\\',
  313. '\\u00e9' => '\\\u00e9', // security check for Unicode unescaping
  314. // Line terminators
  315. "\xe2\x80\xa8" => '\u2028',
  316. "\xe2\x80\xa9" => '\u2029',
  317. ],
  318. 'unicode' => [
  319. "\xc3\xa9" => '\u00e9',
  320. "\xf0\x9d\x92\x9e" => '\ud835\udc9e', // U+1D49E, outside the BMP
  321. ],
  322. 'xmlmeta' => [
  323. '<' => '\u003C', // JSON_HEX_TAG uses uppercase hex digits
  324. '>' => '\u003E',
  325. '&' => '\u0026',
  326. ],
  327. ];
  328. $cases = [];
  329. foreach ( $groups as $name => $rules ) {
  330. $leaveUnescaped = in_array( $name, $unescapedGroups );
  331. foreach ( $rules as $from => $to ) {
  332. $cases[] = [ $from, '"' . ( $leaveUnescaped ? $from : $to ) . '"' ];
  333. }
  334. }
  335. return $cases;
  336. }
  337. }