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  }