Skip to content

Instantly share code, notes, and snippets.

@fjl
Created May 1, 2013 00:29
Show Gist options
  • Save fjl/5492931 to your computer and use it in GitHub Desktop.
Save fjl/5492931 to your computer and use it in GitHub Desktop.
Quick experiment after a discussion at the Berlin Lispers Meetup
(defpackage #:mock
(:use :cl)
(:export
#:mock-labels
#:call-mocked-function
#:mock-call-args
#:mock-call-return-values
#:mocked-function-called-p
#:mocked-function-calls))
(in-package #:mock)
(defstruct mock-state
(calls (make-hash-table :test 'eq)))
(defstruct mock-call
args return-values)
(defvar *mock-state-chain* nil)
(defun binding-name (binding)
(etypecase binding
(list (car binding))
(symbol binding)))
(defun make-mock-lambda (binding orig-definition state)
(let* ((args (gensym "ARGS"))
(results (gensym "RESULTS"))
(name (binding-name binding))
(body (etypecase binding
(list `(apply (lambda ,(second binding) ,@(cddr binding))
,args))
(symbol '(call-mocked-function)))))
`(lambda (&rest ,args)
(macrolet ((call-mocked-function (&rest changed-args)
(if (null changed-args)
`(apply ,',orig-definition ,',args)
`(funcall ,',orig-definition ,@changed-args))))
(let ((,results (multiple-value-list ,body)))
(push (make-mock-call :args ,args :return-values ,results)
(gethash ',name (mock-state-calls ,state)))
(values-list ,results))))))
(defmacro mock-labels (bindings &body body)
(let ((temps (loop for b in bindings collect (gensym)))
(state (gensym "MOCK-STATE")))
`(let* ((,state (make-mock-state))
(*mock-state-chain* (cons ,state *mock-state-chain*))
,@temps)
(unwind-protect
(progn
,@(loop for binding in bindings
for temp in temps
for name = (binding-name binding)
collect `(setf ,temp (fdefinition ',name))
collect `(setf (fdefinition ',name)
,(make-mock-lambda binding temp state)))
,@body)
,@(loop for binding in bindings
for temp in temps
for name = (binding-name binding)
collect `(setf (fdefinition ',name) ,temp))))))
(defmacro call-mocked-function (&rest args)
(declare (ignore args))
(error "~A used outside of ~A definition"
'call-mocked-function 'mock-labels))
(defun mocked-function-calls (name)
(loop for s in *mock-state-chain*
thereis (gethash name (mock-state-calls s))))
(defun mocked-function-called-p (name)
(not (null (mocked-function-calls name))))
;; (defun foobar (x y)
;; (+ x y))
;; (defun mock-test-1 ()
;; (mock-labels ((foobar (x y) (call-mocked-function x (1+ y))))
;; (values (foobar 2 3)
;; (foobar 1 1)
;; (mocked-function-calls 'foobar))))
;; (defun mock-test-2 ()
;; (mock-labels (foobar)
;; (values (foobar 2 3)
;; (foobar 1 1)
;; (mocked-function-calls 'foobar))))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment