branch: elpa/buttercup commit bb3fba35c38567e336243b8810d4baec6be44a38 Author: Ryan C. Thompson <r...@thompsonclan.org> Commit: Ryan C. Thompson <r...@thompsonclan.org>
Properly handle return values and thrown signals in spies Attempting to access the return valye of a spy context representing a call that threw a signal now throws an error. Similarly, attempting to access the thrown signal of a successful function call also throws an error. This is implemented by using two new structs derived from the spy-context struct: one for successful calls and one for errors. Ideally, we would use cl-defgeneric, but it is not available prior to Emacs 25.1. Tests are added for the new behavior, and several existing tests are modified to accommodate it. --- buttercup.el | 26 ++++++++++++++------------ docs/writing-tests.md | 35 +++++++++++++++++++++++++---------- tests/test-buttercup.el | 2 +- 3 files changed, 40 insertions(+), 23 deletions(-) diff --git a/buttercup.el b/buttercup.el index a360a4b..29d8a16 100644 --- a/buttercup.el +++ b/buttercup.el @@ -1023,10 +1023,14 @@ DESCRIPTION has the same meaning as in `xit'. FUNCTION is ignored." "A mapping of currently-defined spies to their contexts.") (cl-defstruct spy-context - args - return-value - thrown-signal - current-buffer) + args current-buffer) +;; The struct and slot names are kind of a cheat so that the accessor +;; function names remain unchanged: `spy-context-return-value' and +;; `spy-context-thrown-signal'. +(cl-defstruct (spy-context-return (:include spy-context)) + value) +(cl-defstruct (spy-context-thrown (:include spy-context)) + signal) (defun spy-on (symbol &optional keyword arg) "Create a spy (mock) for the function SYMBOL. @@ -1118,10 +1122,9 @@ responsibility to ensure ARG is a command." returned t) (buttercup--spy-calls-add this-spy-function - (make-spy-context :args args - :return-value return-value - :thrown-signal nil - :current-buffer (current-buffer))) + (make-spy-context-return :args args + :value return-value + :current-buffer (current-buffer))) return-value) (error ;; If returned is non-nil, then the error we caught @@ -1129,10 +1132,9 @@ responsibility to ensure ARG is a command." (unless returned (buttercup--spy-calls-add this-spy-function - (make-spy-context :args args - :return-value nil - :thrown-signal err - :current-buffer (current-buffer)))) + (make-spy-context-thrown :args args + :signal err + :current-buffer (current-buffer)))) ;; Regardless, we only caught this error in order to ;; record it, so we need to re-throw it. (signal (car err) (cdr err))))))) diff --git a/docs/writing-tests.md b/docs/writing-tests.md index 643303c..9307f43 100644 --- a/docs/writing-tests.md +++ b/docs/writing-tests.md @@ -600,9 +600,9 @@ Finally, `spy-calls-reset` clears all tracking for a spy. (expect (spy-calls-all 'set-foo) :to-equal - `(,(make-spy-context :current-buffer (current-buffer) - :args '(123) - :return-value nil)))) + `(,(make-spy-context-return :current-buffer (current-buffer) + :args '(123) + :value nil)))) (it "has a shortcut to the most recent call" (set-foo 123) @@ -610,9 +610,9 @@ Finally, `spy-calls-reset` clears all tracking for a spy. (expect (spy-calls-most-recent 'set-foo) :to-equal - (make-spy-context :current-buffer (current-buffer) - :args '(456 "baz") - :return-value nil))) + (make-spy-context-return :current-buffer (current-buffer) + :args '(456 "baz") + :value nil))) (it "has a shortcut to the first call" (set-foo 123) @@ -620,9 +620,9 @@ Finally, `spy-calls-reset` clears all tracking for a spy. (expect (spy-calls-first 'set-foo) :to-equal - (make-spy-context :current-buffer (current-buffer) - :args '(123) - :return-value nil))) + (make-spy-context-return :current-buffer (current-buffer) + :args '(123) + :value nil))) (it "tracks the return values and error signals of each call" ;; Set up `set-foo' so that it can either return a value or throw @@ -634,13 +634,28 @@ Finally, `spy-calls-reset` clears all tracking for a spy. (error "Value must not be negative")))) (expect (set-foo 1) :to-be 1) (expect (set-foo -1) :to-throw 'error) + (expect (spy-context-return-p (spy-calls-first 'set-foo))) (expect (spy-context-return-value (spy-calls-first 'set-foo)) :to-be 1) + ;; Trying to get the thrown signal from a call that didn't throw a + ;; signal is an error (expect (spy-context-thrown-signal + (spy-calls-first 'set-foo)) + :to-throw) + + (expect (spy-context-thrown-p (spy-calls-most-recent 'set-foo))) + (expect + (spy-context-thrown-signal + (spy-calls-most-recent 'set-foo)) + :to-equal '(error "Value must not be negative")) + ;; Trying to get the return value from a call that threw a signal + ;; raises an error + (expect + (spy-context-return-value (spy-calls-most-recent 'set-foo)) - :to-equal (list 'error "Value must not be negative"))) + :to-throw)) (it "can be reset" (set-foo 123) diff --git a/tests/test-buttercup.el b/tests/test-buttercup.el index c2e8317..6368202 100644 --- a/tests/test-buttercup.el +++ b/tests/test-buttercup.el @@ -933,7 +933,7 @@ (expect (test-function-throws-on-negative -5) :to-throw 'error) (expect (spy-context-thrown-signal (spy-calls-first 'test-function-throws-on-negative)) - :to-be nil) + :to-throw) (expect (spy-context-thrown-signal (spy-calls-most-recent 'test-function-throws-on-negative)) :to-equal '(error "x is less than zero"))))))