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