branch: elpa/buttercup commit 9b87afc57f5131259957a8bfd5d4bf53917e4beb Merge: c720cef 8d91893 Author: Ola Nilsson <ola.nils...@gmail.com> Commit: Ola Nilsson <ola.nils...@gmail.com>
Merge branch 'snogge/spy-context-tweaks' * spy-context-tweaks: Optimize spy-calls-count-errors and spy-calls-count-returned Tweak the spy-contexts to be more backward compatible --- buttercup.el | 64 ++++++++++++++++++++++++++++++++++++++------------- docs/writing-tests.md | 50 +++++++++++++++++++++++----------------- 2 files changed, 77 insertions(+), 37 deletions(-) diff --git a/buttercup.el b/buttercup.el index ffe5b42..5a10099 100644 --- a/buttercup.el +++ b/buttercup.el @@ -1022,16 +1022,50 @@ DESCRIPTION has the same meaning as in `xit'. FUNCTION is ignored." :weakness 'key) "A mapping of currently-defined spies to their contexts.") -(cl-defstruct spy-context +;; The base struct has no constructor so a factory function +;; `make-spy-context' masquerading as a constructor can be defined +;; later. +(cl-defstruct (spy-context (:constructor nil)) 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)) +(cl-defstruct (spy-context-return (:include spy-context) + (:conc-name spy-context--return-)) value) -(cl-defstruct (spy-context-thrown (:include spy-context)) +(cl-defstruct (spy-context-thrown (:include spy-context) + (:conc-name spy-context--thrown-)) signal) +(cl-defun make-spy-context (&key args current-buffer + (return-value nil has-return-value) + (thrown-signal nil has-thrown-signal)) + "Constructor for objects of type spy-context. +ARGS is the argument list of the called function. +CURRENT-BUFFER is the buffer that was current when the spy was called. +RETURN-VALUE is the returned value, if any. +THROWN-SIGNAL is the signal raised by the function, if any. +Only one of RETURN-VALUE and THROWN-SIGNAL may be given. Giving +none of them is equivalent to `:return-value nil'." + (cond + ((and has-return-value has-thrown-signal) + (error "Only one of :return-value and :thrown-signal may be given")) + (has-thrown-signal (make-spy-context-thrown :args args + :current-buffer current-buffer + :signal thrown-signal)) + (t (make-spy-context-return :args args + :current-buffer current-buffer + :value return-value)))) + +(defun spy-context-return-value (context) + "Access slot \"return-value\" of `spy-context' struct CONTEXT." + (unless (spy-context-return-p context) + (error "Not a returning context")) + (spy-context--return-value context)) + +(defun spy-context-thrown-signal (context) + "Access slot \"thrown-signal\" of `spy-context' struct CONTEXT." + (unless (spy-context-thrown-p context) + (error "Not a signal-raising context")) + (spy-context--thrown-signal context)) + (defun spy-on (symbol &optional keyword arg) "Create a spy (mock) for the function SYMBOL. @@ -1122,9 +1156,9 @@ responsibility to ensure ARG is a command." returned t) (buttercup--spy-calls-add this-spy-function - (make-spy-context-return :args args - :value return-value - :current-buffer (current-buffer))) + (make-spy-context :args args + :return-value return-value + :current-buffer (current-buffer))) return-value) (error ;; If returned is non-nil, then the error we caught @@ -1132,9 +1166,9 @@ responsibility to ensure ARG is a command." (unless returned (buttercup--spy-calls-add this-spy-function - (make-spy-context-thrown :args args - :signal err - :current-buffer (current-buffer)))) + (make-spy-context :args args + :thrown-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))))))) @@ -1197,13 +1231,11 @@ responsibility to ensure ARG is a command." (defun spy-calls-count-returned (spy) "Return the number of times SPY has been called successfully so far." - (length (cl-remove-if-not 'spy-context-return-p - (spy-calls-all spy)))) + (cl-count-if 'spy-context-return-p (spy-calls-all spy))) (defun spy-calls-count-errors (spy) "Return the number of times SPY has been called and thrown errors so far." - (length (cl-remove-if-not 'spy-context-thrown-p - (spy-calls-all spy)))) + (cl-count-if 'spy-context-thrown-p (spy-calls-all spy))) (defun spy-calls-args-for (spy index) "Return the context of the INDEXth call to SPY." diff --git a/docs/writing-tests.md b/docs/writing-tests.md index 94d9129..9d6cc54 100644 --- a/docs/writing-tests.md +++ b/docs/writing-tests.md @@ -532,17 +532,17 @@ and then `t` once at least one call happens. `spy-calls-count` returns the number of times the spy was called. `spy-calls-args-for` returns the arguments passed to a given call (by index). `spy-calls-all-args` returns the arguments to all calls. `spy-calls-all` returns the -current buffer and arguments passed to all calls. -`spy-calls-most-recent` returns the current buffer and arguments for -the most recent call. `spy-calls-first` returns the current buffer and -arguments for the first call. - -Each spy context is a struct with 3 slots. A successful function call -is represented by a `spy-context-return` struct with slots `args`, -`current-buffer`, and `value`. A function call the signalled an error -is represented by a `spy-context-thrown` struct with slots `args`, -`current-buffer`, and `signal`. See the examples below for accessing -these slots. +context (current buffer, arguments passed and return status) of all +calls. `spy-calls-most-recent` returns the context of the most recent +call. `spy-calls-first` returns the context for the first call. + +Contexts are represented by instances of the `spy-context` struct with +the slots `args`, `current-buffer`, `return-value` and +`thrown-signal`. The `return-value` and `thrown-signal` slots +represent the return status. Calling `spy-context-return-value` for a +context representing a raised signal (or vice versa) will raise an +error. Test the context type with `spy-context-return-p` and +`spy-context-thrown-p`. Finally, `spy-calls-reset` clears all tracking for a spy. @@ -603,9 +603,8 @@ Finally, `spy-calls-reset` clears all tracking for a spy. (expect (spy-calls-all 'set-foo) :to-equal - `(,(make-spy-context-return :current-buffer (current-buffer) - :args '(123) - :value nil)))) + `(,(make-spy-context :current-buffer (current-buffer) + :args '(123))))) (it "has a shortcut to the most recent call" (set-foo 123) @@ -613,9 +612,8 @@ Finally, `spy-calls-reset` clears all tracking for a spy. (expect (spy-calls-most-recent 'set-foo) :to-equal - (make-spy-context-return :current-buffer (current-buffer) - :args '(456 "baz") - :value nil))) + (make-spy-context :current-buffer (current-buffer) + :args '(456 "baz")))) (it "has a shortcut to the first call" (set-foo 123) @@ -623,9 +621,8 @@ Finally, `spy-calls-reset` clears all tracking for a spy. (expect (spy-calls-first 'set-foo) :to-equal - (make-spy-context-return :current-buffer (current-buffer) - :args '(123) - :value nil))) + (make-spy-context :current-buffer (current-buffer) + :args '(123)))) (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 @@ -658,7 +655,18 @@ Finally, `spy-calls-reset` clears all tracking for a spy. (expect (spy-context-return-value (spy-calls-most-recent 'set-foo)) - :to-throw)) + :to-throw) + ;; Use :return-value and :thrown-signal to create matching spy-contexts + (expect + (spy-calls-all 'set-foo) + :to-equal + (list + (make-spy-context :args '(1) + :current-buffer (current-buffer) + :return-value 1) + (make-spy-context :args '(-1) + :current-buffer (current-buffer) + :thrown-signal '(error "Value must not be negative"))))) (it "counts the number of successful and failed calls" ;; Set up `set-foo' so that it can either return a value or throw