context.go 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. package convey
  2. import (
  3. "fmt"
  4. "github.com/jtolds/gls"
  5. "github.com/smartystreets/goconvey/convey/reporting"
  6. )
  7. type conveyErr struct {
  8. fmt string
  9. params []interface{}
  10. }
  11. func (e *conveyErr) Error() string {
  12. return fmt.Sprintf(e.fmt, e.params...)
  13. }
  14. func conveyPanic(fmt string, params ...interface{}) {
  15. panic(&conveyErr{fmt, params})
  16. }
  17. const (
  18. missingGoTest = `Top-level calls to Convey(...) need a reference to the *testing.T.
  19. Hint: Convey("description here", t, func() { /* notice that the second argument was the *testing.T (t)! */ }) `
  20. extraGoTest = `Only the top-level call to Convey(...) needs a reference to the *testing.T.`
  21. noStackContext = "Convey operation made without context on goroutine stack.\n" +
  22. "Hint: Perhaps you meant to use `Convey(..., func(c C){...})` ?"
  23. differentConveySituations = "Different set of Convey statements on subsequent pass!\nDid not expect %#v."
  24. multipleIdenticalConvey = "Multiple convey suites with identical names: %#v"
  25. )
  26. const (
  27. failureHalt = "___FAILURE_HALT___"
  28. nodeKey = "node"
  29. )
  30. ///////////////////////////////// Stack Context /////////////////////////////////
  31. func getCurrentContext() *context {
  32. ctx, ok := ctxMgr.GetValue(nodeKey)
  33. if ok {
  34. return ctx.(*context)
  35. }
  36. return nil
  37. }
  38. func mustGetCurrentContext() *context {
  39. ctx := getCurrentContext()
  40. if ctx == nil {
  41. conveyPanic(noStackContext)
  42. }
  43. return ctx
  44. }
  45. //////////////////////////////////// Context ////////////////////////////////////
  46. // context magically handles all coordination of Convey's and So assertions.
  47. //
  48. // It is tracked on the stack as goroutine-local-storage with the gls package,
  49. // or explicitly if the user decides to call convey like:
  50. //
  51. // Convey(..., func(c C) {
  52. // c.So(...)
  53. // })
  54. //
  55. // This implements the `C` interface.
  56. type context struct {
  57. reporter reporting.Reporter
  58. children map[string]*context
  59. resets []func()
  60. executedOnce bool
  61. expectChildRun *bool
  62. complete bool
  63. focus bool
  64. failureMode FailureMode
  65. }
  66. // rootConvey is the main entry point to a test suite. This is called when
  67. // there's no context in the stack already, and items must contain a `t` object,
  68. // or this panics.
  69. func rootConvey(items ...interface{}) {
  70. entry := discover(items)
  71. if entry.Test == nil {
  72. conveyPanic(missingGoTest)
  73. }
  74. expectChildRun := true
  75. ctx := &context{
  76. reporter: buildReporter(),
  77. children: make(map[string]*context),
  78. expectChildRun: &expectChildRun,
  79. focus: entry.Focus,
  80. failureMode: defaultFailureMode.combine(entry.FailMode),
  81. }
  82. ctxMgr.SetValues(gls.Values{nodeKey: ctx}, func() {
  83. ctx.reporter.BeginStory(reporting.NewStoryReport(entry.Test))
  84. defer ctx.reporter.EndStory()
  85. for ctx.shouldVisit() {
  86. ctx.conveyInner(entry.Situation, entry.Func)
  87. expectChildRun = true
  88. }
  89. })
  90. }
  91. //////////////////////////////////// Methods ////////////////////////////////////
  92. func (ctx *context) SkipConvey(items ...interface{}) {
  93. ctx.Convey(items, skipConvey)
  94. }
  95. func (ctx *context) FocusConvey(items ...interface{}) {
  96. ctx.Convey(items, focusConvey)
  97. }
  98. func (ctx *context) Convey(items ...interface{}) {
  99. entry := discover(items)
  100. // we're a branch, or leaf (on the wind)
  101. if entry.Test != nil {
  102. conveyPanic(extraGoTest)
  103. }
  104. if ctx.focus && !entry.Focus {
  105. return
  106. }
  107. var inner_ctx *context
  108. if ctx.executedOnce {
  109. var ok bool
  110. inner_ctx, ok = ctx.children[entry.Situation]
  111. if !ok {
  112. conveyPanic(differentConveySituations, entry.Situation)
  113. }
  114. } else {
  115. if _, ok := ctx.children[entry.Situation]; ok {
  116. conveyPanic(multipleIdenticalConvey, entry.Situation)
  117. }
  118. inner_ctx = &context{
  119. reporter: ctx.reporter,
  120. children: make(map[string]*context),
  121. expectChildRun: ctx.expectChildRun,
  122. focus: entry.Focus,
  123. failureMode: ctx.failureMode.combine(entry.FailMode),
  124. }
  125. ctx.children[entry.Situation] = inner_ctx
  126. }
  127. if inner_ctx.shouldVisit() {
  128. ctxMgr.SetValues(gls.Values{nodeKey: inner_ctx}, func() {
  129. inner_ctx.conveyInner(entry.Situation, entry.Func)
  130. })
  131. }
  132. }
  133. func (ctx *context) SkipSo(stuff ...interface{}) {
  134. ctx.assertionReport(reporting.NewSkipReport())
  135. }
  136. func (ctx *context) So(actual interface{}, assert assertion, expected ...interface{}) {
  137. if result := assert(actual, expected...); result == assertionSuccess {
  138. ctx.assertionReport(reporting.NewSuccessReport())
  139. } else {
  140. ctx.assertionReport(reporting.NewFailureReport(result))
  141. }
  142. }
  143. func (ctx *context) Reset(action func()) {
  144. /* TODO: Failure mode configuration */
  145. ctx.resets = append(ctx.resets, action)
  146. }
  147. func (ctx *context) Print(items ...interface{}) (int, error) {
  148. fmt.Fprint(ctx.reporter, items...)
  149. return fmt.Print(items...)
  150. }
  151. func (ctx *context) Println(items ...interface{}) (int, error) {
  152. fmt.Fprintln(ctx.reporter, items...)
  153. return fmt.Println(items...)
  154. }
  155. func (ctx *context) Printf(format string, items ...interface{}) (int, error) {
  156. fmt.Fprintf(ctx.reporter, format, items...)
  157. return fmt.Printf(format, items...)
  158. }
  159. //////////////////////////////////// Private ////////////////////////////////////
  160. // shouldVisit returns true iff we should traverse down into a Convey. Note
  161. // that just because we don't traverse a Convey this time, doesn't mean that
  162. // we may not traverse it on a subsequent pass.
  163. func (c *context) shouldVisit() bool {
  164. return !c.complete && *c.expectChildRun
  165. }
  166. // conveyInner is the function which actually executes the user's anonymous test
  167. // function body. At this point, Convey or RootConvey has decided that this
  168. // function should actually run.
  169. func (ctx *context) conveyInner(situation string, f func(C)) {
  170. // Record/Reset state for next time.
  171. defer func() {
  172. ctx.executedOnce = true
  173. // This is only needed at the leaves, but there's no harm in also setting it
  174. // when returning from branch Convey's
  175. *ctx.expectChildRun = false
  176. }()
  177. // Set up+tear down our scope for the reporter
  178. ctx.reporter.Enter(reporting.NewScopeReport(situation))
  179. defer ctx.reporter.Exit()
  180. // Recover from any panics in f, and assign the `complete` status for this
  181. // node of the tree.
  182. defer func() {
  183. ctx.complete = true
  184. if problem := recover(); problem != nil {
  185. if problem, ok := problem.(*conveyErr); ok {
  186. panic(problem)
  187. }
  188. if problem != failureHalt {
  189. ctx.reporter.Report(reporting.NewErrorReport(problem))
  190. }
  191. } else {
  192. for _, child := range ctx.children {
  193. if !child.complete {
  194. ctx.complete = false
  195. return
  196. }
  197. }
  198. }
  199. }()
  200. // Resets are registered as the `f` function executes, so nil them here.
  201. // All resets are run in registration order (FIFO).
  202. ctx.resets = []func(){}
  203. defer func() {
  204. for _, r := range ctx.resets {
  205. // panics handled by the previous defer
  206. r()
  207. }
  208. }()
  209. if f == nil {
  210. // if f is nil, this was either a Convey(..., nil), or a SkipConvey
  211. ctx.reporter.Report(reporting.NewSkipReport())
  212. } else {
  213. f(ctx)
  214. }
  215. }
  216. // assertionReport is a helper for So and SkipSo which makes the report and
  217. // then possibly panics, depending on the current context's failureMode.
  218. func (ctx *context) assertionReport(r *reporting.AssertionResult) {
  219. ctx.reporter.Report(r)
  220. if r.Failure != "" && ctx.failureMode == FailureHalts {
  221. panic(failureHalt)
  222. }
  223. }