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 needMarker bool
62 }
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83 var (
84 inBuffer = 4096
85 outBuffer = 1024
86 )
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104 func NewConverter(w io.Writer, pkg string, mode Mode) *Converter {
105 c := new(Converter)
106 *c = Converter{
107 w: w,
108 pkg: pkg,
109 mode: mode,
110 start: time.Now(),
111 input: lineBuffer{
112 b: make([]byte, 0, inBuffer),
113 line: c.handleInputLine,
114 part: c.output.write,
115 },
116 output: lineBuffer{
117 b: make([]byte, 0, outBuffer),
118 line: c.writeOutputEvent,
119 part: c.writeOutputEvent,
120 },
121 }
122 c.writeEvent(&event{Action: "start"})
123 return c
124 }
125
126
127 func (c *Converter) Write(b []byte) (int, error) {
128 c.input.write(b)
129 return len(b), nil
130 }
131
132
133 func (c *Converter) Exited(err error) {
134 if err == nil {
135 if c.result != "skip" {
136 c.result = "pass"
137 }
138 } else {
139 c.result = "fail"
140 }
141 }
142
143 const marker = byte(0x16)
144
145 var (
146
147 bigPass = []byte("PASS")
148
149
150 bigFail = []byte("FAIL")
151
152
153
154 bigFailErrorPrefix = []byte("FAIL\t")
155
156
157 emptyName = []byte("=== NAME")
158 emptyNameLine = []byte("=== NAME \n")
159
160 updates = [][]byte{
161 []byte("=== RUN "),
162 []byte("=== PAUSE "),
163 []byte("=== CONT "),
164 []byte("=== NAME "),
165 []byte("=== PASS "),
166 []byte("=== FAIL "),
167 []byte("=== SKIP "),
168 }
169
170 reports = [][]byte{
171 []byte("--- PASS: "),
172 []byte("--- FAIL: "),
173 []byte("--- SKIP: "),
174 []byte("--- BENCH: "),
175 }
176
177 fourSpace = []byte(" ")
178
179 skipLinePrefix = []byte("? \t")
180 skipLineSuffix = []byte("\t[no test files]")
181 )
182
183
184
185
186 func (c *Converter) handleInputLine(line []byte) {
187 if len(line) == 0 {
188 return
189 }
190 sawMarker := false
191 if c.needMarker && line[0] != marker {
192 c.output.write(line)
193 return
194 }
195 if line[0] == marker {
196 c.output.flush()
197 sawMarker = true
198 line = line[1:]
199 }
200
201
202 trim := line
203 if len(trim) > 0 && trim[len(trim)-1] == '\n' {
204 trim = trim[:len(trim)-1]
205 if len(trim) > 0 && trim[len(trim)-1] == '\r' {
206 trim = trim[:len(trim)-1]
207 }
208 }
209
210
211 if bytes.Equal(trim, emptyName) {
212 line = emptyNameLine
213 trim = line[:len(line)-1]
214 }
215
216
217 if bytes.Equal(trim, bigPass) || bytes.Equal(trim, bigFail) || bytes.HasPrefix(trim, bigFailErrorPrefix) {
218 c.flushReport(0)
219 c.testName = ""
220 c.needMarker = sawMarker
221 c.output.write(line)
222 if bytes.Equal(trim, bigPass) {
223 c.result = "pass"
224 } else {
225 c.result = "fail"
226 }
227
228 return
229 }
230
231
232
233 if bytes.HasPrefix(line, skipLinePrefix) && bytes.HasSuffix(trim, skipLineSuffix) && len(c.report) == 0 {
234 c.result = "skip"
235 }
236
237
238
239
240 actionColon := false
241 origLine := line
242 ok := false
243 indent := 0
244 for _, magic := range updates {
245 if bytes.HasPrefix(line, magic) {
246 ok = true
247 break
248 }
249 }
250 if !ok {
251
252
253
254
255
256 for bytes.HasPrefix(line, fourSpace) {
257 line = line[4:]
258 indent++
259 }
260 for _, magic := range reports {
261 if bytes.HasPrefix(line, magic) {
262 actionColon = true
263 ok = true
264 break
265 }
266 }
267 }
268
269
270 if !ok {
271
272
273
274
275
276
277
278 if indent > 0 && indent <= len(c.report) {
279 c.testName = c.report[indent-1].Test
280 }
281 c.output.write(origLine)
282 return
283 }
284
285
286 i := 0
287 if actionColon {
288 i = bytes.IndexByte(line, ':') + 1
289 }
290 if i == 0 {
291 i = len(updates[0])
292 }
293 action := strings.ToLower(strings.TrimSuffix(strings.TrimSpace(string(line[4:i])), ":"))
294 name := strings.TrimSpace(string(line[i:]))
295
296 e := &event{Action: action}
297 if line[0] == '-' {
298
299 if i := strings.Index(name, " ("); i >= 0 {
300 if strings.HasSuffix(name, "s)") {
301 t, err := strconv.ParseFloat(name[i+2:len(name)-2], 64)
302 if err == nil {
303 if c.mode&Timestamp != 0 {
304 e.Elapsed = &t
305 }
306 }
307 }
308 name = name[:i]
309 }
310 if len(c.report) < indent {
311
312
313 c.output.write(origLine)
314 return
315 }
316
317 c.needMarker = sawMarker
318 c.flushReport(indent)
319 e.Test = name
320 c.testName = name
321 c.report = append(c.report, e)
322 c.output.write(origLine)
323 return
324 }
325
326
327 c.needMarker = sawMarker
328 c.flushReport(0)
329 c.testName = name
330
331 if action == "name" {
332
333
334 return
335 }
336
337 if action == "pause" {
338
339
340
341 c.output.write(origLine)
342 }
343 c.writeEvent(e)
344 if action != "pause" {
345 c.output.write(origLine)
346 }
347
348 return
349 }
350
351
352 func (c *Converter) flushReport(depth int) {
353 c.testName = ""
354 for len(c.report) > depth {
355 e := c.report[len(c.report)-1]
356 c.report = c.report[:len(c.report)-1]
357 c.writeEvent(e)
358 }
359 }
360
361
362
363
364 func (c *Converter) Close() error {
365 c.input.flush()
366 c.output.flush()
367 if c.result != "" {
368 e := &event{Action: c.result}
369 if c.mode&Timestamp != 0 {
370 dt := time.Since(c.start).Round(1 * time.Millisecond).Seconds()
371 e.Elapsed = &dt
372 }
373 c.writeEvent(e)
374 }
375 return nil
376 }
377
378
379 func (c *Converter) writeOutputEvent(out []byte) {
380 c.writeEvent(&event{
381 Action: "output",
382 Output: (*textBytes)(&out),
383 })
384 }
385
386
387
388 func (c *Converter) writeEvent(e *event) {
389 e.Package = c.pkg
390 if c.mode&Timestamp != 0 {
391 t := time.Now()
392 e.Time = &t
393 }
394 if e.Test == "" {
395 e.Test = c.testName
396 }
397 js, err := json.Marshal(e)
398 if err != nil {
399
400 fmt.Fprintf(c.w, "testjson internal error: %v\n", err)
401 return
402 }
403 js = append(js, '\n')
404 c.w.Write(js)
405 }
406
407
408
409
410
411
412
413
414
415
416
417 type lineBuffer struct {
418 b []byte
419 mid bool
420 line func([]byte)
421 part func([]byte)
422 }
423
424
425 func (l *lineBuffer) write(b []byte) {
426 for len(b) > 0 {
427
428 m := copy(l.b[len(l.b):cap(l.b)], b)
429 l.b = l.b[:len(l.b)+m]
430 b = b[m:]
431
432
433 i := 0
434 for i < len(l.b) {
435 j, w := indexEOL(l.b[i:])
436 if j < 0 {
437 if !l.mid {
438 if j := bytes.IndexByte(l.b[i:], '\t'); j >= 0 {
439 if isBenchmarkName(bytes.TrimRight(l.b[i:i+j], " ")) {
440 l.part(l.b[i : i+j+1])
441 l.mid = true
442 i += j + 1
443 }
444 }
445 }
446 break
447 }
448 e := i + j + w
449 if l.mid {
450
451 l.part(l.b[i:e])
452 l.mid = false
453 } else {
454
455 l.line(l.b[i:e])
456 }
457 i = e
458 }
459
460
461 if i == 0 && len(l.b) == cap(l.b) {
462
463
464 t := trimUTF8(l.b)
465 l.part(l.b[:t])
466 l.b = l.b[:copy(l.b, l.b[t:])]
467 l.mid = true
468 }
469
470
471
472 if i > 0 {
473 l.b = l.b[:copy(l.b, l.b[i:])]
474 }
475 }
476 }
477
478
479
480
481
482
483 func indexEOL(b []byte) (pos, wid int) {
484 for i, c := range b {
485 if c == '\n' {
486 return i, 1
487 }
488 if c == marker && i > 0 {
489 return i, 0
490 }
491 }
492 return -1, 0
493 }
494
495
496 func (l *lineBuffer) flush() {
497 if len(l.b) > 0 {
498
499 l.part(l.b)
500 l.b = l.b[:0]
501 }
502 }
503
504 var benchmark = []byte("Benchmark")
505
506
507
508 func isBenchmarkName(b []byte) bool {
509 if !bytes.HasPrefix(b, benchmark) {
510 return false
511 }
512 if len(b) == len(benchmark) {
513 return true
514 }
515 r, _ := utf8.DecodeRune(b[len(benchmark):])
516 return !unicode.IsLower(r)
517 }
518
519
520
521
522
523
524 func trimUTF8(b []byte) int {
525
526 for i := 1; i < utf8.UTFMax && i <= len(b); i++ {
527 if c := b[len(b)-i]; c&0xc0 != 0x80 {
528 switch {
529 case c&0xe0 == 0xc0:
530 if i < 2 {
531 return len(b) - i
532 }
533 case c&0xf0 == 0xe0:
534 if i < 3 {
535 return len(b) - i
536 }
537 case c&0xf8 == 0xf0:
538 if i < 4 {
539 return len(b) - i
540 }
541 }
542 break
543 }
544 }
545 return len(b)
546 }
547
View as plain text