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