context.go 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  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. stackMode StackMode
  66. }
  67. // rootConvey is the main entry point to a test suite. This is called when
  68. // there's no context in the stack already, and items must contain a `t` object,
  69. // or this panics.
  70. func rootConvey(items ...interface{}) {
  71. entry := discover(items)
  72. if entry.Test == nil {
  73. conveyPanic(missingGoTest)
  74. }
  75. expectChildRun := true
  76. ctx := &context{
  77. reporter: buildReporter(),
  78. children: make(map[string]*context),
  79. expectChildRun: &expectChildRun,
  80. focus: entry.Focus,
  81. failureMode: defaultFailureMode.combine(entry.FailMode),
  82. stackMode: defaultStackMode.combine(entry.StackMode),
  83. }
  84. ctxMgr.SetValues(gls.Values{nodeKey: ctx}, func() {
  85. ctx.reporter.BeginStory(reporting.NewStoryReport(entry.Test))
  86. defer ctx.reporter.EndStory()
  87. for ctx.shouldVisit() {
  88. ctx.conveyInner(entry.Situation, entry.Func)
  89. expectChildRun = true
  90. }
  91. })
  92. }
  93. //////////////////////////////////// Methods ////////////////////////////////////
  94. func (ctx *context) SkipConvey(items ...interface{}) {
  95. ctx.Convey(items, skipConvey)
  96. }
  97. func (ctx *context) FocusConvey(items ...interface{}) {
  98. ctx.Convey(items, focusConvey)
  99. }
  100. func (ctx *context) Convey(items ...interface{}) {
  101. entry := discover(items)
  102. // we're a branch, or leaf (on the wind)
  103. if entry.Test != nil {
  104. conveyPanic(extraGoTest)
  105. }
  106. if ctx.focus && !entry.Focus {
  107. return
  108. }
  109. var inner_ctx *context
  110. if ctx.executedOnce {
  111. var ok bool
  112. inner_ctx, ok = ctx.children[entry.Situation]
  113. if !ok {
  114. conveyPanic(differentConveySituations, entry.Situation)
  115. }
  116. } else {
  117. if _, ok := ctx.children[entry.Situation]; ok {
  118. conveyPanic(multipleIdenticalConvey, entry.Situation)
  119. }
  120. inner_ctx = &context{
  121. reporter: ctx.reporter,
  122. children: make(map[string]*context),
  123. expectChildRun: ctx.expectChildRun,
  124. focus: entry.Focus,
  125. failureMode: ctx.failureMode.combine(entry.FailMode),
  126. stackMode: ctx.stackMode.combine(entry.StackMode),
  127. }
  128. ctx.children[entry.Situation] = inner_ctx
  129. }
  130. if inner_ctx.shouldVisit() {
  131. ctxMgr.SetValues(gls.Values{nodeKey: inner_ctx}, func() {
  132. inner_ctx.conveyInner(entry.Situation, entry.Func)
  133. })
  134. }
  135. }
  136. func (ctx *context) SkipSo(stuff ...interface{}) {
  137. ctx.assertionReport(reporting.NewSkipReport())
  138. }
  139. func (ctx *context) So(actual interface{}, assert Assertion, expected ...interface{}) {
  140. if result := assert(actual, expected...); result == assertionSuccess {
  141. ctx.assertionReport(reporting.NewSuccessReport())
  142. } else {
  143. ctx.assertionReport(reporting.NewFailureReport(result, ctx.shouldShowStack()))
  144. }
  145. }
  146. func (ctx *context) SoMsg(msg string, actual interface{}, assert Assertion, expected ...interface{}) {
  147. if result := assert(actual, expected...); result == assertionSuccess {
  148. ctx.assertionReport(reporting.NewSuccessReport())
  149. return
  150. } else {
  151. ctx.reporter.Enter(reporting.NewScopeReport(msg))
  152. defer ctx.reporter.Exit()
  153. ctx.assertionReport(reporting.NewFailureReport(result, ctx.shouldShowStack()))
  154. }
  155. }
  156. func (ctx *context) Reset(action func()) {
  157. /* TODO: Failure mode configuration */
  158. ctx.resets = append(ctx.resets, action)
  159. }
  160. func (ctx *context) Print(items ...interface{}) (int, error) {
  161. fmt.Fprint(ctx.reporter, items...)
  162. return fmt.Print(items...)
  163. }
  164. func (ctx *context) Println(items ...interface{}) (int, error) {
  165. fmt.Fprintln(ctx.reporter, items...)
  166. return fmt.Println(items...)
  167. }
  168. func (ctx *context) Printf(format string, items ...interface{}) (int, error) {
  169. fmt.Fprintf(ctx.reporter, format, items...)
  170. return fmt.Printf(format, items...)
  171. }
  172. //////////////////////////////////// Private ////////////////////////////////////
  173. // shouldVisit returns true iff we should traverse down into a Convey. Note
  174. // that just because we don't traverse a Convey this time, doesn't mean that
  175. // we may not traverse it on a subsequent pass.
  176. func (c *context) shouldVisit() bool {
  177. return !c.complete && *c.expectChildRun
  178. }
  179. func (c *context) shouldShowStack() bool {
  180. return c.stackMode == StackFail
  181. }
  182. // conveyInner is the function which actually executes the user's anonymous test
  183. // function body. At this point, Convey or RootConvey has decided that this
  184. // function should actually run.
  185. func (ctx *context) conveyInner(situation string, f func(C)) {
  186. // Record/Reset state for next time.
  187. defer func() {
  188. ctx.executedOnce = true
  189. // This is only needed at the leaves, but there's no harm in also setting it
  190. // when returning from branch Convey's
  191. *ctx.expectChildRun = false
  192. }()
  193. // Set up+tear down our scope for the reporter
  194. ctx.reporter.Enter(reporting.NewScopeReport(situation))
  195. defer ctx.reporter.Exit()
  196. // Recover from any panics in f, and assign the `complete` status for this
  197. // node of the tree.
  198. defer func() {
  199. ctx.complete = true
  200. if problem := recover(); problem != nil {
  201. if problem, ok := problem.(*conveyErr); ok {
  202. panic(problem)
  203. }
  204. if problem != failureHalt {
  205. ctx.reporter.Report(reporting.NewErrorReport(problem))
  206. }
  207. } else {
  208. for _, child := range ctx.children {
  209. if !child.complete {
  210. ctx.complete = false
  211. return
  212. }
  213. }
  214. }
  215. }()
  216. // Resets are registered as the `f` function executes, so nil them here.
  217. // All resets are run in registration order (FIFO).
  218. ctx.resets = []func(){}
  219. defer func() {
  220. for _, r := range ctx.resets {
  221. // panics handled by the previous defer
  222. r()
  223. }
  224. }()
  225. if f == nil {
  226. // if f is nil, this was either a Convey(..., nil), or a SkipConvey
  227. ctx.reporter.Report(reporting.NewSkipReport())
  228. } else {
  229. f(ctx)
  230. }
  231. }
  232. // assertionReport is a helper for So and SkipSo which makes the report and
  233. // then possibly panics, depending on the current context's failureMode.
  234. func (ctx *context) assertionReport(r *reporting.AssertionResult) {
  235. ctx.reporter.Report(r)
  236. if r.Failure != "" && ctx.failureMode == FailureHalts {
  237. panic(failureHalt)
  238. }
  239. }