Index: test/codegen/lib/convert/json_test.dart |
diff --git a/test/codegen/lib/convert/json_test.dart b/test/codegen/lib/convert/json_test.dart |
new file mode 100644 |
index 0000000000000000000000000000000000000000..ef7b077d4c8c622a5f59044de9fd3b9c8073236f |
--- /dev/null |
+++ b/test/codegen/lib/convert/json_test.dart |
@@ -0,0 +1,328 @@ |
+// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
+// for details. All rights reserved. Use of this source code is governed by a |
+// BSD-style license that can be found in the LICENSE file. |
+// Disable background compilation so that the Issue 24908 can be reproduced. |
+// VMOptions=--no-background-compilation |
+ |
+library json_test; |
+ |
+import "package:expect/expect.dart"; |
+import "dart:convert"; |
+ |
+bool badFormat(e) => e is FormatException; |
+ |
+void testJson(json, expected) { |
+ compare(expected, actual, path) { |
+ if (expected is List) { |
+ Expect.isTrue(actual is List); |
+ Expect.equals(expected.length, actual.length, "$path: List length"); |
+ for (int i = 0; i < expected.length; i++) { |
+ compare(expected[i], actual[i], "$path[$i]"); |
+ } |
+ } else if (expected is Map) { |
+ Expect.isTrue(actual is Map); |
+ Expect.equals(expected.length, actual.length, "$path: Map size"); |
+ expected.forEach((key, value) { |
+ Expect.isTrue(actual.containsKey(key)); |
+ compare(value, actual[key], "$path[$key]"); |
+ }); |
+ } else if (expected is num) { |
+ Expect.equals(expected is int, actual is int, "$path: same number type"); |
+ Expect.isTrue(expected.compareTo(actual) == 0, |
+ "$path: Expected: $expected, was: $actual"); |
+ } else { |
+ // String, bool, null. |
+ Expect.equals(expected, actual, path); |
+ } |
+ } |
+ for (var reviver in [null, (k, v) => v]) { |
+ for (var split in [0, 1, 2, 3]) { |
+ var name = (reviver == null) ? "" : "reviver:"; |
+ var sink = new ChunkedConversionSink.withCallback((values) { |
+ var value = values[0]; |
+ compare(expected, value, "$name$value"); |
+ }); |
+ var decoderSink = JSON.decoder.startChunkedConversion(sink); |
+ switch (split) { |
+ case 0: |
+ // Split after first char. |
+ decoderSink.add(json.substring(0, 1)); |
+ decoderSink.add(json.substring(1)); |
+ decoderSink.close(); |
+ break; |
+ case 1: |
+ // Split before last char. |
+ int length = json.length; |
+ decoderSink.add(json.substring(0, length - 1)); |
+ decoderSink.add(json.substring(length - 1)); |
+ decoderSink.close(); |
+ break; |
+ case 2: |
+ // Split in middle. |
+ int half = json.length ~/ 2; |
+ decoderSink.add(json.substring(0, half)); |
+ decoderSink.add(json.substring(half)); |
+ decoderSink.close(); |
+ break; |
+ case 3: |
+ // Split in three chunks. |
+ int length = json.length; |
+ int third = length ~/ 3; |
+ decoderSink.add(json.substring(0, third)); |
+ decoderSink.add(json.substring(third, 2 * third)); |
+ decoderSink.add(json.substring(2 * third)); |
+ decoderSink.close(); |
+ break; |
+ } |
+ } |
+ } |
+} |
+ |
+String escape(String s) { |
+ var sb = new StringBuffer(); |
+ for (int i = 0; i < s.length; i++) { |
+ int code = s.codeUnitAt(i); |
+ if (code == '\\'.codeUnitAt(0)) sb.write(r'\\'); |
+ else if (code == '\"'.codeUnitAt(0)) sb.write(r'\"'); |
+ else if (code >= 32 && code < 127) sb.writeCharCode(code); |
+ else { |
+ String hex = '000${code.toRadixString(16)}'; |
+ sb.write(r'\u' '${hex.substring(hex.length - 4)}'); |
+ } |
+ } |
+ return '$sb'; |
+} |
+ |
+void testThrows(json) { |
+ Expect.throws(() => JSON.decode(json), badFormat, "json = '${escape(json)}'"); |
+} |
+ |
+testNumbers() { |
+ // Positive tests for number formats. |
+ var integerList = ["0","9","9999"]; |
+ var signList = ["", "-"]; |
+ var fractionList = ["", ".0", ".1", ".99999"]; |
+ var exponentList = [""]; |
+ for (var exphead in ["e", "E", "e-", "E-", "e+", "E+"]) { |
+ for (var expval in ["0", "1", "200"]) { |
+ exponentList.add("$exphead$expval"); |
+ } |
+ } |
+ |
+ for (var integer in integerList) { |
+ for (var sign in signList) { |
+ for (var fraction in fractionList) { |
+ for (var exp in exponentList) { |
+ for (var ws in ["", " ", "\t"]) { |
+ var literal = "$ws$sign$integer$fraction$exp$ws"; |
+ var expectedValue = num.parse(literal); |
+ testJson(literal, expectedValue); |
+ } |
+ } |
+ } |
+ } |
+ } |
+ |
+ // Negative tests (syntax error). |
+ // testError thoroughly tests the given parts with a lot of valid |
+ // values for the other parts. |
+ testError({signs, integers, fractions, exponents}) { |
+ def(value, defaultValue) { |
+ if (value == null) return defaultValue; |
+ if (value is List) return value; |
+ return [value]; |
+ } |
+ signs = def(signs, signList); |
+ integers = def(integers, integerList); |
+ fractions = def(fractions, fractionList); |
+ exponents = def(exponents, exponentList); |
+ for (var integer in integers) { |
+ for (var sign in signs) { |
+ for (var fraction in fractions) { |
+ for (var exponent in exponents) { |
+ var literal = "$sign$integer$fraction$exponent"; |
+ testThrows(literal); |
+ } |
+ } |
+ } |
+ } |
+ } |
+ // Doubles overflow to Infinity. |
+ testJson("1e+400", double.INFINITY); |
+ // (Integers do not, but we don't have those on dart2js). |
+ |
+ // Integer part cannot be omitted: |
+ testError(integers: ""); |
+ |
+ // Test for "Initial zero only allowed for zero integer part" moved to |
+ // json_strict_test.dart because IE's JSON.decode accepts additional initial |
+ // zeros. |
+ |
+ // Only minus allowed as sign. |
+ testError(signs: "+"); |
+ // Requires digits after decimal point. |
+ testError(fractions: "."); |
+ // Requires exponent digts, and only digits. |
+ testError(exponents: ["e", "e+", "e-", "e.0"]); |
+ |
+ // No whitespace inside numbers. |
+ // Additional case "- 2.2e+2" in json_strict_test.dart. |
+ testThrows("-2 .2e+2"); |
+ testThrows("-2. 2e+2"); |
+ testThrows("-2.2 e+2"); |
+ testThrows("-2.2e +2"); |
+ testThrows("-2.2e+ 2"); |
+ |
+ testThrows("[2.,2]"); |
+ testThrows("{2.:2}"); |
+ |
+ testThrows("NaN"); |
+ testThrows("Infinity"); |
+ testThrows("-Infinity"); |
+ Expect.throws(() => JSON.encode(double.NAN)); |
+ Expect.throws(() => JSON.encode(double.INFINITY)); |
+ Expect.throws(() => JSON.encode(double.NEGATIVE_INFINITY)); |
+} |
+ |
+testStrings() { |
+ // String parser accepts and understands escapes. |
+ var input = r'"\u0000\uffff\n\r\f\t\b\/\\\"' '\x20\ufffd\uffff"'; |
+ var expected = "\u0000\uffff\n\r\f\t\b\/\\\"\x20\ufffd\uffff"; |
+ testJson(input, expected); |
+ // Empty string. |
+ testJson(r'""', ""); |
+ // Escape first. |
+ var escapes = { |
+ "f": "\f", |
+ "b": "\b", |
+ "n": "\n", |
+ "r": "\r", |
+ "t": "\t", |
+ r"\": r"\", |
+ '"': '"', |
+ "/": "/", |
+ }; |
+ escapes.forEach((esc, lit) { |
+ testJson('"\\$esc........"', "$lit........"); |
+ // Escape last. |
+ testJson('"........\\$esc"', "........$lit"); |
+ // Escape middle. |
+ testJson('"....\\$esc...."', "....$lit...."); |
+ }); |
+ |
+ // Does not accept single quotes. |
+ testThrows(r"''"); |
+ // Throws on unterminated strings. |
+ testThrows(r'"......\"'); |
+ // Throws on unterminated escapes. |
+ testThrows(r'"\'); // ' is not escaped. |
+ testThrows(r'"\a"'); |
+ testThrows(r'"\u"'); |
+ testThrows(r'"\u1"'); |
+ testThrows(r'"\u12"'); |
+ testThrows(r'"\u123"'); |
+ testThrows(r'"\ux"'); |
+ testThrows(r'"\u1x"'); |
+ testThrows(r'"\u12x"'); |
+ testThrows(r'"\u123x"'); |
+ // Throws on bad escapes. |
+ testThrows(r'"\a"'); |
+ testThrows(r'"\x00"'); |
+ testThrows(r'"\c2"'); |
+ testThrows(r'"\000"'); |
+ testThrows(r'"\u{0}"'); |
+ testThrows(r'"\%"'); |
+ testThrows('"\\\x00"'); // Not raw string! |
+ // Throws on control characters. |
+ for (int i = 0; i < 32; i++) { |
+ var string = new String.fromCharCodes([0x22,i,0x22]); // '"\x00"' etc. |
+ testThrows(string); |
+ } |
+} |
+ |
+ |
+testObjects() { |
+ testJson(r'{}', {}); |
+ testJson(r'{"x":42}', {"x":42}); |
+ testJson(r'{"x":{"x":{"x":42}}}', {"x": {"x": {"x": 42}}}); |
+ testJson(r'{"x":10,"x":42}', {"x": 42}); |
+ testJson(r'{"":42}', {"": 42}); |
+ |
+ // Keys must be strings. |
+ testThrows(r'{x:10}'); |
+ testThrows(r'{true:10}'); |
+ testThrows(r'{false:10}'); |
+ testThrows(r'{null:10}'); |
+ testThrows(r'{42:10}'); |
+ testThrows(r'{42e1:10}'); |
+ testThrows(r'{-42:10}'); |
+ testThrows(r'{["text"]:10}'); |
+ testThrows(r'{:10}'); |
+} |
+ |
+testArrays() { |
+ testJson(r'[]', []); |
+ testJson(r'[1.1e1,"string",true,false,null,{}]', |
+ [1.1e1, "string", true, false, null, {}]); |
+ testJson(r'[[[[[[]]]],[[[]]],[[]]]]', [[[[[[]]]],[[[]]],[[]]]]); |
+ testJson(r'[{},[{}],{"x":[]}]', [{},[{}],{"x":[]}]); |
+ |
+ testThrows(r'[1,,2]'); |
+ testThrows(r'[1,2,]'); |
+ testThrows(r'[,2]'); |
+} |
+ |
+testWords() { |
+ testJson(r'true', true); |
+ testJson(r'false', false); |
+ testJson(r'null', null); |
+ testJson(r'[true]', [true]); |
+ testJson(r'{"true":true}', {"true": true}); |
+ |
+ testThrows(r'truefalse'); |
+ testThrows(r'trues'); |
+ testThrows(r'nulll'); |
+ testThrows(r'full'); |
+ testThrows(r'nul'); |
+ testThrows(r'tru'); |
+ testThrows(r'fals'); |
+ testThrows(r'\null'); |
+ testThrows(r't\rue'); |
+ testThrows(r't\rue'); |
+} |
+ |
+testWhitespace() { |
+ // Valid white-space characters. |
+ var v = '\t\r\n\ '; |
+ // Invalid white-space and non-recognized characters. |
+ var invalids = ['\x00', '\f', '\x08', '\\', '\xa0','\u2028', '\u2029']; |
+ |
+ // Valid whitespace accepted "everywhere". |
+ testJson('$v[${v}-2.2e2$v,$v{$v"key"$v:${v}true$v}$v,$v"ab"$v]$v', |
+ [-2.2e2, {"key": true}, "ab"]); |
+ |
+ // IE9 accepts invalid characters at the end, so some of these tests have been |
+ // moved to json_strict_test.dart. |
+ for (var i in invalids) { |
+ testThrows('${i}"s"'); |
+ testThrows('42${i}'); |
+ testThrows('$i[]'); |
+ testThrows('[$i]'); |
+ testThrows('[$i"s"]'); |
+ testThrows('["s"$i]'); |
+ testThrows('$i{"k":"v"}'); |
+ testThrows('{$i"k":"v"}'); |
+ testThrows('{"k"$i:"v"}'); |
+ testThrows('{"k":$i"v"}'); |
+ testThrows('{"k":"v"$i}'); |
+ } |
+} |
+ |
+main() { |
+ testNumbers(); |
+ testStrings(); |
+ testWords(); |
+ testObjects(); |
+ testArrays(); |
+ testWhitespace(); |
+} |