1
2
3
4
5 package test2json
6
7 import (
8 "bytes"
9 "encoding/json"
10 "flag"
11 "fmt"
12 "io"
13 "io/ioutil"
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 := ioutil.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 := ioutil.WriteFile(js, buf.Bytes(), 0666); err != nil {
50 t.Fatal(err)
51 }
52 return
53 }
54
55 want, err := ioutil.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("even2", func(t *testing.T) {
77 buf.Reset()
78 c = NewConverter(&buf, "", 0)
79 in = append([]byte{}, orig...)
80 for i := 0; i < len(in); i += 2 {
81 if i+2 <= len(in) {
82 writeAndKill(c, in[i:i+2])
83 } else {
84 writeAndKill(c, in[i:])
85 }
86 }
87 c.Close()
88 diffJSON(t, buf.Bytes(), want)
89 })
90
91
92 t.Run("odd2", func(t *testing.T) {
93 buf.Reset()
94 c = NewConverter(&buf, "", 0)
95 in = append([]byte{}, orig...)
96 if len(in) > 0 {
97 writeAndKill(c, in[:1])
98 }
99 for i := 1; i < len(in); i += 2 {
100 if i+2 <= len(in) {
101 writeAndKill(c, in[i:i+2])
102 } else {
103 writeAndKill(c, in[i:])
104 }
105 }
106 c.Close()
107 diffJSON(t, buf.Bytes(), want)
108 })
109
110
111
112 for b := 5; b <= 8; b++ {
113 t.Run(fmt.Sprintf("tiny%d", b), func(t *testing.T) {
114 oldIn := inBuffer
115 oldOut := outBuffer
116 defer func() {
117 inBuffer = oldIn
118 outBuffer = oldOut
119 }()
120 inBuffer = 64
121 outBuffer = b
122 buf.Reset()
123 c = NewConverter(&buf, "", 0)
124 in = append([]byte{}, orig...)
125 writeAndKill(c, in)
126 c.Close()
127 diffJSON(t, buf.Bytes(), want)
128 })
129 }
130 })
131 }
132 }
133
134
135
136
137 func writeAndKill(w io.Writer, b []byte) {
138 w.Write(b)
139 for i := range b {
140 b[i] = 'Z'
141 }
142 }
143
144
145
146 func diffJSON(t *testing.T, have, want []byte) {
147 t.Helper()
148 type event map[string]any
149
150
151 parseEvents := func(b []byte) ([]event, []string) {
152 t.Helper()
153 var events []event
154 var lines []string
155 for _, line := range bytes.SplitAfter(b, []byte("\n")) {
156 if len(line) > 0 {
157 line = bytes.TrimSpace(line)
158 var e event
159 err := json.Unmarshal(line, &e)
160 if err != nil {
161 t.Errorf("unmarshal %s: %v", b, err)
162 continue
163 }
164 events = append(events, e)
165 lines = append(lines, string(line))
166 }
167 }
168 return events, lines
169 }
170 haveEvents, haveLines := parseEvents(have)
171 wantEvents, wantLines := parseEvents(want)
172 if t.Failed() {
173 return
174 }
175
176
177
178
179
180 i := 0
181 j := 0
182
183
184
185
186 fail := func() {
187 var buf bytes.Buffer
188 show := func(i int, lines []string) {
189 for k := -2; k < 5; k++ {
190 marker := ""
191 if k == 0 {
192 marker = "» "
193 }
194 if 0 <= i+k && i+k < len(lines) {
195 fmt.Fprintf(&buf, "\t%s%s\n", marker, lines[i+k])
196 }
197 }
198 if i >= len(lines) {
199
200 fmt.Fprintf(&buf, "\t» \n")
201 }
202 }
203 fmt.Fprintf(&buf, "have:\n")
204 show(i, haveLines)
205 fmt.Fprintf(&buf, "want:\n")
206 show(j, wantLines)
207 t.Fatal(buf.String())
208 }
209
210 var outputTest string
211 var wantOutput, haveOutput string
212
213
214 getTest := func(e event) string {
215 s, _ := e["Test"].(string)
216 return s
217 }
218
219
220
221 checkOutput := func() {
222 for i < len(haveEvents) && haveEvents[i]["Action"] == "output" && getTest(haveEvents[i]) == outputTest {
223 haveOutput += haveEvents[i]["Output"].(string)
224 i++
225 }
226 if haveOutput != wantOutput {
227 t.Errorf("output mismatch for Test=%q:\nhave %q\nwant %q", outputTest, haveOutput, wantOutput)
228 fail()
229 }
230 haveOutput = ""
231 wantOutput = ""
232 }
233
234
235 for j = range wantEvents {
236 e := wantEvents[j]
237 if e["Action"] == "output" && getTest(e) == outputTest {
238 wantOutput += e["Output"].(string)
239 continue
240 }
241 checkOutput()
242 if e["Action"] == "output" {
243 outputTest = getTest(e)
244 wantOutput += e["Output"].(string)
245 continue
246 }
247 if i >= len(haveEvents) {
248 t.Errorf("early end of event stream: missing event")
249 fail()
250 }
251 if !reflect.DeepEqual(haveEvents[i], e) {
252 t.Errorf("events out of sync")
253 fail()
254 }
255 i++
256 }
257 checkOutput()
258 if i < len(haveEvents) {
259 t.Errorf("extra events in stream")
260 fail()
261 }
262 }
263
264 func TestTrimUTF8(t *testing.T) {
265 s := "hello α ☺ 😂 world"
266 b := []byte(s)
267 for i := 0; i < len(s); i++ {
268 j := trimUTF8(b[:i])
269 u := string([]rune(s[:j])) + string([]rune(s[j:]))
270 if u != s {
271 t.Errorf("trimUTF8(%q) = %d (-%d), not at boundary (split: %q %q)", s[:i], j, i-j, s[:j], s[j:])
272 }
273 if utf8.FullRune(b[j:i]) {
274 t.Errorf("trimUTF8(%q) = %d (-%d), too early (missed: %q)", s[:j], j, i-j, s[j:i])
275 }
276 }
277 }
278
View as plain text