oss.indeed.com/go/go-opine@v1.3.0/internal/gotest/run.go (about)

     1  package gotest
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"os"
     7  	"os/exec"
     8  	"strconv"
     9  )
    10  
    11  // Option can be passed to Run to change how it behaves (e.g. test
    12  // with -race, or write verbose output somewhere).
    13  type Option func(o *options) error
    14  
    15  type options struct {
    16  	race         bool
    17  	coverprofile string
    18  	coverpkg     string
    19  	covermode    string
    20  	p            int
    21  	accepters    []resultAccepter
    22  }
    23  
    24  // Race runs tests with -race.
    25  func Race() Option {
    26  	return func(o *options) error {
    27  		o.race = true
    28  		return nil
    29  	}
    30  }
    31  
    32  // CoverProfile runs tests with -coverprofile=<path>.
    33  func CoverProfile(path string) Option {
    34  	return func(o *options) error {
    35  		o.coverprofile = path
    36  		return nil
    37  	}
    38  }
    39  
    40  // CoverPkg runs tests with -coverpkg=<patterns>.
    41  func CoverPkg(patterns string) Option {
    42  	return func(o *options) error {
    43  		o.coverpkg = patterns
    44  		return nil
    45  	}
    46  }
    47  
    48  // CoverMode runs tests with -covermode=<mode>.
    49  func CoverMode(mode string) Option {
    50  	return func(o *options) error {
    51  		o.covermode = mode
    52  		return nil
    53  	}
    54  }
    55  
    56  // P runs tests with -p=<p>. This controls the number of test binaries
    57  // that can be run in parallel.
    58  //
    59  // See the -p option of "go help build" for more information.
    60  func P(p int) Option {
    61  	return func(o *options) error {
    62  		if p <= 0 {
    63  			return fmt.Errorf("gotest: invalid option -p: %q", p)
    64  		}
    65  		o.p = p
    66  		return nil
    67  	}
    68  }
    69  
    70  // QuietOutput writes output similar to "go test" (without "-v")
    71  // to the provided writer.
    72  func QuietOutput(to io.Writer) Option {
    73  	return func(o *options) error {
    74  		o.accepters = append(o.accepters, &quietOutput{to: to})
    75  		return nil
    76  	}
    77  }
    78  
    79  // QuietOutput writes output similar to "go test -v" to the
    80  // provided writer.
    81  func VerboseOutput(to io.Writer) Option {
    82  	return func(o *options) error {
    83  		o.accepters = append(o.accepters, &verboseOutput{to: to})
    84  		return nil
    85  	}
    86  }
    87  
    88  // Run runs go test.
    89  func Run(opts ...Option) error {
    90  	var o options
    91  	for _, opt := range opts {
    92  		if err := opt(&o); err != nil {
    93  			return err
    94  		}
    95  	}
    96  
    97  	// Now build the command to run with the realized options
    98  	// struct.
    99  	args := []string{"test", "-v", "-json"}
   100  	if o.race {
   101  		args = append(args, "-race")
   102  	}
   103  	if o.coverprofile != "" {
   104  		args = append(args, "-coverprofile="+o.coverprofile)
   105  	}
   106  	if o.coverpkg != "" {
   107  		args = append(args, "-coverpkg="+o.coverpkg)
   108  	}
   109  	if o.covermode != "" {
   110  		args = append(args, "-covermode="+o.covermode)
   111  	}
   112  	if o.p != 0 {
   113  		args = append(args, "-p="+strconv.Itoa(o.p))
   114  	}
   115  	args = append(args, "./...")
   116  	cmd := exec.Command("go", args...)
   117  	cmd.Stderr = os.Stderr
   118  
   119  	cmdStdout, err := cmd.StdoutPipe()
   120  	if err != nil {
   121  		return err
   122  	}
   123  	if err := cmd.Start(); err != nil {
   124  		return err
   125  	}
   126  	if err := parseGoTestJSONOutput(cmdStdout, newMultiResultAccepter(o.accepters...)); err != nil {
   127  		_ = cmd.Process.Kill()
   128  		_ = cmd.Wait()
   129  		return err
   130  	}
   131  	if err := cmd.Wait(); err != nil {
   132  		return fmt.Errorf("go test failed: %w", err)
   133  	}
   134  
   135  	return nil
   136  }
   137  
   138  func parseGoTestJSONOutput(r io.Reader, to resultAccepter) error {
   139  	grouper := newResultPackageGrouper(to)
   140  	aggregator := newResultAggregator(newRemoveCoverageOutput(newWorkaroundGoIssue35180ResultAccepter(grouper)))
   141  	parser := newEventStreamParser(aggregator)
   142  	if err := parser.Parse(r); err != nil {
   143  		return err
   144  	}
   145  	if err := aggregator.CheckAllEventsConsumed(); err != nil {
   146  		return err
   147  	}
   148  	if err := grouper.CheckAllResultsConsumed(); err != nil {
   149  		return err
   150  	}
   151  	return nil
   152  }