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 [-test.paniconexit0]] 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 must be invoked with -test.v. Additionally passing 22 // -test.paniconexit0 will cause test2json to exit with a non-zero 23 // status if one of the tests being run calls os.Exit(0). 24 // 25 // Note that test2json is only intended for converting a single test 26 // binary's output. To convert the output of a "go test" command, 27 // use "go test -json" instead of invoking test2json directly. 28 // 29 // Output Format 30 // 31 // The JSON stream is a newline-separated sequence of TestEvent objects 32 // corresponding to the Go struct: 33 // 34 // type TestEvent struct { 35 // Time time.Time // encodes as an RFC3339-format string 36 // Action string 37 // Package string 38 // Test string 39 // Elapsed float64 // seconds 40 // Output string 41 // } 42 // 43 // The Time field holds the time the event happened. 44 // It is conventionally omitted for cached test results. 45 // 46 // The Action field is one of a fixed set of action descriptions: 47 // 48 // run - the test has started running 49 // pause - the test has been paused 50 // cont - the test has continued running 51 // pass - the test passed 52 // bench - the benchmark printed log output but did not fail 53 // fail - the test or benchmark failed 54 // output - the test printed output 55 // skip - the test was skipped or the package contained no tests 56 // 57 // The Package field, if present, specifies the package being tested. 58 // When the go command runs parallel tests in -json mode, events from 59 // different tests are interlaced; the Package field allows readers to 60 // separate them. 61 // 62 // The Test field, if present, specifies the test, example, or benchmark 63 // function that caused the event. Events for the overall package test 64 // do not set Test. 65 // 66 // The Elapsed field is set for "pass" and "fail" events. It gives the time 67 // elapsed for the specific test or the overall package test that passed or failed. 68 // 69 // The Output field is set for Action == "output" and is a portion of the test's output 70 // (standard output and standard error merged together). The output is 71 // unmodified except that invalid UTF-8 output from a test is coerced 72 // into valid UTF-8 by use of replacement characters. With that one exception, 73 // the concatenation of the Output fields of all output events is the exact 74 // output of the test execution. 75 // 76 // When a benchmark runs, it typically produces a single line of output 77 // giving timing results. That line is reported in an event with Action == "output" 78 // and no Test field. If a benchmark logs output or reports a failure 79 // (for example, by using b.Log or b.Error), that extra output is reported 80 // as a sequence of events with Test set to the benchmark name, terminated 81 // by a final event with Action == "bench" or "fail". 82 // Benchmarks have no events with Action == "run", "pause", or "cont". 83 // 84 package main 85 86 import ( 87 "flag" 88 "fmt" 89 exec "internal/execabs" 90 "io" 91 "os" 92 93 "cmd/internal/test2json" 94 ) 95 96 var ( 97 flagP = flag.String("p", "", "report `pkg` as the package being tested in each event") 98 flagT = flag.Bool("t", false, "include timestamps in events") 99 ) 100 101 func usage() { 102 fmt.Fprintf(os.Stderr, "usage: go tool test2json [-p pkg] [-t] [./pkg.test -test.v]\n") 103 os.Exit(2) 104 } 105 106 func main() { 107 flag.Usage = usage 108 flag.Parse() 109 110 var mode test2json.Mode 111 if *flagT { 112 mode |= test2json.Timestamp 113 } 114 c := test2json.NewConverter(os.Stdout, *flagP, mode) 115 defer c.Close() 116 117 if flag.NArg() == 0 { 118 io.Copy(c, os.Stdin) 119 } else { 120 args := flag.Args() 121 cmd := exec.Command(args[0], args[1:]...) 122 w := &countWriter{0, c} 123 cmd.Stdout = w 124 cmd.Stderr = w 125 err := cmd.Run() 126 if err != nil { 127 if w.n > 0 { 128 // Assume command printed why it failed. 129 } else { 130 fmt.Fprintf(c, "test2json: %v\n", err) 131 } 132 } 133 c.Exited(err) 134 if err != nil { 135 c.Close() 136 os.Exit(1) 137 } 138 } 139 } 140 141 type countWriter struct { 142 n int64 143 w io.Writer 144 } 145 146 func (w *countWriter) Write(b []byte) (int, error) { 147 w.n += int64(len(b)) 148 return w.w.Write(b) 149 } 150