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 }