gotest.tools/gotestsum@v1.11.0/cmd/tool/slowest/slowest.go (about) 1 package slowest 2 3 import ( 4 "fmt" 5 "io" 6 "io/ioutil" 7 "os" 8 "time" 9 10 "github.com/dnephin/pflag" 11 "gotest.tools/gotestsum/internal/aggregate" 12 "gotest.tools/gotestsum/internal/log" 13 "gotest.tools/gotestsum/testjson" 14 ) 15 16 // Run the command 17 func Run(name string, args []string) error { 18 flags, opts := setupFlags(name) 19 switch err := flags.Parse(args); { 20 case err == pflag.ErrHelp: 21 return nil 22 case err != nil: 23 usage(os.Stderr, name, flags) 24 return err 25 } 26 return run(opts) 27 } 28 29 func setupFlags(name string) (*pflag.FlagSet, *options) { 30 opts := &options{} 31 flags := pflag.NewFlagSet(name, pflag.ContinueOnError) 32 flags.SetInterspersed(false) 33 flags.Usage = func() { 34 usage(os.Stdout, name, flags) 35 } 36 flags.StringVar(&opts.jsonfile, "jsonfile", os.Getenv("GOTESTSUM_JSONFILE"), 37 "path to test2json output, defaults to stdin") 38 flags.DurationVar(&opts.threshold, "threshold", 100*time.Millisecond, 39 "test cases with elapsed time greater than threshold are slow tests") 40 flags.IntVar(&opts.topN, "num", 0, 41 "print at most num slowest tests, instead of all tests above the threshold") 42 flags.StringVar(&opts.skipStatement, "skip-stmt", "", 43 "add this go statement to slow tests, instead of printing the list of slow tests") 44 flags.BoolVar(&opts.debug, "debug", false, 45 "enable debug logging.") 46 return flags, opts 47 } 48 49 func usage(out io.Writer, name string, flags *pflag.FlagSet) { 50 fmt.Fprintf(out, `Usage: 51 %[1]s [flags] 52 53 Read a json file and print or update tests which are slower than threshold. 54 The json file may be created with 'gotestsum --jsonfile' or 'go test -json'. 55 If a TestCase appears more than once in the json file, it will only appear once 56 in the output, and the median value of all the elapsed times will be used. 57 58 By default this command will print the list of tests slower than threshold to stdout. 59 The list will be sorted from slowest to fastest. 60 61 If --skip-stmt is set, instead of printing the list to stdout, the AST for the 62 Go source code in the working directory tree will be modified. The value of 63 --skip-stmt will be added to Go test files as the first statement in all the test 64 functions which are slower than threshold. 65 66 The --skip-stmt flag may be set to the name of a predefined statement, or to 67 Go source code which will be parsed as a go/ast.Stmt. Currently there is only one 68 predefined statement, --skip-stmt=testing.Short, which uses this Go statement: 69 70 if testing.Short() { 71 t.Skip("too slow for testing.Short") 72 } 73 74 75 Alternatively, a custom --skip-stmt may be provided as a string: 76 77 skip_stmt=' 78 if os.GetEnv("TEST_FAST") != "" { 79 t.Skip("too slow for TEST_FAST") 80 } 81 ' 82 go test -json -short ./... | %[1]s --skip-stmt "$skip_stmt" 83 84 Note that this tool does not add imports, so using a custom statement may require 85 you to add imports to the file. 86 87 Go build flags, such as build tags, may be set using the GOFLAGS environment 88 variable, following the same rules as the go toolchain. See 89 https://golang.org/cmd/go/#hdr-Environment_variables. 90 91 Flags: 92 `, name) 93 flags.SetOutput(out) 94 flags.PrintDefaults() 95 } 96 97 type options struct { 98 threshold time.Duration 99 topN int 100 jsonfile string 101 skipStatement string 102 debug bool 103 } 104 105 func run(opts *options) error { 106 if opts.debug { 107 log.SetLevel(log.DebugLevel) 108 } 109 in, err := jsonfileReader(opts.jsonfile) 110 if err != nil { 111 return fmt.Errorf("failed to read jsonfile: %v", err) 112 } 113 defer func() { 114 if err := in.Close(); err != nil { 115 log.Errorf("Failed to close file %v: %v", opts.jsonfile, err) 116 } 117 }() 118 119 exec, err := testjson.ScanTestOutput(testjson.ScanConfig{Stdout: in}) 120 if err != nil { 121 return fmt.Errorf("failed to scan testjson: %v", err) 122 } 123 124 tcs := aggregate.Slowest(exec, opts.threshold, opts.topN) 125 if opts.skipStatement != "" { 126 skipStmt, err := parseSkipStatement(opts.skipStatement) 127 if err != nil { 128 return fmt.Errorf("failed to parse skip expr: %v", err) 129 } 130 return writeTestSkip(tcs, skipStmt) 131 } 132 for _, tc := range tcs { 133 fmt.Printf("%s %s %v\n", tc.Package, tc.Test, tc.Elapsed) 134 } 135 136 return nil 137 } 138 139 func jsonfileReader(v string) (io.ReadCloser, error) { 140 switch v { 141 case "", "-": 142 return ioutil.NopCloser(os.Stdin), nil 143 default: 144 return os.Open(v) 145 } 146 }