Repository navigation
Expand file tree
/
Copy pathbroken_json_test.go
More file actions
159 lines (151 loc) · 6.68 KB
/
Copy pathbroken_json_test.go
File metadata and controls
159 lines (151 loc) · 6.68 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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
package devstatscode
import (
"bytes"
"testing"
lib "github.com/cncf/devstatscode"
)
func TestIsPlainLine(t *testing.T) {
var testCases = []struct {
line []byte
expected bool
}{
{line: []byte(`{"id":"1"}`), expected: true},
{line: []byte(""), expected: true},
{line: []byte(`{"a":"zażółć"}`), expected: true},
{line: []byte("{\"id\":\"1\"}\x00"), expected: false},
{line: []byte("\x00"), expected: false},
{line: []byte("{\"a\":\"\xff\"}"), expected: false},
}
for index, test := range testCases {
got := lib.IsPlainLine(test.line)
if got != test.expected {
t.Errorf("test number %d, expected %v, got %v", index+1, test.expected, got)
}
}
}
func TestTrimJSONWS(t *testing.T) {
var testCases = []struct {
in, expected string
}{
{in: " \t\r\n{}\n\r\t ", expected: "{}"},
{in: " ", expected: ""},
{in: "", expected: ""},
{in: "{}", expected: "{}"},
// Vertical tab, form feed and NBSP are not JSON whitespace.
{in: "\x0b{}\x0c", expected: "\x0b{}\x0c"},
{in: "\u00a0{}", expected: "\u00a0{}"},
}
for index, test := range testCases {
got := string(lib.TrimJSONWS([]byte(test.in)))
if got != test.expected {
t.Errorf("test number %d, expected %q, got %q", index+1, test.expected, got)
}
}
}
func TestSplitNULSegments(t *testing.T) {
var testCases = []struct {
line string
expected []string
}{
{line: "a\x00\x00\x00b", expected: []string{"a", "b"}},
{line: "\x00\x00", expected: []string{}},
{line: " \x00 x \x00\n", expected: []string{"x"}},
{line: "abc", expected: []string{"abc"}},
}
for index, test := range testCases {
got := lib.SplitNULSegments([]byte(test.line))
if len(got) != len(test.expected) {
t.Errorf("test number %d, expected %d segments %q, got %d %q", index+1, len(test.expected), test.expected, len(got), got)
continue
}
for i := range got {
if string(got[i]) != test.expected[i] {
t.Errorf("test number %d, segment %d: expected %q, got %q", index+1, i, test.expected[i], string(got[i]))
}
}
}
}
func jsonChunk(s string) lib.JSONChunk {
return lib.JSONChunk{Bytes: []byte(s)}
}
func brokenChunk(s string) lib.JSONChunk {
return lib.JSONChunk{Bytes: []byte(s), Broken: true}
}
func TestRecoverJSONChunks(t *testing.T) {
ev := `{"id":"29055979701","type":"IssueCommentEvent"}`
ev2 := `{"id":"29056015615","type":"PushEvent","repo":{"name":"yowmamasita/anmeldung"}}`
nuls := string(bytes.Repeat([]byte{0}, 1409))
oldFmt := `{"repository":{"name":"rust","owner":"rust-lang"},"actor":"bors","type":"PushEvent","created_at":"2012/03/11 12:00:00 -0700"}`
var testCases = []struct {
name string
line string
expected []lib.JSONChunk
}{
{name: "clean line", line: `{"id":"1","type":"PushEvent"}`, expected: []lib.JSONChunk{jsonChunk(`{"id":"1","type":"PushEvent"}`)}},
{name: "clean line with whitespace", line: " {\"id\":\"1\"}\r\n", expected: []lib.JSONChunk{jsonChunk(`{"id":"1"}`)}},
{name: "empty", line: "", expected: nil},
{name: "NULs only", line: "\x00\x00\x00", expected: nil},
{name: "whitespace and NULs", line: " \t\x00 \n", expected: nil},
{name: "trailing NULs after event", line: ev + nuls, expected: []lib.JSONChunk{jsonChunk(ev)}},
// The real 2023-05-14-19 damage: event + 1409 NULs + glued event.
{name: "event NULs event", line: ev + nuls + ev2, expected: []lib.JSONChunk{jsonChunk(ev), jsonChunk(ev2)}},
// The middle event is cut by NULs: both halves are broken, never merged.
{
name: "three chunks, NULs inside event",
line: "{\"id\":\"1\"}\x00\x00{\"id\":\x00\"2\"}\x00{\"id\":\"3\"}",
expected: []lib.JSONChunk{
jsonChunk(`{"id":"1"}`), brokenChunk(`{"id":`), brokenChunk(`"2"}`), jsonChunk(`{"id":"3"}`),
},
},
{
name: "glued without NULs",
line: `{"id":"1"}{"id":"2"} {"id":"3"}`,
expected: []lib.JSONChunk{jsonChunk(`{"id":"1"}`), jsonChunk(`{"id":"2"}`), jsonChunk(`{"id":"3"}`)},
},
{name: "garbage tail", line: `{"id":"1"}garbage`, expected: []lib.JSONChunk{jsonChunk(`{"id":"1"}`), brokenChunk("garbage")}},
{name: "garbage head", line: `garbage{"id":"1"}`, expected: []lib.JSONChunk{brokenChunk(`garbage{"id":"1"}`)}},
{name: "extra closing brace", line: `{"id":"1"}}`, expected: []lib.JSONChunk{jsonChunk(`{"id":"1"}`), brokenChunk("}")}},
{name: "truncated event", line: `{"id":"1","type":"Pu`, expected: []lib.JSONChunk{brokenChunk(`{"id":"1","type":"Pu`)}},
{name: "truncated second event", line: `{"id":"1"}{"id":"2" `, expected: []lib.JSONChunk{jsonChunk(`{"id":"1"}`), brokenChunk(`{"id":"2"`)}},
{name: "lone brace", line: `{"id":"1"} {`, expected: []lib.JSONChunk{jsonChunk(`{"id":"1"}`), brokenChunk("{")}},
{
name: "invalid UTF-8 segment is broken whole",
line: "{\"id\":\"1\"}\x00{\"a\":\"\xff\xfe\"}{\"id\":\"3\"}\x00{\"id\":\"4\"}",
expected: []lib.JSONChunk{
jsonChunk(`{"id":"1"}`), brokenChunk("{\"a\":\"\xff\xfe\"}{\"id\":\"3\"}"), jsonChunk(`{"id":"4"}`),
},
},
{
name: "non-object values",
line: `[1,2] "s" 7 null {"id":"1"}`,
expected: []lib.JSONChunk{
jsonChunk("[1,2]"), jsonChunk(`"s"`), jsonChunk("7"), jsonChunk("null"), jsonChunk(`{"id":"1"}`),
},
},
{name: "number glued to letters", line: "123abc", expected: []lib.JSONChunk{brokenChunk("123abc")}},
// json.Decoder: a string/number/literal must be followed by whitespace or EOF, an object/array is self-delimiting.
{name: "string glued to object", line: `"s"{"id":"1"}`, expected: []lib.JSONChunk{brokenChunk(`"s"{"id":"1"}`)}},
{name: "number glued to object", line: `7{"id":"1"}`, expected: []lib.JSONChunk{brokenChunk(`7{"id":"1"}`)}},
{name: "object string object", line: `{"id":"1"}"s"{"id":"2"}`, expected: []lib.JSONChunk{jsonChunk(`{"id":"1"}`), brokenChunk(`"s"{"id":"2"}`)}},
{name: "array then number", line: "[1]2", expected: []lib.JSONChunk{jsonChunk("[1]"), jsonChunk("2")}},
{name: "object then string", line: `{"id":"1"}"s"`, expected: []lib.JSONChunk{jsonChunk(`{"id":"1"}`), jsonChunk(`"s"`)}},
{
name: "whitespace around NULs",
line: " \n{\"id\":\"1\"}\r\n\x00\x00 \t{\"id\":\"2\"}\n \x00",
expected: []lib.JSONChunk{jsonChunk(`{"id":"1"}`), jsonChunk(`{"id":"2"}`)},
},
{name: "old format event", line: oldFmt, expected: []lib.JSONChunk{jsonChunk(oldFmt)}},
}
for index, test := range testCases {
got := lib.RecoverJSONChunks([]byte(test.line))
if len(got) != len(test.expected) {
t.Errorf("test number %d (%s), expected %d chunks %+v, got %d %+v", index+1, test.name, len(test.expected), test.expected, len(got), got)
continue
}
for i := range got {
if got[i].Broken != test.expected[i].Broken || !bytes.Equal(got[i].Bytes, test.expected[i].Bytes) {
t.Errorf("test number %d (%s), chunk %d: expected %+v, got %+v", index+1, test.name, i, test.expected[i], got[i])
}
}
}
}