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  }