branch: elpa/buttercup commit 548df0db2cca32ca6c20f48f3f62c59e6627ba4f Author: Jorgen Schaefer <cont...@jorgenschaefer.de> Commit: Jorgen Schaefer <cont...@jorgenschaefer.de>
Spies: Other tracking properties. --- README.md | 113 ++++++++++++++++++++++++++++++++ buttercup-test.el | 190 ++++++++++++++++++++++++++++-------------------------- buttercup.el | 67 ++++++++++++++++--- 3 files changed, 270 insertions(+), 100 deletions(-) diff --git a/README.md b/README.md index 03c3f87..f56d3b2 100644 --- a/README.md +++ b/README.md @@ -452,6 +452,119 @@ will `signal` the specified value as an error. :to-throw 'error)))) ``` +### Other tracking properties + +Every call to a spy is tracked and exposed using the `spy-calls` +accessor. + +`spy-calls-any` returns `nil` if the spy has not been called at all, +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. + +Finally, `spy-calls-reset` clears all tracking for a spy. + +```Lisp +(describe "A spy" + (let (set-foo foo) + (before-each + (fset 'set-foo (lambda (val &rest ignored) + (setq foo val))) + (spy-on 'set-foo)) + + (it "tracks if it was called at all" + (expect (spy-calls-any 'set-foo) + :to-equal + nil) + + (set-foo 5) + + (expect (spy-calls-any 'set-foo) + :to-equal + t)) + + (it "tracks the number of times it was called" + (expect (spy-calls-count 'set-foo) + :to-equal + 0) + + (set-foo 2) + (set-foo 3) + + (expect (spy-calls-count 'set-foo) + :to-equal + 2)) + + (it "tracks the arguments of each call" + (set-foo 123) + (set-foo 456 "baz") + + (expect (spy-calls-args-for 'set-foo 0) + :to-equal + '(123)) + + (expect (spy-calls-args-for 'set-foo 1) + :to-equal + '(456 "baz"))) + + (it "tracks the arguments of all calls" + (set-foo 123) + (set-foo 456 "baz") + + (expect (spy-calls-all-args 'set-foo) + :to-equal + '((123) + (456 "baz")))) + + (it "can provide the context and arguments to all calls" + (set-foo 123) + + (expect (spy-calls-all 'set-foo) + :to-equal + `(,(make-spy-context :current-buffer (current-buffer) + :args '(123) + :return-value nil)))) + + (it "has a shortcut to the most recent call" + (set-foo 123) + (set-foo 456 "baz") + + (expect (spy-calls-most-recent 'set-foo) + :to-equal + (make-spy-context :current-buffer (current-buffer) + :args '(456 "baz") + :return-value nil))) + + (it "has a shortcut to the first call" + (set-foo 123) + (set-foo 456 "baz") + + (expect (spy-calls-first 'set-foo) + :to-equal + (make-spy-context :current-buffer (current-buffer) + :args '(123) + :return-value nil))) + + (it "can be reset" + (set-foo 123) + (set-foo 456 "baz") + + (expect (spy-calls-any 'set-foo) + :to-be + t) + + (spy-calls-reset 'set-foo) + + (expect (spy-calls-any 'set-foo) + :to-be + nil)))) +``` + ## Test Runners diff --git a/buttercup-test.el b/buttercup-test.el index fc8d10c..c361a0b 100644 --- a/buttercup-test.el +++ b/buttercup-test.el @@ -328,103 +328,113 @@ ;;;;;;;;; ;;; Spies -(defun test-function (a b) - (+ a b)) - -(describe "The `spy-on' function" - (it "replaces a symbol's function slot" - (spy-on 'test-function) - (expect (test-function 1 2) :to-be nil)) - - (it "restores the old value after a spec run" - (expect (test-function 1 2) :to-equal 3))) - -(describe "The :to-have-been-called matcher" - (before-each - (spy-on 'test-function)) - - (it "returns false if the spy was not called" - (expect (buttercup--apply-matcher :to-have-been-called '(test-function)) - :to-be - nil)) - - (it "returns true if the spy was called at all" - (test-function 1 2 3) - (expect (buttercup--apply-matcher :to-have-been-called '(test-function)) - :to-be - t))) - -(describe "The :to-have-been-called-with matcher" - (before-each - (spy-on 'test-function)) - - (it "returns false if the spy was not called at all" - (expect (buttercup--apply-matcher - :to-have-been-called-with '(test-function 1 2 3)) - :to-be - nil)) - - (it "returns false if the spy was called with different arguments" - (test-function 3 2 1) - (expect (buttercup--apply-matcher - :to-have-been-called-with '(test-function 1 2 3)) - :to-be - nil)) - - (it "returns true if the spy was called with those arguments" - (test-function 1 2 3) - (expect (buttercup--apply-matcher - :to-have-been-called-with '(test-function 1 2 3)) - :to-be - t))) - -(describe "The :and-call-through keyword functionality" - (before-each - (spy-on 'test-function :and-call-through)) - - (it "tracks calls to the function" - (test-function 42 23) - - (expect 'test-function :to-have-been-called)) - - (it "passes the arguments to the original function" - (expect (test-function 2 3) - :to-equal - 5))) +(describe "The Spy " + (let (test-function) + (before-each + (fset 'test-function (lambda (a b) + (+ a b)))) + + (describe "`spy-on' function" + (it "replaces a symbol's function slot" + (spy-on 'test-function) + (expect (test-function 1 2) :to-be nil)) + + (it "restores the old value after a spec run" + (expect (test-function 1 2) :to-equal 3))) + + (describe ":to-have-been-called matcher" + (before-each + (spy-on 'test-function)) + + (it "returns false if the spy was not called" + (expect (buttercup--apply-matcher :to-have-been-called + '(test-function)) + :to-be + nil)) + + (it "returns true if the spy was called at all" + (test-function 1 2 3) + (expect (buttercup--apply-matcher :to-have-been-called + '(test-function)) + :to-be + t))) + + (describe ":to-have-been-called-with matcher" + (before-each + (spy-on 'test-function)) + + (it "returns false if the spy was not called at all" + (expect (buttercup--apply-matcher + :to-have-been-called-with '(test-function 1 2 3)) + :to-be + nil)) + + (it "returns false if the spy was called with different arguments" + (test-function 3 2 1) + (expect (buttercup--apply-matcher + :to-have-been-called-with '(test-function 1 2 3)) + :to-be + nil)) + + (it "returns true if the spy was called with those arguments" + (test-function 1 2 3) + (expect (buttercup--apply-matcher + :to-have-been-called-with '(test-function 1 2 3)) + :to-be + t))) + + (describe ":and-call-through keyword functionality" + (before-each + (spy-on 'test-function :and-call-through)) + + (it "tracks calls to the function" + (test-function 42 23) + + (expect 'test-function :to-have-been-called)) + + (it "passes the arguments to the original function" + (expect (test-function 2 3) + :to-equal + 5))) -(describe "The :and-return-value keyword functionality" - (before-each - (spy-on 'test-function :and-return-value 23)) + (describe ":and-return-value keyword functionality" + (before-each + (spy-on 'test-function :and-return-value 23)) - (it "tracks calls to the function" - (test-function 42 23) + (it "tracks calls to the function" + (test-function 42 23) - (expect 'test-function :to-have-been-called)) + (expect 'test-function :to-have-been-called)) - (it "returns the specified value" - (expect (test-function 2 3) - :to-equal - 23))) + (it "returns the specified value" + (expect (test-function 2 3) + :to-equal + 23))) -(describe "The :and-call-fake keyword functionality" - (before-each - (spy-on 'test-function :and-call-fake (lambda (a b) 1001))) + (describe ":and-call-fake keyword functionality" + (before-each + (spy-on 'test-function :and-call-fake (lambda (a b) 1001))) - (it "tracks calls to the function" - (test-function 42 23) + (it "tracks calls to the function" + (test-function 42 23) - (expect 'test-function :to-have-been-called)) + (expect 'test-function :to-have-been-called)) - (it "returns the specified value" - (expect (test-function 2 3) - :to-equal - 1001))) + (it "returns the specified value" + (expect (test-function 2 3) + :to-equal + 1001))) -(describe "The :and-throw-error keyword functionality" - (before-each - (spy-on 'test-function :and-throw-error 'error)) + (describe ":and-throw-error keyword functionality" + (before-each + (spy-on 'test-function :and-throw-error 'error)) - (it "throws an error when called" - (expect (lambda () (test-function 1 2)) - :to-throw - 'error "Stubbed error"))) + (it "throws an error when called" + (expect (lambda () (test-function 1 2)) + :to-throw + 'error "Stubbed error"))) + + + + ) + ) diff --git a/buttercup.el b/buttercup.el index d02bc07..0885be6 100644 --- a/buttercup.el +++ b/buttercup.el @@ -368,29 +368,42 @@ A disabled spec is not run." (defvar buttercup--spy-calls (make-hash-table :test 'eq :weakness 'key)) +(cl-defstruct spy-context + args + return-value + current-buffer) + (defun spy-on (symbol &optional keyword arg) (let ((old-value (symbol-function symbol)) (new-value nil)) (cond ((eq keyword :and-call-through) (setq new-value (lambda (&rest args) - (buttercup--spy-add-call new-value args) - (apply old-value args)))) + (let ((return-value (apply old-value args))) + (buttercup--spy-add-call new-value + args + return-value) + return-value)))) ((eq keyword :and-return-value) (setq new-value (lambda (&rest args) - (buttercup--spy-add-call new-value args) + (buttercup--spy-add-call new-value + args + arg) arg))) ((eq keyword :and-call-fake) (setq new-value (lambda (&rest args) - (buttercup--spy-add-call new-value args) - (apply arg args)))) + (let ((return-value (apply arg args))) + (buttercup--spy-add-call new-value + args + return-value) + return-value)))) ((eq keyword :and-throw-error) (setq new-value (lambda (&rest args) - (buttercup--spy-add-call new-value args) + (buttercup--spy-add-call new-value args nil) (signal arg "Stubbed error")))) ((not keyword) (setq new-value (lambda (&rest args) - (buttercup--spy-add-call new-value args) + (buttercup--spy-add-call new-value args nil) nil)))) (fset symbol new-value) (buttercup--add-cleanup (lambda () (fset symbol old-value))))) @@ -402,10 +415,12 @@ A disabled spec is not run." (append buttercup--cleanup-forms (list function))))) -(defun buttercup--spy-add-call (spy args) +(defun buttercup--spy-add-call (spy args return-value) (puthash spy (append (buttercup--spy-calls spy) - (list args)) + (list (make-spy-context :current-buffer (current-buffer) + :args args + :return-value return-value))) buttercup--spy-calls)) (defun buttercup--spy-calls (spy) @@ -423,11 +438,43 @@ A disabled spec is not run." (let* ((spy (if (symbolp spy) (symbol-function spy) spy)) - (calls (buttercup--spy-calls spy))) + (calls (mapcar 'spy-context-args (buttercup--spy-calls spy)))) (if (member args calls) t nil))) +(defun spy-calls-any (spy) + (if (buttercup--spy-calls (symbol-function spy)) + t + nil)) + +(defun spy-calls-count (spy) + (length (buttercup--spy-calls (symbol-function spy)))) + +(defun spy-calls-args-for (spy index) + (let ((context (elt (buttercup--spy-calls (symbol-function spy)) + index))) + (if context + (spy-context-args context) + nil))) + +(defun spy-calls-all-args (spy) + (mapcar 'spy-context-args (buttercup--spy-calls (symbol-function spy)))) + +(defun spy-calls-all (spy) + (buttercup--spy-calls (symbol-function spy))) + +(defun spy-calls-most-recent (spy) + (car (last (buttercup--spy-calls (symbol-function spy))))) + +(defun spy-calls-first (spy) + (car (buttercup--spy-calls (symbol-function spy)))) + +(defun spy-calls-reset (spy) + (puthash (symbol-function spy) + nil + buttercup--spy-calls)) + ;; (let* ((buttercup--descriptions (cons description ;; buttercup--descriptions)) ;; (debugger (lambda (&rest args)