github.com/kdevb0x/go@v0.0.0-20180115030120-39687051e9e7/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 origLine := line 179 ok := false 180 indent := 0 181 for _, magic := range updates { 182 if bytes.HasPrefix(line, magic) { 183 ok = true 184 break 185 } 186 } 187 if !ok { 188 // "--- PASS: " 189 // "--- FAIL: " 190 // "--- SKIP: " 191 // "--- BENCH: " 192 // but possibly indented. 193 for bytes.HasPrefix(line, fourSpace) { 194 line = line[4:] 195 indent++ 196 } 197 for _, magic := range reports { 198 if bytes.HasPrefix(line, magic) { 199 ok = true 200 break 201 } 202 } 203 } 204 205 if !ok { 206 // Not a special test output line. 207 c.output.write(origLine) 208 return 209 } 210 211 // Parse out action and test name. 212 i := bytes.IndexByte(line, ':') + 1 213 if i == 0 { 214 i = len(updates[0]) 215 } 216 action := strings.ToLower(strings.TrimSuffix(strings.TrimSpace(string(line[4:i])), ":")) 217 name := strings.TrimSpace(string(line[i:])) 218 219 e := &event{Action: action} 220 if line[0] == '-' { // PASS or FAIL report 221 // Parse out elapsed time. 222 if i := strings.Index(name, " ("); i >= 0 { 223 if strings.HasSuffix(name, "s)") { 224 t, err := strconv.ParseFloat(name[i+2:len(name)-2], 64) 225 if err == nil { 226 if c.mode&Timestamp != 0 { 227 e.Elapsed = &t 228 } 229 } 230 } 231 name = name[:i] 232 } 233 if len(c.report) < indent { 234 // Nested deeper than expected. 235 // Treat this line as plain output. 236 c.output.write(origLine) 237 return 238 } 239 // Flush reports at this indentation level or deeper. 240 c.flushReport(indent) 241 e.Test = name 242 c.testName = name 243 c.report = append(c.report, e) 244 c.output.write(origLine) 245 return 246 } 247 // === update. 248 // Finish any pending PASS/FAIL reports. 249 c.flushReport(0) 250 c.testName = name 251 252 if action == "pause" { 253 // For a pause, we want to write the pause notification before 254 // delivering the pause event, just so it doesn't look like the test 255 // is generating output immediately after being paused. 256 c.output.write(origLine) 257 } 258 c.writeEvent(e) 259 if action != "pause" { 260 c.output.write(origLine) 261 } 262 263 return 264 } 265 266 // flushReport flushes all pending PASS/FAIL reports at levels >= depth. 267 func (c *converter) flushReport(depth int) { 268 c.testName = "" 269 for len(c.report) > depth { 270 e := c.report[len(c.report)-1] 271 c.report = c.report[:len(c.report)-1] 272 c.writeEvent(e) 273 } 274 } 275 276 // Close marks the end of the go test output. 277 // It flushes any pending input and then output (only partial lines at this point) 278 // and then emits the final overall package-level pass/fail event. 279 func (c *converter) Close() error { 280 c.input.flush() 281 c.output.flush() 282 e := &event{Action: "fail"} 283 if c.result != "" { 284 e.Action = c.result 285 } 286 if c.mode&Timestamp != 0 { 287 dt := time.Since(c.start).Round(1 * time.Millisecond).Seconds() 288 e.Elapsed = &dt 289 } 290 c.writeEvent(e) 291 return nil 292 } 293 294 // writeOutputEvent writes a single output event with the given bytes. 295 func (c *converter) writeOutputEvent(out []byte) { 296 c.writeEvent(&event{ 297 Action: "output", 298 Output: (*textBytes)(&out), 299 }) 300 } 301 302 // writeEvent writes a single event. 303 // It adds the package, time (if requested), and test name (if needed). 304 func (c *converter) writeEvent(e *event) { 305 e.Package = c.pkg 306 if c.mode&Timestamp != 0 { 307 t := time.Now() 308 e.Time = &t 309 } 310 if e.Test == "" { 311 e.Test = c.testName 312 } 313 js, err := json.Marshal(e) 314 if err != nil { 315 // Should not happen - event is valid for json.Marshal. 316 c.w.Write([]byte(fmt.Sprintf("testjson internal error: %v\n", err))) 317 return 318 } 319 js = append(js, '\n') 320 c.w.Write(js) 321 } 322 323 // A lineBuffer is an I/O buffer that reacts to writes by invoking 324 // input-processing callbacks on whole lines or (for long lines that 325 // have been split) line fragments. 326 // 327 // It should be initialized with b set to a buffer of length 0 but non-zero capacity, 328 // and line and part set to the desired input processors. 329 // The lineBuffer will call line(x) for any whole line x (including the final newline) 330 // that fits entirely in cap(b). It will handle input lines longer than cap(b) by 331 // calling part(x) for sections of the line. The line will be split at UTF8 boundaries, 332 // and the final call to part for a long line includes the final newline. 333 type lineBuffer struct { 334 b []byte // buffer 335 mid bool // whether we're in the middle of a long line 336 line func([]byte) // line callback 337 part func([]byte) // partial line callback 338 } 339 340 // write writes b to the buffer. 341 func (l *lineBuffer) write(b []byte) { 342 for len(b) > 0 { 343 // Copy what we can into b. 344 m := copy(l.b[len(l.b):cap(l.b)], b) 345 l.b = l.b[:len(l.b)+m] 346 b = b[m:] 347 348 // Process lines in b. 349 i := 0 350 for i < len(l.b) { 351 j := bytes.IndexByte(l.b[i:], '\n') 352 if j < 0 { 353 if !l.mid { 354 if j := bytes.IndexByte(l.b[i:], '\t'); j >= 0 { 355 if isBenchmarkName(bytes.TrimRight(l.b[i:i+j], " ")) { 356 l.part(l.b[i : i+j+1]) 357 l.mid = true 358 i += j + 1 359 } 360 } 361 } 362 break 363 } 364 e := i + j + 1 365 if l.mid { 366 // Found the end of a partial line. 367 l.part(l.b[i:e]) 368 l.mid = false 369 } else { 370 // Found a whole line. 371 l.line(l.b[i:e]) 372 } 373 i = e 374 } 375 376 // Whatever's left in l.b is a line fragment. 377 if i == 0 && len(l.b) == cap(l.b) { 378 // The whole buffer is a fragment. 379 // Emit it as the beginning (or continuation) of a partial line. 380 t := trimUTF8(l.b) 381 l.part(l.b[:t]) 382 l.b = l.b[:copy(l.b, l.b[t:])] 383 l.mid = true 384 } 385 386 // There's room for more input. 387 // Slide it down in hope of completing the line. 388 if i > 0 { 389 l.b = l.b[:copy(l.b, l.b[i:])] 390 } 391 } 392 } 393 394 // flush flushes the line buffer. 395 func (l *lineBuffer) flush() { 396 if len(l.b) > 0 { 397 // Must be a line without a \n, so a partial line. 398 l.part(l.b) 399 l.b = l.b[:0] 400 } 401 } 402 403 var benchmark = []byte("Benchmark") 404 405 // isBenchmarkName reports whether b is a valid benchmark name 406 // that might appear as the first field in a benchmark result line. 407 func isBenchmarkName(b []byte) bool { 408 if !bytes.HasPrefix(b, benchmark) { 409 return false 410 } 411 if len(b) == len(benchmark) { // just "Benchmark" 412 return true 413 } 414 r, _ := utf8.DecodeRune(b[len(benchmark):]) 415 return !unicode.IsLower(r) 416 } 417 418 // trimUTF8 returns a length t as close to len(b) as possible such that b[:t] 419 // does not end in the middle of a possibly-valid UTF-8 sequence. 420 // 421 // If a large text buffer must be split before position i at the latest, 422 // splitting at position trimUTF(b[:i]) avoids splitting a UTF-8 sequence. 423 func trimUTF8(b []byte) int { 424 // Scan backward to find non-continuation byte. 425 for i := 1; i < utf8.UTFMax && i <= len(b); i++ { 426 if c := b[len(b)-i]; c&0xc0 != 0x80 { 427 switch { 428 case c&0xe0 == 0xc0: 429 if i < 2 { 430 return len(b) - i 431 } 432 case c&0xf0 == 0xe0: 433 if i < 3 { 434 return len(b) - i 435 } 436 case c&0xf8 == 0xf0: 437 if i < 4 { 438 return len(b) - i 439 } 440 } 441 break 442 } 443 } 444 return len(b) 445 }