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 return
228 }
229
230
231
232 if bytes.HasPrefix(line, skipLinePrefix) && bytes.HasSuffix(trim, skipLineSuffix) && len(c.report) == 0 {
233 c.result = "skip"
234 }
235
236
237
238
239 actionColon := false
240 origLine := line
241 ok := false
242 indent := 0
243 for _, magic := range updates {
244 if bytes.HasPrefix(line, magic) {
245 ok = true
246 break
247 }
248 }
249 if !ok {
250
251
252
253
254
255 for bytes.HasPrefix(line, fourSpace) {
256 line = line[4:]
257 indent++
258 }
259 for _, magic := range reports {
260 if bytes.HasPrefix(line, magic) {
261 actionColon = true
262 ok = true
263 break
264 }
265 }
266 }
267
268
269 if !ok {
270
271
272
273
274
275
276
277 if indent > 0 && indent <= len(c.report) {
278 c.testName = c.report[indent-1].Test
279 }
280 c.output.write(origLine)
281 return
282 }
283
284
285 i := 0
286 if actionColon {
287 i = bytes.IndexByte(line, ':') + 1
288 }
289 if i == 0 {
290 i = len(updates[0])
291 }
292 action := strings.ToLower(strings.TrimSuffix(strings.TrimSpace(string(line[4:i])), ":"))
293 name := strings.TrimSpace(string(line[i:]))
294
295 e := &event{Action: action}
296 if line[0] == '-' {
297
298 if i := strings.Index(name, " ("); i >= 0 {
299 if strings.HasSuffix(name, "s)") {
300 t, err := strconv.ParseFloat(name[i+2:len(name)-2], 64)
301 if err == nil {
302 if c.mode&Timestamp != 0 {
303 e.Elapsed = &t
304 }
305 }
306 }
307 name = name[:i]
308 }
309 if len(c.report) < indent {
310
311
312 c.output.write(origLine)
313 return
314 }
315
316 c.needMarker = sawMarker
317 c.flushReport(indent)
318 e.Test = name
319 c.testName = name
320 c.report = append(c.report, e)
321 c.output.write(origLine)
322 return
323 }
324
325
326 c.needMarker = sawMarker
327 c.flushReport(0)
328 c.testName = name
329
330 if action == "name" {
331
332
333 return
334 }
335
336 if action == "pause" {
337
338
339
340 c.output.write(origLine)
341 }
342 c.writeEvent(e)
343 if action != "pause" {
344 c.output.write(origLine)
345 }
346
347 return
348 }
349
350
351 func (c *Converter) flushReport(depth int) {
352 c.testName = ""
353 for len(c.report) > depth {
354 e := c.report[len(c.report)-1]
355 c.report = c.report[:len(c.report)-1]
356 c.writeEvent(e)
357 }
358 }
359
360
361
362
363 func (c *Converter) Close() error {
364 c.input.flush()
365 c.output.flush()
366 if c.result != "" {
367 e := &event{Action: c.result}
368 if c.mode&Timestamp != 0 {
369 dt := time.Since(c.start).Round(1 * time.Millisecond).Seconds()
370 e.Elapsed = &dt
371 }
372 c.writeEvent(e)
373 }
374 return nil
375 }
376
377
378 func (c *Converter) writeOutputEvent(out []byte) {
379 c.writeEvent(&event{
380 Action: "output",
381 Output: (*textBytes)(&out),
382 })
383 }
384
385
386
387 func (c *Converter) writeEvent(e *event) {
388 e.Package = c.pkg
389 if c.mode&Timestamp != 0 {
390 t := time.Now()
391 e.Time = &t
392 }
393 if e.Test == "" {
394 e.Test = c.testName
395 }
396 js, err := json.Marshal(e)
397 if err != nil {
398
399 fmt.Fprintf(c.w, "testjson internal error: %v\n", err)
400 return
401 }
402 js = append(js, '\n')
403 c.w.Write(js)
404 }
405
406
407
408
409
410
411
412
413
414
415
416 type lineBuffer struct {
417 b []byte
418 mid bool
419 line func([]byte)
420 part func([]byte)
421 }
422
423
424 func (l *lineBuffer) write(b []byte) {
425 for len(b) > 0 {
426
427 m := copy(l.b[len(l.b):cap(l.b)], b)
428 l.b = l.b[:len(l.b)+m]
429 b = b[m:]
430
431
432 i := 0
433 for i < len(l.b) {
434 j, w := indexEOL(l.b[i:])
435 if j < 0 {
436 if !l.mid {
437 if j := bytes.IndexByte(l.b[i:], '\t'); j >= 0 {
438 if isBenchmarkName(bytes.TrimRight(l.b[i:i+j], " ")) {
439 l.part(l.b[i : i+j+1])
440 l.mid = true
441 i += j + 1
442 }
443 }
444 }
445 break
446 }
447 e := i + j + w
448 if l.mid {
449
450 l.part(l.b[i:e])
451 l.mid = false
452 } else {
453
454 l.line(l.b[i:e])
455 }
456 i = e
457 }
458
459
460 if i == 0 && len(l.b) == cap(l.b) {
461
462
463 t := trimUTF8(l.b)
464 l.part(l.b[:t])
465 l.b = l.b[:copy(l.b, l.b[t:])]
466 l.mid = true
467 }
468
469
470
471 if i > 0 {
472 l.b = l.b[:copy(l.b, l.b[i:])]
473 }
474 }
475 }
476
477
478
479
480
481
482 func indexEOL(b []byte) (pos, wid int) {
483 for i, c := range b {
484 if c == '\n' {
485 return i, 1
486 }
487 if c == marker && i > 0 {
488 return i, 0
489 }
490 }
491 return -1, 0
492 }
493
494
495 func (l *lineBuffer) flush() {
496 if len(l.b) > 0 {
497
498 l.part(l.b)
499 l.b = l.b[:0]
500 }
501 }
502
503 var benchmark = []byte("Benchmark")
504
505
506
507 func isBenchmarkName(b []byte) bool {
508 if !bytes.HasPrefix(b, benchmark) {
509 return false
510 }
511 if len(b) == len(benchmark) {
512 return true
513 }
514 r, _ := utf8.DecodeRune(b[len(benchmark):])
515 return !unicode.IsLower(r)
516 }
517
518
519
520
521
522
523 func trimUTF8(b []byte) int {
524
525 for i := 1; i < utf8.UTFMax && i <= len(b); i++ {
526 if c := b[len(b)-i]; c&0xc0 != 0x80 {
527 switch {
528 case c&0xe0 == 0xc0:
529 if i < 2 {
530 return len(b) - i
531 }
532 case c&0xf0 == 0xe0:
533 if i < 3 {
534 return len(b) - i
535 }
536 case c&0xf8 == 0xf0:
537 if i < 4 {
538 return len(b) - i
539 }
540 }
541 break
542 }
543 }
544 return len(b)
545 }
546
View as plain text