gotest.tools/gotestsum@v1.11.0/cmd/watch.go (about) 1 package cmd 2 3 import ( 4 "bufio" 5 "context" 6 "fmt" 7 "io/ioutil" 8 "os" 9 "os/exec" 10 11 "gotest.tools/gotestsum/internal/filewatcher" 12 "gotest.tools/gotestsum/testjson" 13 ) 14 15 func runWatcher(opts *options) error { 16 ctx, cancel := context.WithCancel(context.Background()) 17 defer cancel() 18 19 w := &watchRuns{opts: *opts} 20 return filewatcher.Watch(ctx, opts.packages, w.run) 21 } 22 23 type watchRuns struct { 24 opts options 25 prevExec *testjson.Execution 26 } 27 28 func (w *watchRuns) run(event filewatcher.Event) error { 29 if event.Debug { 30 path, cleanup, err := delveInitFile(w.prevExec) 31 if err != nil { 32 return fmt.Errorf("failed to write delve init file: %w", err) 33 } 34 defer cleanup() 35 o := delveOpts{ 36 pkgPath: event.PkgPath, 37 args: w.opts.args, 38 initFilePath: path, 39 } 40 if err := runDelve(o); !IsExitCoder(err) { 41 return fmt.Errorf("delve failed: %w", err) 42 } 43 return nil 44 } 45 46 var dir string 47 if w.opts.watchChdir { 48 dir, event.PkgPath = event.PkgPath, "./" 49 } 50 51 opts := w.opts // shallow copy opts 52 opts.packages = append([]string{}, opts.packages...) 53 opts.packages = append(opts.packages, event.PkgPath) 54 opts.packages = append(opts.packages, event.Args...) 55 56 var err error 57 if w.prevExec, err = runSingle(&opts, dir); !IsExitCoder(err) { 58 return err 59 } 60 return nil 61 } 62 63 // runSingle is similar to run. It doesn't support rerun-fails. It may be 64 // possible to share runSingle with run, but the defer close on the handler 65 // would require at least 3 return values, so for now it is a copy. 66 func runSingle(opts *options, dir string) (*testjson.Execution, error) { 67 ctx, cancel := context.WithCancel(context.Background()) 68 defer cancel() 69 70 if err := opts.Validate(); err != nil { 71 return nil, err 72 } 73 74 goTestProc, err := startGoTestFn(ctx, dir, goTestCmdArgs(opts, rerunOpts{})) 75 if err != nil { 76 return nil, err 77 } 78 79 handler, err := newEventHandler(opts) 80 if err != nil { 81 return nil, err 82 } 83 defer handler.Close() // nolint: errcheck 84 cfg := testjson.ScanConfig{ 85 Stdout: goTestProc.stdout, 86 Stderr: goTestProc.stderr, 87 Handler: handler, 88 Stop: cancel, 89 } 90 exec, err := testjson.ScanTestOutput(cfg) 91 handler.Flush() 92 if err != nil { 93 return exec, finishRun(opts, exec, err) 94 } 95 err = goTestProc.cmd.Wait() 96 return exec, finishRun(opts, exec, err) 97 } 98 99 func delveInitFile(exec *testjson.Execution) (string, func(), error) { 100 fh, err := ioutil.TempFile("", "gotestsum-delve-init") 101 if err != nil { 102 return "", nil, err 103 } 104 remove := func() { 105 os.Remove(fh.Name()) // nolint: errcheck 106 } 107 108 buf := bufio.NewWriter(fh) 109 for _, tc := range exec.Failed() { 110 fmt.Fprintf(buf, "break %s\n", tc.Test.Name()) 111 } 112 buf.WriteString("continue\n") 113 if err := buf.Flush(); err != nil { 114 remove() 115 return "", nil, err 116 } 117 return fh.Name(), remove, nil 118 } 119 120 type delveOpts struct { 121 pkgPath string 122 args []string 123 initFilePath string 124 } 125 126 func runDelve(opts delveOpts) error { 127 pkg := opts.pkgPath 128 args := []string{"dlv", "test", "--wd", pkg} 129 args = append(args, "--output", "gotestsum-watch-debug.test") 130 args = append(args, "--init", opts.initFilePath) 131 args = append(args, pkg, "--") 132 args = append(args, opts.args...) 133 134 cmd := exec.Command(args[0], args[1:]...) 135 cmd.Stdin = os.Stdin 136 cmd.Stdout = os.Stdout 137 cmd.Stderr = os.Stderr 138 139 return cmd.Run() 140 }