1// Copyright 2017 The Go Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style 3// license that can be found in the LICENSE file. 4 5// Package test2json implements conversion of test binary output to JSON. 6// It is used by cmd/test2json and cmd/go. 7// 8// See the cmd/test2json documentation for details of the JSON encoding. 9// 10// The file test2json.go was copied from upstream go at 11// src/cmd/internal/test2json/test2json.go, revision 12// 1b86bdbdc3991c13c6ed156100a5f4918fdd9c6b. At the time of writing this was 13// deemed the best way of depending on this code that is otherwise not exposed 14// outside of the go toolchain. These files should be kept in sync. 15 16package bzltestutil 17 18import ( 19 "bytes" 20 "encoding/json" 21 "fmt" 22 "io" 23 "strconv" 24 "strings" 25 "time" 26 "unicode" 27 "unicode/utf8" 28) 29 30// Mode controls details of the conversion. 31type Mode int 32 33const ( 34 Timestamp Mode = 1 << iota // include Time in events 35) 36 37// event is the JSON struct we emit. 38type event struct { 39 Time *time.Time `json:",omitempty"` 40 Action string 41 Package string `json:",omitempty"` 42 Test string `json:",omitempty"` 43 Elapsed *float64 `json:",omitempty"` 44 Output *textBytes `json:",omitempty"` 45} 46 47// textBytes is a hack to get JSON to emit a []byte as a string 48// without actually copying it to a string. 49// It implements encoding.TextMarshaler, which returns its text form as a []byte, 50// and then json encodes that text form as a string (which was our goal). 51type textBytes []byte 52 53func (b textBytes) MarshalText() ([]byte, error) { return b, nil } 54 55// A Converter holds the state of a test-to-JSON conversion. 56// It implements io.WriteCloser; the caller writes test output in, 57// and the converter writes JSON output to w. 58type Converter struct { 59 w io.Writer // JSON output stream 60 pkg string // package to name in events 61 mode Mode // mode bits 62 start time.Time // time converter started 63 testName string // name of current test, for output attribution 64 report []*event // pending test result reports (nested for subtests) 65 result string // overall test result if seen 66 input lineBuffer // input buffer 67 output lineBuffer // output buffer 68} 69 70// inBuffer and outBuffer are the input and output buffer sizes. 71// They're variables so that they can be reduced during testing. 72// 73// The input buffer needs to be able to hold any single test 74// directive line we want to recognize, like: 75// 76// <many spaces> --- PASS: very/nested/s/u/b/t/e/s/t 77// 78// If anyone reports a test directive line > 4k not working, it will 79// be defensible to suggest they restructure their test or test names. 80// 81// The output buffer must be >= utf8.UTFMax, so that it can 82// accumulate any single UTF8 sequence. Lines that fit entirely 83// within the output buffer are emitted in single output events. 84// Otherwise they are split into multiple events. 85// The output buffer size therefore limits the size of the encoding 86// of a single JSON output event. 1k seems like a reasonable balance 87// between wanting to avoid splitting an output line and not wanting to 88// generate enormous output events. 89var ( 90 inBuffer = 4096 91 outBuffer = 1024 92) 93 94// NewConverter returns a "test to json" converter. 95// Writes on the returned writer are written as JSON to w, 96// with minimal delay. 97// 98// The writes to w are whole JSON events ending in \n, 99// so that it is safe to run multiple tests writing to multiple converters 100// writing to a single underlying output stream w. 101// As long as the underlying output w can handle concurrent writes 102// from multiple goroutines, the result will be a JSON stream 103// describing the relative ordering of execution in all the concurrent tests. 104// 105// The mode flag adjusts the behavior of the converter. 106// Passing ModeTime includes event timestamps and elapsed times. 107// 108// The pkg string, if present, specifies the import path to 109// report in the JSON stream. 110func NewConverter(w io.Writer, pkg string, mode Mode) *Converter { 111 c := new(Converter) 112 *c = Converter{ 113 w: w, 114 pkg: pkg, 115 mode: mode, 116 start: time.Now(), 117 input: lineBuffer{ 118 b: make([]byte, 0, inBuffer), 119 line: c.handleInputLine, 120 part: c.output.write, 121 }, 122 output: lineBuffer{ 123 b: make([]byte, 0, outBuffer), 124 line: c.writeOutputEvent, 125 part: c.writeOutputEvent, 126 }, 127 } 128 return c 129} 130 131// Write writes the test input to the converter. 132func (c *Converter) Write(b []byte) (int, error) { 133 c.input.write(b) 134 return len(b), nil 135} 136 137// Exited marks the test process as having exited with the given error. 138func (c *Converter) Exited(err error) { 139 if err == nil { 140 c.result = "pass" 141 } else { 142 c.result = "fail" 143 } 144} 145 146var ( 147 // printed by test on successful run. 148 bigPass = []byte("PASS\n") 149 150 // printed by test after a normal test failure. 151 bigFail = []byte("FAIL\n") 152 153 // printed by 'go test' along with an error if the test binary terminates 154 // with an error. 155 bigFailErrorPrefix = []byte("FAIL\t") 156 157 updates = [][]byte{ 158 []byte("=== RUN "), 159 []byte("=== PAUSE "), 160 []byte("=== CONT "), 161 } 162 163 reports = [][]byte{ 164 []byte("--- PASS: "), 165 []byte("--- FAIL: "), 166 []byte("--- SKIP: "), 167 []byte("--- BENCH: "), 168 } 169 170 fourSpace = []byte(" ") 171 172 skipLinePrefix = []byte("? \t") 173 skipLineSuffix = []byte("\t[no test files]\n") 174) 175 176// handleInputLine handles a single whole test output line. 177// It must write the line to c.output but may choose to do so 178// before or after emitting other events. 179func (c *Converter) handleInputLine(line []byte) { 180 // Final PASS or FAIL. 181 if bytes.Equal(line, bigPass) || bytes.Equal(line, bigFail) || bytes.HasPrefix(line, bigFailErrorPrefix) { 182 c.flushReport(0) 183 c.output.write(line) 184 if bytes.Equal(line, bigPass) { 185 c.result = "pass" 186 } else { 187 c.result = "fail" 188 } 189 return 190 } 191 192 // Special case for entirely skipped test binary: "? \tpkgname\t[no test files]\n" is only line. 193 // Report it as plain output but remember to say skip in the final summary. 194 if bytes.HasPrefix(line, skipLinePrefix) && bytes.HasSuffix(line, skipLineSuffix) && len(c.report) == 0 { 195 c.result = "skip" 196 } 197 198 // "=== RUN " 199 // "=== PAUSE " 200 // "=== CONT " 201 actionColon := false 202 origLine := line 203 ok := false 204 indent := 0 205 for _, magic := range updates { 206 if bytes.HasPrefix(line, magic) { 207 ok = true 208 break 209 } 210 } 211 if !ok { 212 // "--- PASS: " 213 // "--- FAIL: " 214 // "--- SKIP: " 215 // "--- BENCH: " 216 // but possibly indented. 217 for bytes.HasPrefix(line, fourSpace) { 218 line = line[4:] 219 indent++ 220 } 221 for _, magic := range reports { 222 if bytes.HasPrefix(line, magic) { 223 actionColon = true 224 ok = true 225 break 226 } 227 } 228 } 229 230 // Not a special test output line. 231 if !ok { 232 // Lookup the name of the test which produced the output using the 233 // indentation of the output as an index into the stack of the current 234 // subtests. 235 // If the indentation is greater than the number of current subtests 236 // then the output must have included extra indentation. We can't 237 // determine which subtest produced this output, so we default to the 238 // old behaviour of assuming the most recently run subtest produced it. 239 if indent > 0 && indent <= len(c.report) { 240 c.testName = c.report[indent-1].Test 241 } 242 c.output.write(origLine) 243 return 244 } 245 246 // Parse out action and test name. 247 i := 0 248 if actionColon { 249 i = bytes.IndexByte(line, ':') + 1 250 } 251 if i == 0 { 252 i = len(updates[0]) 253 } 254 action := strings.ToLower(strings.TrimSuffix(strings.TrimSpace(string(line[4:i])), ":")) 255 name := strings.TrimSpace(string(line[i:])) 256 257 e := &event{Action: action} 258 if line[0] == '-' { // PASS or FAIL report 259 // Parse out elapsed time. 260 if i := strings.Index(name, " ("); i >= 0 { 261 if strings.HasSuffix(name, "s)") { 262 t, err := strconv.ParseFloat(name[i+2:len(name)-2], 64) 263 if err == nil { 264 if c.mode&Timestamp != 0 { 265 e.Elapsed = &t 266 } 267 } 268 } 269 name = name[:i] 270 } 271 if len(c.report) < indent { 272 // Nested deeper than expected. 273 // Treat this line as plain output. 274 c.output.write(origLine) 275 return 276 } 277 // Flush reports at this indentation level or deeper. 278 c.flushReport(indent) 279 e.Test = name 280 c.testName = name 281 c.report = append(c.report, e) 282 c.output.write(origLine) 283 return 284 } 285 // === update. 286 // Finish any pending PASS/FAIL reports. 287 c.flushReport(0) 288 c.testName = name 289 290 if action == "pause" { 291 // For a pause, we want to write the pause notification before 292 // delivering the pause event, just so it doesn't look like the test 293 // is generating output immediately after being paused. 294 c.output.write(origLine) 295 } 296 c.writeEvent(e) 297 if action != "pause" { 298 c.output.write(origLine) 299 } 300 301 return 302} 303 304// flushReport flushes all pending PASS/FAIL reports at levels >= depth. 305func (c *Converter) flushReport(depth int) { 306 c.testName = "" 307 for len(c.report) > depth { 308 e := c.report[len(c.report)-1] 309 c.report = c.report[:len(c.report)-1] 310 c.writeEvent(e) 311 } 312} 313 314// Close marks the end of the go test output. 315// It flushes any pending input and then output (only partial lines at this point) 316// and then emits the final overall package-level pass/fail event. 317func (c *Converter) Close() error { 318 c.input.flush() 319 c.output.flush() 320 if c.result != "" { 321 e := &event{Action: c.result} 322 if c.mode&Timestamp != 0 { 323 dt := time.Since(c.start).Round(1 * time.Millisecond).Seconds() 324 e.Elapsed = &dt 325 } 326 c.writeEvent(e) 327 } 328 return nil 329} 330 331// writeOutputEvent writes a single output event with the given bytes. 332func (c *Converter) writeOutputEvent(out []byte) { 333 c.writeEvent(&event{ 334 Action: "output", 335 Output: (*textBytes)(&out), 336 }) 337} 338 339// writeEvent writes a single event. 340// It adds the package, time (if requested), and test name (if needed). 341func (c *Converter) writeEvent(e *event) { 342 e.Package = c.pkg 343 if c.mode&Timestamp != 0 { 344 t := time.Now() 345 e.Time = &t 346 } 347 if e.Test == "" { 348 e.Test = c.testName 349 } 350 js, err := json.Marshal(e) 351 if err != nil { 352 // Should not happen - event is valid for json.Marshal. 353 c.w.Write([]byte(fmt.Sprintf("testjson internal error: %v\n", err))) 354 return 355 } 356 js = append(js, '\n') 357 c.w.Write(js) 358} 359 360// A lineBuffer is an I/O buffer that reacts to writes by invoking 361// input-processing callbacks on whole lines or (for long lines that 362// have been split) line fragments. 363// 364// It should be initialized with b set to a buffer of length 0 but non-zero capacity, 365// and line and part set to the desired input processors. 366// The lineBuffer will call line(x) for any whole line x (including the final newline) 367// that fits entirely in cap(b). It will handle input lines longer than cap(b) by 368// calling part(x) for sections of the line. The line will be split at UTF8 boundaries, 369// and the final call to part for a long line includes the final newline. 370type lineBuffer struct { 371 b []byte // buffer 372 mid bool // whether we're in the middle of a long line 373 line func([]byte) // line callback 374 part func([]byte) // partial line callback 375} 376 377// write writes b to the buffer. 378func (l *lineBuffer) write(b []byte) { 379 for len(b) > 0 { 380 // Copy what we can into b. 381 m := copy(l.b[len(l.b):cap(l.b)], b) 382 l.b = l.b[:len(l.b)+m] 383 b = b[m:] 384 385 // Process lines in b. 386 i := 0 387 for i < len(l.b) { 388 j := bytes.IndexByte(l.b[i:], '\n') 389 if j < 0 { 390 if !l.mid { 391 if j := bytes.IndexByte(l.b[i:], '\t'); j >= 0 { 392 if isBenchmarkName(bytes.TrimRight(l.b[i:i+j], " ")) { 393 l.part(l.b[i : i+j+1]) 394 l.mid = true 395 i += j + 1 396 } 397 } 398 } 399 break 400 } 401 e := i + j + 1 402 if l.mid { 403 // Found the end of a partial line. 404 l.part(l.b[i:e]) 405 l.mid = false 406 } else { 407 // Found a whole line. 408 l.line(l.b[i:e]) 409 } 410 i = e 411 } 412 413 // Whatever's left in l.b is a line fragment. 414 if i == 0 && len(l.b) == cap(l.b) { 415 // The whole buffer is a fragment. 416 // Emit it as the beginning (or continuation) of a partial line. 417 t := trimUTF8(l.b) 418 l.part(l.b[:t]) 419 l.b = l.b[:copy(l.b, l.b[t:])] 420 l.mid = true 421 } 422 423 // There's room for more input. 424 // Slide it down in hope of completing the line. 425 if i > 0 { 426 l.b = l.b[:copy(l.b, l.b[i:])] 427 } 428 } 429} 430 431// flush flushes the line buffer. 432func (l *lineBuffer) flush() { 433 if len(l.b) > 0 { 434 // Must be a line without a \n, so a partial line. 435 l.part(l.b) 436 l.b = l.b[:0] 437 } 438} 439 440var benchmark = []byte("Benchmark") 441 442// isBenchmarkName reports whether b is a valid benchmark name 443// that might appear as the first field in a benchmark result line. 444func isBenchmarkName(b []byte) bool { 445 if !bytes.HasPrefix(b, benchmark) { 446 return false 447 } 448 if len(b) == len(benchmark) { // just "Benchmark" 449 return true 450 } 451 r, _ := utf8.DecodeRune(b[len(benchmark):]) 452 return !unicode.IsLower(r) 453} 454 455// trimUTF8 returns a length t as close to len(b) as possible such that b[:t] 456// does not end in the middle of a possibly-valid UTF-8 sequence. 457// 458// If a large text buffer must be split before position i at the latest, 459// splitting at position trimUTF(b[:i]) avoids splitting a UTF-8 sequence. 460func trimUTF8(b []byte) int { 461 // Scan backward to find non-continuation byte. 462 for i := 1; i < utf8.UTFMax && i <= len(b); i++ { 463 if c := b[len(b)-i]; c&0xc0 != 0x80 { 464 switch { 465 case c&0xe0 == 0xc0: 466 if i < 2 { 467 return len(b) - i 468 } 469 case c&0xf0 == 0xe0: 470 if i < 3 { 471 return len(b) - i 472 } 473 case c&0xf8 == 0xf0: 474 if i < 4 { 475 return len(b) - i 476 } 477 } 478 break 479 } 480 } 481 return len(b) 482} 483