I came across this in the LXD test suite today, which was hard to track
down, so I figured I'd let everyone know about it.

We have a nice helper in testing.IsolationSuite with "PatchValue()" that
will change a global for you during the test, and then during TearDown()
will cleanup the patch it made.
It turns out that if your test doesn't have a pointer receiver this fails,
because the "suite" object is a copy, so when PatchValue calls AddCleanup
to do s.testStack = append(...) the suite object goes away before TearDown
is called.

You can see this with the attached test suite.

Example:

func (s mySuite) TestFoo(c *gc.C) {
 // This is unsafe because s.PatchValue ends up modifying s.testStack but
that attribute only exists
 // for the life of the TestFoo function
  s.PatchValue(&globalVar, "newvalue")
}

I tried adding the attached patch so that we catch places we are using
AddCleanup unsafely, but it fails a few tests I wasn't expecting, so I'm
not sure if I'm actually doing the right thing.

John
=:->
diff --git a/cleanup.go b/cleanup.go
index 48a48bb..2b88f7e 100644
--- a/cleanup.go
+++ b/cleanup.go
@@ -18,10 +18,13 @@ type cleanupStack []CleanupFunc
 type CleanupSuite struct {
        testStack  cleanupStack
        suiteStack cleanupStack
+       suiteSuite *CleanupSuite
+       testSuite  *CleanupSuite
 }
 
 func (s *CleanupSuite) SetUpSuite(c *gc.C) {
        s.suiteStack = nil
+       s.suiteSuite = s
 }
 
 func (s *CleanupSuite) TearDownSuite(c *gc.C) {
@@ -30,6 +33,7 @@ func (s *CleanupSuite) TearDownSuite(c *gc.C) {
 
 func (s *CleanupSuite) SetUpTest(c *gc.C) {
        s.testStack = nil
+       s.testSuite = s
 }
 
 func (s *CleanupSuite) TearDownTest(c *gc.C) {
@@ -45,12 +49,18 @@ func (s *CleanupSuite) callStack(c *gc.C, stack 
cleanupStack) {
 // AddCleanup pushes the cleanup function onto the stack of functions to be
 // called during TearDownTest.
 func (s *CleanupSuite) AddCleanup(cleanup CleanupFunc) {
+       if s != s.testSuite {
+               panic("testSuite changed from SetUp to TearDown")
+       }
        s.testStack = append(s.testStack, cleanup)
 }
 
 // AddSuiteCleanup pushes the cleanup function onto the stack of functions to
 // be called during TearDownSuite.
 func (s *CleanupSuite) AddSuiteCleanup(cleanup CleanupFunc) {
+       if s != s.testSuite {
+               panic("test suite changed from SetUpSuite before 
AddSuiteCleanup")
+       }
        s.suiteStack = append(s.suiteStack, cleanup)
 }
 
package lxdclient_test

import (
	"github.com/juju/testing"
	gc "gopkg.in/check.v1"
)

var global string = "original"

type nonPointer struct {
	testing.IsolationSuite
}

var _ = gc.Suite(&nonPointer{})

type pointer struct {
	testing.IsolationSuite
}

var _ = gc.Suite(&pointer{})

func (n nonPointer) TestPatchGlobal(c *gc.C) {
	n.PatchValue(&global, "patched")
	c.Assert(global, gc.Equals, "patched")
}

func (n nonPointer) TestUnpatchedGlobal(c *gc.C) {
	// Will fail if TestPatchGlobal runs first
	c.Assert(global, gc.Equals, "original")
}

func (p *pointer) TestPatchGlobal(c *gc.C) {
	p.PatchValue(&global, "patched")
	c.Assert(global, gc.Equals, "patched")
}

func (p *pointer) TestPatchedGlobal(c *gc.C) {
	// Should always pass, unless nonPointer.TestPatchGlobal runs first
	c.Assert(global, gc.Equals, "original")
}
-- 
Juju-dev mailing list
[email protected]
Modify settings or unsubscribe at: 
https://lists.ubuntu.com/mailman/listinfo/juju-dev

Reply via email to