xref: /aosp_15_r20/external/bazelbuild-rules_go/go/tools/bzltestutil/test2json.go (revision 9bb1b549b6a84214c53be0924760be030e66b93a)
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