async_assertion.go 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. package asyncassertion
  2. import (
  3. "errors"
  4. "fmt"
  5. "reflect"
  6. "time"
  7. "github.com/onsi/gomega/internal/oraclematcher"
  8. "github.com/onsi/gomega/types"
  9. )
  10. type AsyncAssertionType uint
  11. const (
  12. AsyncAssertionTypeEventually AsyncAssertionType = iota
  13. AsyncAssertionTypeConsistently
  14. )
  15. type AsyncAssertion struct {
  16. asyncType AsyncAssertionType
  17. actualInput interface{}
  18. timeoutInterval time.Duration
  19. pollingInterval time.Duration
  20. failWrapper *types.GomegaFailWrapper
  21. offset int
  22. }
  23. func New(asyncType AsyncAssertionType, actualInput interface{}, failWrapper *types.GomegaFailWrapper, timeoutInterval time.Duration, pollingInterval time.Duration, offset int) *AsyncAssertion {
  24. actualType := reflect.TypeOf(actualInput)
  25. if actualType.Kind() == reflect.Func {
  26. if actualType.NumIn() != 0 || actualType.NumOut() == 0 {
  27. panic("Expected a function with no arguments and one or more return values.")
  28. }
  29. }
  30. return &AsyncAssertion{
  31. asyncType: asyncType,
  32. actualInput: actualInput,
  33. failWrapper: failWrapper,
  34. timeoutInterval: timeoutInterval,
  35. pollingInterval: pollingInterval,
  36. offset: offset,
  37. }
  38. }
  39. func (assertion *AsyncAssertion) Should(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool {
  40. assertion.failWrapper.TWithHelper.Helper()
  41. return assertion.match(matcher, true, optionalDescription...)
  42. }
  43. func (assertion *AsyncAssertion) ShouldNot(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool {
  44. assertion.failWrapper.TWithHelper.Helper()
  45. return assertion.match(matcher, false, optionalDescription...)
  46. }
  47. func (assertion *AsyncAssertion) buildDescription(optionalDescription ...interface{}) string {
  48. switch len(optionalDescription) {
  49. case 0:
  50. return ""
  51. default:
  52. return fmt.Sprintf(optionalDescription[0].(string), optionalDescription[1:]...) + "\n"
  53. }
  54. }
  55. func (assertion *AsyncAssertion) actualInputIsAFunction() bool {
  56. actualType := reflect.TypeOf(assertion.actualInput)
  57. return actualType.Kind() == reflect.Func && actualType.NumIn() == 0 && actualType.NumOut() > 0
  58. }
  59. func (assertion *AsyncAssertion) pollActual() (interface{}, error) {
  60. if assertion.actualInputIsAFunction() {
  61. values := reflect.ValueOf(assertion.actualInput).Call([]reflect.Value{})
  62. extras := []interface{}{}
  63. for _, value := range values[1:] {
  64. extras = append(extras, value.Interface())
  65. }
  66. success, message := vetExtras(extras)
  67. if !success {
  68. return nil, errors.New(message)
  69. }
  70. return values[0].Interface(), nil
  71. }
  72. return assertion.actualInput, nil
  73. }
  74. func (assertion *AsyncAssertion) matcherMayChange(matcher types.GomegaMatcher, value interface{}) bool {
  75. if assertion.actualInputIsAFunction() {
  76. return true
  77. }
  78. return oraclematcher.MatchMayChangeInTheFuture(matcher, value)
  79. }
  80. func (assertion *AsyncAssertion) match(matcher types.GomegaMatcher, desiredMatch bool, optionalDescription ...interface{}) bool {
  81. timer := time.Now()
  82. timeout := time.After(assertion.timeoutInterval)
  83. description := assertion.buildDescription(optionalDescription...)
  84. var matches bool
  85. var err error
  86. mayChange := true
  87. value, err := assertion.pollActual()
  88. if err == nil {
  89. mayChange = assertion.matcherMayChange(matcher, value)
  90. matches, err = matcher.Match(value)
  91. }
  92. assertion.failWrapper.TWithHelper.Helper()
  93. fail := func(preamble string) {
  94. errMsg := ""
  95. message := ""
  96. if err != nil {
  97. errMsg = "Error: " + err.Error()
  98. } else {
  99. if desiredMatch {
  100. message = matcher.FailureMessage(value)
  101. } else {
  102. message = matcher.NegatedFailureMessage(value)
  103. }
  104. }
  105. assertion.failWrapper.TWithHelper.Helper()
  106. assertion.failWrapper.Fail(fmt.Sprintf("%s after %.3fs.\n%s%s%s", preamble, time.Since(timer).Seconds(), description, message, errMsg), 3+assertion.offset)
  107. }
  108. if assertion.asyncType == AsyncAssertionTypeEventually {
  109. for {
  110. if err == nil && matches == desiredMatch {
  111. return true
  112. }
  113. if !mayChange {
  114. fail("No future change is possible. Bailing out early")
  115. return false
  116. }
  117. select {
  118. case <-time.After(assertion.pollingInterval):
  119. value, err = assertion.pollActual()
  120. if err == nil {
  121. mayChange = assertion.matcherMayChange(matcher, value)
  122. matches, err = matcher.Match(value)
  123. }
  124. case <-timeout:
  125. fail("Timed out")
  126. return false
  127. }
  128. }
  129. } else if assertion.asyncType == AsyncAssertionTypeConsistently {
  130. for {
  131. if !(err == nil && matches == desiredMatch) {
  132. fail("Failed")
  133. return false
  134. }
  135. if !mayChange {
  136. return true
  137. }
  138. select {
  139. case <-time.After(assertion.pollingInterval):
  140. value, err = assertion.pollActual()
  141. if err == nil {
  142. mayChange = assertion.matcherMayChange(matcher, value)
  143. matches, err = matcher.Match(value)
  144. }
  145. case <-timeout:
  146. return true
  147. }
  148. }
  149. }
  150. return false
  151. }
  152. func vetExtras(extras []interface{}) (bool, string) {
  153. for i, extra := range extras {
  154. if extra != nil {
  155. zeroValue := reflect.Zero(reflect.TypeOf(extra)).Interface()
  156. if !reflect.DeepEqual(zeroValue, extra) {
  157. message := fmt.Sprintf("Unexpected non-nil/non-zero extra argument at index %d:\n\t<%T>: %#v", i+1, extra, extra)
  158. return false, message
  159. }
  160. }
  161. }
  162. return true, ""
  163. }