1// SPDX-License-Identifier: MIT
2
3package expr
4
5import (
6 "math"
7 "strings"
8 "testing"
9)
10
11// The accepted and rejected inputs below were established by running
12// fromJSON through the runner's own code (gen/runner): Json.NET's reader, not
13// JSON.parse.
14func TestFromJSONValid(t *testing.T) {
15 tests := []struct {
16 in string
17 want string // ToJSON of the result
18 }{
19 {"null", "null"},
20 {" true ", "true"},
21 {"\t\r\n false \n", "false"},
22 {"0", "0"},
23 {"-12.5e1", "-125"},
24 {"1E2", "100"},
25 {"0.1e1", "1"},
26 {`"a\u00e9\n"`, `"aé\n"`},
27 {`"\ud83d\ude00"`, `"😀"`},
28 {`"\/"`, `"/"`},
29 {`[]`, `[]`},
30 {`{}`, `{}`},
31 {`[ 1 , [ ] , { } ]`, "[\n 1,\n [],\n {}\n]"},
32 // An unpaired surrogate escape becomes U+FFFD.
33 {`"\ud800"`, "\"\ufffd\""},
34 {`"\udc00x"`, "\"\ufffdx\""},
35 {`"\ud800\u0041"`, "\"\ufffdA\""},
36 // Repeated keys: the last value, at the first position and spelling;
37 // keys compare case-insensitively.
38 {`{"a":1,"A":2}`, "{\n \"a\": 2\n}"},
39 {`{"a":1,"a":2}`, "{\n \"a\": 2\n}"},
40 // Document order, array-index keys included.
41 {`{"b":1,"1":2,"a":3,"0":4}`, "{\n \"b\": 1,\n \"1\": 2,\n \"a\": 3,\n \"0\": 4\n}"},
42 // Leniency of Json.NET.
43 {`{"a":1,}`, "{\n \"a\": 1\n}"},
44 {`{a:1,$b:2,c_d:3}`, "{\n \"a\": 1,\n \"$b\": 2,\n \"c_d\": 3\n}"},
45 {`{'a':'x\'y'}`, "{\n \"a\": \"x'y\"\n}"},
46 {`[1,]`, "[\n 1\n]"},
47 {`[,1]`, "[\n \"\",\n 1\n]"},
48 {`[1,,2]`, "[\n 1,\n \"\",\n 2\n]"},
49 {"1.", "1"}, {".5", "0.5"}, {"-.5", "-0.5"}, {"1.e2", "100"},
50 {"0x10", "16"}, {"0X1f", "31"},
51 {"NaN", "NaN"}, {"Infinity", "Infinity"}, {"-Infinity", "-Infinity"},
52 {"undefined", `""`},
53 {"1 2", "1"}, {"[] []", "[]"}, {"{} x", "{}"}, {`"a""b"`, `"a"`}, {"true false", "true"},
54 {"\u00a01", "1"}, {"1\u00a0", "1"}, {"\u00851", "1"},
55 {"\"a\nb\"", `"a\nb"`}, {"\"\x01\"", `"\u0001"`},
56 }
57 for _, tc := range tests {
58 v, err := FromJSON([]byte(tc.in))
59 if err != nil {
60 t.Errorf("FromJSON(%q): %v", tc.in, err)
61 continue
62 }
63 if got := ToJSON(v); got != tc.want {
64 t.Errorf("FromJSON(%q) -> %q, want %q", tc.in, got, tc.want)
65 }
66 }
67}
68
69// TestFromJSONLeadingZeros: Json.NET reads a non-negative integer with a
70// leading zero as octal (Convert.ToInt64(s, 8)), a negative one as decimal, and
71// rejects a leading zero before a fraction or an exponent. Established with the
72// runner's code.
73func TestFromJSONLeadingZeros(t *testing.T) {
74 for _, tc := range []struct {
75 src string
76 want string // ToJSON, or "" for an error
77 }{
78 {"010", "8"}, {"050", "40"}, {"00", "0"}, {"007", "7"}, {"0777", "511"}, {"00777", "511"},
79 {"08", ""}, {"09", ""}, {"01.5", ""}, {"012e1", ""}, {"00.5", ""}, {"010.0", ""}, {"00e1", ""}, {"010e", ""},
80 {"0.5", "0.5"}, {"0e1", "0"}, {"0E1", "0"}, {"0.", "0"}, {"0.e1", "0"}, {"0e", ""},
81 {"-010", "-10"}, {"-09", "-9"}, {"-00", "0"}, {"-0777", "-777"}, {"-01.5", "-1.5"}, {"-012e1", "-120"},
82 {"[010]", "[\n 8\n]"}, {"[08]", ""}, {`{"a":010}`, "{\n \"a\": 8\n}"}, {`{"a":08}`, ""}, {`{"a":01.5}`, ""},
83 // 64-bit wrap of Convert.ToInt64.
84 {"0777777777777777777777", "9.22337203685478E+18"},
85 {"01777777777777777777777", "-1"},
86 {"07777777777777777777777", ""},
87 {"0000000000000000000012345", "5349"},
88 {"0000000000000000000012349", ""},
89 } {
90 v, err := FromJSON([]byte(tc.src))
91 switch {
92 case tc.want == "" && err == nil:
93 t.Errorf("FromJSON(%q) = %s, want an error", tc.src, ToJSON(v))
94 case tc.want != "" && err != nil:
95 t.Errorf("FromJSON(%q): %v", tc.src, err)
96 case err == nil && ToJSON(v) != tc.want:
97 t.Errorf("FromJSON(%q) = %s, want %s", tc.src, ToJSON(v), tc.want)
98 }
99 }
100}
101
102// TestFromJSONSlashAndComma: a "/" after a literal is an error when it could
103// start a comment (comments are not ported, so it fails consistently), as it is
104// for Json.NET after null, true, false, NaN and Infinity and between the values
105// of an array or object; after a number or string at the top level the text is
106// ignored. A comma where the document should start is an undefined value.
107func TestFromJSONSlashAndComma(t *testing.T) {
108 for _, tc := range []struct {
109 src string
110 want string // ToJSON, or "" for an error
111 }{
112 {"null/x", ""}, {"null/", ""}, {"true/x", ""}, {"false/x", ""}, {"NaN/x", ""}, {"Infinity/x", ""}, {"-Infinity/x", ""},
113 {"undefined/x", ""}, {"null//c", ""}, {"null/*c*/", ""}, {"NaN/*c*/", ""}, {"[null/x]", ""}, {"[null/*c*/]", ""}, {`{"a":null/x}`, ""},
114 {"[1/2]", ""}, {"[1 /2]", ""}, {`{"a":1/2}`, ""}, {"[1/*c*/,2]", ""}, {"[1//c\n,2]", ""},
115 {"null /x", "null"}, {"1/x", "1"}, {"1/", "1"}, {"1 /", "1"}, {"1.5/x", "1.5"}, {`"a"/x`, `"a"`}, {"[null,1]/x", "[\n null,\n 1\n]"},
116 {",", `""`}, {",1", `""`}, {",,1", `""`}, {",x", `""`}, {" ,1", `""`}, {`,"a"`, `""`}, {",]", `""`},
117 {"[,1]", "[\n \"\",\n 1\n]"}, {"[1,,2]", "[\n 1,\n \"\",\n 2\n]"}, {"[,]", "[\n \"\"\n]"},
118 {"{,}", ""}, {`{"a":,1}`, ""}, {`{"a":1,,"b":2}`, ""},
119 } {
120 v, err := FromJSON([]byte(tc.src))
121 switch {
122 case tc.want == "" && err == nil:
123 t.Errorf("FromJSON(%q) = %s, want an error", tc.src, ToJSON(v))
124 case tc.want != "" && err != nil:
125 t.Errorf("FromJSON(%q): %v", tc.src, err)
126 case err == nil && ToJSON(v) != tc.want:
127 t.Errorf("FromJSON(%q) = %s, want %s", tc.src, ToJSON(v), tc.want)
128 }
129 }
130}
131
132func TestFromJSONInvalid(t *testing.T) {
133 for _, in := range []string{
134 "", " ", "{", "}", "[", "]", `{"a"`, `{"a":`, `{"a":1`, `{,}`, `{"a" 1}`, `[1 2]`,
135 "+1", "-", "1e", "1e+", "--1", "-0x10", "nul", "truee", "tru", "True", "TRUE", "NULL", "nan", "+Infinity", "1_2",
136 `"abc`, `"\x"`, `"\u12"`, `"\u12g4"`, `"\`, "\x00", "\ufeff1", "@", "new Foo",
137 } {
138 if v, err := FromJSON([]byte(in)); err == nil {
139 t.Errorf("FromJSON(%q) = %v, want error", in, ToJSON(v))
140 }
141 }
142}
143
144func TestFromJSONNumbers(t *testing.T) {
145 for in, want := range map[string]float64{
146 "1e999": math.Inf(1), "-1e999": math.Inf(-1), "1e-999": 0, "123456789012345678901234567890": 1.2345678901234568e29, "5e-324": 5e-324,
147 } {
148 v, err := FromJSON([]byte(in))
149 if err != nil {
150 t.Errorf("FromJSON(%q): %v", in, err)
151 continue
152 }
153 if got := float64(v.(Number)); got != want {
154 t.Errorf("FromJSON(%q) = %v, want %v", in, got, want)
155 }
156 }
157 // Json.NET reads the integer "-0" as 0, but "-0.0" is a float with a sign.
158 if v, _ := FromJSON([]byte("-0")); math.Signbit(float64(v.(Number))) {
159 t.Error(`"-0" kept its sign`)
160 }
161 v, _ := FromJSON([]byte("-0.0"))
162 if !math.Signbit(float64(v.(Number))) {
163 t.Error("-0.0 lost its sign")
164 }
165 if got := ToJSON(v); got != "-0" {
166 t.Errorf("ToJSON(-0.0) = %q", got)
167 }
168}
169
170// TestFromJSONBigIntegers: Json.NET gives integers beyond 64 bits as BigInteger
171// and (Double)BigInteger truncates; established with the runner's code
172// (fromJSON('N') == N, where the literal N rounds to nearest).
173func TestFromJSONBigIntegers(t *testing.T) {
174 for _, tc := range []struct {
175 src string
176 want float64
177 }{
178 {"9223372036854775807", 9223372036854775807}, // fits a long: nearest
179 {"-9223372036854775808", -9223372036854775808}, // exact
180 {"12345678901234567890", 12345678901234567890}, // 64 bits, above long: truncated to the same double here
181 {"18446744073709551615", 18446744073709549568}, // 2^64-1 truncates to 2^64-2048, not 2^64
182 {"18446744073709551617", 18446744073709551616}, // 2^64+1 truncates to 2^64
183 {"-18446744073709551615", -18446744073709549568},
184 {"12345678901234567890.0", 12345678901234567168}, // a decimal point: an ordinary float
185 {"1e30", 1e30},
186 } {
187 v, err := FromJSON([]byte(tc.src))
188 if err != nil {
189 t.Errorf("FromJSON(%q): %v", tc.src, err)
190 continue
191 }
192 if got := float64(v.(Number)); got != tc.want {
193 t.Errorf("FromJSON(%q) = %v, want %v", tc.src, got, tc.want)
194 }
195 }
196}
197
198func TestFromJSONInvalidUTF8(t *testing.T) {
199 v, err := FromJSON([]byte("\"a\xffb\""))
200 if err != nil {
201 t.Fatal(err)
202 }
203 if s := string(v.(String)); s != "a\ufffdb" {
204 t.Errorf("got %q", s)
205 }
206}
207
208func TestFromJSONDeepNesting(t *testing.T) {
209 // Json.NET's reader allows 64 levels (JsonReader.MaxDepth).
210 for _, n := range []int{1, 63, 64} {
211 if _, err := FromJSON([]byte(strings.Repeat("[", n) + "1" + strings.Repeat("]", n))); err != nil {
212 t.Errorf("depth %d: %v", n, err)
213 }
214 }
215 for _, open := range []string{"[", `{"a":`} {
216 close := map[string]string{"[": "]", `{"a":`: "}"}[open]
217 if _, err := FromJSON([]byte(strings.Repeat(open, 65) + "1" + strings.Repeat(close, 65))); err == nil {
218 t.Errorf("depth 65 of %q accepted", open)
219 }
220 }
221}
222
223func TestToJSONFormatting(t *testing.T) {
224 d := NewDictionary()
225 d.Add("name", String("x"))
226 d.Add("list", NewArray(Number(1), Null{}, Boolean(true), NewArray(), NewDictionary()))
227 inner := NewDictionary()
228 inner.Add("k", String("v"))
229 d.Add("obj", inner)
230 want := `{
231 "name": "x",
232 "list": [
233 1,
234 null,
235 true,
236 [],
237 {}
238 ],
239 "obj": {
240 "k": "v"
241 }
242}`
243 if got := ToJSON(d); got != want {
244 t.Errorf("got\n%s\nwant\n%s", got, want)
245 }
246 tests := []struct {
247 v Value
248 want string
249 }{
250 {nil, "null"},
251 {Null{}, "null"},
252 // The runner writes numbers with ConvertToString (G15), so NaN and
253 // the infinities are written as such.
254 {Number(math.NaN()), "NaN"},
255 {Number(math.Inf(1)), "Infinity"},
256 {Number(math.Inf(-1)), "-Infinity"},
257 {Number(math.Copysign(0, -1)), "-0"},
258 {Number(1e21), "1E+21"},
259 {Number(1e-7), "1E-07"},
260 {Number(123456789), "123456789"},
261 {String("\u0000\u001f\u007f\u0080\u2028"), `"\u0000\u001f` + "\u007f\u0080" + `\u2028"`},
262 {String("\"\\\b\f\n\r\t"), `"\"\\\b\f\n\r\t"`},
263 {String("</script>&'"), `"</script>&'"`},
264 {String("\xff"), "\"\ufffd\""},
265 {(*Array)(nil), "null"},
266 {(*Dictionary)(nil), "null"},
267 {NewArray(nil), "[\n null\n]"},
268 }
269 for _, tc := range tests {
270 if got := ToJSON(tc.v); got != tc.want {
271 t.Errorf("ToJSON(%#v) = %q, want %q", tc.v, got, tc.want)
272 }
273 }
274}
275
276func TestDictionary(t *testing.T) {
277 d := NewDictionary()
278 if !d.Add("Name", String("first")) {
279 t.Fatal("Add failed")
280 }
281 if d.Add("NAME", String("second")) {
282 t.Error("case variant was added")
283 }
284 d.Add("other", Number(1))
285 if v, ok := d.Get("nAmE"); !ok || v.(String) != "first" {
286 t.Errorf("Get = %v, %v", v, ok)
287 }
288 if _, ok := d.Get("missing"); ok {
289 t.Error("Get of missing key succeeded")
290 }
291 if got := strings.Join(d.Keys(), ","); got != "Name,other" {
292 t.Errorf("Keys = %s", got)
293 }
294 if d.Len() != 2 || len(d.Values()) != 2 || len(d.Pairs()) != 2 || d.Pairs()[0].Key != "Name" {
295 t.Errorf("Len/Values/Pairs inconsistent: %d", d.Len())
296 }
297 // Non-ASCII keys fold like JavaScript's toLowerCase.
298 d.Add("ΑΣ", String("sigma"))
299 if _, ok := d.Get("ας"); !ok {
300 t.Error("final sigma key not found")
301 }
302 var zero Dictionary
303 zero.Add("a", Null{})
304 if _, ok := zero.Get("A"); !ok {
305 t.Error("zero Dictionary is not usable")
306 }
307 var nilDict *Dictionary
308 if _, ok := nilDict.Get("a"); ok || nilDict.Len() != 0 || nilDict.Keys() != nil || nilDict.Values() != nil || nilDict.Pairs() != nil {
309 t.Error("nil Dictionary is not empty")
310 }
311}
312
313func TestArray(t *testing.T) {
314 a := NewArray(String("x"), nil)
315 if a.Len() != 2 || a.At(0).(String) != "x" || a.At(1).Kind() != KindNull || a.At(2).Kind() != KindNull || a.At(-1).Kind() != KindNull {
316 t.Errorf("At/Len wrong: %v", a)
317 }
318 a.Append(Number(3))
319 if a.Len() != 3 || a.Filtered() {
320 t.Error("Append/Filtered")
321 }
322 items := a.Items()
323 items[0] = Null{}
324 if a.At(0).Kind() != KindString {
325 t.Error("Items does not copy")
326 }
327 var nilArr *Array
328 if nilArr.Len() != 0 || nilArr.Items() != nil || nilArr.At(0).Kind() != KindNull || nilArr.Filtered() {
329 t.Error("nil Array is not empty")
330 }
331 src := []Value{String("a")}
332 b := NewArray(src...)
333 src[0] = String("changed")
334 if b.At(0).(String) != "a" {
335 t.Error("NewArray does not copy")
336 }
337}
338
339func TestCoerceAndTruthy(t *testing.T) {
340 for _, tc := range []struct {
341 v Value
342 s string
343 truthy bool
344 }{
345 {nil, "", false},
346 {Null{}, "", false},
347 {Boolean(true), "true", true},
348 {Boolean(false), "false", false},
349 {Number(0), "0", false},
350 {Number(math.Copysign(0, -1)), "-0", false},
351 {Number(math.NaN()), "NaN", false},
352 {Number(2), "2", true},
353 {Number(math.Inf(-1)), "-Infinity", true},
354 {String(""), "", false},
355 {String("0"), "0", true},
356 {String("false"), "false", true},
357 {NewArray(), "Array", true},
358 {NewDictionary(), "Object", true},
359 {(*Array)(nil), "", false},
360 } {
361 if got := CoerceString(tc.v); got != tc.s {
362 t.Errorf("CoerceString(%#v) = %q, want %q", tc.v, got, tc.s)
363 }
364 if got := Truthy(tc.v); got != tc.truthy {
365 t.Errorf("Truthy(%#v) = %v, want %v", tc.v, got, tc.truthy)
366 }
367 }
368 if KindDictionary.String() != "Object" || Kind(99).String() != "unknown" {
369 t.Error("Kind.String")
370 }
371}
372
373func TestToNumber(t *testing.T) {
374 for _, tc := range []struct {
375 v Value
376 want float64
377 }{
378 {nil, 0}, {Null{}, 0}, {Boolean(true), 1}, {Boolean(false), 0}, {Number(2.5), 2.5}, {String(" 7 "), 7}, {String("x"), math.NaN()}, {NewArray(), math.NaN()}, {NewDictionary(), math.NaN()},
379 } {
380 got := toNumber(tc.v)
381 if got != tc.want && !(math.IsNaN(got) && math.IsNaN(tc.want)) {
382 t.Errorf("toNumber(%#v) = %v, want %v", tc.v, got, tc.want)
383 }
384 }
385}