mirror of
https://github.com/dart-lang/sdk
synced 2024-10-06 17:15:02 +00:00
df97aca1fa
Closes https://github.com/dart-lang/sdk/pull/50764 GitOrigin-RevId: ee2fe9a75d50e877f4ad2fe3743acdbc04f186ef Change-Id: Ia73cd22da4e6ec95e84772aa4e1345ce2dbde215 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/276360 Reviewed-by: Erik Ernst <eernst@google.com> Commit-Queue: Erik Ernst <eernst@google.com>
420 lines
12 KiB
Dart
420 lines
12 KiB
Dart
// 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";
|
|
|
|
void testJson(jsonText, 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] in $jsonText");
|
|
}
|
|
} else if (expected is Map) {
|
|
Expect.isTrue(actual is Map);
|
|
Expect.equals(expected.length, actual.length,
|
|
"$path: Map size in $jsonText");
|
|
expected.forEach((key, value) {
|
|
Expect.isTrue(actual.containsKey(key));
|
|
compare(value, actual[key], "$path[$key] in $jsonText");
|
|
});
|
|
} else if (expected is num) {
|
|
Expect.equals(expected is int, actual is int,
|
|
"$path: not same number type in $jsonText");
|
|
Expect.isTrue(expected.compareTo(actual) == 0,
|
|
"$path: Expected: $expected, was: $actual in $jsonText");
|
|
} else {
|
|
// String, bool, null.
|
|
Expect.equals(expected, actual, "$path in $jsonText");
|
|
}
|
|
}
|
|
|
|
for (var reviver in [null, (k, v) => v]) {
|
|
for (var split in [0, 1, 2, 3, 4, 5]) {
|
|
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(jsonText.substring(0, 1));
|
|
decoderSink.add(jsonText.substring(1));
|
|
decoderSink.close();
|
|
break;
|
|
case 1:
|
|
// Split before last char.
|
|
int length = jsonText.length;
|
|
decoderSink.add(jsonText.substring(0, length - 1));
|
|
decoderSink.add(jsonText.substring(length - 1));
|
|
decoderSink.close();
|
|
break;
|
|
case 2:
|
|
// Split in middle.
|
|
int half = jsonText.length ~/ 2;
|
|
decoderSink.add(jsonText.substring(0, half));
|
|
decoderSink.add(jsonText.substring(half));
|
|
decoderSink.close();
|
|
break;
|
|
case 3:
|
|
// Split in three chunks.
|
|
int length = jsonText.length;
|
|
int third = length ~/ 3;
|
|
decoderSink.add(jsonText.substring(0, third));
|
|
decoderSink.add(jsonText.substring(third, 2 * third));
|
|
decoderSink.add(jsonText.substring(2 * third));
|
|
decoderSink.close();
|
|
break;
|
|
case 4:
|
|
// Use .decode
|
|
sink.add([json.decode(jsonText)]);
|
|
break;
|
|
case 5:
|
|
// Use jsonDecode
|
|
sink.add([jsonDecode(jsonText)]);
|
|
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(jsonText) {
|
|
var message = "json = '${escape(jsonText)}'";
|
|
Expect.throwsFormatException(() => json.decode(jsonText),
|
|
"json.decode, $message");
|
|
Expect.throwsFormatException(() => jsonDecode(jsonText),
|
|
"jsonDecode, $message");
|
|
Expect.throwsFormatException(() => json.decoder.convert(jsonText),
|
|
"json.decoder.convert, $message");
|
|
Expect.throwsFormatException(() =>
|
|
utf8.decoder.fuse(json.decoder).convert(utf8.encode(jsonText)),
|
|
"utf8.decoder.fuse(json.decoder) o utf.encode, $message");
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Regression test.
|
|
// Detect and handle overflow on integer literals by making them doubles
|
|
// (work like `num.parse`).
|
|
testJson("9223372036854774784", 9223372036854774784);
|
|
testJson("-9223372036854775808", -9223372036854775808);
|
|
testJson("9223372036854775808", 9223372036854775808.0);
|
|
testJson("-9223372036854775809", -9223372036854775809.0);
|
|
testJson("9223372036854775808.0", 9223372036854775808.0);
|
|
testJson("9223372036854775810", 9223372036854775810.0);
|
|
testJson("18446744073709551616.0", 18446744073709551616.0);
|
|
testJson("1e309", double.infinity);
|
|
testJson("-1e309", double.negativeInfinity);
|
|
testJson("1e-325", 0.0);
|
|
testJson("-1e-325", -0.0);
|
|
// No overflow on exponent.
|
|
testJson("1e18446744073709551616", double.infinity);
|
|
testJson("-1e18446744073709551616", double.negativeInfinity);
|
|
testJson("1e-18446744073709551616", 0.0);
|
|
testJson("-1e-18446744073709551616", -0.0);
|
|
|
|
// (Wrapping numbers in list because the chunked parsing handles top-level
|
|
// numbers by buffering and then parsing using platform parser).
|
|
testJson("[9223372036854774784]", [9223372036854774784]);
|
|
testJson("[-9223372036854775808]", [-9223372036854775808]);
|
|
testJson("[9223372036854775808]", [9223372036854775808.0]);
|
|
testJson("[-9223372036854775809]", [-9223372036854775809.0]);
|
|
testJson("[9223372036854775808.0]", [9223372036854775808.0]);
|
|
testJson("[9223372036854775810]", [9223372036854775810.0]);
|
|
testJson("[18446744073709551616.0]", [18446744073709551616.0]);
|
|
testJson("[1e309]", [double.infinity]);
|
|
testJson("[-1e309]", [double.negativeInfinity]);
|
|
testJson("[1e-325]", [0.0]);
|
|
testJson("[-1e-325]", [-0.0]);
|
|
// No overflow on exponent.
|
|
testJson("[1e18446744073709551616]", [double.infinity]);
|
|
testJson("[-1e18446744073709551616]", [double.negativeInfinity]);
|
|
testJson("[1e-18446744073709551616]", [0.0]);
|
|
testJson("[-1e-18446744073709551616]", [-0.0]);
|
|
|
|
// 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 jsonDecode accepts additional initial
|
|
// zeros.
|
|
|
|
// Only minus allowed as sign.
|
|
testError(signs: "+");
|
|
// Requires digits after decimal point.
|
|
testError(fractions: ".");
|
|
// Requires exponent digits, 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("01");
|
|
testThrows("0.");
|
|
testThrows(".0");
|
|
testThrows("0.e1");
|
|
|
|
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.negativeInfinity));
|
|
Expect.throws(() => jsonEncode(double.nan));
|
|
Expect.throws(() => jsonEncode(double.infinity));
|
|
Expect.throws(() => jsonEncode(double.negativeInfinity));
|
|
}
|
|
|
|
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();
|
|
}
|