| // Copyright 2017 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| // Test2json converts go test output to a machine-readable JSON stream. |
| // |
| // Usage: |
| // |
| // go tool test2json [-p pkg] [-t] [./pkg.test -test.v [-test.paniconexit0]] |
| // |
| // Test2json runs the given test command and converts its output to JSON; |
| // with no command specified, test2json expects test output on standard input. |
| // It writes a corresponding stream of JSON events to standard output. |
| // There is no unnecessary input or output buffering, so that |
| // the JSON stream can be read for “live updates” of test status. |
| // |
| // The -p flag sets the package reported in each test event. |
| // |
| // The -t flag requests that time stamps be added to each test event. |
| // |
| // The test must be invoked with -test.v. Additionally passing |
| // -test.paniconexit0 will cause test2json to exit with a non-zero |
| // status if one of the tests being run calls os.Exit(0). |
| // |
| // Note that test2json is only intended for converting a single test |
| // binary's output. To convert the output of a "go test" command, |
| // use "go test -json" instead of invoking test2json directly. |
| // |
| // # Output Format |
| // |
| // The JSON stream is a newline-separated sequence of TestEvent objects |
| // corresponding to the Go struct: |
| // |
| // type TestEvent struct { |
| // Time time.Time // encodes as an RFC3339-format string |
| // Action string |
| // Package string |
| // Test string |
| // Elapsed float64 // seconds |
| // Output string |
| // } |
| // |
| // The Time field holds the time the event happened. |
| // It is conventionally omitted for cached test results. |
| // |
| // The Action field is one of a fixed set of action descriptions: |
| // |
| // run - the test has started running |
| // pause - the test has been paused |
| // cont - the test has continued running |
| // pass - the test passed |
| // bench - the benchmark printed log output but did not fail |
| // fail - the test or benchmark failed |
| // output - the test printed output |
| // skip - the test was skipped or the package contained no tests |
| // |
| // The Package field, if present, specifies the package being tested. |
| // When the go command runs parallel tests in -json mode, events from |
| // different tests are interlaced; the Package field allows readers to |
| // separate them. |
| // |
| // The Test field, if present, specifies the test, example, or benchmark |
| // function that caused the event. Events for the overall package test |
| // do not set Test. |
| // |
| // The Elapsed field is set for "pass" and "fail" events. It gives the time |
| // elapsed for the specific test or the overall package test that passed or failed. |
| // |
| // The Output field is set for Action == "output" and is a portion of the test's output |
| // (standard output and standard error merged together). The output is |
| // unmodified except that invalid UTF-8 output from a test is coerced |
| // into valid UTF-8 by use of replacement characters. With that one exception, |
| // the concatenation of the Output fields of all output events is the exact |
| // output of the test execution. |
| // |
| // When a benchmark runs, it typically produces a single line of output |
| // giving timing results. That line is reported in an event with Action == "output" |
| // and no Test field. If a benchmark logs output or reports a failure |
| // (for example, by using b.Log or b.Error), that extra output is reported |
| // as a sequence of events with Test set to the benchmark name, terminated |
| // by a final event with Action == "bench" or "fail". |
| // Benchmarks have no events with Action == "run", "pause", or "cont". |
| package main |
| |
| import ( |
| "flag" |
| "fmt" |
| exec "internal/execabs" |
| "io" |
| "os" |
| |
| "cmd/internal/test2json" |
| ) |
| |
| var ( |
| flagP = flag.String("p", "", "report `pkg` as the package being tested in each event") |
| flagT = flag.Bool("t", false, "include timestamps in events") |
| ) |
| |
| func usage() { |
| fmt.Fprintf(os.Stderr, "usage: go tool test2json [-p pkg] [-t] [./pkg.test -test.v]\n") |
| os.Exit(2) |
| } |
| |
| func main() { |
| flag.Usage = usage |
| flag.Parse() |
| |
| var mode test2json.Mode |
| if *flagT { |
| mode |= test2json.Timestamp |
| } |
| c := test2json.NewConverter(os.Stdout, *flagP, mode) |
| defer c.Close() |
| |
| if flag.NArg() == 0 { |
| io.Copy(c, os.Stdin) |
| } else { |
| args := flag.Args() |
| cmd := exec.Command(args[0], args[1:]...) |
| w := &countWriter{0, c} |
| cmd.Stdout = w |
| cmd.Stderr = w |
| err := cmd.Run() |
| if err != nil { |
| if w.n > 0 { |
| // Assume command printed why it failed. |
| } else { |
| fmt.Fprintf(c, "test2json: %v\n", err) |
| } |
| } |
| c.Exited(err) |
| if err != nil { |
| c.Close() |
| os.Exit(1) |
| } |
| } |
| } |
| |
| type countWriter struct { |
| n int64 |
| w io.Writer |
| } |
| |
| func (w *countWriter) Write(b []byte) (int, error) { |
| w.n += int64(len(b)) |
| return w.w.Write(b) |
| } |