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