1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
|
;;;; testiere.lisp
(in-package #:testiere)
(defun stub-names (stubs) (mapcar 'first stubs))
(defun build-test (name spec)
(match spec
((list :fails inputs)
`(assert
(handler-case (progn (,name ,@inputs) nil)
(error (e) (declare (ignore e)) t))))
((list :signals inputs condition)
`(assert
(handler-case (progn (,name ,@inputs) nil)
(,condition (c) (declare (ignore c)) t)
(condition (c) (declare (ignore c)) nil))))
((list* :with-stubs redefs more-specs)
(let* ((assoc-vars
(loop for (stub-name . more) in redefs
collect (list stub-name (gensym (symbol-name stub-name)))))
(cache-binding-forms
(loop for (stub-name tmp-var) in assoc-vars
collect `(,tmp-var (fdefinition ',stub-name))))
(redef-forms
(loop for (stub-name lambda-list . body) in redefs
collect `(setf (fdefinition ',stub-name)
(function (lambda ,lambda-list ,@body)))))
(clean-up
(loop for (stub-name . more) in redefs
for binding = (assoc stub-name assoc-vars)
collect `(setf (fdefinition ',stub-name) ,(second binding)))))
`(let ,cache-binding-forms
(unwind-protect
(progn
,@redef-forms
,@(mapcar (lambda (s) (build-test name s)) more-specs))
,@clean-up))))
((list* :with-bindings bindings more-specs)
`(let ,bindings
,@(mapcar (lambda (s) (build-test name s)) more-specs)))
((list :afterp inputs thunk-test)
`(progn (,name ,@inputs)
(assert (funcall ,thunk-test))))
((list :outputp inputs output-test)
`(assert (funcall ,output-test (,name ,@inputs))))
((list comparator-function inputs expected-output)
`(assert (,comparator-function (,name ,@inputs) ,expected-output)))))
(defun extract-tests (name body)
(let ((end-pos (position :end body))
(start-pos (position :tests body)))
(when (and end-pos start-pos)
(let ((specs (subseq body (1+ start-pos) end-pos))
(before (subseq body 0 start-pos))
(after (nthcdr (1+ end-pos) body)))
(list (mapcar (lambda (spec) (build-test name spec)) specs)
(append before after))))))
(defmacro defun+ (name lambda-list &body body)
"Like regular DEFUN, but with embedded unit tests. If those tests
would fail, the function fails to be defined. "
(destructuring-bind (tests function-body) (extract-tests name body)
`(labels ((,name ,lambda-list ,@function-body))
(handler-case
(progn ,@tests
(defun ,name ,lambda-list ,@function-body))
(error (e)
(format t "~a~%Not defining ~a" e ',name))))))
(defun parse-defmethod (args)
(match args
((guard (list* qualifier lambda-list body)
(and (not (listp qualifier))
(listp lambda-list)))
(list (list qualifier lambda-list) body))
((list* lambda-list body)
(list (list lambda-list) body))
(_ (error "Malformed DEFMETHOD: ~a " args))))
(defmacro defmethod+ (name &rest args)
"Like regular DEFMETHOD, but with embedded unit tests. If those
test would fail, the method fails to be defined. "
(destructuring-bind (qual-and-lambda-list body) (parse-defmethod args)
(destructuring-bind (tests function-body) (extract-tests name body)
`(progn
(defmethod ,name ,@qual-and-lambda-list ,@function-body)
,@tests))))
|