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  }