chain.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521
  1. package httpexpect
  2. import (
  3. "fmt"
  4. "sync"
  5. "testing"
  6. "github.com/stretchr/testify/assert"
  7. )
  8. // Every matcher struct, e.g. Value, Object, Array, etc. contains a chain instance.
  9. //
  10. // Most important chain fields are:
  11. //
  12. // - AssertionContext: provides test name, current request and response, and path
  13. // to current assertion starting from chain root
  14. //
  15. // - AssertionHandler: provides methods to handle successful and failed assertions;
  16. // may be defined by user, but usually we just use DefaulAssertionHandler
  17. //
  18. // - AssertionSeverity: severity to be used for failures (fatal or non-fatal)
  19. //
  20. // - Reference to parent: every chain remembers its parent chain; on failure,
  21. // chain automatically marks its parents failed
  22. //
  23. // - Failure flags: flags indicating whether a failure occurred on chain, or
  24. // on any of its children
  25. //
  26. // Chains are linked into a tree. Child chain corresponds to nested matchers
  27. // and assertions. For example, when the user invokes:
  28. //
  29. // e.GET("/test").Expect().JSON().IsEqual(...)
  30. //
  31. // each nested call (GET, Expect, JSON, Equal) will create a child chain.
  32. //
  33. // There are two ways to create a child chain:
  34. //
  35. // - use enter() / leave()
  36. // - use clone()
  37. //
  38. // enter() creates a chain to be used during assertion. After calling enter(), you
  39. // can use fail() to report any failures, which will pass it to AssertionHandler
  40. // and mark chain as failed.
  41. //
  42. // After assertion is done, you should call leave(). If there were no failures,
  43. // leave() will notify AssertionHandler about succeeded assertion. Otherwise,
  44. // leave() will mark its parent as failed and notify grand-, grand-grand-, etc
  45. // parents that they have failed children.
  46. //
  47. // If the assertion wants to create child matcher struct, it should invoke clone()
  48. // after calling enter() and before calling leave().
  49. //
  50. // enter() receives assertion name as an argument. This name is appended to the
  51. // path in AssertionContext. If you call clone() on this chain, it will inherit
  52. // this path. This way chain maintains path of the nested assertions.
  53. //
  54. // Typical workflow looks like:
  55. //
  56. // // create temporary chain for assertion
  57. // opChain := array.chain.enter("AssertionName()")
  58. //
  59. // // optional: report assertion failure
  60. // opChain.fail(...)
  61. //
  62. // // optional: create child matcher
  63. // child := &Value{chain: opChain.clone(), ...}
  64. //
  65. // // if there was a failure, propagate it back to array.chain and notify
  66. // // parents of array.chain that they have failed children
  67. // opChain.leave()
  68. type chain struct {
  69. mu sync.Mutex
  70. parent *chain
  71. state chainState
  72. flags chainFlags
  73. context AssertionContext
  74. handler AssertionHandler
  75. severity AssertionSeverity
  76. failure *AssertionFailure
  77. }
  78. // If enabled, chain will panic if used incorrectly or gets illformed AssertionFailure.
  79. // Used only in our own tests.
  80. var chainValidation = false
  81. type chainState int
  82. const (
  83. stateCloned chainState = iota // chain was created using clone()
  84. stateEntered // chain was created using enter()
  85. stateLeaved // leave() was called
  86. )
  87. type chainFlags int
  88. const (
  89. flagFailed chainFlags = (1 << iota) // fail() was called on this chain
  90. flagFailedChildren // fail() was called on any child
  91. )
  92. type chainResult bool
  93. const (
  94. success chainResult = true
  95. failure chainResult = false
  96. )
  97. // Construct chain using config.
  98. func newChainWithConfig(name string, config Config) *chain {
  99. config.validate()
  100. c := &chain{
  101. context: AssertionContext{},
  102. handler: config.AssertionHandler,
  103. severity: SeverityError,
  104. }
  105. c.context.TestName = config.TestName
  106. if name != "" {
  107. c.context.Path = []string{name}
  108. c.context.AliasedPath = []string{name}
  109. } else {
  110. c.context.Path = []string{}
  111. c.context.AliasedPath = []string{}
  112. }
  113. if config.Environment != nil {
  114. c.context.Environment = config.Environment
  115. } else {
  116. c.context.Environment = newEnvironment(c)
  117. }
  118. c.context.TestingTB = isTestingTB(c.handler)
  119. return c
  120. }
  121. // Construct chain using DefaultAssertionHandler and provided Reporter.
  122. func newChainWithDefaults(name string, reporter Reporter, flag ...chainFlags) *chain {
  123. if reporter == nil {
  124. panic("Reporter is nil")
  125. }
  126. c := &chain{
  127. context: AssertionContext{},
  128. handler: &DefaultAssertionHandler{
  129. Formatter: &DefaultFormatter{},
  130. Reporter: reporter,
  131. },
  132. severity: SeverityError,
  133. }
  134. if name != "" {
  135. c.context.Path = []string{name}
  136. c.context.AliasedPath = []string{name}
  137. } else {
  138. c.context.Path = []string{}
  139. c.context.AliasedPath = []string{}
  140. }
  141. c.context.Environment = newEnvironment(c)
  142. c.context.TestingTB = isTestingTB(c.handler)
  143. for _, f := range flag {
  144. c.flags |= f
  145. }
  146. return c
  147. }
  148. // Get environment instance.
  149. // Root chain constructor either gets environment from config or creates a new one.
  150. // Child chains inherit environment from parent.
  151. func (c *chain) env() *Environment {
  152. c.mu.Lock()
  153. defer c.mu.Unlock()
  154. return c.context.Environment
  155. }
  156. // Make this chain to be root.
  157. // Chain's parent field is cleared.
  158. // Failures wont be propagated to the upper chains anymore.
  159. func (c *chain) setRoot() {
  160. c.mu.Lock()
  161. defer c.mu.Unlock()
  162. if chainValidation && c.state == stateLeaved {
  163. panic("can't use chain after leave")
  164. }
  165. c.parent = nil
  166. }
  167. // Set severity of reported failures.
  168. // Chain always overrides failure severity with configured one.
  169. func (c *chain) setSeverity(severity AssertionSeverity) {
  170. c.mu.Lock()
  171. defer c.mu.Unlock()
  172. if chainValidation && c.state == stateLeaved {
  173. panic("can't use chain after leave")
  174. }
  175. c.severity = severity
  176. }
  177. // Reset aliased path to given string.
  178. func (c *chain) setAlias(name string) {
  179. c.mu.Lock()
  180. defer c.mu.Unlock()
  181. if chainValidation && c.state == stateLeaved {
  182. panic("can't use chain after leave")
  183. }
  184. if name != "" {
  185. c.context.AliasedPath = []string{name}
  186. } else {
  187. c.context.AliasedPath = []string{}
  188. }
  189. }
  190. // Store request name in AssertionContext.
  191. // Child chains inherit context from parent.
  192. func (c *chain) setRequestName(name string) {
  193. c.mu.Lock()
  194. defer c.mu.Unlock()
  195. if chainValidation && c.state == stateLeaved {
  196. panic("can't use chain after leave")
  197. }
  198. c.context.RequestName = name
  199. }
  200. // Store request pointer in AssertionContext.
  201. // Child chains inherit context from parent.
  202. func (c *chain) setRequest(req *Request) {
  203. c.mu.Lock()
  204. defer c.mu.Unlock()
  205. if chainValidation && c.state == stateLeaved {
  206. panic("can't use chain after leave")
  207. }
  208. if chainValidation && c.context.Request != nil {
  209. panic("context.Request already set")
  210. }
  211. c.context.Request = req
  212. }
  213. // Store response pointer in AssertionContext.
  214. // Child chains inherit context from parent.
  215. func (c *chain) setResponse(resp *Response) {
  216. c.mu.Lock()
  217. defer c.mu.Unlock()
  218. if chainValidation && c.state == stateLeaved {
  219. panic("can't use chain after leave")
  220. }
  221. if chainValidation && c.context.Response != nil {
  222. panic("context.Response already set")
  223. }
  224. c.context.Response = resp
  225. }
  226. // Set assertion handler
  227. // Chain always overrides assertion handler with given one.
  228. func (c *chain) setHandler(handler AssertionHandler) {
  229. c.mu.Lock()
  230. defer c.mu.Unlock()
  231. if chainValidation && c.state == stateLeaved {
  232. panic("can't use chain after leave")
  233. }
  234. c.handler = handler
  235. c.context.TestingTB = isTestingTB(handler)
  236. }
  237. // Create chain clone.
  238. // Typically is called between enter() and leave().
  239. func (c *chain) clone() *chain {
  240. c.mu.Lock()
  241. defer c.mu.Unlock()
  242. if chainValidation && c.state == stateLeaved {
  243. panic("can't use chain after leave")
  244. }
  245. contextCopy := c.context
  246. contextCopy.Path = append(([]string)(nil), contextCopy.Path...)
  247. contextCopy.AliasedPath = append(([]string)(nil), c.context.AliasedPath...)
  248. return &chain{
  249. parent: c,
  250. state: stateCloned,
  251. // flagFailedChildren is not inherited because the newly created clone
  252. // doesn't have children
  253. flags: (c.flags & ^flagFailedChildren),
  254. context: contextCopy,
  255. handler: c.handler,
  256. severity: c.severity,
  257. // failure is not inherited because it should be reported only once
  258. // by the chain where it happened
  259. failure: nil,
  260. }
  261. }
  262. // Create temporary chain clone to be used in assertion.
  263. // If name is not empty, it is appended to the path.
  264. // You must call leave() at the end of assertion.
  265. func (c *chain) enter(name string, args ...interface{}) *chain {
  266. chainCopy := c.clone()
  267. chainCopy.state = stateEntered
  268. if name != "" {
  269. chainCopy.context.Path = append(chainCopy.context.Path, fmt.Sprintf(name, args...))
  270. chainCopy.context.AliasedPath =
  271. append(c.context.AliasedPath, fmt.Sprintf(name, args...))
  272. }
  273. return chainCopy
  274. }
  275. // Like enter(), but it replaces last element of the path instead appending to it.
  276. // Must be called between enter() and leave().
  277. func (c *chain) replace(name string, args ...interface{}) *chain {
  278. if chainValidation {
  279. func() {
  280. c.mu.Lock()
  281. defer c.mu.Unlock()
  282. if c.state != stateEntered {
  283. panic("replace allowed only between enter/leave")
  284. }
  285. if len(c.context.Path) == 0 {
  286. panic("replace allowed only if path is non-empty")
  287. }
  288. if len(c.context.AliasedPath) == 0 {
  289. panic("replace allowed only if aliased path is non-empty")
  290. }
  291. }()
  292. }
  293. chainCopy := c.clone()
  294. chainCopy.state = stateEntered
  295. if len(chainCopy.context.Path) != 0 {
  296. last := len(chainCopy.context.Path) - 1
  297. chainCopy.context.Path[last] = fmt.Sprintf(name, args...)
  298. }
  299. if len(chainCopy.context.AliasedPath) != 0 {
  300. last := len(chainCopy.context.AliasedPath) - 1
  301. chainCopy.context.AliasedPath[last] = fmt.Sprintf(name, args...)
  302. }
  303. return chainCopy
  304. }
  305. // Finalize assertion.
  306. // Report success of failure to AssertionHandler.
  307. // In case of failure, also recursively notify parents and grandparents
  308. // that they have faield children.
  309. // Must be called after enter().
  310. // Chain can't be used after this call.
  311. func (c *chain) leave() {
  312. var (
  313. parent *chain
  314. flags chainFlags
  315. context AssertionContext
  316. handler AssertionHandler
  317. failure *AssertionFailure
  318. )
  319. func() {
  320. c.mu.Lock()
  321. defer c.mu.Unlock()
  322. if chainValidation && c.state != stateEntered {
  323. panic("unpaired enter/leave")
  324. }
  325. c.state = stateLeaved
  326. parent = c.parent
  327. flags = c.flags
  328. context = c.context
  329. handler = c.handler
  330. failure = c.failure
  331. }()
  332. if flags&(flagFailed|flagFailedChildren) == 0 {
  333. handler.Success(&context)
  334. }
  335. if flags&(flagFailed) != 0 && failure != nil {
  336. handler.Failure(&context, failure)
  337. if chainValidation {
  338. if err := validateAssertion(failure); err != nil {
  339. panic(err)
  340. }
  341. }
  342. }
  343. if flags&(flagFailed|flagFailedChildren) != 0 && parent != nil {
  344. parent.mu.Lock()
  345. parent.flags |= flagFailed
  346. p := parent.parent
  347. parent.mu.Unlock()
  348. for p != nil {
  349. p.mu.Lock()
  350. p.flags |= flagFailedChildren
  351. pp := p.parent
  352. p.mu.Unlock()
  353. p = pp
  354. }
  355. }
  356. }
  357. // Mark chain as failed.
  358. // Remember failure inside chain. It will be reported in leave().
  359. // Subsequent fail() call will be ignored.
  360. // Must be called between enter() and leave().
  361. func (c *chain) fail(failure AssertionFailure) {
  362. c.mu.Lock()
  363. defer c.mu.Unlock()
  364. if chainValidation && c.state != stateEntered {
  365. panic("fail allowed only between enter/leave")
  366. }
  367. if c.flags&flagFailed != 0 {
  368. return
  369. }
  370. c.flags |= flagFailed
  371. failure.Severity = c.severity
  372. if c.severity == SeverityError {
  373. failure.IsFatal = true
  374. }
  375. failure.Stacktrace = stacktrace()
  376. c.failure = &failure
  377. }
  378. // Check if chain failed.
  379. func (c *chain) failed() bool {
  380. c.mu.Lock()
  381. defer c.mu.Unlock()
  382. return c.flags&flagFailed != 0
  383. }
  384. // Check if chain or any of its children failed.
  385. func (c *chain) treeFailed() bool {
  386. c.mu.Lock()
  387. defer c.mu.Unlock()
  388. return c.flags&(flagFailed|flagFailedChildren) != 0
  389. }
  390. // Report failure unless chain has specified state.
  391. // For tests.
  392. func (c *chain) assert(t testing.TB, result chainResult) {
  393. c.mu.Lock()
  394. defer c.mu.Unlock()
  395. switch result {
  396. case success:
  397. assert.Equal(t, chainFlags(0), c.flags&flagFailed,
  398. "expected: chain is in success state")
  399. case failure:
  400. assert.NotEqual(t, chainFlags(0), c.flags&flagFailed,
  401. "expected: chain is in failure state")
  402. }
  403. }
  404. // Report failure unless chain has specified flags.
  405. // For tests.
  406. func (c *chain) assertFlags(t testing.TB, flags chainFlags) {
  407. c.mu.Lock()
  408. defer c.mu.Unlock()
  409. assert.Equal(t, flags, c.flags,
  410. "expected: chain has specified flags")
  411. }
  412. // Clear failure flags.
  413. // For tests.
  414. func (c *chain) clear() {
  415. c.mu.Lock()
  416. defer c.mu.Unlock()
  417. c.flags &= ^(flagFailed | flagFailedChildren)
  418. }
  419. // Whether handler outputs to testing.TB
  420. func isTestingTB(in AssertionHandler) bool {
  421. h, ok := in.(*DefaultAssertionHandler)
  422. if !ok {
  423. return false
  424. }
  425. switch h.Reporter.(type) {
  426. case *AssertReporter, *RequireReporter, *FatalReporter, testing.TB:
  427. return true
  428. }
  429. return false
  430. }