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  }