Source file src/cmd/test2json/main.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  // Test2json converts go test output to a machine-readable JSON stream.
     6  //
     7  // Usage:
     8  //
     9  //	go tool test2json [-p pkg] [-t] [./pkg.test -test.v=test2json]
    10  //
    11  // Test2json runs the given test command and converts its output to JSON;
    12  // with no command specified, test2json expects test output on standard input.
    13  // It writes a corresponding stream of JSON events to standard output.
    14  // There is no unnecessary input or output buffering, so that
    15  // the JSON stream can be read for “live updates” of test status.
    16  //
    17  // The -p flag sets the package reported in each test event.
    18  //
    19  // The -t flag requests that time stamps be added to each test event.
    20  //
    21  // The test should be invoked with -test.v=test2json. Using only -test.v
    22  // (or -test.v=true) is permissible but produces lower fidelity results.
    23  //
    24  // Note that "go test -json" takes care of invoking test2json correctly,
    25  // so "go tool test2json" is only needed when a test binary is being run
    26  // separately from "go test". Use "go test -json" whenever possible.
    27  //
    28  // Note also that test2json is only intended for converting a single test
    29  // binary's output. To convert the output of a "go test" command that
    30  // runs multiple packages, again use "go test -json".
    31  //
    32  // # Output Format
    33  //
    34  // The JSON stream is a newline-separated sequence of TestEvent objects
    35  // corresponding to the Go struct:
    36  //
    37  //	type TestEvent struct {
    38  //		Time        time.Time // encodes as an RFC3339-format string
    39  //		Action      string
    40  //		Package     string
    41  //		Test        string
    42  //		Elapsed     float64 // seconds
    43  //		Output      string
    44  //		OutputType  string
    45  //		FailedBuild string
    46  //	}
    47  //
    48  // The Time field holds the time the event happened.
    49  // It is conventionally omitted for cached test results.
    50  //
    51  // The Action field is one of a fixed set of action descriptions:
    52  //
    53  //	start  - the test binary is about to be executed
    54  //	run    - the test has started running
    55  //	pause  - the test has been paused
    56  //	cont   - the test has continued running
    57  //	pass   - the test passed
    58  //	bench  - the benchmark printed log output but did not fail
    59  //	fail   - the test or benchmark failed
    60  //	output - the test printed output
    61  //	skip   - the test was skipped or the package contained no tests
    62  //
    63  // Every JSON stream begins with a "start" event.
    64  //
    65  // The Package field, if present, specifies the package being tested.
    66  // When the go command runs parallel tests in -json mode, events from
    67  // different tests are interlaced; the Package field allows readers to
    68  // separate them.
    69  //
    70  // The Test field, if present, specifies the test, example, or benchmark
    71  // function that caused the event. Events for the overall package test
    72  // do not set Test.
    73  //
    74  // The Elapsed field is set for "pass" and "fail" events. It gives the time
    75  // elapsed for the specific test or the overall package test that passed or failed.
    76  //
    77  // The Output field is set for Action == "output" and is a portion of the test's output
    78  // (standard output and standard error merged together). The output is
    79  // unmodified except that invalid UTF-8 output from a test is coerced
    80  // into valid UTF-8 by use of replacement characters. With that one exception,
    81  // the concatenation of the Output fields of all output events is the exact
    82  // output of the test execution.
    83  //
    84  // The FailedBuild field is set for Action == "fail" if the test failure was
    85  // caused by a build failure. It contains the package ID of the package that
    86  // failed to build. This matches the ImportPath field of the "go list" output,
    87  // as well as the BuildEvent.ImportPath field as emitted by "go build -json".
    88  //
    89  // The OutputType field *may* be set for Action == "output" and indicates the
    90  // type of output. OutputType will be one of the following:
    91  //
    92  //	(blank)        - regular output
    93  //	frame          - test framing, such as "=== RUN ..." or "--- FAIL: ..."
    94  //	error          - an error produced by Error(f) or Fatal(f)
    95  //	error-continue - continuation of a multi-line error
    96  //
    97  // When a benchmark runs, it typically produces a single line of output
    98  // giving timing results. That line is reported in an event with Action == "output"
    99  // and no Test field. If a benchmark logs output or reports a failure
   100  // (for example, by using b.Log or b.Error), that extra output is reported
   101  // as a sequence of events with Test set to the benchmark name, terminated
   102  // by a final event with Action == "bench" or "fail".
   103  // Benchmarks have no events with Action == "pause".
   104  package main
   105  
   106  import (
   107  	"flag"
   108  	"fmt"
   109  	"io"
   110  	"os"
   111  	"os/exec"
   112  	"os/signal"
   113  
   114  	"cmd/internal/telemetry/counter"
   115  	"cmd/internal/test2json"
   116  )
   117  
   118  var (
   119  	flagP = flag.String("p", "", "report `pkg` as the package being tested in each event")
   120  	flagT = flag.Bool("t", false, "include timestamps in events")
   121  )
   122  
   123  func usage() {
   124  	fmt.Fprintf(os.Stderr, "usage: go tool test2json [-p pkg] [-t] [./pkg.test -test.v]\n")
   125  	os.Exit(2)
   126  }
   127  
   128  // ignoreSignals ignore the interrupt signals.
   129  func ignoreSignals() {
   130  	signal.Ignore(signalsToIgnore...)
   131  }
   132  
   133  func main() {
   134  	counter.Open()
   135  
   136  	flag.Usage = usage
   137  	flag.Parse()
   138  	counter.Inc("test2json/invocations")
   139  	counter.CountFlags("test2json/flag:", *flag.CommandLine)
   140  
   141  	var mode test2json.Mode
   142  	if *flagT {
   143  		mode |= test2json.Timestamp
   144  	}
   145  	c := test2json.NewConverter(os.Stdout, *flagP, mode)
   146  	defer c.Close()
   147  
   148  	if flag.NArg() == 0 {
   149  		io.Copy(c, os.Stdin)
   150  	} else {
   151  		args := flag.Args()
   152  		cmd := exec.Command(args[0], args[1:]...)
   153  		w := &countWriter{0, c}
   154  		cmd.Stdout = w
   155  		cmd.Stderr = w
   156  		ignoreSignals()
   157  		err := cmd.Run()
   158  		if err != nil {
   159  			if w.n > 0 {
   160  				// Assume command printed why it failed.
   161  			} else {
   162  				fmt.Fprintf(c, "test2json: %v\n", err)
   163  			}
   164  		}
   165  		c.Exited(err)
   166  		if err != nil {
   167  			c.Close()
   168  			os.Exit(1)
   169  		}
   170  	}
   171  }
   172  
   173  type countWriter struct {
   174  	n int64
   175  	w io.Writer
   176  }
   177  
   178  func (w *countWriter) Write(b []byte) (int, error) {
   179  	w.n += int64(len(b))
   180  	return w.w.Write(b)
   181  }
   182  

View as plain text