Created
May 1, 2013 00:29
-
-
Save fjl/5492931 to your computer and use it in GitHub Desktop.
Quick experiment after a discussion at the Berlin Lispers Meetup
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(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