Skip to content

Commit

Permalink
Defer running top-level container nodes until RunSpecs is called
Browse files Browse the repository at this point in the history
A common lifecycle gotcha is to try to dynamically generate tests based on configuration data.  This fix defers evaluation of the top-level container blocks (Describe, Context, etc.) until RunSpecs.  This allows the user to load any configuration information in the TestX hook just before RunSpecs is called and leverage that configuration information to inform how the testing tree is generated.

[Fixes onsi#693]
  • Loading branch information
onsi committed Jun 18, 2020
1 parent 87dffce commit d44dedf
Show file tree
Hide file tree
Showing 4 changed files with 60 additions and 18 deletions.
6 changes: 0 additions & 6 deletions extensions/table/table_entry.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,6 @@ type TableEntry struct {
}

func (t TableEntry) generateIt(itBody reflect.Value) {
if t.codeLocation == (types.CodeLocation{}) {
// The user created the TableEntry struct directly instead of having used the (F/P/X)Entry constructors.
// Therefore default to the code location of the surrounding DescribeTable.
t.codeLocation = codelocation.New(5)
}

var description string
descriptionValue := reflect.ValueOf(t.Description)
switch descriptionValue.Kind() {
Expand Down
2 changes: 0 additions & 2 deletions integration/fail_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,6 @@ var _ = Describe("Failing Specs", func() {
Ω(output).ShouldNot(ContainSubstring("table_entry.go"))
Ω(output).Should(MatchRegexp(`a TableEntry constructed by Entry \[It\]\n.*fail_fixture_test\.go:110`),
"the output of a failing Entry should include its file path and line number")
Ω(output).Should(MatchRegexp(`a directly constructed TableEntry \[It\]\n.*fail_fixture_test\.go:106`),
"the output of a failing TableEntry should include the surrounding DescribeTable's file path and line number")

Ω(output).Should(ContainSubstring("0 Passed | 19 Failed"))
})
Expand Down
54 changes: 44 additions & 10 deletions internal/suite/suite.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,25 +22,37 @@ type ginkgoTestingT interface {
Fail()
}

type deferredContainerNode struct {
text string
body func()
flag types.FlagType
codeLocation types.CodeLocation
}

type Suite struct {
topLevelContainer *containernode.ContainerNode
currentContainer *containernode.ContainerNode
containerIndex int
beforeSuiteNode leafnodes.SuiteNode
afterSuiteNode leafnodes.SuiteNode
runner *specrunner.SpecRunner
failer *failer.Failer
running bool

deferredContainerNodes []deferredContainerNode

containerIndex int
beforeSuiteNode leafnodes.SuiteNode
afterSuiteNode leafnodes.SuiteNode
runner *specrunner.SpecRunner
failer *failer.Failer
running bool
expandTopLevelNodes bool
}

func New(failer *failer.Failer) *Suite {
topLevelContainer := containernode.New("[Top Level]", types.FlagTypeNone, types.CodeLocation{})

return &Suite{
topLevelContainer: topLevelContainer,
currentContainer: topLevelContainer,
failer: failer,
containerIndex: 1,
topLevelContainer: topLevelContainer,
currentContainer: topLevelContainer,
failer: failer,
containerIndex: 1,
deferredContainerNodes: []deferredContainerNode{},
}
}

Expand All @@ -53,6 +65,11 @@ func (suite *Suite) Run(t ginkgoTestingT, description string, reporters []report
panic("ginkgo.parallel.node is one-indexed and must be <= ginkgo.parallel.total")
}

suite.expandTopLevelNodes = true
for _, deferredNode := range suite.deferredContainerNodes {
suite.PushContainerNode(deferredNode.text, deferredNode.body, deferredNode.flag, deferredNode.codeLocation)
}

r := rand.New(rand.NewSource(config.RandomSeed))
suite.topLevelContainer.Shuffle(r)
iterator, hasProgrammaticFocus := suite.generateSpecsIterator(description, config)
Expand Down Expand Up @@ -137,6 +154,23 @@ func (suite *Suite) SetSynchronizedAfterSuiteNode(bodyA interface{}, bodyB inter
}

func (suite *Suite) PushContainerNode(text string, body func(), flag types.FlagType, codeLocation types.CodeLocation) {
/*
We defer walking the container nodes (which immediately evaluates the `body` function)
until `RunSpecs` is called. We do this by storing off the deferred container nodes. Then, when
`RunSpecs` is called we actually go through and add the container nodes to the test structure.
This allows us to defer calling all the `body` functions until _after_ the top level functions
have been walked, _after_ func init()s have been called, and _after_ `go test` has called `flag.Parse()`.
This allows users to load up configuration information in the `TestX` go test hook just before `RunSpecs`
is invoked and solves issues like #693 and makes the lifecycle easier to reason about.
*/
if !suite.expandTopLevelNodes {
suite.deferredContainerNodes = append(suite.deferredContainerNodes, deferredContainerNode{text, body, flag, codeLocation})
return
}

container := containernode.New(text, flag, codeLocation)
suite.currentContainer.PushContainerNode(container)

Expand Down
16 changes: 16 additions & 0 deletions internal/suite/suite_suite_test.go
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
package suite_test

import (
"fmt"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"

"testing"
)

var dynamicallyGeneratedTests = []string{}

func Test(t *testing.T) {
RegisterFailHandler(Fail)
dynamicallyGeneratedTests = []string{"Test A", "Test B", "Test C"}
RunSpecs(t, "Suite")
}

var numBeforeSuiteRuns = 0
var numAfterSuiteRuns = 0
var numDynamicallyGeneratedTests = 0

var _ = BeforeSuite(func() {
numBeforeSuiteRuns++
Expand All @@ -23,6 +29,16 @@ var _ = AfterSuite(func() {
numAfterSuiteRuns++
Ω(numBeforeSuiteRuns).Should(Equal(1))
Ω(numAfterSuiteRuns).Should(Equal(1))
Ω(numDynamicallyGeneratedTests).Should(Equal(3), "Expected three test to be dynamically generated")
})

var _ = Describe("Top-level cotnainer node lifecycle", func() {
for _, test := range dynamicallyGeneratedTests {
numDynamicallyGeneratedTests += 1
It(fmt.Sprintf("runs dynamically generated test: %s", test), func() {
Ω(true).Should(BeTrue())
})
}
})

//Fakes
Expand Down

0 comments on commit d44dedf

Please sign in to comment.