github.com/bir3/gocompiler@v0.9.2202/src/cmd/internal/test2json/test2json.go (about) 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 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 // Mode controls details of the conversion. 24 type Mode int 25 26 const ( 27 Timestamp Mode = 1 << iota // include Time in events 28 ) 29 30 // event is the JSON struct we emit. 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 // textBytes is a hack to get JSON to emit a []byte as a string 41 // without actually copying it to a string. 42 // It implements encoding.TextMarshaler, which returns its text form as a []byte, 43 // and then json encodes that text form as a string (which was our goal). 44 type textBytes []byte 45 46 func (b textBytes) MarshalText() ([]byte, error) { return b, nil } 47 48 // A Converter holds the state of a test-to-JSON conversion. 49 // It implements io.WriteCloser; the caller writes test output in, 50 // and the converter writes JSON output to w. 51 type Converter struct { 52 w io.Writer // JSON output stream 53 pkg string // package to name in events 54 mode Mode // mode bits 55 start time.Time // time converter started 56 testName string // name of current test, for output attribution 57 report []*event // pending test result reports (nested for subtests) 58 result string // overall test result if seen 59 input lineBuffer // input buffer 60 output lineBuffer // output buffer 61 needMarker bool // require ^V marker to introduce test framing line 62 } 63 64 // inBuffer and outBuffer are the input and output buffer sizes. 65 // They're variables so that they can be reduced during testing. 66 // 67 // The input buffer needs to be able to hold any single test 68 // directive line we want to recognize, like: 69 // 70 // <many spaces> --- PASS: very/nested/s/u/b/t/e/s/t 71 // 72 // If anyone reports a test directive line > 4k not working, it will 73 // be defensible to suggest they restructure their test or test names. 74 // 75 // The output buffer must be >= utf8.UTFMax, so that it can 76 // accumulate any single UTF8 sequence. Lines that fit entirely 77 // within the output buffer are emitted in single output events. 78 // Otherwise they are split into multiple events. 79 // The output buffer size therefore limits the size of the encoding 80 // of a single JSON output event. 1k seems like a reasonable balance 81 // between wanting to avoid splitting an output line and not wanting to 82 // generate enormous output events. 83 var ( 84 inBuffer = 4096 85 outBuffer = 1024 86 ) 87 88 // NewConverter returns a "test to json" converter. 89 // Writes on the returned writer are written as JSON to w, 90 // with minimal delay. 91 // 92 // The writes to w are whole JSON events ending in \n, 93 // so that it is safe to run multiple tests writing to multiple converters 94 // writing to a single underlying output stream w. 95 // As long as the underlying output w can handle concurrent writes 96 // from multiple goroutines, the result will be a JSON stream 97 // describing the relative ordering of execution in all the concurrent tests. 98 // 99 // The mode flag adjusts the behavior of the converter. 100 // Passing ModeTime includes event timestamps and elapsed times. 101 // 102 // The pkg string, if present, specifies the import path to 103 // report in the JSON stream. 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 // Write writes the test input to the converter. 127 func (c *Converter) Write(b []byte) (int, error) { 128 c.input.write(b) 129 return len(b), nil 130 } 131 132 // Exited marks the test process as having exited with the given error. 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) // ^V 144 145 var ( 146 // printed by test on successful run. 147 bigPass = []byte("PASS") 148 149 // printed by test after a normal test failure. 150 bigFail = []byte("FAIL") 151 152 // printed by 'go test' along with an error if the test binary terminates 153 // with an error. 154 bigFailErrorPrefix = []byte("FAIL\t") 155 156 // an === NAME line with no test name, if trailing spaces are deleted 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 // handleInputLine handles a single whole test output line. 184 // It must write the line to c.output but may choose to do so 185 // before or after emitting other events. 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 // Trim is line without \n or \r\n. 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 // === CONT followed by an empty test name can lose its trailing spaces. 211 if bytes.Equal(trim, emptyName) { 212 line = emptyNameLine 213 trim = line[:len(line)-1] 214 } 215 216 // Final PASS or FAIL. 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 // Special case for entirely skipped test binary: "? \tpkgname\t[no test files]\n" is only line. 231 // Report it as plain output but remember to say skip in the final summary. 232 if bytes.HasPrefix(line, skipLinePrefix) && bytes.HasSuffix(trim, skipLineSuffix) && len(c.report) == 0 { 233 c.result = "skip" 234 } 235 236 // "=== RUN " 237 // "=== PAUSE " 238 // "=== CONT " 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 // "--- PASS: " 251 // "--- FAIL: " 252 // "--- SKIP: " 253 // "--- BENCH: " 254 // but possibly indented. 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 // Not a special test output line. 269 if !ok { 270 // Lookup the name of the test which produced the output using the 271 // indentation of the output as an index into the stack of the current 272 // subtests. 273 // If the indentation is greater than the number of current subtests 274 // then the output must have included extra indentation. We can't 275 // determine which subtest produced this output, so we default to the 276 // old behaviour of assuming the most recently run subtest produced it. 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 // Parse out action and test name. 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] == '-' { // PASS or FAIL report 297 // Parse out elapsed time. 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 // Nested deeper than expected. 311 // Treat this line as plain output. 312 c.output.write(origLine) 313 return 314 } 315 // Flush reports at this indentation level or deeper. 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 // === update. 325 // Finish any pending PASS/FAIL reports. 326 c.needMarker = sawMarker 327 c.flushReport(0) 328 c.testName = name 329 330 if action == "name" { 331 // This line is only generated to get c.testName right. 332 // Don't emit an event. 333 return 334 } 335 336 if action == "pause" { 337 // For a pause, we want to write the pause notification before 338 // delivering the pause event, just so it doesn't look like the test 339 // is generating output immediately after being paused. 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 // flushReport flushes all pending PASS/FAIL reports at levels >= depth. 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 // Close marks the end of the go test output. 361 // It flushes any pending input and then output (only partial lines at this point) 362 // and then emits the final overall package-level pass/fail event. 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 // writeOutputEvent writes a single output event with the given bytes. 378 func (c *Converter) writeOutputEvent(out []byte) { 379 c.writeEvent(&event{ 380 Action: "output", 381 Output: (*textBytes)(&out), 382 }) 383 } 384 385 // writeEvent writes a single event. 386 // It adds the package, time (if requested), and test name (if needed). 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 // Should not happen - event is valid for json.Marshal. 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 // A lineBuffer is an I/O buffer that reacts to writes by invoking 407 // input-processing callbacks on whole lines or (for long lines that 408 // have been split) line fragments. 409 // 410 // It should be initialized with b set to a buffer of length 0 but non-zero capacity, 411 // and line and part set to the desired input processors. 412 // The lineBuffer will call line(x) for any whole line x (including the final newline) 413 // that fits entirely in cap(b). It will handle input lines longer than cap(b) by 414 // calling part(x) for sections of the line. The line will be split at UTF8 boundaries, 415 // and the final call to part for a long line includes the final newline. 416 type lineBuffer struct { 417 b []byte // buffer 418 mid bool // whether we're in the middle of a long line 419 line func([]byte) // line callback 420 part func([]byte) // partial line callback 421 } 422 423 // write writes b to the buffer. 424 func (l *lineBuffer) write(b []byte) { 425 for len(b) > 0 { 426 // Copy what we can into l.b. 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 // Process lines in l.b. 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 // Found the end of a partial line. 450 l.part(l.b[i:e]) 451 l.mid = false 452 } else { 453 // Found a whole line. 454 l.line(l.b[i:e]) 455 } 456 i = e 457 } 458 459 // Whatever's left in l.b is a line fragment. 460 if i == 0 && len(l.b) == cap(l.b) { 461 // The whole buffer is a fragment. 462 // Emit it as the beginning (or continuation) of a partial line. 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 // There's room for more input. 470 // Slide it down in hope of completing the line. 471 if i > 0 { 472 l.b = l.b[:copy(l.b, l.b[i:])] 473 } 474 } 475 } 476 477 // indexEOL finds the index of a line ending, 478 // returning its position and output width. 479 // A line ending is either a \n or the empty string just before a ^V not beginning a line. 480 // The output width for \n is 1 (meaning it should be printed) 481 // but the output width for ^V is 0 (meaning it should be left to begin the next line). 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 { // test -v=json emits ^V at start of framing lines 488 return i, 0 489 } 490 } 491 return -1, 0 492 } 493 494 // flush flushes the line buffer. 495 func (l *lineBuffer) flush() { 496 if len(l.b) > 0 { 497 // Must be a line without a \n, so a partial line. 498 l.part(l.b) 499 l.b = l.b[:0] 500 } 501 } 502 503 var benchmark = []byte("Benchmark") 504 505 // isBenchmarkName reports whether b is a valid benchmark name 506 // that might appear as the first field in a benchmark result line. 507 func isBenchmarkName(b []byte) bool { 508 if !bytes.HasPrefix(b, benchmark) { 509 return false 510 } 511 if len(b) == len(benchmark) { // just "Benchmark" 512 return true 513 } 514 r, _ := utf8.DecodeRune(b[len(benchmark):]) 515 return !unicode.IsLower(r) 516 } 517 518 // trimUTF8 returns a length t as close to len(b) as possible such that b[:t] 519 // does not end in the middle of a possibly-valid UTF-8 sequence. 520 // 521 // If a large text buffer must be split before position i at the latest, 522 // splitting at position trimUTF(b[:i]) avoids splitting a UTF-8 sequence. 523 func trimUTF8(b []byte) int { 524 // Scan backward to find non-continuation byte. 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 }