Codebase list golang-gomega / e2091c5
Eventually/Consistently will forward an attached context to functions that ask for one Onsi Fakhouri 1 year, 6 months ago
2 changed file(s) with 176 addition(s) and 78 deletion(s). Raw diff Collapse all Expand all
11
22 import (
33 "context"
4 "errors"
54 "fmt"
65 "reflect"
76 "runtime"
1817 AsyncAssertionTypeConsistently
1918 )
2019
20 func (at AsyncAssertionType) String() string {
21 switch at {
22 case AsyncAssertionTypeEventually:
23 return "Eventually"
24 case AsyncAssertionTypeConsistently:
25 return "Consistently"
26 }
27 return "INVALID ASYNC ASSERTION TYPE"
28 }
29
2130 type AsyncAssertion struct {
2231 asyncType AsyncAssertionType
2332
2433 actualIsFunc bool
25 actualValue interface{}
26 actualFunc func() ([]reflect.Value, error)
34 actual interface{}
2735
2836 timeoutInterval time.Duration
2937 pollingInterval time.Duration
4250 g: g,
4351 }
4452
45 switch actualType := reflect.TypeOf(actualInput); {
46 case actualInput == nil || actualType.Kind() != reflect.Func:
47 out.actualValue = actualInput
48 case actualType.NumIn() == 0 && actualType.NumOut() > 0:
53 out.actual = actualInput
54 if actualInput != nil && reflect.TypeOf(actualInput).Kind() == reflect.Func {
4955 out.actualIsFunc = true
50 out.actualFunc = func() ([]reflect.Value, error) {
51 return reflect.ValueOf(actualInput).Call([]reflect.Value{}), nil
52 }
53 case actualType.NumIn() == 1 && actualType.In(0).Implements(reflect.TypeOf((*types.Gomega)(nil)).Elem()):
54 out.actualIsFunc = true
55 out.actualFunc = func() (values []reflect.Value, err error) {
56 var assertionFailure error
57 assertionCapturingGomega := NewGomega(g.DurationBundle).ConfigureWithFailHandler(func(message string, callerSkip ...int) {
58 skip := 0
59 if len(callerSkip) > 0 {
60 skip = callerSkip[0]
61 }
62 _, file, line, _ := runtime.Caller(skip + 1)
63 assertionFailure = fmt.Errorf("Assertion in callback at %s:%d failed:\n%s", file, line, message)
64 panic("stop execution")
65 })
66
67 defer func() {
68 if actualType.NumOut() == 0 {
69 if assertionFailure == nil {
70 values = []reflect.Value{reflect.Zero(reflect.TypeOf((*error)(nil)).Elem())}
71 } else {
72 values = []reflect.Value{reflect.ValueOf(assertionFailure)}
73 }
74 } else {
75 err = assertionFailure
76 }
77 if e := recover(); e != nil && assertionFailure == nil {
78 panic(e)
79 }
80 }()
81
82 values = reflect.ValueOf(actualInput).Call([]reflect.Value{reflect.ValueOf(assertionCapturingGomega)})
83 return
84 }
85 default:
86 msg := fmt.Sprintf("The function passed to Gomega's async assertions should either take no arguments and return values, or take a single Gomega interface that it can use to make assertions within the body of the function. When taking a Gomega interface the function can optionally return values or return nothing. The function you passed takes %d arguments and returns %d values.", actualType.NumIn(), actualType.NumOut())
87 g.Fail(msg, offset+4)
8856 }
8957
9058 return out
144112 return fmt.Sprintf(optionalDescription[0].(string), optionalDescription[1:]...) + "\n"
145113 }
146114
147 func (assertion *AsyncAssertion) pollActual() (interface{}, error) {
115 func (assertion *AsyncAssertion) processReturnValues(values []reflect.Value) (interface{}, error) {
116 if len(values) == 0 {
117 return nil, fmt.Errorf("No values were returned by the function passed to Gomega")
118 }
119 actual := values[0].Interface()
120 for i, extraValue := range values[1:] {
121 extra := extraValue.Interface()
122 if extra == nil {
123 continue
124 }
125 zero := reflect.Zero(extraValue.Type()).Interface()
126 if reflect.DeepEqual(extra, zero) {
127 continue
128 }
129 return actual, fmt.Errorf("Unexpected non-nil/non-zero argument at index %d:\n\t<%T>: %#v", i+1, extra, extra)
130 }
131 return actual, nil
132 }
133
134 var gomegaType = reflect.TypeOf((*types.Gomega)(nil)).Elem()
135 var contextType = reflect.TypeOf(new(context.Context)).Elem()
136
137 func (assertion *AsyncAssertion) invalidFunctionError(t reflect.Type) error {
138 return fmt.Errorf(`The function passed to %s had an invalid signature of %s. Functions passed to %s must either:
139
140 (a) have return values or
141 (b) take a Gomega interface as their first argument and use that Gomega instance to make assertions.
142
143 You can learn more at https://onsi.github.io/gomega/#eventually
144 `, assertion.asyncType, t, assertion.asyncType)
145 }
146
147 func (assertion *AsyncAssertion) noConfiguredContextForFunctionError(t reflect.Type) error {
148 return fmt.Errorf(`The function passed to %s requested a context.Context, but no context has been provided to %s. Please pass one in using %s().WithContext().
149
150 You can learn more at https://onsi.github.io/gomega/#eventually
151 `, assertion.asyncType, t, assertion.asyncType)
152 }
153
154 func (assertion *AsyncAssertion) buildActualPoller() (func() (interface{}, error), error) {
148155 if !assertion.actualIsFunc {
149 return assertion.actualValue, nil
150 }
151
152 values, err := assertion.actualFunc()
153 if err != nil {
154 return nil, err
155 }
156 extras := []interface{}{nil}
157 for _, value := range values[1:] {
158 extras = append(extras, value.Interface())
159 }
160 success, message := vetActuals(extras, 0)
161 if !success {
162 return nil, errors.New(message)
163 }
164
165 return values[0].Interface(), nil
156 return func() (interface{}, error) { return assertion.actual, nil }, nil
157 }
158 actualValue := reflect.ValueOf(assertion.actual)
159 actualType := reflect.TypeOf(assertion.actual)
160 numIn, numOut := actualType.NumIn(), actualType.NumOut()
161
162 if numIn == 0 && numOut == 0 {
163 return nil, assertion.invalidFunctionError(actualType)
164 } else if numIn == 0 {
165 return func() (interface{}, error) { return assertion.processReturnValues(actualValue.Call([]reflect.Value{})) }, nil
166 }
167 takesGomega, takesContext := actualType.In(0).Implements(gomegaType), actualType.In(0).Implements(contextType)
168 if takesGomega && numIn > 1 && actualType.In(1).Implements(contextType) {
169 takesContext = true
170 }
171 if !takesGomega && numOut == 0 {
172 return nil, assertion.invalidFunctionError(actualType)
173 }
174 if takesContext && assertion.ctx == nil {
175 return nil, assertion.noConfiguredContextForFunctionError(actualType)
176 }
177 remainingIn := numIn
178 if takesGomega {
179 remainingIn -= 1
180 }
181 if takesContext {
182 remainingIn -= 1
183 }
184 if remainingIn > 0 {
185 return nil, assertion.invalidFunctionError(actualType)
186 }
187
188 var assertionFailure error
189 inValues := []reflect.Value{}
190 if takesGomega {
191 inValues = append(inValues, reflect.ValueOf(NewGomega(assertion.g.DurationBundle).ConfigureWithFailHandler(func(message string, callerSkip ...int) {
192 skip := 0
193 if len(callerSkip) > 0 {
194 skip = callerSkip[0]
195 }
196 _, file, line, _ := runtime.Caller(skip + 1)
197 assertionFailure = fmt.Errorf("Assertion in callback at %s:%d failed:\n%s", file, line, message)
198 panic("stop execution")
199 })))
200 }
201 if takesContext {
202 inValues = append(inValues, reflect.ValueOf(assertion.ctx))
203 }
204
205 return func() (actual interface{}, err error) {
206 var values []reflect.Value
207 assertionFailure = nil
208 defer func() {
209 if numOut == 0 {
210 actual = assertionFailure
211 } else {
212 actual, err = assertion.processReturnValues(values)
213 if assertionFailure != nil {
214 err = assertionFailure
215 }
216 }
217 if e := recover(); e != nil && assertionFailure == nil {
218 panic(e)
219 }
220 }()
221 values = actualValue.Call(inValues)
222 return
223 }, nil
166224 }
167225
168226 func (assertion *AsyncAssertion) matcherMayChange(matcher types.GomegaMatcher, value interface{}) bool {
185243 var err error
186244 mayChange := true
187245
188 value, err := assertion.pollActual()
246 assertion.g.THelper()
247
248 pollActual, err := assertion.buildActualPoller()
249 if err != nil {
250 assertion.g.Fail(err.Error(), 2+assertion.offset)
251 return false
252 }
253
254 value, err := pollActual()
189255 if err == nil {
190256 mayChange = assertion.matcherMayChange(matcher, value)
191257 matches, err = matcher.Match(value)
192258 }
193
194 assertion.g.THelper()
195259
196260 messageGenerator := func() string {
197261 // can be called out of band by Ginkgo if the user requests a progress report
239303
240304 select {
241305 case <-time.After(assertion.pollingInterval):
242 v, e := assertion.pollActual()
306 v, e := pollActual()
243307 lock.Lock()
244308 value, err = v, e
245309 lock.Unlock()
271335
272336 select {
273337 case <-time.After(assertion.pollingInterval):
274 v, e := assertion.pollActual()
338 v, e := pollActual()
275339 lock.Lock()
276340 value, err = v, e
277341 lock.Unlock()
768768 })
769769 })
770770
771 Context("when passed a function that takes a context", func() {
772 It("forwards its own configured context", func() {
773 ctx := context.WithValue(context.Background(), "key", "value")
774 Eventually(func(ctx context.Context) string {
775 return ctx.Value("key").(string)
776 }).WithContext(ctx).Should(Equal("value"))
777 })
778
779 It("forwards its own configured context _and_ a Gomega if requested", func() {
780 ctx := context.WithValue(context.Background(), "key", "value")
781 Eventually(func(g Gomega, ctx context.Context) {
782 g.Expect(ctx.Value("key").(string)).To(Equal("schmalue"))
783 }).WithContext(ctx).Should(MatchError(ContainSubstring("Expected\n <string>: value\nto equal\n <string>: schmalue")))
784 })
785
786 Context("when the assertion does not have an attached context", func() {
787 It("errors", func() {
788 ig.G.Eventually(func(ctx context.Context) string {
789 return ctx.Value("key").(string)
790 }).Should(Equal("value"))
791 Ω(ig.FailureMessage).Should(ContainSubstring("The function passed to Eventually requested a context.Context, but no context has been provided to func(context.Context) string. Please pass one in using Eventually().WithContext()."))
792 Ω(ig.FailureSkip).Should(Equal([]int{2}))
793 })
794 })
795 })
796
771797 Describe("when passed an invalid function", func() {
772 It("errors immediately", func() {
773 ig.G.Eventually(func() {})
774 Ω(ig.FailureMessage).Should(Equal("The function passed to Gomega's async assertions should either take no arguments and return values, or take a single Gomega interface that it can use to make assertions within the body of the function. When taking a Gomega interface the function can optionally return values or return nothing. The function you passed takes 0 arguments and returns 0 values."))
775 Ω(ig.FailureSkip).Should(Equal([]int{4}))
798 It("errors with a failure", func() {
799 ig.G.Eventually(func() {}).Should(Equal("foo"))
800 Ω(ig.FailureMessage).Should(ContainSubstring("The function passed to Eventually had an invalid signature of func()"))
801 Ω(ig.FailureSkip).Should(Equal([]int{2}))
802
803 ig.G.Consistently(func(ctx context.Context) {}).Should(Equal("foo"))
804 Ω(ig.FailureMessage).Should(ContainSubstring("The function passed to Consistently had an invalid signature of func(context.Context)"))
805 Ω(ig.FailureSkip).Should(Equal([]int{2}))
776806
777807 ig = NewInstrumentedGomega()
778 ig.G.Eventually(func(g Gomega, foo string) {})
779 Ω(ig.FailureMessage).Should(Equal("The function passed to Gomega's async assertions should either take no arguments and return values, or take a single Gomega interface that it can use to make assertions within the body of the function. When taking a Gomega interface the function can optionally return values or return nothing. The function you passed takes 2 arguments and returns 0 values."))
780 Ω(ig.FailureSkip).Should(Equal([]int{4}))
808 ig.G.Eventually(func(g Gomega, foo string) {}).Should(Equal("foo"))
809 Ω(ig.FailureMessage).Should(ContainSubstring("The function passed to Eventually had an invalid signature of func(types.Gomega, string)"))
810 Ω(ig.FailureSkip).Should(Equal([]int{2}))
811
812 ig.G.Eventually(func(ctx context.Context, g Gomega) {}).Should(Equal("foo"))
813 Ω(ig.FailureMessage).Should(ContainSubstring("The function passed to Eventually had an invalid signature of func(context.Context, types.Gomega)"))
814 Ω(ig.FailureSkip).Should(Equal([]int{2}))
781815
782816 ig = NewInstrumentedGomega()
783 ig.G.Eventually(func(foo string) {})
784 Ω(ig.FailureMessage).Should(Equal("The function passed to Gomega's async assertions should either take no arguments and return values, or take a single Gomega interface that it can use to make assertions within the body of the function. When taking a Gomega interface the function can optionally return values or return nothing. The function you passed takes 1 arguments and returns 0 values."))
785 Ω(ig.FailureSkip).Should(Equal([]int{4}))
817 ig.G.Eventually(func(foo string) {}).Should(Equal("foo"))
818 Ω(ig.FailureMessage).Should(ContainSubstring("The function passed to Eventually had an invalid signature of func(string)"))
819 Ω(ig.FailureSkip).Should(Equal([]int{2}))
786820 })
787821 })
788822 })