github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/cmd/testfilter/main.go (about) 1 // Copyright 2019 The Cockroach Authors. 2 // 3 // Use of this software is governed by the Business Source License 4 // included in the file licenses/BSL.txt. 5 // 6 // As of the Change Date specified in that file, in accordance with 7 // the Business Source License, use of this software will be governed 8 // by the Apache License, Version 2.0, included in the file 9 // licenses/APL.txt. 10 11 // testfilter is a utility to manipulate JSON streams in [test2json] format. 12 // Standard input is read and each line starting with `{` and ending with `}` 13 // parsed (and expected to parse successfully). Lines not matching this pattern 14 // are classified as output not related to the test and, depending on the args 15 // passed to `testfilter`, are passed through or removed. The arguments available 16 // are `--mode=(strip|omit|convert)`, where: 17 // 18 // strip: omit output for non-failing tests, pass everything else through. In 19 // particular, non-test output and tests that never terminate are passed through. 20 // omit: print only failing tests. Note that test2json does not close scopes for 21 // tests that are running in parallel (in the same package) with a "foreground" 22 // test that panics, so it will pass through *only* the one foreground test. 23 // Note also that package scopes are omitted; test2json does not reliably close 24 // them on panic/Exit anyway. 25 // convert: 26 // no filtering is performed, but any test2json input is translated back into 27 // its pure Go test framework text representation. This is useful for output 28 // intended for human eyes. 29 // 30 // [test2json]: https://golang.org/cmd/test2json/ 31 package main 32 33 import ( 34 "bufio" 35 "encoding/json" 36 "flag" 37 "fmt" 38 "io" 39 "os" 40 "strings" 41 "time" 42 43 "github.com/cockroachdb/errors" 44 ) 45 46 const modeUsage = `strip: 47 omit output for non-failing tests, but print run/pass/skip events for all tests 48 omit: 49 only emit failing tests 50 convert: 51 don't perform any filtering, simply convert the json back to original test format' 52 ` 53 54 type modeT byte 55 56 const ( 57 modeStrip modeT = iota 58 modeOmit 59 modeConvert 60 ) 61 62 func (m *modeT) Set(s string) error { 63 switch s { 64 case "strip": 65 *m = modeStrip 66 case "omit": 67 *m = modeOmit 68 case "convert": 69 *m = modeConvert 70 default: 71 return errors.New("unsupported mode") 72 } 73 return nil 74 } 75 76 func (m *modeT) String() string { 77 switch *m { 78 case modeStrip: 79 return "strip" 80 case modeOmit: 81 return "omit" 82 case modeConvert: 83 return "convert" 84 default: 85 return "unknown" 86 } 87 } 88 89 var modeVar = modeStrip 90 91 func init() { 92 flag.Var(&modeVar, "mode", modeUsage) 93 } 94 95 type testEvent struct { 96 Time time.Time // encodes as an RFC3339-format string 97 Action string 98 Package string 99 Test string 100 Elapsed float64 // seconds 101 Output string 102 } 103 104 func main() { 105 flag.Parse() 106 if err := filter(os.Stdin, os.Stdout, modeVar); err != nil { 107 fmt.Fprintln(os.Stderr, err) 108 os.Exit(1) 109 } 110 } 111 112 func filter(in io.Reader, out io.Writer, mode modeT) error { 113 scanner := bufio.NewScanner(in) 114 type tup struct { 115 pkg string 116 test string 117 } 118 type ent struct { 119 first, last string // RUN and (SKIP|PASS|FAIL) 120 strings.Builder // output 121 } 122 m := map[tup]*ent{} 123 ev := &testEvent{} 124 var n int // number of JSON lines parsed 125 var passFailLine string // catch common error of piping non-json test output in 126 for scanner.Scan() { 127 line := scanner.Text() // has no EOL marker 128 if len(line) <= 2 || line[0] != '{' || line[len(line)-1] != '}' { 129 // Not test2json output, pass it through except in `omit` mode. 130 // It's important that we still see build errors etc when running 131 // in -mode=strip. 132 if passFailLine == "" && (strings.Contains(line, "PASS") || strings.Contains(line, "FAIL")) { 133 passFailLine = line 134 } 135 if mode != modeOmit { 136 fmt.Fprintln(out, line) 137 } 138 continue 139 } 140 *ev = testEvent{} 141 if err := json.Unmarshal([]byte(line), ev); err != nil { 142 return err 143 } 144 n++ 145 146 if mode == modeConvert { 147 if ev.Action == "output" { 148 fmt.Fprint(out, ev.Output) 149 } 150 continue 151 } 152 153 if ev.Test == "" { 154 // Skip all package output when omitting. Unfortunately package 155 // events aren't always well-formed. For example, if a test panics, 156 // the package will never receive a fail event (arguably a bug), so 157 // it's not trivial to print only failing packages. Besides, there's 158 // not much package output typically (only init functions), so not 159 // worth getting fancy about. 160 if mode == modeOmit { 161 continue 162 } 163 } 164 key := tup{ev.Package, ev.Test} 165 buf := m[key] 166 if buf == nil { 167 buf = &ent{first: line} 168 m[key] = buf 169 } 170 if _, err := fmt.Fprintln(buf, line); err != nil { 171 return err 172 } 173 switch ev.Action { 174 case "pass", "skip", "fail": 175 buf.last = line 176 delete(m, key) 177 if ev.Action == "fail" { 178 fmt.Fprint(out, buf.String()) 179 } else if mode == modeStrip { 180 // Output only the start and end of test so that we preserve the 181 // timing information. However, the output is omitted. 182 fmt.Fprintln(out, buf.first) 183 fmt.Fprintln(out, buf.last) 184 } 185 case "run", "pause", "cont", "bench", "output": 186 default: 187 // We must have parsed some JSON that wasn't a testData. 188 return fmt.Errorf("unknown input: %s", line) 189 } 190 } 191 // Some scopes might still be open. To the best of my knowledge, this is due 192 // to a panic/premature exit of a test binary. In that case, it seems that 193 // neither is the package scope closed, nor the scopes for any tests that 194 // were running in parallel, so we pass that through if stripping, but not 195 // when omitting. 196 if mode == modeStrip { 197 for key := range m { 198 fmt.Fprintln(out, m[key].String()) 199 } 200 } 201 // TODO(tbg): would like to return an error here for sanity, but the 202 // JSON just isn't well-formed all the time. For example, at the time 203 // of writing, here's a repro: 204 // make benchshort PKG=./pkg/bench BENCHES=BenchmarkIndexJoin 2>&1 | \ 205 // testfilter -mode=strip 206 // Interestingly it works once we remove the `log.Scope(b).Close` in 207 // that test. Adding TESTFLAGS=-v doesn't matter apparently. 208 // if len(m) != 0 { 209 // return fmt.Errorf("%d tests did not terminate (a package likely exited prematurely)", len(m)) 210 // } 211 if mode != modeConvert && n == 0 && passFailLine != "" { 212 // Without this, if the input to this command wasn't even JSON, we would 213 // pass. That's a mistake we should avoid at all costs. Note that even 214 // `go test -run - ./some/pkg` produces n>0 due to the start/pass events 215 // for the package, so if we're here then 100% something weird is going 216 // on. 217 return fmt.Errorf("not a single test was parsed, but detected test output: %s", passFailLine) 218 } 219 return nil 220 }