Source file src/cmd/internal/test2json/test2json.go
1
2
3
4
5
6
7
8
9 package test2json
10
11 import (
12 "bytes"
13 "encoding/json"
14 "fmt"
15 "io"
16 "strconv"
17 "strings"
18 "time"
19 "unicode"
20 "unicode/utf8"
21 )
22
23
24 type Mode int
25
26 const (
27 Timestamp Mode = 1 << iota
28 )
29
30
31 type event struct {
32 Time *time.Time `json:",omitempty"`
33 Action string
34 Package string `json:",omitempty"`
35 Test string `json:",omitempty"`
36 Elapsed *float64 `json:",omitempty"`
37 Output *textBytes `json:",omitempty"`
38 }
39
40
41
42
43
44 type textBytes []byte
45
46 func (b textBytes) MarshalText() ([]byte, error) { return b, nil }
47
48
49
50
51 type converter struct {
52 w io.Writer
53 pkg string
54 mode Mode
55 start time.Time
56 testName string
57 report []*event
58 result string
59 input lineBuffer
60 output lineBuffer
61 }
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82 var (
83 inBuffer = 4096
84 outBuffer = 1024
85 )
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103 func NewConverter(w io.Writer, pkg string, mode Mode) io.WriteCloser {
104 c := new(converter)
105 *c = converter{
106 w: w,
107 pkg: pkg,
108 mode: mode,
109 start: time.Now(),
110 input: lineBuffer{
111 b: make([]byte, 0, inBuffer),
112 line: c.handleInputLine,
113 part: c.output.write,
114 },
115 output: lineBuffer{
116 b: make([]byte, 0, outBuffer),
117 line: c.writeOutputEvent,
118 part: c.writeOutputEvent,
119 },
120 }
121 return c
122 }
123
124
125 func (c *converter) Write(b []byte) (int, error) {
126 c.input.write(b)
127 return len(b), nil
128 }
129
130 var (
131 bigPass = []byte("PASS\n")
132 bigFail = []byte("FAIL\n")
133
134 updates = [][]byte{
135 []byte("=== RUN "),
136 []byte("=== PAUSE "),
137 []byte("=== CONT "),
138 }
139
140 reports = [][]byte{
141 []byte("--- PASS: "),
142 []byte("--- FAIL: "),
143 []byte("--- SKIP: "),
144 []byte("--- BENCH: "),
145 }
146
147 fourSpace = []byte(" ")
148
149 skipLinePrefix = []byte("? \t")
150 skipLineSuffix = []byte("\t[no test files]\n")
151 )
152
153
154
155
156 func (c *converter) handleInputLine(line []byte) {
157
158 if bytes.Equal(line, bigPass) || bytes.Equal(line, bigFail) {
159 c.flushReport(0)
160 c.output.write(line)
161 if bytes.Equal(line, bigPass) {
162 c.result = "pass"
163 } else {
164 c.result = "fail"
165 }
166 return
167 }
168
169
170
171 if bytes.HasPrefix(line, skipLinePrefix) && bytes.HasSuffix(line, skipLineSuffix) && len(c.report) == 0 {
172 c.result = "skip"
173 }
174
175
176
177
178 actionColon := false
179 origLine := line
180 ok := false
181 indent := 0
182 for _, magic := range updates {
183 if bytes.HasPrefix(line, magic) {
184 ok = true
185 break
186 }
187 }
188 if !ok {
189
190
191
192
193
194 for bytes.HasPrefix(line, fourSpace) {
195 line = line[4:]
196 indent++
197 }
198 for _, magic := range reports {
199 if bytes.HasPrefix(line, magic) {
200 actionColon = true
201 ok = true
202 break
203 }
204 }
205 }
206
207 if !ok {
208
209 c.output.write(origLine)
210 return
211 }
212
213
214 i := 0
215 if actionColon {
216 i = bytes.IndexByte(line, ':') + 1
217 }
218 if i == 0 {
219 i = len(updates[0])
220 }
221 action := strings.ToLower(strings.TrimSuffix(strings.TrimSpace(string(line[4:i])), ":"))
222 name := strings.TrimSpace(string(line[i:]))
223
224 e := &event{Action: action}
225 if line[0] == '-' {
226
227 if i := strings.Index(name, " ("); i >= 0 {
228 if strings.HasSuffix(name, "s)") {
229 t, err := strconv.ParseFloat(name[i+2:len(name)-2], 64)
230 if err == nil {
231 if c.mode&Timestamp != 0 {
232 e.Elapsed = &t
233 }
234 }
235 }
236 name = name[:i]
237 }
238 if len(c.report) < indent {
239
240
241 c.output.write(origLine)
242 return
243 }
244
245 c.flushReport(indent)
246 e.Test = name
247 c.testName = name
248 c.report = append(c.report, e)
249 c.output.write(origLine)
250 return
251 }
252
253
254 c.flushReport(0)
255 c.testName = name
256
257 if action == "pause" {
258
259
260
261 c.output.write(origLine)
262 }
263 c.writeEvent(e)
264 if action != "pause" {
265 c.output.write(origLine)
266 }
267
268 return
269 }
270
271
272 func (c *converter) flushReport(depth int) {
273 c.testName = ""
274 for len(c.report) > depth {
275 e := c.report[len(c.report)-1]
276 c.report = c.report[:len(c.report)-1]
277 c.writeEvent(e)
278 }
279 }
280
281
282
283
284 func (c *converter) Close() error {
285 c.input.flush()
286 c.output.flush()
287 e := &event{Action: "fail"}
288 if c.result != "" {
289 e.Action = c.result
290 }
291 if c.mode&Timestamp != 0 {
292 dt := time.Since(c.start).Round(1 * time.Millisecond).Seconds()
293 e.Elapsed = &dt
294 }
295 c.writeEvent(e)
296 return nil
297 }
298
299
300 func (c *converter) writeOutputEvent(out []byte) {
301 c.writeEvent(&event{
302 Action: "output",
303 Output: (*textBytes)(&out),
304 })
305 }
306
307
308
309 func (c *converter) writeEvent(e *event) {
310 e.Package = c.pkg
311 if c.mode&Timestamp != 0 {
312 t := time.Now()
313 e.Time = &t
314 }
315 if e.Test == "" {
316 e.Test = c.testName
317 }
318 js, err := json.Marshal(e)
319 if err != nil {
320
321 c.w.Write([]byte(fmt.Sprintf("testjson internal error: %v\n", err)))
322 return
323 }
324 js = append(js, '\n')
325 c.w.Write(js)
326 }
327
328
329
330
331
332
333
334
335
336
337
338 type lineBuffer struct {
339 b []byte
340 mid bool
341 line func([]byte)
342 part func([]byte)
343 }
344
345
346 func (l *lineBuffer) write(b []byte) {
347 for len(b) > 0 {
348
349 m := copy(l.b[len(l.b):cap(l.b)], b)
350 l.b = l.b[:len(l.b)+m]
351 b = b[m:]
352
353
354 i := 0
355 for i < len(l.b) {
356 j := bytes.IndexByte(l.b[i:], '\n')
357 if j < 0 {
358 if !l.mid {
359 if j := bytes.IndexByte(l.b[i:], '\t'); j >= 0 {
360 if isBenchmarkName(bytes.TrimRight(l.b[i:i+j], " ")) {
361 l.part(l.b[i : i+j+1])
362 l.mid = true
363 i += j + 1
364 }
365 }
366 }
367 break
368 }
369 e := i + j + 1
370 if l.mid {
371
372 l.part(l.b[i:e])
373 l.mid = false
374 } else {
375
376 l.line(l.b[i:e])
377 }
378 i = e
379 }
380
381
382 if i == 0 && len(l.b) == cap(l.b) {
383
384
385 t := trimUTF8(l.b)
386 l.part(l.b[:t])
387 l.b = l.b[:copy(l.b, l.b[t:])]
388 l.mid = true
389 }
390
391
392
393 if i > 0 {
394 l.b = l.b[:copy(l.b, l.b[i:])]
395 }
396 }
397 }
398
399
400 func (l *lineBuffer) flush() {
401 if len(l.b) > 0 {
402
403 l.part(l.b)
404 l.b = l.b[:0]
405 }
406 }
407
408 var benchmark = []byte("Benchmark")
409
410
411
412 func isBenchmarkName(b []byte) bool {
413 if !bytes.HasPrefix(b, benchmark) {
414 return false
415 }
416 if len(b) == len(benchmark) {
417 return true
418 }
419 r, _ := utf8.DecodeRune(b[len(benchmark):])
420 return !unicode.IsLower(r)
421 }
422
423
424
425
426
427
428 func trimUTF8(b []byte) int {
429
430 for i := 1; i < utf8.UTFMax && i <= len(b); i++ {
431 if c := b[len(b)-i]; c&0xc0 != 0x80 {
432 switch {
433 case c&0xe0 == 0xc0:
434 if i < 2 {
435 return len(b) - i
436 }
437 case c&0xf0 == 0xe0:
438 if i < 3 {
439 return len(b) - i
440 }
441 case c&0xf8 == 0xf0:
442 if i < 4 {
443 return len(b) - i
444 }
445 }
446 break
447 }
448 }
449 return len(b)
450 }
451
View as plain text