main 98594d0

json_test.go RawHistoryFolder

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}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 3

↑↓ moveenter openesc close