github.com/coreos/mantle@v0.13.0/harness/suite.go (about) 1 // Copyright 2017 CoreOS, Inc. 2 // Copyright 2009 The Go Authors. 3 // 4 // Licensed under the Apache License, Version 2.0 (the "License"); 5 // you may not use this file except in compliance with the License. 6 // You may obtain a copy of the License at 7 // 8 // http://www.apache.org/licenses/LICENSE-2.0 9 // 10 // Unless required by applicable law or agreed to in writing, software 11 // distributed under the License is distributed on an "AS IS" BASIS, 12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 // See the License for the specific language governing permissions and 14 // limitations under the License. 15 16 package harness 17 18 import ( 19 "errors" 20 "flag" 21 "fmt" 22 "io" 23 "os" 24 "path/filepath" 25 "runtime" 26 "runtime/debug" 27 "runtime/pprof" 28 "runtime/trace" 29 "strings" 30 "sync" 31 "time" 32 33 "github.com/coreos/mantle/harness/reporters" 34 "github.com/coreos/mantle/harness/testresult" 35 ) 36 37 const ( 38 defaultOutputDir = "_harness_temp" 39 ) 40 41 var ( 42 SuiteEmpty = errors.New("harness: no tests to run") 43 SuiteFailed = errors.New("harness: test suite failed") 44 ) 45 46 // Options 47 type Options struct { 48 // The temporary directory in which to write profile files, logs, etc. 49 OutputDir string 50 51 // Report as tests are run; default is silent for success. 52 Verbose bool 53 54 // Run only tests matching a regexp. 55 Match string 56 57 // Enable memory profiling. 58 MemProfile bool 59 MemProfileRate int 60 61 // Enable CPU profiling. 62 CpuProfile bool 63 64 // Enable goroutine block profiling. 65 BlockProfile bool 66 BlockProfileRate int 67 68 // Enable execution trace. 69 ExecutionTrace bool 70 71 // Panic Suite execution after a timeout (0 means unlimited). 72 Timeout time.Duration 73 74 // Limit number of tests to run in parallel (0 means GOMAXPROCS). 75 Parallel int 76 77 Reporters reporters.Reporters 78 } 79 80 // FlagSet can be used to setup options via command line flags. 81 // An optional prefix can be prepended to each flag. 82 // Defaults can be specified prior to calling FlagSet. 83 func (o *Options) FlagSet(prefix string, errorHandling flag.ErrorHandling) *flag.FlagSet { 84 o.init() 85 name := strings.Trim(prefix, ".-") 86 f := flag.NewFlagSet(name, errorHandling) 87 f.StringVar(&o.OutputDir, prefix+"outputdir", o.OutputDir, 88 "write profiles, logs, and other data to temporary `dir`") 89 f.BoolVar(&o.Verbose, prefix+"v", o.Verbose, 90 "verbose: print additional output") 91 f.StringVar(&o.Match, prefix+"run", o.Match, 92 "run only tests matching `regexp`") 93 f.BoolVar(&o.MemProfile, prefix+"memprofile", o.MemProfile, 94 "write a memory profile to 'dir/mem.prof'") 95 f.IntVar(&o.MemProfileRate, prefix+"memprofilerate", o.MemProfileRate, 96 "set memory profiling `rate` (see runtime.MemProfileRate)") 97 f.BoolVar(&o.CpuProfile, prefix+"cpuprofile", o.CpuProfile, 98 "write a cpu profile to 'dir/cpu.prof'") 99 f.BoolVar(&o.BlockProfile, prefix+"blockprofile", o.BlockProfile, 100 "write a goroutine blocking profile to 'dir/block.prof'") 101 f.IntVar(&o.BlockProfileRate, prefix+"blockprofilerate", o.BlockProfileRate, 102 "set blocking profile `rate` (see runtime.SetBlockProfileRate)") 103 f.BoolVar(&o.ExecutionTrace, prefix+"trace", o.ExecutionTrace, 104 "write an execution trace to 'dir/exec.trace'") 105 f.DurationVar(&o.Timeout, prefix+"timeout", o.Timeout, 106 "fail test binary execution after duration `d` (0 means unlimited)") 107 f.IntVar(&o.Parallel, prefix+"parallel", o.Parallel, 108 "run at most `n` tests in parallel") 109 return f 110 } 111 112 // init fills in any default values that shouldn't be the zero value. 113 func (o *Options) init() { 114 if o.OutputDir == "" { 115 o.OutputDir = defaultOutputDir 116 } 117 if o.MemProfileRate < 1 { 118 o.MemProfileRate = runtime.MemProfileRate 119 } 120 if o.BlockProfileRate < 1 { 121 o.BlockProfileRate = 1 122 } 123 if o.Parallel < 1 { 124 o.Parallel = runtime.GOMAXPROCS(0) 125 } 126 } 127 128 // Suite is a type passed to a TestMain function to run the actual tests. 129 // Suite manages the execution of a set of test functions. 130 type Suite struct { 131 opts Options 132 tests Tests 133 match *matcher 134 135 // mu protects the following fields which are used to manage 136 // parallel test execution. 137 mu sync.Mutex 138 139 // Channel used to signal tests that are ready to be run in parallel. 140 startParallel chan bool 141 142 // running is the number of tests currently running in parallel. 143 // This does not include tests that are waiting for subtests to complete. 144 running int 145 146 // waiting is the number tests waiting to be run in parallel. 147 waiting int 148 } 149 150 func (c *Suite) waitParallel() { 151 c.mu.Lock() 152 if c.running < c.opts.Parallel { 153 c.running++ 154 c.mu.Unlock() 155 return 156 } 157 c.waiting++ 158 c.mu.Unlock() 159 <-c.startParallel 160 } 161 162 func (c *Suite) release() { 163 c.mu.Lock() 164 if c.waiting == 0 { 165 c.running-- 166 c.mu.Unlock() 167 return 168 } 169 c.waiting-- 170 c.mu.Unlock() 171 c.startParallel <- true // Pick a waiting test to be run. 172 } 173 174 // NewSuite creates a new test suite. 175 // All parameters in Options cannot be modified once given to Suite. 176 func NewSuite(opts Options, tests Tests) *Suite { 177 opts.init() 178 return &Suite{ 179 opts: opts, 180 tests: tests, 181 match: newMatcher(opts.Match, "Match"), 182 startParallel: make(chan bool), 183 } 184 } 185 186 // Run runs the tests. Returns SuiteFailed for any test failure. 187 func (s *Suite) Run() (err error) { 188 flushProfile := func(name string, f *os.File) { 189 err2 := pprof.Lookup(name).WriteTo(f, 0) 190 if err == nil && err2 != nil { 191 err = fmt.Errorf("harness: can't write %s profile: %v", name, err2) 192 } 193 f.Close() 194 } 195 196 outputDir, err := CleanOutputDir(s.opts.OutputDir) 197 if err != nil { 198 return err 199 } 200 s.opts.OutputDir = outputDir 201 202 tap, err := os.Create(s.outputPath("test.tap")) 203 if err != nil { 204 return err 205 } 206 defer tap.Close() 207 if _, err := fmt.Fprintf(tap, "1..%d\n", len(s.tests)); err != nil { 208 return err 209 } 210 211 reportDir := s.outputPath("reports") 212 if err := os.Mkdir(reportDir, 0777); err != nil { 213 return err 214 } 215 defer func() { 216 if reportErr := s.opts.Reporters.Output(reportDir); reportErr != nil && err != nil { 217 err = reportErr 218 } 219 }() 220 221 if s.opts.MemProfile { 222 runtime.MemProfileRate = s.opts.MemProfileRate 223 f, err := os.Create(s.outputPath("mem.prof")) 224 if err != nil { 225 return err 226 } 227 defer func() { 228 runtime.GC() // materialize all statistics 229 flushProfile("heap", f) 230 }() 231 } 232 if s.opts.BlockProfile { 233 f, err := os.Create(s.outputPath("block.prof")) 234 if err != nil { 235 return err 236 } 237 runtime.SetBlockProfileRate(s.opts.BlockProfileRate) 238 defer func() { 239 runtime.SetBlockProfileRate(0) // stop profile 240 flushProfile("block", f) 241 }() 242 } 243 if s.opts.CpuProfile { 244 f, err := os.Create(s.outputPath("cpu.prof")) 245 if err != nil { 246 return err 247 } 248 defer f.Close() 249 if err := pprof.StartCPUProfile(f); err != nil { 250 return fmt.Errorf("harness: can't start cpu profile: %v", err) 251 } 252 defer pprof.StopCPUProfile() // flushes profile to disk 253 } 254 if s.opts.ExecutionTrace { 255 f, err := os.Create(s.outputPath("exec.trace")) 256 if err != nil { 257 return err 258 } 259 defer f.Close() 260 if err := trace.Start(f); err != nil { 261 return fmt.Errorf("harness: can't start tacing: %v", err) 262 } 263 defer trace.Stop() // flushes trace to disk 264 } 265 if s.opts.Timeout > 0 { 266 timer := time.AfterFunc(s.opts.Timeout, func() { 267 debug.SetTraceback("all") 268 panic(fmt.Sprintf("harness: tests timed out after %v", s.opts.Timeout)) 269 }) 270 defer timer.Stop() 271 } 272 273 return s.runTests(os.Stdout, tap) 274 } 275 276 func (s *Suite) runTests(out, tap io.Writer) error { 277 s.running = 1 // Set the count to 1 for the main (sequential) test. 278 t := &H{ 279 signal: make(chan bool), 280 barrier: make(chan bool), 281 w: out, 282 tap: tap, 283 suite: s, 284 reporters: s.opts.Reporters, 285 } 286 tRunner(t, func(t *H) { 287 for name, test := range s.tests { 288 t.Run(name, test) 289 } 290 // Run catching the signal rather than the tRunner as a separate 291 // goroutine to avoid adding a goroutine during the sequential 292 // phase as this pollutes the stacktrace output when aborting. 293 go func() { <-t.signal }() 294 }) 295 if !t.ran { 296 return SuiteEmpty 297 } 298 if t.Failed() { 299 s.opts.Reporters.SetResult(testresult.Fail) 300 return SuiteFailed 301 } 302 303 s.opts.Reporters.SetResult(testresult.Pass) 304 305 return nil 306 } 307 308 // outputPath returns the file name under Options.OutputDir. 309 func (s *Suite) outputPath(path string) string { 310 return filepath.Join(s.opts.OutputDir, path) 311 }