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