github.com/aristanetworks/goarista@v0.0.0-20240514173732-cca2755bbd44/cmd/json2test/main.go (about) 1 // Copyright (c) 2018 Arista Networks, Inc. 2 // Use of this source code is governed by the Apache License 2.0 3 // that can be found in the COPYING file. 4 5 // json2test reformats 'go test -json' output as text as if the -json 6 // flag were not passed to go test. It is useful if you want to 7 // analyze go test -json output, but still want a human readable test 8 // log. 9 // 10 // Usage: 11 // 12 // go test -json > out.txt; <analysis program> out.txt; cat out.txt | json2test 13 package main 14 15 import ( 16 "bufio" 17 "encoding/json" 18 "errors" 19 "flag" 20 "fmt" 21 "io" 22 "log" 23 "os" 24 "time" 25 ) 26 27 var errTestFailure = errors.New("testfailure") 28 29 func main() { 30 verbose := flag.Bool("v", false, "Verbose output. "+ 31 "By default only failed tests emit verbose output in test result summary.") 32 flag.Parse() 33 err := writeTestOutput(os.Stdin, os.Stdout, *verbose) 34 if err == errTestFailure { 35 os.Exit(1) 36 } else if err != nil { 37 log.Fatal(err) 38 } 39 } 40 41 type testEvent struct { 42 Time time.Time // encodes as an RFC3339-format string 43 Action string 44 Package string 45 Test string 46 Elapsed float64 // seconds 47 Output string 48 } 49 50 type test struct { 51 pkg string 52 test string 53 } 54 55 type outputBuffer struct { 56 output []string 57 } 58 59 func (o *outputBuffer) push(s string) { 60 o.output = append(o.output, s) 61 } 62 63 type testFailure struct { 64 t test 65 o outputBuffer 66 } 67 68 func writeTestOutput(in io.Reader, out io.Writer, verbose bool) error { 69 testOutputBuffer := map[test]*outputBuffer{} 70 var failures []testFailure 71 d := json.NewDecoder(in) 72 73 buf := bufio.NewWriter(out) 74 defer buf.Flush() 75 for { 76 var e testEvent 77 if err := d.Decode(&e); err != nil { 78 break 79 } 80 81 switch e.Action { 82 default: 83 continue 84 case "run": 85 testOutputBuffer[test{pkg: e.Package, test: e.Test}] = new(outputBuffer) 86 case "pass": 87 if !verbose && e.Test == "" { 88 // Match go test output: 89 // ok foo/bar 2.109s 90 fmt.Fprintf(buf, "ok \t%s\t%.3fs\n", e.Package, e.Elapsed) 91 } 92 // Don't hold onto text for passing 93 delete(testOutputBuffer, test{pkg: e.Package, test: e.Test}) 94 case "fail": 95 if !verbose { 96 if e.Test != "" { 97 // Match go test output: 98 // --- FAIL: TestFooBar (0.00s) 99 fmt.Fprintf(buf, "--- FAIL: %s (%.3f)\n", e.Test, e.Elapsed) 100 } else { 101 // Match go test output: 102 // FAIL foo/bar 1.444s 103 fmt.Fprintf(buf, "FAIL\t%s\t%.3fs\n", e.Package, e.Elapsed) 104 } 105 } 106 // fail may be for a package, which won't have an entry in 107 // testOutputBuffer because packages don't have a "run" 108 // action. 109 t := test{pkg: e.Package, test: e.Test} 110 if o, ok := testOutputBuffer[t]; ok { 111 f := testFailure{t: t, o: *o} 112 delete(testOutputBuffer, t) 113 failures = append(failures, f) 114 } 115 case "output": 116 if verbose { 117 buf.WriteString(e.Output) 118 } 119 // output may be for a package, which won't have an entry 120 // in testOutputBuffer because packages don't have a "run" 121 // action. 122 if o, ok := testOutputBuffer[test{pkg: e.Package, test: e.Test}]; ok { 123 o.push(e.Output) 124 } 125 } 126 } 127 if len(failures) == 0 { 128 return nil 129 } 130 buf.WriteString("\nTest failures:\n") 131 for i, f := range failures { 132 fmt.Fprintf(buf, "[%d] %s.%s\n", i+1, f.t.pkg, f.t.test) 133 for _, s := range f.o.output { 134 buf.WriteString(s) 135 } 136 if i < len(failures)-1 { 137 buf.WriteByte('\n') 138 } 139 } 140 return errTestFailure 141 }