Repository navigation
Expand file tree
/
Copy pathpath_bytes_nested_internal_test.go
More file actions
124 lines (115 loc) · 3.59 KB
/
Copy pathpath_bytes_nested_internal_test.go
File metadata and controls
124 lines (115 loc) · 3.59 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
//go:build !tinygo
package gnata
import (
"encoding/json"
"fmt"
"math/rand/v2"
"runtime/debug"
"strings"
"testing"
"unicode/utf8"
"github.com/tidwall/gjson"
)
// randomNestedJSON returns a random JSON value rich in nested arrays and in
// objects with the keys stepNestedArray looks up.
func randomNestedJSON(r *rand.Rand, depth int) string {
switch n := r.IntN(10); {
case depth > 6 || n < 3:
return []string{`1`, `-2.5e3`, `"s"`, `"a\"]\\"`, `true`, `null`, `[]`, `{}`, `"\u00e9[{"`}[r.IntN(9)]
case n < 7:
items := make([]string, r.IntN(4))
for i := range items {
items[i] = randomNestedJSON(r, depth+1)
}
return "[" + strings.Join(items, ", ") + "]"
default:
fields := make([]string, r.IntN(4))
for i := range fields {
fields[i] = `"` + []string{"a", "b", "a.b", "a"}[r.IntN(4)] + `": ` + randomNestedJSON(r, depth+1)
}
return "{" + strings.Join(fields, ",") + "}"
}
}
func TestValidJSON(t *testing.T) {
r := rand.New(rand.NewPCG(1, 2))
mutations := []string{"", ",", "]", "}", "[", "{", `"`, ":", "\\", "x", " ", "\x01", "\xff", "01", "-", "1."}
for range 200_000 {
s := randomNestedJSON(r, 0)
if !validJSON(s) {
t.Fatalf("validJSON rejects valid JSON %q", s)
}
if i := r.IntN(len(s) + 1); r.IntN(2) == 0 {
s = s[:i] + mutations[r.IntN(len(mutations))] + s[i:]
} else if i < len(s) {
s = s[:i] + s[i+1:]
}
// validJSON also rejects invalid UTF-8, which json.Valid accepts.
if want := json.Valid([]byte(s)) && utf8.ValidString(s); validJSON(s) != want {
t.Fatalf("validJSON(%q) = %v, want %v", s, !want, want)
}
}
}
// splitStep is the field lookup over the array r as one context, splitting
// each nested array with gjson and collecting the values found at any depth.
func splitStep(step string, r *gjson.Result) (any, bool) {
var frame rawArrayFrame
var collect func(items []gjson.Result)
collect = func(items []gjson.Result) {
for _, item := range items {
if item.IsArray() {
collect(item.Array())
} else {
frame.add(objectField(step, &item))
}
}
}
collect(r.Array())
return frame.result()
}
// stepNestedArray gives what splitting each nested array with gjson gives.
func TestStepNestedArrayMatchesSplitting(t *testing.T) {
r := rand.New(rand.NewPCG(3, 4))
for range 50_000 {
raw := "[" + randomNestedJSON(r, 0) + "," + randomNestedJSON(r, 0) + "]"
root := gjson.Parse(raw)
for _, step := range []string{"a", "b", "a.b", "c"} {
want, wantOK := splitStep(step, &root)
got, gotOK := stepNestedArray(step, raw)
if gotOK != wantOK || !sameStepValue(got, want) {
t.Fatalf("%s over %s: got %v %v, want %v %v", step, raw, got, gotOK, want, wantOK)
}
}
}
}
func sameStepValue(a, b any) bool {
switch x := a.(type) {
case gjson.Result:
y, ok := b.(gjson.Result)
return ok && x.Type == y.Type && x.Raw == y.Raw
case []gjson.Result:
y, ok := b.([]gjson.Result)
if !ok || len(x) != len(y) {
return false
}
for i := range x {
if x[i].Type != y[i].Type || x[i].Raw != y[i].Raw {
return false
}
}
return true
}
return a == nil && b == nil
}
// A field path over arrays nested 200,000 deep walks the raw JSON once,
// without recursing per level: under a 2 MB stack limit, splitting each
// nested array recursively would overflow.
func TestDeepNestedArrayPathBytes(t *testing.T) {
prev := debug.SetMaxStack(2 << 20)
t.Cleanup(func() { debug.SetMaxStack(prev) })
const depth = 200_000
data := strings.Repeat("[", depth) + `{"a":1}` + strings.Repeat("]", depth)
got, ok := walkPureStepsBytes([]string{"a"}, []byte(data), false)
if !ok || fmt.Sprint(got) != "1" {
t.Fatalf("got %v %v, want 1", got, ok)
}
}