github.com/coreos/mantle@v0.13.0/harness/harness.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 "bytes" 20 "context" 21 "errors" 22 "fmt" 23 "io" 24 "io/ioutil" 25 "log" 26 "os" 27 "path/filepath" 28 "runtime" 29 "strings" 30 "sync" 31 "time" 32 33 "github.com/coreos/mantle/harness/reporters" 34 "github.com/coreos/mantle/harness/testresult" 35 ) 36 37 // H is a type passed to Test functions to manage test state and support formatted test logs. 38 // Logs are accumulated during execution and dumped to standard output when done. 39 // 40 // A test ends when its Test function returns or calls any of the methods 41 // FailNow, Fatal, Fatalf, SkipNow, Skip, or Skipf. Those methods, as well as 42 // the Parallel method, must be called only from the goroutine running the 43 // Test function. 44 // 45 // The other reporting methods, such as the variations of Log and Error, 46 // may be called simultaneously from multiple goroutines. 47 type H struct { 48 mu sync.RWMutex // guards output, failed, and done. 49 output bytes.Buffer // Output generated by test. 50 w io.Writer // For flushToParent. 51 tap io.Writer // Optional TAP log of test results. 52 logger *log.Logger 53 ctx context.Context 54 cancel context.CancelFunc 55 ran bool // Test (or one of its subtests) was executed. 56 failed bool // Test has failed. 57 skipped bool // Test has been skipped. 58 finished bool // Test function has completed. 59 done bool // Test is finished and all subtests have completed. 60 hasSub bool 61 62 suite *Suite 63 parent *H 64 level int // Nesting depth of test. 65 name string // Name of test. 66 start time.Time // Time test started 67 duration time.Duration 68 barrier chan bool // To signal parallel subtests they may start. 69 signal chan bool // To signal a test is done. 70 sub []*H // Queue of subtests to be run in parallel. 71 72 isParallel bool 73 74 reporters reporters.Reporters 75 } 76 77 func (c *H) parentContext() context.Context { 78 if c == nil || c.parent == nil || c.parent.ctx == nil { 79 return context.Background() 80 } 81 return c.parent.ctx 82 } 83 84 // Verbose reports whether the Suite's Verbose option is set. 85 func (h *H) Verbose() bool { 86 return h.suite.opts.Verbose 87 } 88 89 func (c *H) status() testresult.TestResult { 90 if c.Failed() { 91 return testresult.Fail 92 } else if c.Skipped() { 93 return testresult.Skip 94 } 95 return testresult.Pass 96 } 97 98 // flushToParent writes c.output to the parent after first writing the header 99 // with the given format and arguments. 100 func (c *H) flushToParent(format string, args ...interface{}) { 101 p := c.parent 102 p.mu.Lock() 103 defer p.mu.Unlock() 104 105 fmt.Fprintf(p.w, format, args...) 106 107 status := c.status() 108 109 // TODO: include test numbers in TAP output. 110 if p.tap != nil { 111 name := strings.Replace(c.name, "#", "", -1) 112 if status == testresult.Fail { 113 fmt.Fprintf(p.tap, "not ok - %s\n", name) 114 } else if status == testresult.Skip { 115 fmt.Fprintf(p.tap, "ok - %s # SKIP\n", name) 116 } else { 117 fmt.Fprintf(p.tap, "ok - %s\n", name) 118 } 119 } 120 121 c.mu.Lock() 122 defer c.mu.Unlock() 123 outputBufferCopy := c.output 124 outputBufferCopy.WriteTo(p.w) 125 } 126 127 type indenter struct { 128 c *H 129 } 130 131 func (w indenter) Write(b []byte) (n int, err error) { 132 n = len(b) 133 for len(b) > 0 { 134 end := bytes.IndexByte(b, '\n') 135 if end == -1 { 136 end = len(b) 137 } else { 138 end++ 139 } 140 // An indent of 4 spaces will neatly align the dashes with the status 141 // indicator of the parent. 142 const indent = " " 143 w.c.output.WriteString(indent) 144 w.c.output.Write(b[:end]) 145 b = b[end:] 146 } 147 return 148 } 149 150 // fmtDuration returns a string representing d in the form "87.00s". 151 func fmtDuration(d time.Duration) string { 152 return fmt.Sprintf("%.2fs", d.Seconds()) 153 } 154 155 // Name returns the name of the running test or benchmark. 156 func (c *H) Name() string { 157 return c.name 158 } 159 160 // Context returns the context for the current test. 161 // The context is cancelled when the test finishes. 162 // A goroutine started during a test can wait for the 163 // context's Done channel to become readable as a signal that the 164 // test is over, so that the goroutine can exit. 165 func (c *H) Context() context.Context { 166 return c.ctx 167 } 168 169 func (c *H) setRan() { 170 if c.parent != nil { 171 c.parent.setRan() 172 } 173 c.mu.Lock() 174 defer c.mu.Unlock() 175 c.ran = true 176 } 177 178 // Fail marks the function as having failed but continues execution. 179 func (c *H) Fail() { 180 if c.parent != nil { 181 c.parent.Fail() 182 } 183 c.mu.Lock() 184 defer c.mu.Unlock() 185 // c.done needs to be locked to synchronize checks to c.done in parent tests. 186 if c.done { 187 panic("Fail in goroutine after " + c.name + " has completed") 188 } 189 c.failed = true 190 } 191 192 // Failed reports whether the function has failed. 193 func (c *H) Failed() bool { 194 c.mu.RLock() 195 defer c.mu.RUnlock() 196 return c.failed 197 } 198 199 // FailNow marks the function as having failed and stops its execution. 200 // Execution will continue at the next test. 201 // FailNow must be called from the goroutine running the 202 // test function, not from other goroutines 203 // created during the test. Calling FailNow does not stop 204 // those other goroutines. 205 func (c *H) FailNow() { 206 c.Fail() 207 208 // Calling runtime.Goexit will exit the goroutine, which 209 // will run the deferred functions in this goroutine, 210 // which will eventually run the deferred lines in tRunner, 211 // which will signal to the test loop that this test is done. 212 // 213 // A previous version of this code said: 214 // 215 // c.duration = ... 216 // c.signal <- c.self 217 // runtime.Goexit() 218 // 219 // This previous version duplicated code (those lines are in 220 // tRunner no matter what), but worse the goroutine teardown 221 // implicit in runtime.Goexit was not guaranteed to complete 222 // before the test exited. If a test deferred an important cleanup 223 // function (like removing temporary files), there was no guarantee 224 // it would run on a test failure. Because we send on c.signal during 225 // a top-of-stack deferred function now, we know that the send 226 // only happens after any other stacked defers have completed. 227 c.finished = true 228 runtime.Goexit() 229 } 230 231 // log generates the output. It's always at the same stack depth. 232 func (c *H) log(s string) { 233 c.mu.Lock() 234 defer c.mu.Unlock() 235 c.logger.Output(3, s) 236 } 237 238 // Log formats its arguments using default formatting, analogous to Println, 239 // and records the text in the error log. The text will be printed only if 240 // the test fails or the -harness.v flag is set. 241 func (c *H) Log(args ...interface{}) { c.log(fmt.Sprintln(args...)) } 242 243 // Logf formats its arguments according to the format, analogous to Printf, and 244 // records the text in the error log. A final newline is added if not provided. 245 // The text will be printed only if the test fails or the -harness.v flag is set. 246 func (c *H) Logf(format string, args ...interface{}) { c.log(fmt.Sprintf(format, args...)) } 247 248 // Error is equivalent to Log followed by Fail. 249 func (c *H) Error(args ...interface{}) { 250 c.log(fmt.Sprintln(args...)) 251 c.Fail() 252 } 253 254 // Errorf is equivalent to Logf followed by Fail. 255 func (c *H) Errorf(format string, args ...interface{}) { 256 c.log(fmt.Sprintf(format, args...)) 257 c.Fail() 258 } 259 260 // Fatal is equivalent to Log followed by FailNow. 261 func (c *H) Fatal(args ...interface{}) { 262 c.log(fmt.Sprintln(args...)) 263 c.FailNow() 264 } 265 266 // Fatalf is equivalent to Logf followed by FailNow. 267 func (c *H) Fatalf(format string, args ...interface{}) { 268 c.log(fmt.Sprintf(format, args...)) 269 c.FailNow() 270 } 271 272 // Skip is equivalent to Log followed by SkipNow. 273 func (c *H) Skip(args ...interface{}) { 274 c.log(fmt.Sprintln(args...)) 275 c.SkipNow() 276 } 277 278 // Skipf is equivalent to Logf followed by SkipNow. 279 func (c *H) Skipf(format string, args ...interface{}) { 280 c.log(fmt.Sprintf(format, args...)) 281 c.SkipNow() 282 } 283 284 // SkipNow marks the test as having been skipped and stops its execution. 285 // If a test fails (see Error, Errorf, Fail) and is then skipped, 286 // it is still considered to have failed. 287 // Execution will continue at the next test. See also FailNow. 288 // SkipNow must be called from the goroutine running the test, not from 289 // other goroutines created during the test. Calling SkipNow does not stop 290 // those other goroutines. 291 func (c *H) SkipNow() { 292 c.skip() 293 c.finished = true 294 runtime.Goexit() 295 } 296 297 func (c *H) skip() { 298 c.mu.Lock() 299 defer c.mu.Unlock() 300 c.skipped = true 301 } 302 303 // Skipped reports whether the test was skipped. 304 func (c *H) Skipped() bool { 305 c.mu.RLock() 306 defer c.mu.RUnlock() 307 return c.skipped 308 } 309 310 func (h *H) mkOutputDir() (dir string, err error) { 311 dir = h.suite.outputPath(h.name) 312 if err = os.MkdirAll(dir, 0777); err != nil { 313 err = fmt.Errorf("Failed to create output dir: %v", err) 314 } 315 return 316 } 317 318 // OutputDir returns the path to a directory for storing data used by 319 // the current test. Only test frameworks should care about this. 320 // Individual tests should normally use H.TempDir or H.TempFile 321 func (h *H) OutputDir() string { 322 dir, err := h.mkOutputDir() 323 if err != nil { 324 h.log(err.Error()) 325 h.FailNow() 326 } 327 return dir 328 } 329 330 // TempDir creates a new directory under OutputDir. 331 // No cleanup is required. 332 func (h *H) TempDir(prefix string) string { 333 dir, err := h.mkOutputDir() 334 if err != nil { 335 h.log(err.Error()) 336 h.FailNow() 337 } 338 tmp, err := ioutil.TempDir(dir, prefix) 339 if err != nil { 340 h.log(fmt.Sprintf("Failed to create temp dir: %v", err)) 341 h.FailNow() 342 } 343 return tmp 344 } 345 346 // TempFile creates a new file under Outputdir. 347 // No cleanup is required. 348 func (h *H) TempFile(prefix string) *os.File { 349 dir, err := h.mkOutputDir() 350 if err != nil { 351 h.log(err.Error()) 352 h.FailNow() 353 } 354 tmp, err := ioutil.TempFile(dir, prefix) 355 if err != nil { 356 h.log(fmt.Sprintf("Failed to create temp file: %v", err)) 357 h.FailNow() 358 } 359 return tmp 360 } 361 362 // Parallel signals that this test is to be run in parallel with (and only with) 363 // other parallel tests. 364 func (t *H) Parallel() { 365 if t.isParallel { 366 panic("testing: t.Parallel called multiple times") 367 } 368 t.isParallel = true 369 370 // We don't want to include the time we spend waiting for serial tests 371 // in the test duration. Record the elapsed time thus far and reset the 372 // timer afterwards. 373 t.duration += time.Since(t.start) 374 375 // Add to the list of tests to be released by the parent. 376 t.parent.sub = append(t.parent.sub, t) 377 378 t.signal <- true // Release calling test. 379 <-t.parent.barrier // Wait for the parent test to complete. 380 t.suite.waitParallel() 381 t.start = time.Now() 382 } 383 384 func tRunner(t *H, fn func(t *H)) { 385 t.ctx, t.cancel = context.WithCancel(t.parentContext()) 386 defer t.cancel() 387 388 // When this goroutine is done, either because fn(t) 389 // returned normally or because a test failure triggered 390 // a call to runtime.Goexit, record the duration and send 391 // a signal saying that the test is done. 392 defer func() { 393 t.duration += time.Now().Sub(t.start) 394 // If the test panicked, print any test output before dying. 395 err := recover() 396 if !t.finished && err == nil { 397 err = fmt.Errorf("test executed panic(nil) or runtime.Goexit") 398 } 399 if err != nil { 400 t.Fail() 401 t.report() 402 panic(err) 403 } 404 405 if len(t.sub) > 0 { 406 // Run parallel subtests. 407 // Decrease the running count for this test. 408 t.suite.release() 409 // Release the parallel subtests. 410 close(t.barrier) 411 // Wait for subtests to complete. 412 for _, sub := range t.sub { 413 <-sub.signal 414 } 415 if !t.isParallel { 416 // Reacquire the count for sequential tests. See comment in Run. 417 t.suite.waitParallel() 418 } 419 } else if t.isParallel { 420 // Only release the count for this test if it was run as a parallel 421 // test. See comment in Run method. 422 t.suite.release() 423 } 424 t.report() // Report after all subtests have finished. 425 426 // Do not lock t.done to allow race detector to detect race in case 427 // the user does not appropriately synchronize a goroutine. 428 t.done = true 429 if t.parent != nil && !t.hasSub { 430 t.setRan() 431 } 432 t.signal <- true 433 }() 434 435 t.start = time.Now() 436 fn(t) 437 t.finished = true 438 } 439 440 // Run runs f as a subtest of t called name. It reports whether f succeeded. 441 // Run will block until all its parallel subtests have completed. 442 func (t *H) Run(name string, f func(t *H)) bool { 443 t.hasSub = true 444 testName, ok := t.suite.match.fullName(t, name) 445 if !ok { 446 return true 447 } 448 t = &H{ 449 barrier: make(chan bool), 450 signal: make(chan bool), 451 name: testName, 452 suite: t.suite, 453 parent: t, 454 level: t.level + 1, 455 reporters: t.reporters, 456 } 457 t.w = indenter{t} 458 // Indent logs 8 spaces to distinguish them from sub-test headers. 459 const indent = " " 460 t.logger = log.New(&t.output, indent, log.Lshortfile) 461 462 if t.suite.opts.Verbose { 463 // Print directly to root's io.Writer so there is no delay. 464 root := t.parent 465 for ; root.parent != nil; root = root.parent { 466 } 467 fmt.Fprintf(root.w, "=== RUN %s\n", t.name) 468 } 469 // Instead of reducing the running count of this test before calling the 470 // tRunner and increasing it afterwards, we rely on tRunner keeping the 471 // count correct. This ensures that a sequence of sequential tests runs 472 // without being preempted, even when their parent is a parallel test. This 473 // may especially reduce surprises if *parallel == 1. 474 go tRunner(t, f) 475 <-t.signal 476 return !t.failed 477 } 478 479 func (t *H) report() { 480 if t.parent == nil { 481 return 482 } 483 dstr := fmtDuration(t.duration) 484 format := "--- %s: %s (%s)\n" 485 486 status := t.status() 487 if status == testresult.Fail || t.suite.opts.Verbose { 488 t.flushToParent(format, status, t.name, dstr) 489 } 490 491 // TODO: store multiple buffers for subtests without indentation 492 // potentially add a TeeWriter which will output to both buffers 493 // 494 // original comment from PR --- 495 // euank: As a followup, we could potentially use something other 496 // than c.output here. c.output came from an indenter. We don't 497 // need the indentation for reporters. One way we could do that is 498 // by replacing H.w with a TeeWriter which tees to an indented sink 499 // and a normal sink, and then use whichever is appropriate. We 500 // could also write verbosely to the 'reporter sink'. I'm fine with 501 // this being a TODO if you don't want to tackle it in this initial 502 // PR. 503 t.reporters.ReportTest(t.name, status, t.duration, t.output.Bytes()) 504 } 505 506 // CleanOutputDir creates/empties an output directory and returns the cleaned path. 507 // If the path already exists it must be named similar to `_foo_temp` 508 // or contain `.harness_temp` to indicate removal is safe; we don't 509 // want users wrecking things by accident with `--output-dir /tmp` 510 func CleanOutputDir(path string) (string, error) { 511 // Clean up the path to ensure errors are clear. 512 path = filepath.Clean(path) 513 514 if path == "." { 515 return "", errors.New("harness: no output directory provided") 516 } 517 518 // Remove any existing data if it is safe to do so. 519 marker := filepath.Join(path, ".harness_temp") 520 base := filepath.Base(path) 521 safe := base[0] == '_' && strings.HasSuffix(base, "_temp") 522 if !safe { 523 if _, err := os.Stat(marker); err == nil { 524 safe = true 525 } 526 } 527 if safe { 528 if err := os.RemoveAll(path); err != nil { 529 return "", err 530 } 531 } 532 533 if err := os.Mkdir(path, 0777); err != nil { 534 if !safe && os.IsExist(err) { 535 return "", fmt.Errorf("harness: refused to remove existing output directory: %s", path) 536 } 537 return "", err 538 } 539 540 f, err := os.Create(marker) 541 if err != nil { 542 return "", err 543 } 544 f.Close() 545 546 return path, nil 547 }