oss.indeed.com/go/go-opine@v1.3.0/internal/gotest/result.go (about) 1 package gotest 2 3 import ( 4 "errors" 5 "fmt" 6 "strings" 7 "time" 8 ) 9 10 // resultKey identifies a result. 11 type resultKey struct { 12 Package string 13 Test string 14 } 15 16 // result is a test result. The result is for either a single test or for 17 // a package, in which case Key.Test is empty. 18 type result struct { 19 Key resultKey 20 Outcome string 21 Output string 22 Elapsed time.Duration 23 } 24 25 // resultAccepter accepts results. 26 type resultAccepter interface { 27 Accept(res result) error 28 } 29 30 // multiResultAccepter accepts results and forwards them on to zero or 31 // more downstream result accepters. 32 type multiResultAccepter struct { 33 accepters []resultAccepter 34 } 35 36 var _ resultAccepter = (*multiResultAccepter)(nil) 37 38 func newMultiResultAccepter(accepter ...resultAccepter) *multiResultAccepter { 39 return &multiResultAccepter{accepters: accepter} 40 } 41 42 // Accept forwards the result to the downstream resultAccepters. If any 43 // resultAccepter returns an error processing stops immediately and that 44 // error is returned to the caller. 45 func (m multiResultAccepter) Accept(res result) error { 46 for _, accepter := range m.accepters { 47 if err := accepter.Accept(res); err != nil { 48 return err 49 } 50 } 51 return nil 52 } 53 54 // resultAggregator is an eventAccepter that aggregates events for the same 55 // test or package into results. Completed results are passed to the 56 // resultAccepter. 57 type resultAggregator struct { 58 to resultAccepter 59 events map[resultKey][]event 60 err error 61 } 62 63 func newResultAggregator(to resultAccepter) *resultAggregator { 64 return &resultAggregator{ 65 to: to, 66 events: make(map[resultKey][]event), 67 } 68 } 69 70 // Accept adds an event to the internal state and provides any result 71 // completed by the event to the resultAccepter. 72 // 73 // If the resultAccepter returns an error the resultAggregator will enter 74 // an error state causing the current accept and all subsequent accepts to 75 // fail. This error will also be returned by Close. 76 func (a *resultAggregator) Accept(e event) error { 77 if a.err != nil { 78 return fmt.Errorf("permanent error state: %w", a.err) 79 } 80 81 rk := resultKey{ 82 Package: e.Package, 83 Test: e.Test, 84 } 85 86 if !isTestOrPackageComplete(e.Action) { 87 a.events[rk] = append(a.events[rk], e) 88 return nil 89 } 90 91 var output strings.Builder 92 for _, prevEvent := range a.events[rk] { 93 output.WriteString(prevEvent.Output) 94 } 95 delete(a.events, rk) 96 output.WriteString(e.Output) 97 98 res := result{ 99 Key: rk, 100 Outcome: e.Action, 101 Output: output.String(), 102 Elapsed: time.Duration(e.Elapsed * float64(time.Second)), 103 } 104 if err := a.to.Accept(res); err != nil { 105 a.setErr(err) 106 return a.err 107 } 108 109 return nil 110 } 111 112 // CheckAllEventsConsumed checks that all events are consumed and that 113 // no error occurred in any Accept. 114 func (a *resultAggregator) CheckAllEventsConsumed() error { 115 if a.err == nil && len(a.events) > 0 { 116 a.setErr(errors.New("not all events were consumed")) 117 } 118 return a.err 119 } 120 121 // setErr puts the resultAggregator into a permanent error state. 122 func (a *resultAggregator) setErr(err error) { 123 a.err = err 124 a.events = nil 125 } 126 127 // resultPackageGrouper accepts results, groups them by package, and 128 // forwards all results for a package when it completes. 129 // 130 // This is necessary because by default Go will run tests from different 131 // packages at the same time. If the output of each result is printed 132 // immediately it will cause confusion regarding which package each test 133 // is in. For example take the following output: 134 // 135 // === RUN Test_Cmd_optionLog 136 // --- PASS: Test_Cmd_optionLog (0.01s) 137 // PASS 138 // ok oss.indeed.com/go/go-opine/internal/run (cached) 139 // 140 // The only way you can tell the Test_Cmd_optionLog package is 141 // oss.indeed.com/go/go-opine/internal/run is by the fact that the package 142 // output is printed immediately after the test output. 143 // 144 // !!WARNING!! This struct relies on the the final result of a package 145 // being the "package result" (i.e. the result that has only a package 146 // and no test). If you filter results before providing them to a 147 // resultPackageGrouper make sure you do not filter out the package result 148 // for any test result you previously provided. Otherwise Close will return 149 // an error about results remaining. 150 type resultPackageGrouper struct { 151 to resultAccepter 152 pkgResults map[string][]result 153 err error 154 } 155 156 var _ resultAccepter = (*resultPackageGrouper)(nil) 157 158 func newResultPackageGrouper(to resultAccepter) *resultPackageGrouper { 159 return &resultPackageGrouper{ 160 to: to, 161 pkgResults: make(map[string][]result), 162 } 163 } 164 165 // Accept adds the result to the resultPackageGrouper internal state and, 166 // if the result is a "package result", forwards all buffered test results 167 // and the package result onward. 168 // 169 // If the resultAccepter returns an error the resultPackageGrouper will enter 170 // an error state causing the current accept and all subsequent accepts to 171 // fail. This error will also be returned by Close. 172 func (r *resultPackageGrouper) Accept(res result) error { 173 if r.err != nil { 174 return fmt.Errorf("permanent error state: %w", r.err) 175 } 176 177 r.pkgResults[res.Key.Package] = append(r.pkgResults[res.Key.Package], res) 178 179 if !isPackageComplete(res) { 180 return nil 181 } 182 183 if err := r.forward(r.pkgResults[res.Key.Package]...); err != nil { 184 return err 185 } 186 delete(r.pkgResults, res.Key.Package) 187 188 return nil 189 } 190 191 // CheckAllEventsConsumed checks that all results are consumed and that 192 // no error occurred in any Accept. 193 func (r *resultPackageGrouper) CheckAllResultsConsumed() error { 194 if r.err == nil && len(r.pkgResults) > 0 { 195 r.setErr(errors.New("not all results were consumed")) 196 } 197 return r.err 198 } 199 200 // forward passes zero or more results on to the resultAccepter. If the 201 // resultAccepter returns an error for any result processing stops, setErr 202 // is called to put the resultPackageGrouper in a permanent error state, and 203 // the error is returned. 204 func (r *resultPackageGrouper) forward(results ...result) error { 205 for _, res := range results { 206 if err := r.to.Accept(res); err != nil { 207 r.setErr(err) 208 return r.err 209 } 210 } 211 return nil 212 } 213 214 // setErr puts the resultPackageGrouper into a permanent error state. 215 func (r *resultPackageGrouper) setErr(err error) { 216 r.err = err 217 r.pkgResults = nil 218 } 219 220 // isTestOrPackageComplete returns true iff the provided event.Action 221 // represents the completion of test or package. 222 func isTestOrPackageComplete(action string) bool { 223 return action == "pass" || action == "fail" || action == "skip" 224 } 225 226 // isPackageComplete returns true iff the provided result represents 227 // the completion of a package. 228 func isPackageComplete(res result) bool { 229 return res.Key.Test == "" 230 }