OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | |
2 // for details. All rights reserved. Use of this source code is governed by a | |
3 // BSD-style license that can be found in the LICENSE file. | |
4 | |
5 library json_test; | |
6 | |
7 import "package:expect/expect.dart"; | |
8 import "dart:convert"; | |
9 | |
10 bool badFormat(e) => e is FormatException; | |
11 | |
12 void testJson(json, expected) { | |
13 var value = JSON.decode(json); | |
14 compare(expected, actual, path) { | |
15 if (expected is List) { | |
16 Expect.isTrue(actual is List); | |
17 Expect.equals(expected.length, actual.length, "$path: List length"); | |
18 for (int i = 0; i < expected.length; i++) { | |
19 compare(expected[i], actual[i], "$path[$i]"); | |
20 } | |
21 } else if (expected is Map) { | |
22 Expect.isTrue(actual is Map); | |
23 Expect.equals(expected.length, actual.length, "$path: Map size"); | |
24 expected.forEach((key, value) { | |
25 Expect.isTrue(actual.containsKey(key)); | |
26 compare(value, actual[key], "$path[$key]"); | |
27 }); | |
28 } else if (expected is num) { | |
29 Expect.equals(expected is int, actual is int, "$path: same number type"); | |
30 Expect.isTrue(expected.compareTo(actual) == 0, | |
31 "$path: $expected vs. $actual"); | |
32 } else { | |
33 // String, bool, null. | |
34 Expect.equals(expected, actual, path); | |
35 } | |
36 } | |
37 compare(expected, value, "value"); | |
38 } | |
39 | |
40 String escape(String s) { | |
41 var sb = new StringBuffer(); | |
42 for (int i = 0; i < s.length; i++) { | |
43 int code = s.codeUnitAt(i); | |
44 if (code == '\\'.codeUnitAt(0)) sb.write(r'\\'); | |
45 else if (code == '\"'.codeUnitAt(0)) sb.write(r'\"'); | |
46 else if (code >= 32 && code < 127) sb.writeCharCode(code); | |
47 else { | |
48 String hex = '000${code.toRadixString(16)}'; | |
49 sb.write(r'\u' '${hex.substring(hex.length - 4)}'); | |
50 } | |
51 } | |
52 return '$sb'; | |
53 } | |
54 | |
55 void testThrows(json) { | |
56 Expect.throws(() => JSON.decode(json), badFormat, "json = '${escape(json)}'"); | |
57 } | |
58 | |
59 testNumbers() { | |
60 // Positive tests for number formats. | |
61 var integerList = ["0","9","9999"]; | |
62 var signList = ["", "-"]; | |
63 var fractionList = ["", ".0", ".1", ".99999"]; | |
64 var exponentList = [""]; | |
65 for (var exphead in ["e", "E", "e-", "E-", "e+", "E+"]) { | |
66 for (var expval in ["0", "1", "200"]) { | |
67 exponentList.add("$exphead$expval"); | |
68 } | |
69 } | |
70 | |
71 for (var integer in integerList) { | |
72 for (var sign in signList) { | |
73 for (var fraction in fractionList) { | |
74 for (var exp in exponentList) { | |
75 var literal = "$sign$integer$fraction$exp"; | |
76 var parseNumber = | |
77 ((fraction == "" && exp == "") ? (String x) => int.parse(x) | |
78 : (String x) => double.parse(x)); | |
79 var expectedValue = parseNumber(literal); | |
80 testJson(literal, expectedValue); | |
81 } | |
82 } | |
83 } | |
84 } | |
85 | |
86 // Negative tests (syntax error). | |
87 // testError thoroughly tests the given parts with a lot of valid | |
88 // values for the other parts. | |
89 testError({signs, integers, fractions, exponents}) { | |
90 def(value, defaultValue) { | |
91 if (value == null) return defaultValue; | |
92 if (value is List) return value; | |
93 return [value]; | |
94 } | |
95 signs = def(signs, signList); | |
96 integers = def(integers, integerList); | |
97 fractions = def(fractions, fractionList); | |
98 exponents = def(exponents, exponentList); | |
99 for (var integer in integers) { | |
100 for (var sign in signs) { | |
101 for (var fraction in fractions) { | |
102 for (var exponent in exponents) { | |
103 var literal = "$sign$integer$fraction$exponent"; | |
104 testThrows(literal); | |
105 } | |
106 } | |
107 } | |
108 } | |
109 } | |
110 // Doubles overflow to Infinity. | |
111 testJson("1e+400", double.INFINITY); | |
112 // (Integers do not, but we don't have those on dart2js). | |
113 | |
114 // Integer part cannot be omitted: | |
115 testError(integers: ""); | |
116 | |
117 // Test for "Initial zero only allowed for zero integer part" moved to | |
118 // json_strict_test.dart because IE's JSON.decode accepts additional initial | |
119 // zeros. | |
120 | |
121 // Only minus allowed as sign. | |
122 testError(signs: "+"); | |
123 // Requires digits after decimal point. | |
124 testError(fractions: "."); | |
125 // Requires exponent digts, and only digits. | |
126 testError(exponents: ["e", "e+", "e-", "e.0"]); | |
127 | |
128 // No whitespace inside numbers. | |
129 // Additional case "- 2.2e+2" in json_strict_test.dart. | |
130 testThrows("-2 .2e+2"); | |
131 testThrows("-2. 2e+2"); | |
132 testThrows("-2.2 e+2"); | |
133 testThrows("-2.2e +2"); | |
134 testThrows("-2.2e+ 2"); | |
135 | |
136 testThrows("[2.,2]"); | |
137 testThrows("{2.:2}"); | |
138 } | |
139 | |
140 testStrings() { | |
141 // String parser accepts and understands escapes. | |
142 var input = r'"\u0000\uffff\n\r\f\t\b\/\\\"' '\x20\ufffd\uffff"'; | |
143 var expected = "\u0000\uffff\n\r\f\t\b\/\\\"\x20\ufffd\uffff"; | |
144 testJson(input, expected); | |
145 // Empty string. | |
146 testJson(r'""', ""); | |
147 // Escape first. | |
148 testJson(r'"\"........"', "\"........"); | |
149 // Escape last. | |
150 testJson(r'"........\""', "........\""); | |
151 // Escape middle. | |
152 testJson(r'"....\"...."', "....\"...."); | |
153 | |
154 // Does not accept single quotes. | |
155 testThrows(r"''"); | |
156 // Throws on unterminated strings. | |
157 testThrows(r'"......\"'); | |
158 // Throws on unterminated escapes. | |
159 testThrows(r'"\'); // ' is not escaped. | |
160 testThrows(r'"\a"'); | |
161 testThrows(r'"\u"'); | |
162 testThrows(r'"\u1"'); | |
163 testThrows(r'"\u12"'); | |
164 testThrows(r'"\u123"'); | |
165 testThrows(r'"\ux"'); | |
166 testThrows(r'"\u1x"'); | |
167 testThrows(r'"\u12x"'); | |
168 testThrows(r'"\u123x"'); | |
169 // Throws on bad escapes. | |
170 testThrows(r'"\a"'); | |
171 testThrows(r'"\x00"'); | |
172 testThrows(r'"\c2"'); | |
173 testThrows(r'"\000"'); | |
174 testThrows(r'"\u{0}"'); | |
175 testThrows(r'"\%"'); | |
176 testThrows('"\\\x00"'); // Not raw string! | |
177 // Throws on control characters. | |
178 for (int i = 0; i < 32; i++) { | |
179 var string = new String.fromCharCodes([0x22,i,0x22]); // '"\x00"' etc. | |
180 testThrows(string); | |
181 } | |
182 } | |
183 | |
184 | |
185 testObjects() { | |
186 testJson(r'{}', {}); | |
187 testJson(r'{"x":42}', {"x":42}); | |
188 testJson(r'{"x":{"x":{"x":42}}}', {"x": {"x": {"x": 42}}}); | |
189 testJson(r'{"x":10,"x":42}', {"x": 42}); | |
190 testJson(r'{"":42}', {"": 42}); | |
191 | |
192 // Keys must be strings. | |
193 testThrows(r'{x:10}'); | |
194 testThrows(r'{true:10}'); | |
195 testThrows(r'{false:10}'); | |
196 testThrows(r'{null:10}'); | |
197 testThrows(r'{42:10}'); | |
198 testThrows(r'{42e1:10}'); | |
199 testThrows(r'{-42:10}'); | |
200 testThrows(r'{["text"]:10}'); | |
201 testThrows(r'{:10}'); | |
202 } | |
203 | |
204 testArrays() { | |
205 testJson(r'[]', []); | |
206 testJson(r'[1.1e1,"string",true,false,null,{}]', | |
207 [1.1e1, "string", true, false, null, {}]); | |
208 testJson(r'[[[[[[]]]],[[[]]],[[]]]]', [[[[[[]]]],[[[]]],[[]]]]); | |
209 testJson(r'[{},[{}],{"x":[]}]', [{},[{}],{"x":[]}]); | |
210 | |
211 testThrows(r'[1,,2]'); | |
212 testThrows(r'[1,2,]'); | |
213 testThrows(r'[,2]'); | |
214 } | |
215 | |
216 testWords() { | |
217 testJson(r'true', true); | |
218 testJson(r'false', false); | |
219 testJson(r'null', null); | |
220 testJson(r'[true]', [true]); | |
221 testJson(r'{"true":true}', {"true": true}); | |
222 | |
223 testThrows(r'truefalse'); | |
224 testThrows(r'trues'); | |
225 testThrows(r'nulll'); | |
226 testThrows(r'full'); | |
227 testThrows(r'nul'); | |
228 testThrows(r'tru'); | |
229 testThrows(r'fals'); | |
230 testThrows(r'\null'); | |
231 testThrows(r't\rue'); | |
232 testThrows(r't\rue'); | |
233 } | |
234 | |
235 testWhitespace() { | |
236 // Valid white-space characters. | |
237 var v = '\t\r\n\ '; | |
238 // Invalid white-space and non-recognized characters. | |
239 var invalids = ['\x00', '\f', '\x08', '\\', '\xa0','\u2028', '\u2029']; | |
240 | |
241 // Valid whitespace accepted "everywhere". | |
242 testJson('$v[${v}-2.2e2$v,$v{$v"key"$v:${v}true$v}$v,$v"ab"$v]$v', | |
243 [-2.2e2, {"key": true}, "ab"]); | |
244 | |
245 // IE9 accepts invalid characters at the end, so some of these tests have been | |
246 // moved to json_strict_test.dart. | |
247 for (var i in invalids) { | |
248 testThrows('${i}"s"'); | |
249 testThrows('42${i}'); | |
250 testThrows('$i[]'); | |
251 testThrows('[$i]'); | |
252 testThrows('[$i"s"]'); | |
253 testThrows('["s"$i]'); | |
254 testThrows('$i{"k":"v"}'); | |
255 testThrows('{$i"k":"v"}'); | |
256 testThrows('{"k"$i:"v"}'); | |
257 testThrows('{"k":$i"v"}'); | |
258 testThrows('{"k":"v"$i}'); | |
259 } | |
260 } | |
261 | |
262 main() { | |
263 testNumbers(); | |
264 testStrings(); | |
265 testWords(); | |
266 testObjects(); | |
267 testArrays(); | |
268 testWhitespace(); | |
269 } | |
OLD | NEW |