main.go 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. package main
  2. import (
  3. "context"
  4. "fmt"
  5. "io"
  6. "os"
  7. "os/exec"
  8. "strings"
  9. "github.com/fatih/color"
  10. "github.com/pkg/errors"
  11. log "github.com/sirupsen/logrus"
  12. "github.com/spf13/pflag"
  13. "gotest.tools/gotestsum/testjson"
  14. )
  15. var version = "master"
  16. func main() {
  17. name := os.Args[0]
  18. flags, opts := setupFlags(name)
  19. switch err := flags.Parse(os.Args[1:]); {
  20. case err == pflag.ErrHelp:
  21. os.Exit(0)
  22. case err != nil:
  23. log.Error(err.Error())
  24. flags.Usage()
  25. os.Exit(1)
  26. }
  27. opts.args = flags.Args()
  28. setupLogging(opts)
  29. if opts.version {
  30. fmt.Fprintf(os.Stdout, "gotestsum version %s\n", version)
  31. os.Exit(0)
  32. }
  33. switch err := run(opts).(type) {
  34. case nil:
  35. case *exec.ExitError:
  36. // go test should already report the error to stderr so just exit with
  37. // the same status code
  38. os.Exit(ExitCodeWithDefault(err))
  39. default:
  40. fmt.Fprintln(os.Stderr, name+": Error: "+err.Error())
  41. os.Exit(3)
  42. }
  43. }
  44. func setupFlags(name string) (*pflag.FlagSet, *options) {
  45. opts := &options{noSummary: newNoSummaryValue()}
  46. flags := pflag.NewFlagSet(name, pflag.ContinueOnError)
  47. flags.SetInterspersed(false)
  48. flags.Usage = func() {
  49. fmt.Fprintf(os.Stderr, `Usage:
  50. %s [flags] [--] [go test flags]
  51. Flags:
  52. `, name)
  53. flags.PrintDefaults()
  54. fmt.Fprint(os.Stderr, `
  55. Formats:
  56. dots print a character for each test
  57. short print a line for each package
  58. short-verbose print a line for each test and package
  59. standard-quiet default go test format
  60. standard-verbose default go test -v format
  61. `)
  62. }
  63. flags.BoolVar(&opts.debug, "debug", false, "enabled debug")
  64. flags.StringVarP(&opts.format, "format", "f",
  65. lookEnvWithDefault("GOTESTSUM_FORMAT", "short"),
  66. "print format of test input")
  67. flags.BoolVar(&opts.rawCommand, "raw-command", false,
  68. "don't prepend 'go test -json' to the 'go test' command")
  69. flags.StringVar(&opts.jsonFile, "jsonfile",
  70. lookEnvWithDefault("GOTESTSUM_JSONFILE", ""),
  71. "write all TestEvents to file")
  72. flags.StringVar(&opts.junitFile, "junitfile",
  73. lookEnvWithDefault("GOTESTSUM_JUNITFILE", ""),
  74. "write a JUnit XML file")
  75. flags.BoolVar(&opts.noColor, "no-color", false, "disable color output")
  76. flags.Var(opts.noSummary, "no-summary",
  77. fmt.Sprintf("do not print summary of: %s", testjson.SummarizeAll.String()))
  78. flags.BoolVar(&opts.version, "version", false, "show version and exit")
  79. return flags, opts
  80. }
  81. func lookEnvWithDefault(key, defValue string) string {
  82. if value := os.Getenv(key); value != "" {
  83. return value
  84. }
  85. return defValue
  86. }
  87. type options struct {
  88. args []string
  89. format string
  90. debug bool
  91. rawCommand bool
  92. jsonFile string
  93. junitFile string
  94. noColor bool
  95. noSummary *noSummaryValue
  96. version bool
  97. }
  98. func setupLogging(opts *options) {
  99. if opts.debug {
  100. log.SetLevel(log.DebugLevel)
  101. }
  102. if opts.noColor {
  103. color.NoColor = true
  104. }
  105. }
  106. // TODO: add flag --max-failures
  107. func run(opts *options) error {
  108. ctx := context.Background()
  109. goTestProc, err := startGoTest(ctx, goTestCmdArgs(opts))
  110. if err != nil {
  111. return errors.Wrapf(err, "failed to run %s %s",
  112. goTestProc.cmd.Path,
  113. strings.Join(goTestProc.cmd.Args, " "))
  114. }
  115. defer goTestProc.cancel()
  116. out := os.Stdout
  117. handler, err := newEventHandler(opts, out, os.Stderr)
  118. if err != nil {
  119. return err
  120. }
  121. defer handler.Close() // nolint: errcheck
  122. exec, err := testjson.ScanTestOutput(testjson.ScanConfig{
  123. Stdout: goTestProc.stdout,
  124. Stderr: goTestProc.stderr,
  125. Handler: handler,
  126. })
  127. if err != nil {
  128. return err
  129. }
  130. if err := testjson.PrintSummary(out, exec, opts.noSummary.value); err != nil {
  131. return err
  132. }
  133. if err := writeJUnitFile(opts.junitFile, exec); err != nil {
  134. return err
  135. }
  136. return goTestProc.cmd.Wait()
  137. }
  138. func goTestCmdArgs(opts *options) []string {
  139. args := opts.args
  140. defaultArgs := []string{"go", "test"}
  141. switch {
  142. case opts.rawCommand:
  143. return args
  144. case len(args) == 0:
  145. return append(defaultArgs, "-json", pathFromEnv("./..."))
  146. case !hasJSONArg(args):
  147. defaultArgs = append(defaultArgs, "-json")
  148. }
  149. if testPath := pathFromEnv(""); testPath != "" {
  150. args = append(args, testPath)
  151. }
  152. return append(defaultArgs, args...)
  153. }
  154. func pathFromEnv(defaultPath string) string {
  155. return lookEnvWithDefault("TEST_DIRECTORY", defaultPath)
  156. }
  157. func hasJSONArg(args []string) bool {
  158. for _, arg := range args {
  159. if arg == "-json" || arg == "--json" {
  160. return true
  161. }
  162. }
  163. return false
  164. }
  165. type proc struct {
  166. cmd *exec.Cmd
  167. stdout io.Reader
  168. stderr io.Reader
  169. cancel func()
  170. }
  171. func startGoTest(ctx context.Context, args []string) (proc, error) {
  172. if len(args) == 0 {
  173. return proc{}, errors.New("missing command to run")
  174. }
  175. ctx, cancel := context.WithCancel(ctx)
  176. p := proc{
  177. cmd: exec.CommandContext(ctx, args[0], args[1:]...),
  178. cancel: cancel,
  179. }
  180. log.Debugf("exec: %s", p.cmd.Args)
  181. var err error
  182. p.stdout, err = p.cmd.StdoutPipe()
  183. if err != nil {
  184. return p, err
  185. }
  186. p.stderr, err = p.cmd.StderrPipe()
  187. if err != nil {
  188. return p, err
  189. }
  190. err = p.cmd.Start()
  191. if err == nil {
  192. log.Debugf("go test pid: %d", p.cmd.Process.Pid)
  193. }
  194. return p, err
  195. }