You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

227 lines
8.6 KiB

  1. package suite
  2. import (
  3. "math/rand"
  4. "net/http"
  5. "time"
  6. "github.com/onsi/ginkgo/internal/spec_iterator"
  7. "github.com/onsi/ginkgo/config"
  8. "github.com/onsi/ginkgo/internal/containernode"
  9. "github.com/onsi/ginkgo/internal/failer"
  10. "github.com/onsi/ginkgo/internal/leafnodes"
  11. "github.com/onsi/ginkgo/internal/spec"
  12. "github.com/onsi/ginkgo/internal/specrunner"
  13. "github.com/onsi/ginkgo/internal/writer"
  14. "github.com/onsi/ginkgo/reporters"
  15. "github.com/onsi/ginkgo/types"
  16. )
  17. type ginkgoTestingT interface {
  18. Fail()
  19. }
  20. type deferredContainerNode struct {
  21. text string
  22. body func()
  23. flag types.FlagType
  24. codeLocation types.CodeLocation
  25. }
  26. type Suite struct {
  27. topLevelContainer *containernode.ContainerNode
  28. currentContainer *containernode.ContainerNode
  29. deferredContainerNodes []deferredContainerNode
  30. containerIndex int
  31. beforeSuiteNode leafnodes.SuiteNode
  32. afterSuiteNode leafnodes.SuiteNode
  33. runner *specrunner.SpecRunner
  34. failer *failer.Failer
  35. running bool
  36. expandTopLevelNodes bool
  37. }
  38. func New(failer *failer.Failer) *Suite {
  39. topLevelContainer := containernode.New("[Top Level]", types.FlagTypeNone, types.CodeLocation{})
  40. return &Suite{
  41. topLevelContainer: topLevelContainer,
  42. currentContainer: topLevelContainer,
  43. failer: failer,
  44. containerIndex: 1,
  45. deferredContainerNodes: []deferredContainerNode{},
  46. }
  47. }
  48. func (suite *Suite) Run(t ginkgoTestingT, description string, reporters []reporters.Reporter, writer writer.WriterInterface, config config.GinkgoConfigType) (bool, bool) {
  49. if config.ParallelTotal < 1 {
  50. panic("ginkgo.parallel.total must be >= 1")
  51. }
  52. if config.ParallelNode > config.ParallelTotal || config.ParallelNode < 1 {
  53. panic("ginkgo.parallel.node is one-indexed and must be <= ginkgo.parallel.total")
  54. }
  55. suite.expandTopLevelNodes = true
  56. for _, deferredNode := range suite.deferredContainerNodes {
  57. suite.PushContainerNode(deferredNode.text, deferredNode.body, deferredNode.flag, deferredNode.codeLocation)
  58. }
  59. r := rand.New(rand.NewSource(config.RandomSeed))
  60. suite.topLevelContainer.Shuffle(r)
  61. iterator, hasProgrammaticFocus := suite.generateSpecsIterator(description, config)
  62. suite.runner = specrunner.New(description, suite.beforeSuiteNode, iterator, suite.afterSuiteNode, reporters, writer, config)
  63. suite.running = true
  64. success := suite.runner.Run()
  65. if !success {
  66. t.Fail()
  67. }
  68. return success, hasProgrammaticFocus
  69. }
  70. func (suite *Suite) generateSpecsIterator(description string, config config.GinkgoConfigType) (spec_iterator.SpecIterator, bool) {
  71. specsSlice := []*spec.Spec{}
  72. suite.topLevelContainer.BackPropagateProgrammaticFocus()
  73. for _, collatedNodes := range suite.topLevelContainer.Collate() {
  74. specsSlice = append(specsSlice, spec.New(collatedNodes.Subject, collatedNodes.Containers, config.EmitSpecProgress))
  75. }
  76. specs := spec.NewSpecs(specsSlice)
  77. specs.RegexScansFilePath = config.RegexScansFilePath
  78. if config.RandomizeAllSpecs {
  79. specs.Shuffle(rand.New(rand.NewSource(config.RandomSeed)))
  80. }
  81. specs.ApplyFocus(description, config.FocusStrings, config.SkipStrings)
  82. if config.SkipMeasurements {
  83. specs.SkipMeasurements()
  84. }
  85. var iterator spec_iterator.SpecIterator
  86. if config.ParallelTotal > 1 {
  87. iterator = spec_iterator.NewParallelIterator(specs.Specs(), config.SyncHost)
  88. resp, err := http.Get(config.SyncHost + "/has-counter")
  89. if err != nil || resp.StatusCode != http.StatusOK {
  90. iterator = spec_iterator.NewShardedParallelIterator(specs.Specs(), config.ParallelTotal, config.ParallelNode)
  91. }
  92. } else {
  93. iterator = spec_iterator.NewSerialIterator(specs.Specs())
  94. }
  95. return iterator, specs.HasProgrammaticFocus()
  96. }
  97. func (suite *Suite) CurrentRunningSpecSummary() (*types.SpecSummary, bool) {
  98. if !suite.running {
  99. return nil, false
  100. }
  101. return suite.runner.CurrentSpecSummary()
  102. }
  103. func (suite *Suite) SetBeforeSuiteNode(body interface{}, codeLocation types.CodeLocation, timeout time.Duration) {
  104. if suite.beforeSuiteNode != nil {
  105. panic("You may only call BeforeSuite once!")
  106. }
  107. suite.beforeSuiteNode = leafnodes.NewBeforeSuiteNode(body, codeLocation, timeout, suite.failer)
  108. }
  109. func (suite *Suite) SetAfterSuiteNode(body interface{}, codeLocation types.CodeLocation, timeout time.Duration) {
  110. if suite.afterSuiteNode != nil {
  111. panic("You may only call AfterSuite once!")
  112. }
  113. suite.afterSuiteNode = leafnodes.NewAfterSuiteNode(body, codeLocation, timeout, suite.failer)
  114. }
  115. func (suite *Suite) SetSynchronizedBeforeSuiteNode(bodyA interface{}, bodyB interface{}, codeLocation types.CodeLocation, timeout time.Duration) {
  116. if suite.beforeSuiteNode != nil {
  117. panic("You may only call BeforeSuite once!")
  118. }
  119. suite.beforeSuiteNode = leafnodes.NewSynchronizedBeforeSuiteNode(bodyA, bodyB, codeLocation, timeout, suite.failer)
  120. }
  121. func (suite *Suite) SetSynchronizedAfterSuiteNode(bodyA interface{}, bodyB interface{}, codeLocation types.CodeLocation, timeout time.Duration) {
  122. if suite.afterSuiteNode != nil {
  123. panic("You may only call AfterSuite once!")
  124. }
  125. suite.afterSuiteNode = leafnodes.NewSynchronizedAfterSuiteNode(bodyA, bodyB, codeLocation, timeout, suite.failer)
  126. }
  127. func (suite *Suite) PushContainerNode(text string, body func(), flag types.FlagType, codeLocation types.CodeLocation) {
  128. /*
  129. We defer walking the container nodes (which immediately evaluates the `body` function)
  130. until `RunSpecs` is called. We do this by storing off the deferred container nodes. Then, when
  131. `RunSpecs` is called we actually go through and add the container nodes to the test structure.
  132. This allows us to defer calling all the `body` functions until _after_ the top level functions
  133. have been walked, _after_ func init()s have been called, and _after_ `go test` has called `flag.Parse()`.
  134. This allows users to load up configuration information in the `TestX` go test hook just before `RunSpecs`
  135. is invoked and solves issues like #693 and makes the lifecycle easier to reason about.
  136. */
  137. if !suite.expandTopLevelNodes {
  138. suite.deferredContainerNodes = append(suite.deferredContainerNodes, deferredContainerNode{text, body, flag, codeLocation})
  139. return
  140. }
  141. container := containernode.New(text, flag, codeLocation)
  142. suite.currentContainer.PushContainerNode(container)
  143. previousContainer := suite.currentContainer
  144. suite.currentContainer = container
  145. suite.containerIndex++
  146. body()
  147. suite.containerIndex--
  148. suite.currentContainer = previousContainer
  149. }
  150. func (suite *Suite) PushItNode(text string, body interface{}, flag types.FlagType, codeLocation types.CodeLocation, timeout time.Duration) {
  151. if suite.running {
  152. suite.failer.Fail("You may only call It from within a Describe, Context or When", codeLocation)
  153. }
  154. suite.currentContainer.PushSubjectNode(leafnodes.NewItNode(text, body, flag, codeLocation, timeout, suite.failer, suite.containerIndex))
  155. }
  156. func (suite *Suite) PushMeasureNode(text string, body interface{}, flag types.FlagType, codeLocation types.CodeLocation, samples int) {
  157. if suite.running {
  158. suite.failer.Fail("You may only call Measure from within a Describe, Context or When", codeLocation)
  159. }
  160. suite.currentContainer.PushSubjectNode(leafnodes.NewMeasureNode(text, body, flag, codeLocation, samples, suite.failer, suite.containerIndex))
  161. }
  162. func (suite *Suite) PushBeforeEachNode(body interface{}, codeLocation types.CodeLocation, timeout time.Duration) {
  163. if suite.running {
  164. suite.failer.Fail("You may only call BeforeEach from within a Describe, Context or When", codeLocation)
  165. }
  166. suite.currentContainer.PushSetupNode(leafnodes.NewBeforeEachNode(body, codeLocation, timeout, suite.failer, suite.containerIndex))
  167. }
  168. func (suite *Suite) PushJustBeforeEachNode(body interface{}, codeLocation types.CodeLocation, timeout time.Duration) {
  169. if suite.running {
  170. suite.failer.Fail("You may only call JustBeforeEach from within a Describe, Context or When", codeLocation)
  171. }
  172. suite.currentContainer.PushSetupNode(leafnodes.NewJustBeforeEachNode(body, codeLocation, timeout, suite.failer, suite.containerIndex))
  173. }
  174. func (suite *Suite) PushJustAfterEachNode(body interface{}, codeLocation types.CodeLocation, timeout time.Duration) {
  175. if suite.running {
  176. suite.failer.Fail("You may only call JustAfterEach from within a Describe or Context", codeLocation)
  177. }
  178. suite.currentContainer.PushSetupNode(leafnodes.NewJustAfterEachNode(body, codeLocation, timeout, suite.failer, suite.containerIndex))
  179. }
  180. func (suite *Suite) PushAfterEachNode(body interface{}, codeLocation types.CodeLocation, timeout time.Duration) {
  181. if suite.running {
  182. suite.failer.Fail("You may only call AfterEach from within a Describe, Context or When", codeLocation)
  183. }
  184. suite.currentContainer.PushSetupNode(leafnodes.NewAfterEachNode(body, codeLocation, timeout, suite.failer, suite.containerIndex))
  185. }