| 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();
 | 
| +}
 | 
| 
 |