github.com/goproxy0/go@v0.0.0-20171111080102-49cc0c489d2c/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/utf8"
    20  )
    21  
    22  // Mode controls details of the conversion.
    23  type Mode int
    24  
    25  const (
    26  	Timestamp Mode = 1 << iota // include Time in events
    27  )
    28  
    29  // event is the JSON struct we emit.
    30  type event struct {
    31  	Time    *time.Time `json:",omitempty"`
    32  	Action  string
    33  	Package string     `json:",omitempty"`
    34  	Test    string     `json:",omitempty"`
    35  	Elapsed *float64   `json:",omitempty"`
    36  	Output  *textBytes `json:",omitempty"`
    37  }
    38  
    39  // textBytes is a hack to get JSON to emit a []byte as a string
    40  // without actually copying it to a string.
    41  // It implements encoding.TextMarshaler, which returns its text form as a []byte,
    42  // and then json encodes that text form as a string (which was our goal).
    43  type textBytes []byte
    44  
    45  func (b textBytes) MarshalText() ([]byte, error) { return b, nil }
    46  
    47  // A converter holds the state of a test-to-JSON conversion.
    48  // It implements io.WriteCloser; the caller writes test output in,
    49  // and the converter writes JSON output to w.
    50  type converter struct {
    51  	w        io.Writer  // JSON output stream
    52  	pkg      string     // package to name in events
    53  	mode     Mode       // mode bits
    54  	start    time.Time  // time converter started
    55  	testName string     // name of current test, for output attribution
    56  	report   []*event   // pending test result reports (nested for subtests)
    57  	passed   bool       // whether we've seen the final whole-package PASS line
    58  	input    lineBuffer // input buffer
    59  	output   lineBuffer // output buffer
    60  }
    61  
    62  // inBuffer and outBuffer are the input and output buffer sizes.
    63  // They're variables so that they can be reduced during testing.
    64  //
    65  // The input buffer needs to be able to hold any single test
    66  // directive line we want to recognize, like:
    67  //
    68  //     <many spaces> --- PASS: very/nested/s/u/b/t/e/s/t
    69  //
    70  // If anyone reports a test directive line > 4k not working, it will
    71  // be defensible to suggest they restructure their test or test names.
    72  //
    73  // The output buffer must be >= utf8.UTFMax, so that it can
    74  // accumulate any single UTF8 sequence. Lines that fit entirely
    75  // within the output buffer are emitted in single output events.
    76  // Otherwise they are split into multiple events.
    77  // The output buffer size therefore limits the size of the encoding
    78  // of a single JSON output event. 1k seems like a reasonable balance
    79  // between wanting to avoid splitting an output line and not wanting to
    80  // generate enormous output events.
    81  var (
    82  	inBuffer  = 4096
    83  	outBuffer = 1024
    84  )
    85  
    86  // NewConverter returns a "test to json" converter.
    87  // Writes on the returned writer are written as JSON to w,
    88  // with minimal delay.
    89  //
    90  // The writes to w are whole JSON events ending in \n,
    91  // so that it is safe to run multiple tests writing to multiple converters
    92  // writing to a single underlying output stream w.
    93  // As long as the underlying output w can handle concurrent writes
    94  // from multiple goroutines, the result will be a JSON stream
    95  // describing the relative ordering of execution in all the concurrent tests.
    96  //
    97  // The mode flag adjusts the behavior of the converter.
    98  // Passing ModeTime includes event timestamps and elapsed times.
    99  //
   100  // The pkg string, if present, specifies the import path to
   101  // report in the JSON stream.
   102  func NewConverter(w io.Writer, pkg string, mode Mode) io.WriteCloser {
   103  	c := new(converter)
   104  	*c = converter{
   105  		w:     w,
   106  		pkg:   pkg,
   107  		mode:  mode,
   108  		start: time.Now(),
   109  		input: lineBuffer{
   110  			b:    make([]byte, 0, inBuffer),
   111  			line: c.handleInputLine,
   112  			part: c.output.write,
   113  		},
   114  		output: lineBuffer{
   115  			b:    make([]byte, 0, outBuffer),
   116  			line: c.writeOutputEvent,
   117  			part: c.writeOutputEvent,
   118  		},
   119  	}
   120  	return c
   121  }
   122  
   123  // Write writes the test input to the converter.
   124  func (c *converter) Write(b []byte) (int, error) {
   125  	c.input.write(b)
   126  	return len(b), nil
   127  }
   128  
   129  var (
   130  	bigPass = []byte("PASS\n")
   131  	bigFail = []byte("FAIL\n")
   132  
   133  	updates = [][]byte{
   134  		[]byte("=== RUN   "),
   135  		[]byte("=== PAUSE "),
   136  		[]byte("=== CONT  "),
   137  	}
   138  
   139  	reports = [][]byte{
   140  		[]byte("--- PASS: "),
   141  		[]byte("--- FAIL: "),
   142  	}
   143  
   144  	fourSpace = []byte("    ")
   145  )
   146  
   147  // handleInputLine handles a single whole test output line.
   148  // It must write the line to c.output but may choose to do so
   149  // before or after emitting other events.
   150  func (c *converter) handleInputLine(line []byte) {
   151  	// Final PASS or FAIL.
   152  	if bytes.Equal(line, bigPass) || bytes.Equal(line, bigFail) {
   153  		c.flushReport(0)
   154  		c.output.write(line)
   155  		c.passed = bytes.Equal(line, bigPass)
   156  		return
   157  	}
   158  
   159  	// "=== RUN   "
   160  	// "=== PAUSE "
   161  	// "=== CONT  "
   162  	origLine := line
   163  	ok := false
   164  	indent := 0
   165  	for _, magic := range updates {
   166  		if bytes.HasPrefix(line, magic) {
   167  			ok = true
   168  			break
   169  		}
   170  	}
   171  	if !ok {
   172  		// "--- PASS: "
   173  		// "--- FAIL: "
   174  		// but possibly indented.
   175  		for bytes.HasPrefix(line, fourSpace) {
   176  			line = line[4:]
   177  			indent++
   178  		}
   179  		for _, magic := range reports {
   180  			if bytes.HasPrefix(line, magic) {
   181  				ok = true
   182  				break
   183  			}
   184  		}
   185  	}
   186  
   187  	if !ok {
   188  		// Not a special test output line.
   189  		c.output.write(origLine)
   190  		return
   191  	}
   192  
   193  	// Parse out action and test name.
   194  	action := strings.ToLower(strings.TrimSuffix(strings.TrimSpace(string(line[4:4+6])), ":"))
   195  	name := strings.TrimSpace(string(line[4+6:]))
   196  
   197  	e := &event{Action: action}
   198  	if line[0] == '-' { // PASS or FAIL report
   199  		// Parse out elapsed time.
   200  		if i := strings.Index(name, " ("); i >= 0 {
   201  			if strings.HasSuffix(name, "s)") {
   202  				t, err := strconv.ParseFloat(name[i+2:len(name)-2], 64)
   203  				if err == nil {
   204  					if c.mode&Timestamp != 0 {
   205  						e.Elapsed = &t
   206  					}
   207  				}
   208  			}
   209  			name = name[:i]
   210  		}
   211  		if len(c.report) < indent {
   212  			// Nested deeper than expected.
   213  			// Treat this line as plain output.
   214  			return
   215  		}
   216  		// Flush reports at this indentation level or deeper.
   217  		c.flushReport(indent)
   218  		e.Test = name
   219  		c.testName = name
   220  		c.report = append(c.report, e)
   221  		c.output.write(origLine)
   222  		return
   223  	}
   224  	// === update.
   225  	// Finish any pending PASS/FAIL reports.
   226  	c.flushReport(0)
   227  	c.testName = name
   228  
   229  	if action == "pause" {
   230  		// For a pause, we want to write the pause notification before
   231  		// delivering the pause event, just so it doesn't look like the test
   232  		// is generating output immediately after being paused.
   233  		c.output.write(origLine)
   234  	}
   235  	c.writeEvent(e)
   236  	if action != "pause" {
   237  		c.output.write(origLine)
   238  	}
   239  
   240  	return
   241  }
   242  
   243  // flushReport flushes all pending PASS/FAIL reports at levels >= depth.
   244  func (c *converter) flushReport(depth int) {
   245  	c.testName = ""
   246  	for len(c.report) > depth {
   247  		e := c.report[len(c.report)-1]
   248  		c.report = c.report[:len(c.report)-1]
   249  		c.writeEvent(e)
   250  	}
   251  }
   252  
   253  // Close marks the end of the go test output.
   254  // It flushes any pending input and then output (only partial lines at this point)
   255  // and then emits the final overall package-level pass/fail event.
   256  func (c *converter) Close() error {
   257  	c.input.flush()
   258  	c.output.flush()
   259  	e := &event{Action: "fail"}
   260  	if c.passed {
   261  		e.Action = "pass"
   262  	}
   263  	if c.mode&Timestamp != 0 {
   264  		dt := time.Since(c.start).Round(1 * time.Millisecond).Seconds()
   265  		e.Elapsed = &dt
   266  	}
   267  	c.writeEvent(e)
   268  	return nil
   269  }
   270  
   271  // writeOutputEvent writes a single output event with the given bytes.
   272  func (c *converter) writeOutputEvent(out []byte) {
   273  	c.writeEvent(&event{
   274  		Action: "output",
   275  		Output: (*textBytes)(&out),
   276  	})
   277  }
   278  
   279  // writeEvent writes a single event.
   280  // It adds the package, time (if requested), and test name (if needed).
   281  func (c *converter) writeEvent(e *event) {
   282  	e.Package = c.pkg
   283  	if c.mode&Timestamp != 0 {
   284  		t := time.Now()
   285  		e.Time = &t
   286  	}
   287  	if e.Test == "" {
   288  		e.Test = c.testName
   289  	}
   290  	js, err := json.Marshal(e)
   291  	if err != nil {
   292  		// Should not happen - event is valid for json.Marshal.
   293  		c.w.Write([]byte(fmt.Sprintf("testjson internal error: %v\n", err)))
   294  		return
   295  	}
   296  	js = append(js, '\n')
   297  	c.w.Write(js)
   298  }
   299  
   300  // A lineBuffer is an I/O buffer that reacts to writes by invoking
   301  // input-processing callbacks on whole lines or (for long lines that
   302  // have been split) line fragments.
   303  //
   304  // It should be initialized with b set to a buffer of length 0 but non-zero capacity,
   305  // and line and part set to the desired input processors.
   306  // The lineBuffer will call line(x) for any whole line x (including the final newline)
   307  // that fits entirely in cap(b). It will handle input lines longer than cap(b) by
   308  // calling part(x) for sections of the line. The line will be split at UTF8 boundaries,
   309  // and the final call to part for a long line includes the final newline.
   310  type lineBuffer struct {
   311  	b    []byte       // buffer
   312  	mid  bool         // whether we're in the middle of a long line
   313  	line func([]byte) // line callback
   314  	part func([]byte) // partial line callback
   315  }
   316  
   317  // write writes b to the buffer.
   318  func (l *lineBuffer) write(b []byte) {
   319  	for len(b) > 0 {
   320  		// Copy what we can into b.
   321  		m := copy(l.b[len(l.b):cap(l.b)], b)
   322  		l.b = l.b[:len(l.b)+m]
   323  		b = b[m:]
   324  
   325  		// Process lines in b.
   326  		i := 0
   327  		for i < len(l.b) {
   328  			j := bytes.IndexByte(l.b[i:], '\n')
   329  			if j < 0 {
   330  				break
   331  			}
   332  			e := i + j + 1
   333  			if l.mid {
   334  				// Found the end of a partial line.
   335  				l.part(l.b[i:e])
   336  				l.mid = false
   337  			} else {
   338  				// Found a whole line.
   339  				l.line(l.b[i:e])
   340  			}
   341  			i = e
   342  		}
   343  
   344  		// Whatever's left in l.b is a line fragment.
   345  		if i == 0 && len(l.b) == cap(l.b) {
   346  			// The whole buffer is a fragment.
   347  			// Emit it as the beginning (or continuation) of a partial line.
   348  			t := trimUTF8(l.b)
   349  			l.part(l.b[:t])
   350  			l.b = l.b[:copy(l.b, l.b[t:])]
   351  			l.mid = true
   352  		}
   353  
   354  		// There's room for more input.
   355  		// Slide it down in hope of completing the line.
   356  		if i > 0 {
   357  			l.b = l.b[:copy(l.b, l.b[i:])]
   358  		}
   359  	}
   360  }
   361  
   362  // flush flushes the line buffer.
   363  func (l *lineBuffer) flush() {
   364  	if len(l.b) > 0 {
   365  		// Must be a line without a \n, so a partial line.
   366  		l.part(l.b)
   367  		l.b = l.b[:0]
   368  	}
   369  }
   370  
   371  // trimUTF8 returns a length t as close to len(b) as possible such that b[:t]
   372  // does not end in the middle of a possibly-valid UTF-8 sequence.
   373  //
   374  // If a large text buffer must be split before position i at the latest,
   375  // splitting at position trimUTF(b[:i]) avoids splitting a UTF-8 sequence.
   376  func trimUTF8(b []byte) int {
   377  	// Scan backward to find non-continuation byte.
   378  	for i := 1; i < utf8.UTFMax && i <= len(b); i++ {
   379  		if c := b[len(b)-i]; c&0xc0 != 0x80 {
   380  			switch {
   381  			case c&0xe0 == 0xc0:
   382  				if i < 2 {
   383  					return len(b) - i
   384  				}
   385  			case c&0xf0 == 0xe0:
   386  				if i < 3 {
   387  					return len(b) - i
   388  				}
   389  			case c&0xf8 == 0xf0:
   390  				if i < 4 {
   391  					return len(b) - i
   392  				}
   393  			}
   394  			break
   395  		}
   396  	}
   397  	return len(b)
   398  }