package suite import ( "math/rand" "net/http" "time" "github.com/onsi/ginkgo/internal/spec_iterator" "github.com/onsi/ginkgo/config" "github.com/onsi/ginkgo/internal/containernode" "github.com/onsi/ginkgo/internal/failer" "github.com/onsi/ginkgo/internal/leafnodes" "github.com/onsi/ginkgo/internal/spec" "github.com/onsi/ginkgo/internal/specrunner" "github.com/onsi/ginkgo/internal/writer" "github.com/onsi/ginkgo/reporters" "github.com/onsi/ginkgo/types" ) 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 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, deferredContainerNodes: []deferredContainerNode{}, } } func (suite *Suite) Run(t ginkgoTestingT, description string, reporters []reporters.Reporter, writer writer.WriterInterface, config config.GinkgoConfigType) (bool, bool) { if config.ParallelTotal < 1 { panic("ginkgo.parallel.total must be >= 1") } if config.ParallelNode > config.ParallelTotal || config.ParallelNode < 1 { 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) suite.runner = specrunner.New(description, suite.beforeSuiteNode, iterator, suite.afterSuiteNode, reporters, writer, config) suite.running = true success := suite.runner.Run() if !success { t.Fail() } return success, hasProgrammaticFocus } func (suite *Suite) generateSpecsIterator(description string, config config.GinkgoConfigType) (spec_iterator.SpecIterator, bool) { specsSlice := []*spec.Spec{} suite.topLevelContainer.BackPropagateProgrammaticFocus() for _, collatedNodes := range suite.topLevelContainer.Collate() { specsSlice = append(specsSlice, spec.New(collatedNodes.Subject, collatedNodes.Containers, config.EmitSpecProgress)) } specs := spec.NewSpecs(specsSlice) specs.RegexScansFilePath = config.RegexScansFilePath if config.RandomizeAllSpecs { specs.Shuffle(rand.New(rand.NewSource(config.RandomSeed))) } specs.ApplyFocus(description, config.FocusStrings, config.SkipStrings) if config.SkipMeasurements { specs.SkipMeasurements() } var iterator spec_iterator.SpecIterator if config.ParallelTotal > 1 { iterator = spec_iterator.NewParallelIterator(specs.Specs(), config.SyncHost) resp, err := http.Get(config.SyncHost + "/has-counter") if err != nil || resp.StatusCode != http.StatusOK { iterator = spec_iterator.NewShardedParallelIterator(specs.Specs(), config.ParallelTotal, config.ParallelNode) } } else { iterator = spec_iterator.NewSerialIterator(specs.Specs()) } return iterator, specs.HasProgrammaticFocus() } func (suite *Suite) CurrentRunningSpecSummary() (*types.SpecSummary, bool) { if !suite.running { return nil, false } return suite.runner.CurrentSpecSummary() } func (suite *Suite) SetBeforeSuiteNode(body interface{}, codeLocation types.CodeLocation, timeout time.Duration) { if suite.beforeSuiteNode != nil { panic("You may only call BeforeSuite once!") } suite.beforeSuiteNode = leafnodes.NewBeforeSuiteNode(body, codeLocation, timeout, suite.failer) } func (suite *Suite) SetAfterSuiteNode(body interface{}, codeLocation types.CodeLocation, timeout time.Duration) { if suite.afterSuiteNode != nil { panic("You may only call AfterSuite once!") } suite.afterSuiteNode = leafnodes.NewAfterSuiteNode(body, codeLocation, timeout, suite.failer) } func (suite *Suite) SetSynchronizedBeforeSuiteNode(bodyA interface{}, bodyB interface{}, codeLocation types.CodeLocation, timeout time.Duration) { if suite.beforeSuiteNode != nil { panic("You may only call BeforeSuite once!") } suite.beforeSuiteNode = leafnodes.NewSynchronizedBeforeSuiteNode(bodyA, bodyB, codeLocation, timeout, suite.failer) } func (suite *Suite) SetSynchronizedAfterSuiteNode(bodyA interface{}, bodyB interface{}, codeLocation types.CodeLocation, timeout time.Duration) { if suite.afterSuiteNode != nil { panic("You may only call AfterSuite once!") } suite.afterSuiteNode = leafnodes.NewSynchronizedAfterSuiteNode(bodyA, bodyB, codeLocation, timeout, suite.failer) } 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) previousContainer := suite.currentContainer suite.currentContainer = container suite.containerIndex++ body() suite.containerIndex-- suite.currentContainer = previousContainer } func (suite *Suite) PushItNode(text string, body interface{}, flag types.FlagType, codeLocation types.CodeLocation, timeout time.Duration) { if suite.running { suite.failer.Fail("You may only call It from within a Describe, Context or When", codeLocation) } suite.currentContainer.PushSubjectNode(leafnodes.NewItNode(text, body, flag, codeLocation, timeout, suite.failer, suite.containerIndex)) } func (suite *Suite) PushMeasureNode(text string, body interface{}, flag types.FlagType, codeLocation types.CodeLocation, samples int) { if suite.running { suite.failer.Fail("You may only call Measure from within a Describe, Context or When", codeLocation) } suite.currentContainer.PushSubjectNode(leafnodes.NewMeasureNode(text, body, flag, codeLocation, samples, suite.failer, suite.containerIndex)) } func (suite *Suite) PushBeforeEachNode(body interface{}, codeLocation types.CodeLocation, timeout time.Duration) { if suite.running { suite.failer.Fail("You may only call BeforeEach from within a Describe, Context or When", codeLocation) } suite.currentContainer.PushSetupNode(leafnodes.NewBeforeEachNode(body, codeLocation, timeout, suite.failer, suite.containerIndex)) } func (suite *Suite) PushJustBeforeEachNode(body interface{}, codeLocation types.CodeLocation, timeout time.Duration) { if suite.running { suite.failer.Fail("You may only call JustBeforeEach from within a Describe, Context or When", codeLocation) } suite.currentContainer.PushSetupNode(leafnodes.NewJustBeforeEachNode(body, codeLocation, timeout, suite.failer, suite.containerIndex)) } func (suite *Suite) PushJustAfterEachNode(body interface{}, codeLocation types.CodeLocation, timeout time.Duration) { if suite.running { suite.failer.Fail("You may only call JustAfterEach from within a Describe or Context", codeLocation) } suite.currentContainer.PushSetupNode(leafnodes.NewJustAfterEachNode(body, codeLocation, timeout, suite.failer, suite.containerIndex)) } func (suite *Suite) PushAfterEachNode(body interface{}, codeLocation types.CodeLocation, timeout time.Duration) { if suite.running { suite.failer.Fail("You may only call AfterEach from within a Describe, Context or When", codeLocation) } suite.currentContainer.PushSetupNode(leafnodes.NewAfterEachNode(body, codeLocation, timeout, suite.failer, suite.containerIndex)) }