1
2
3
4
5 package test2json
6
7 import (
8 "bytes"
9 "encoding/json"
10 "flag"
11 "fmt"
12 "io"
13 "os"
14 "path/filepath"
15 "reflect"
16 "strings"
17 "testing"
18 "unicode/utf8"
19 )
20
21 var update = flag.Bool("update", false, "rewrite testdata/*.json files")
22
23 func TestGolden(t *testing.T) {
24 files, err := filepath.Glob("testdata/*.test")
25 if err != nil {
26 t.Fatal(err)
27 }
28 for _, file := range files {
29 name := strings.TrimSuffix(filepath.Base(file), ".test")
30 t.Run(name, func(t *testing.T) {
31 orig, err := os.ReadFile(file)
32 if err != nil {
33 t.Fatal(err)
34 }
35
36
37
38 var buf bytes.Buffer
39 c := NewConverter(&buf, "", 0)
40 in := append([]byte{}, orig...)
41 for _, line := range bytes.SplitAfter(in, []byte("\n")) {
42 writeAndKill(c, line)
43 }
44 c.Close()
45
46 if *update {
47 js := strings.TrimSuffix(file, ".test") + ".json"
48 t.Logf("rewriting %s", js)
49 if err := os.WriteFile(js, buf.Bytes(), 0666); err != nil {
50 t.Fatal(err)
51 }
52 return
53 }
54
55 want, err := os.ReadFile(strings.TrimSuffix(file, ".test") + ".json")
56 if err != nil {
57 t.Fatal(err)
58 }
59 diffJSON(t, buf.Bytes(), want)
60 if t.Failed() {
61
62 return
63 }
64
65
66 t.Run("bulk", func(t *testing.T) {
67 buf.Reset()
68 c = NewConverter(&buf, "", 0)
69 in = append([]byte{}, orig...)
70 writeAndKill(c, in)
71 c.Close()
72 diffJSON(t, buf.Bytes(), want)
73 })
74
75
76 t.Run("crlf", func(t *testing.T) {
77 buf.Reset()
78 c = NewConverter(&buf, "", 0)
79 in = bytes.ReplaceAll(orig, []byte("\n"), []byte("\r\n"))
80 writeAndKill(c, in)
81 c.Close()
82 diffJSON(t, bytes.ReplaceAll(buf.Bytes(), []byte(`\r\n`), []byte(`\n`)), want)
83 })
84
85
86 t.Run("even2", func(t *testing.T) {
87 buf.Reset()
88 c = NewConverter(&buf, "", 0)
89 in = append([]byte{}, orig...)
90 for i := 0; i < len(in); i += 2 {
91 if i+2 <= len(in) {
92 writeAndKill(c, in[i:i+2])
93 } else {
94 writeAndKill(c, in[i:])
95 }
96 }
97 c.Close()
98 diffJSON(t, buf.Bytes(), want)
99 })
100
101
102 t.Run("odd2", func(t *testing.T) {
103 buf.Reset()
104 c = NewConverter(&buf, "", 0)
105 in = append([]byte{}, orig...)
106 if len(in) > 0 {
107 writeAndKill(c, in[:1])
108 }
109 for i := 1; i < len(in); i += 2 {
110 if i+2 <= len(in) {
111 writeAndKill(c, in[i:i+2])
112 } else {
113 writeAndKill(c, in[i:])
114 }
115 }
116 c.Close()
117 diffJSON(t, buf.Bytes(), want)
118 })
119
120
121
122 for b := 5; b <= 8; b++ {
123 t.Run(fmt.Sprintf("tiny%d", b), func(t *testing.T) {
124 oldIn := inBuffer
125 oldOut := outBuffer
126 defer func() {
127 inBuffer = oldIn
128 outBuffer = oldOut
129 }()
130 inBuffer = 64
131 outBuffer = b
132 buf.Reset()
133 c = NewConverter(&buf, "", 0)
134 in = append([]byte{}, orig...)
135 writeAndKill(c, in)
136 c.Close()
137 diffJSON(t, buf.Bytes(), want)
138 })
139 }
140 })
141 }
142 }
143
144
145
146
147 func writeAndKill(w io.Writer, b []byte) {
148 w.Write(b)
149 for i := range b {
150 b[i] = 'Z'
151 }
152 }
153
154
155
156 func diffJSON(t *testing.T, have, want []byte) {
157 t.Helper()
158 type event map[string]any
159
160
161 parseEvents := func(b []byte) ([]event, []string) {
162 t.Helper()
163 var events []event
164 var lines []string
165 for _, line := range bytes.SplitAfter(b, []byte("\n")) {
166 if len(line) > 0 {
167 line = bytes.TrimSpace(line)
168 var e event
169 err := json.Unmarshal(line, &e)
170 if err != nil {
171 t.Errorf("unmarshal %s: %v", b, err)
172 continue
173 }
174 events = append(events, e)
175 lines = append(lines, string(line))
176 }
177 }
178 return events, lines
179 }
180 haveEvents, haveLines := parseEvents(have)
181 wantEvents, wantLines := parseEvents(want)
182 if t.Failed() {
183 return
184 }
185
186
187
188
189
190 i := 0
191 j := 0
192
193
194
195
196 fail := func() {
197 var buf bytes.Buffer
198 show := func(i int, lines []string) {
199 for k := -2; k < 5; k++ {
200 marker := ""
201 if k == 0 {
202 marker = "» "
203 }
204 if 0 <= i+k && i+k < len(lines) {
205 fmt.Fprintf(&buf, "\t%s%s\n", marker, lines[i+k])
206 }
207 }
208 if i >= len(lines) {
209
210 fmt.Fprintf(&buf, "\t» \n")
211 }
212 }
213 fmt.Fprintf(&buf, "have:\n")
214 show(i, haveLines)
215 fmt.Fprintf(&buf, "want:\n")
216 show(j, wantLines)
217 t.Fatal(buf.String())
218 }
219
220 var outputTest string
221 var wantOutput, haveOutput string
222
223
224 getTest := func(e event) string {
225 s, _ := e["Test"].(string)
226 return s
227 }
228
229
230
231 checkOutput := func() {
232 for i < len(haveEvents) && haveEvents[i]["Action"] == "output" && getTest(haveEvents[i]) == outputTest {
233 haveOutput += haveEvents[i]["Output"].(string)
234 i++
235 }
236 if haveOutput != wantOutput {
237 t.Errorf("output mismatch for Test=%q:\nhave %q\nwant %q", outputTest, haveOutput, wantOutput)
238 fail()
239 }
240 haveOutput = ""
241 wantOutput = ""
242 }
243
244
245 for j = range wantEvents {
246 e := wantEvents[j]
247 if e["Action"] == "output" && getTest(e) == outputTest {
248 wantOutput += e["Output"].(string)
249 continue
250 }
251 checkOutput()
252 if e["Action"] == "output" {
253 outputTest = getTest(e)
254 wantOutput += e["Output"].(string)
255 continue
256 }
257 if i >= len(haveEvents) {
258 t.Errorf("early end of event stream: missing event")
259 fail()
260 }
261 if !reflect.DeepEqual(haveEvents[i], e) {
262 t.Errorf("events out of sync")
263 fail()
264 }
265 i++
266 }
267 checkOutput()
268 if i < len(haveEvents) {
269 t.Errorf("extra events in stream")
270 fail()
271 }
272 }
273
274 func TestTrimUTF8(t *testing.T) {
275 s := "hello α ☺ 😂 world"
276 b := []byte(s)
277 for i := 0; i < len(s); i++ {
278 j := trimUTF8(b[:i])
279 u := string([]rune(s[:j])) + string([]rune(s[j:]))
280 if u != s {
281 t.Errorf("trimUTF8(%q) = %d (-%d), not at boundary (split: %q %q)", s[:i], j, i-j, s[:j], s[j:])
282 }
283 if utf8.FullRune(b[j:i]) {
284 t.Errorf("trimUTF8(%q) = %d (-%d), too early (missed: %q)", s[:j], j, i-j, s[j:i])
285 }
286 }
287 }
288
View as plain text