Permalink
Fetching contributors…
Cannot retrieve contributors at this time
141 lines (126 sloc) 4.33 KB
// Copyright 2013, 2014 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package testing
import (
"os/exec"
gc "gopkg.in/check.v1"
)
// CleanupSuite adds the ability to add cleanup functions that are called
// during either test tear down or suite tear down depending on the method
// called.
type CleanupSuite struct {
testStack []func(*gc.C)
suiteStack []func(*gc.C)
origSuite *CleanupSuite
testsStarted bool
inTest bool
tornDown bool
}
func (s *CleanupSuite) SetUpSuite(c *gc.C) {
s.suiteStack = nil
s.testStack = nil
s.origSuite = s
s.testsStarted = false
s.inTest = false
s.tornDown = false
}
func (s *CleanupSuite) TearDownSuite(c *gc.C) {
s.callStack(c, s.suiteStack)
s.suiteStack = nil
s.origSuite = nil
s.tornDown = true
}
func (s *CleanupSuite) SetUpTest(c *gc.C) {
s.testStack = nil
s.testsStarted = true
s.inTest = true
}
func (s *CleanupSuite) TearDownTest(c *gc.C) {
s.callStack(c, s.testStack)
s.testStack = nil
s.inTest = false
}
func (s *CleanupSuite) callStack(c *gc.C, stack []func(*gc.C)) {
for i := len(stack) - 1; i >= 0; i-- {
stack[i](c)
}
}
// AddCleanup pushes the cleanup function onto the stack of functions to be
// called during TearDownTest or TearDownSuite. TearDownTest will be used if
// SetUpTest has already been called, else we will use TearDownSuite
func (s *CleanupSuite) AddCleanup(cleanup func(*gc.C)) {
if s.origSuite == nil {
// This is either called before SetUpSuite or after
// TearDownSuite. Either way, we can't really trust that we're
// going to call Cleanup correctly.
if s.tornDown {
panic("unsafe to call AddCleanup after TearDownSuite")
} else {
panic("unsafe to call AddCleanup before SetUpSuite")
}
}
if s != s.origSuite {
// If you write a test like:
// func (s MySuite) TestFoo(c *gc.C) {
// s.AddCleanup(foo)
// }
// The AddCleanup call is unsafe because it modifes
// s.origSuite but that object disappears once TestFoo
// returns. So you have to use:
// func (s *MySuite) TestFoo(c *gc.C) if you want the Cleanup
// funcs.
panic("unsafe to call AddCleanup from non pointer receiver test")
}
if !s.inTest {
if s.testsStarted {
// This indicates that we are not currently in a test
// (inTest is false), but that we have already run a
// test for this test suite (testStarted is true).
// Making a Suite-level change here means that only
// some of the tests in the suite will see the change,
// which means it *isn't* a Suite (applies to all
// tests) level change.
panic("unsafe to call AddCleanup after a test has been torn down" +
" before a new test has been set up" +
" (Suite level changes only make sense before first test is run)")
}
// We either haven't called SetUpTest or we've already called
// TearDownTest, consider this a Suite level cleanup.
s.suiteStack = append(s.suiteStack, cleanup)
return
}
s.testStack = append(s.testStack, cleanup)
}
// PatchEnvironment sets the environment variable 'name' the the value passed
// in. The old value is saved and returned to the original value at test tear
// down time using a cleanup function.
func (s *CleanupSuite) PatchEnvironment(name, value string) {
restore := PatchEnvironment(name, value)
s.AddCleanup(func(*gc.C) { restore() })
}
// PatchEnvPathPrepend prepends the given path to the environment $PATH and restores the
// original path on test teardown.
func (s *CleanupSuite) PatchEnvPathPrepend(dir string) {
restore := PatchEnvPathPrepend(dir)
s.AddCleanup(func(*gc.C) { restore() })
}
// PatchValue sets the 'dest' variable the the value passed in. The old value
// is saved and returned to the original value at test tear down time using a
// cleanup function. The value must be assignable to the element type of the
// destination.
func (s *CleanupSuite) PatchValue(dest, value interface{}) {
restore := PatchValue(dest, value)
s.AddCleanup(func(*gc.C) { restore() })
}
// HookCommandOutput calls the package function of the same name to mock out
// the result of a particular comand execution, and will call the restore
// function on test teardown.
func (s *CleanupSuite) HookCommandOutput(
outputFunc *func(cmd *exec.Cmd) ([]byte, error),
output []byte,
err error,
) <-chan *exec.Cmd {
result, restore := HookCommandOutput(outputFunc, output, err)
s.AddCleanup(func(*gc.C) { restore() })
return result
}