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  }