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  }