...

Source file src/pkg/cmd/internal/test2json/test2json.go

     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	}
   451	

View as plain text