|
0 |
;-*-syntax:COMMON-LISP;Package:(RT :use "COMMON-LISP" :colon-mode :external)-*-
|
|
1 |
|
0 | 2 |
#|----------------------------------------------------------------------------|
|
1 | 3 |
| Copyright 1990 by the Massachusetts Institute of Technology, Cambridge MA. |
|
2 | 4 |
| |
|
|
26 | 28 |
#:rem-all-tests #:rem-test)
|
27 | 29 |
(:documentation "The MIT regression tester with pfdietz's modifications"))
|
28 | 30 |
|
|
31 |
;;This was the December 19, 1990 version of the regression tester, but
|
|
32 |
;;has since been modified.
|
|
33 |
|
29 | 34 |
(in-package :regression-test)
|
|
35 |
|
|
36 |
(declaim (ftype (function (t) t) get-entry expanded-eval do-entries))
|
|
37 |
(declaim (type list *entries*))
|
|
38 |
(declaim (ftype (function (t &rest t) t) report-error))
|
|
39 |
(declaim (ftype (function (t &optional t) t) do-entry))
|
30 | 40 |
|
31 | 41 |
(defvar *test* nil "Current test name")
|
32 | 42 |
(defvar *do-tests-when-defined* nil)
|
33 | |
(defvar *entries* '(nil) "Test database")
|
|
43 |
(defvar *entries* '(nil) "Test database. Has a leading dummy cell that does not contain an entry.")
|
|
44 |
(defvar *entries-tail* *entries* "Tail of the *entries* list")
|
|
45 |
(defvar *entries-table* (make-hash-table :test #'equal)
|
|
46 |
"Map the names of entries to the cons cell in *entries* that precedes the one whose car is the entry.")
|
34 | 47 |
(defvar *in-test* nil "Used by TEST")
|
35 | 48 |
(defvar *debug* nil "For debugging")
|
36 | |
(defvar *catch-errors* t
|
37 | |
"When true, causes errors in a test to be caught.")
|
|
49 |
(defvar *catch-errors* t "When true, causes errors in a test to be caught.")
|
38 | 50 |
(defvar *print-circle-on-failure* nil
|
39 | 51 |
"Failure reports are printed with *PRINT-CIRCLE* bound to this value.")
|
40 | |
(defvar *compile-tests* nil
|
41 | |
"When true, compile the tests before running them.")
|
|
52 |
|
|
53 |
(defvar *compile-tests* nil "When true, compile the tests before running them.")
|
|
54 |
(defvar *expanded-eval* nil "When true, convert the tests into a form that is less likely to have compiler optimizations.")
|
42 | 55 |
(defvar *optimization-settings* '((safety 3)))
|
|
56 |
|
43 | 57 |
(defvar *expected-failures* nil
|
44 | 58 |
"A list of test names that are expected to fail.")
|
45 | 59 |
|
46 | |
(defstruct (entry (:conc-name nil)
|
47 | |
(:type list))
|
48 | |
pend name form)
|
49 | |
|
50 | |
(defmacro vals (entry) `(cdddr ,entry))
|
51 | |
|
52 | |
(defmacro defn (entry) `(cdr ,entry))
|
|
60 |
(defvar *notes* (make-hash-table :test 'equal)
|
|
61 |
"A mapping from names of notes to note objects.")
|
|
62 |
|
|
63 |
(defstruct (entry (:conc-name nil))
|
|
64 |
pend name props form vals)
|
|
65 |
|
|
66 |
;;; Note objects are used to attach information to tests.
|
|
67 |
;;; A typical use is to mark tests that depend on a particular
|
|
68 |
;;; part of a set of requirements, or a particular interpretation
|
|
69 |
;;; of the requirements.
|
|
70 |
|
|
71 |
(defstruct note
|
|
72 |
name
|
|
73 |
contents
|
|
74 |
disabled ;; When true, tests with this note are considered inactive
|
|
75 |
)
|
|
76 |
|
|
77 |
;; (defmacro vals (entry) `(cdddr ,entry))
|
|
78 |
|
|
79 |
(defmacro defn (entry)
|
|
80 |
(let ((var (gensym)))
|
|
81 |
`(let ((,var ,entry))
|
|
82 |
(list* (name ,var) (form ,var) (vals ,var)))))
|
|
83 |
|
|
84 |
(defun entry-notes (entry)
|
|
85 |
(let* ((props (props entry))
|
|
86 |
(notes (getf props :notes)))
|
|
87 |
(if (listp notes)
|
|
88 |
notes
|
|
89 |
(list notes))))
|
|
90 |
|
|
91 |
(defun has-disabled-note (entry)
|
|
92 |
(let ((notes (entry-notes entry)))
|
|
93 |
(loop for n in notes
|
|
94 |
for note = (if (note-p n) n
|
|
95 |
(gethash n *notes*))
|
|
96 |
thereis (and note (note-disabled note)))))
|
53 | 97 |
|
54 | 98 |
(defun pending-tests ()
|
55 | |
(do ((l (cdr *entries*) (cdr l))
|
56 | |
(r nil))
|
57 | |
((null l) (nreverse r))
|
58 | |
(when (pend (car l))
|
59 | |
(push (name (car l)) r))))
|
|
99 |
(loop for entry in (cdr *entries*)
|
|
100 |
when (and (pend entry) (not (has-disabled-note entry)))
|
|
101 |
collect (name entry)))
|
60 | 102 |
|
61 | 103 |
(defun rem-all-tests ()
|
62 | 104 |
(setq *entries* (list nil))
|
|
105 |
(setq *entries-tail* *entries*)
|
|
106 |
(clrhash *entries-table*)
|
63 | 107 |
nil)
|
64 | 108 |
|
65 | 109 |
(defun rem-test (&optional (name *test*))
|
66 | |
(do ((l *entries* (cdr l)))
|
67 | |
((null (cdr l)) nil)
|
68 | |
(when (equal (name (cadr l)) name)
|
69 | |
(setf (cdr l) (cddr l))
|
70 | |
(return name))))
|
|
110 |
(let ((pred (gethash name *entries-table*)))
|
|
111 |
(when pred
|
|
112 |
(if (null (cddr pred))
|
|
113 |
(setq *entries-tail* pred)
|
|
114 |
(setf (gethash (name (caddr pred)) *entries-table*) pred))
|
|
115 |
(setf (cdr pred) (cddr pred))
|
|
116 |
(remhash name *entries-table*)
|
|
117 |
name)))
|
71 | 118 |
|
72 | 119 |
(defun get-test (&optional (name *test*))
|
73 | 120 |
(defn (get-entry name)))
|
74 | 121 |
|
75 | 122 |
(defun get-entry (name)
|
76 | |
(let ((entry (find name (cdr *entries*)
|
77 | |
:key #'name
|
78 | |
:test #'equal)))
|
|
123 |
(let ((entry ;; (find name (the list (cdr *entries*))
|
|
124 |
;; :key #'name :test #'equal)
|
|
125 |
(cadr (gethash name *entries-table*))
|
|
126 |
))
|
79 | 127 |
(when (null entry)
|
80 | 128 |
(report-error t
|
81 | 129 |
"~%No test with name ~:@(~S~)."
|
82 | 130 |
name))
|
83 | 131 |
entry))
|
84 | 132 |
|
85 | |
(defmacro deftest (name form &rest values)
|
86 | |
`(add-entry '(t ,name ,form .,values)))
|
|
133 |
(defmacro deftest (name &rest body)
|
|
134 |
(let* ((p body)
|
|
135 |
(properties
|
|
136 |
(loop while (keywordp (first p))
|
|
137 |
unless (cadr p)
|
|
138 |
do (error "Poorly formed deftest: ~A~%"
|
|
139 |
(list* 'deftest name body))
|
|
140 |
append (list (pop p) (pop p))))
|
|
141 |
(form (pop p))
|
|
142 |
(vals p))
|
|
143 |
`(add-entry (make-entry :pend t
|
|
144 |
:name ',name
|
|
145 |
:props ',properties
|
|
146 |
:form ',form
|
|
147 |
:vals ',vals))))
|
87 | 148 |
|
88 | 149 |
(defun add-entry (entry)
|
89 | |
(setq entry (copy-list entry))
|
90 | |
(do ((l *entries* (cdr l))) (nil)
|
91 | |
(when (null (cdr l))
|
92 | |
(setf (cdr l) (list entry))
|
93 | |
(return nil))
|
94 | |
(when (equal (name (cadr l))
|
95 | |
(name entry))
|
96 | |
(setf (cadr l) entry)
|
|
150 |
(setq entry (copy-entry entry))
|
|
151 |
(let* ((pred (gethash (name entry) *entries-table*)))
|
|
152 |
(cond
|
|
153 |
(pred
|
|
154 |
(setf (cadr pred) entry)
|
97 | 155 |
(report-error nil
|
98 | 156 |
"Redefining test ~:@(~S~)"
|
99 | |
(name entry))
|
100 | |
(return nil)))
|
|
157 |
(name entry)))
|
|
158 |
(t
|
|
159 |
(setf (gethash (name entry) *entries-table*) *entries-tail*)
|
|
160 |
(setf (cdr *entries-tail*) (cons entry nil))
|
|
161 |
(setf *entries-tail* (cdr *entries-tail*))
|
|
162 |
)))
|
101 | 163 |
(when *do-tests-when-defined*
|
102 | 164 |
(do-entry entry))
|
103 | 165 |
(setq *test* (name entry)))
|
|
107 | 169 |
(apply #'format t args)
|
108 | 170 |
(if error? (throw '*debug* nil)))
|
109 | 171 |
(error? (apply #'error args))
|
110 | |
(t (apply #'warn args))))
|
|
172 |
(t (apply #'warn args)))
|
|
173 |
nil)
|
111 | 174 |
|
112 | 175 |
(defun do-test (&optional (name *test*))
|
113 | |
(do-entry (get-entry name)))
|
|
176 |
#-sbcl (do-entry (get-entry name))
|
|
177 |
#+sbcl (handler-bind ((sb-ext:code-deletion-note #'muffle-warning))
|
|
178 |
(do-entry (get-entry name))))
|
|
179 |
|
|
180 |
(defun my-aref (a &rest args)
|
|
181 |
(apply #'aref a args))
|
|
182 |
|
|
183 |
(defun my-row-major-aref (a index)
|
|
184 |
(row-major-aref a index))
|
114 | 185 |
|
115 | 186 |
(defun equalp-with-case (x y)
|
116 | |
"Like EQUALP, but doesn't do case conversion of characters."
|
|
187 |
"Like EQUALP, but doesn't do case conversion of characters.
|
|
188 |
Currently doesn't work on arrays of dimension > 2."
|
117 | 189 |
(cond
|
118 | 190 |
((eq x y) t)
|
119 | 191 |
((consp x)
|
|
122 | 194 |
(equalp-with-case (cdr x) (cdr y))))
|
123 | 195 |
((and (typep x 'array)
|
124 | 196 |
(= (array-rank x) 0))
|
125 | |
(equalp-with-case (aref x) (aref y)))
|
|
197 |
(equalp-with-case (my-aref x) (my-aref y)))
|
126 | 198 |
((typep x 'vector)
|
127 | 199 |
(and (typep y 'vector)
|
128 | 200 |
(let ((x-len (length x))
|
129 | 201 |
(y-len (length y)))
|
130 | 202 |
(and (eql x-len y-len)
|
131 | 203 |
(loop
|
132 | |
for e1 across x
|
133 | |
for e2 across y
|
|
204 |
for i from 0 below x-len
|
|
205 |
for e1 = (my-aref x i)
|
|
206 |
for e2 = (my-aref y i)
|
134 | 207 |
always (equalp-with-case e1 e2))))))
|
135 | 208 |
((and (typep x 'array)
|
136 | 209 |
(typep y 'array)
|
137 | 210 |
(not (equal (array-dimensions x)
|
138 | 211 |
(array-dimensions y))))
|
139 | 212 |
nil)
|
|
213 |
|
140 | 214 |
((typep x 'array)
|
141 | 215 |
(and (typep y 'array)
|
142 | 216 |
(let ((size (array-total-size x)))
|
143 | 217 |
(loop for i from 0 below size
|
144 | |
always (equalp-with-case (row-major-aref x i)
|
145 | |
(row-major-aref y i))))))
|
|
218 |
always (equalp-with-case (my-row-major-aref x i)
|
|
219 |
(my-row-major-aref y i))))))
|
|
220 |
|
146 | 221 |
(t (eql x y))))
|
147 | 222 |
|
148 | 223 |
(defun do-entry (entry &optional
|
|
160 | 235 |
(setf r
|
161 | 236 |
(flet ((%do
|
162 | 237 |
()
|
163 | |
(if *compile-tests*
|
164 | |
(multiple-value-list
|
165 | |
(funcall (compile
|
166 | |
nil
|
167 | |
`(lambda ()
|
168 | |
(declare
|
169 | |
(optimize ,@*optimization-settings*))
|
170 | |
,(form entry)))))
|
|
238 |
(cond
|
|
239 |
(*compile-tests*
|
171 | 240 |
(multiple-value-list
|
172 | |
(eval (form entry))))))
|
|
241 |
(funcall (compile
|
|
242 |
nil
|
|
243 |
`(lambda ()
|
|
244 |
(declare
|
|
245 |
(optimize ,@*optimization-settings*))
|
|
246 |
,(form entry))))))
|
|
247 |
(*expanded-eval*
|
|
248 |
(multiple-value-list
|
|
249 |
(expanded-eval (form entry))))
|
|
250 |
(t
|
|
251 |
(multiple-value-list
|
|
252 |
(eval (form entry)))))))
|
173 | 253 |
(if *catch-errors*
|
174 | 254 |
(handler-bind
|
175 | |
((style-warning #'muffle-warning)
|
176 | |
(error #'(lambda (c)
|
177 | |
(setf aborted t)
|
178 | |
(setf r (list c))
|
179 | |
(return-from aborted nil))))
|
180 | |
(%do))
|
|
255 |
(#-ecl (style-warning #'muffle-warning)
|
|
256 |
(error #'(lambda (c)
|
|
257 |
(setf aborted t)
|
|
258 |
(setf r (list c))
|
|
259 |
(return-from aborted nil))))
|
|
260 |
(%do))
|
181 | 261 |
(%do)))))
|
182 | 262 |
|
183 | 263 |
(setf (pend entry)
|
|
193 | 273 |
*test* (form entry)
|
194 | 274 |
(length (vals entry))
|
195 | 275 |
(vals entry))
|
196 | |
(format s "Actual value~P: ~
|
|
276 |
(handler-case
|
|
277 |
(let ((st (format nil "Actual value~P: ~
|
197 | 278 |
~{~S~^~%~15t~}.~%"
|
198 | |
(length r) r)))))
|
|
279 |
(length r) r)))
|
|
280 |
(format s "~A" st))
|
|
281 |
(error () (format s "Actual value: #<error during printing>~%")
|
|
282 |
))
|
|
283 |
(finish-output s)
|
|
284 |
))))
|
199 | 285 |
(when (not (pend entry)) *test*))
|
|
286 |
|
|
287 |
(defun expanded-eval (form)
|
|
288 |
"Split off top level of a form and eval separately. This reduces the chance that
|
|
289 |
compiler optimizations will fold away runtime computation."
|
|
290 |
(if (not (consp form))
|
|
291 |
(eval form)
|
|
292 |
(let ((op (car form)))
|
|
293 |
(cond
|
|
294 |
((eq op 'let)
|
|
295 |
(let* ((bindings (loop for b in (cadr form)
|
|
296 |
collect (if (consp b) b (list b nil))))
|
|
297 |
(vars (mapcar #'car bindings))
|
|
298 |
(binding-forms (mapcar #'cadr bindings)))
|
|
299 |
(apply
|
|
300 |
(the function
|
|
301 |
(eval `(lambda ,vars ,@(cddr form))))
|
|
302 |
(mapcar #'eval binding-forms))))
|
|
303 |
((and (eq op 'let*) (cadr form))
|
|
304 |
(let* ((bindings (loop for b in (cadr form)
|
|
305 |
collect (if (consp b) b (list b nil))))
|
|
306 |
(vars (mapcar #'car bindings))
|
|
307 |
(binding-forms (mapcar #'cadr bindings)))
|
|
308 |
(funcall
|
|
309 |
(the function
|
|
310 |
(eval `(lambda (,(car vars) &aux ,@(cdr bindings)) ,@(cddr form))))
|
|
311 |
(eval (car binding-forms)))))
|
|
312 |
((eq op 'progn)
|
|
313 |
(loop for e on (cdr form)
|
|
314 |
do (if (null (cdr e)) (return (eval (car e)))
|
|
315 |
(eval (car e)))))
|
|
316 |
((and (symbolp op) (fboundp op)
|
|
317 |
(not (macro-function op))
|
|
318 |
(not (special-operator-p op)))
|
|
319 |
(apply (symbol-function op)
|
|
320 |
(mapcar #'eval (cdr form))))
|
|
321 |
(t (eval form))))))
|
200 | 322 |
|
201 | 323 |
(defun continue-testing ()
|
202 | 324 |
(if *in-test*
|
|
213 | 335 |
(stream out :direction :output)
|
214 | 336 |
(do-entries stream))))
|
215 | 337 |
|
216 | |
(defun do-entries (s)
|
|
338 |
(defun do-entries* (s)
|
217 | 339 |
(format s "~&Doing ~A pending test~:P ~
|
218 | 340 |
of ~A tests total.~%"
|
219 | |
(count t (cdr *entries*)
|
220 | |
:key #'pend)
|
|
341 |
(count t (the list (cdr *entries*)) :key #'pend)
|
221 | 342 |
(length (cdr *entries*)))
|
|
343 |
(finish-output s)
|
222 | 344 |
(dolist (entry (cdr *entries*))
|
223 | |
(when (pend entry)
|
|
345 |
(when (and (pend entry)
|
|
346 |
(not (has-disabled-note entry)))
|
224 | 347 |
(format s "~@[~<~%~:; ~:@(~S~)~>~]"
|
225 | |
(do-entry entry s))))
|
|
348 |
(do-entry entry s))
|
|
349 |
(finish-output s)
|
|
350 |
))
|
226 | 351 |
(let ((pending (pending-tests))
|
227 | 352 |
(expected-table (make-hash-table :test #'equal)))
|
228 | 353 |
(dolist (ex *expected-failures*)
|
|
250 | 375 |
(length new-failures)
|
251 | 376 |
new-failures)))
|
252 | 377 |
))
|
|
378 |
(finish-output s)
|
253 | 379 |
(null pending))))
|
|
380 |
|
|
381 |
(defun do-entries (s)
|
|
382 |
#-sbcl (do-entries* s)
|
|
383 |
#+sbcl (handler-bind ((sb-ext:code-deletion-note #'muffle-warning))
|
|
384 |
(do-entries* s)))
|
|
385 |
|
|
386 |
;;; Note handling functions and macros
|
|
387 |
|
|
388 |
(defmacro defnote (name contents &optional disabled)
|
|
389 |
`(eval-when (:load-toplevel :execute)
|
|
390 |
(let ((note (make-note :name ',name
|
|
391 |
:contents ',contents
|
|
392 |
:disabled ',disabled)))
|
|
393 |
(setf (gethash (note-name note) *notes*) note)
|
|
394 |
note)))
|
|
395 |
|
|
396 |
(defun disable-note (n)
|
|
397 |
(let ((note (if (note-p n) n
|
|
398 |
(setf n (gethash n *notes*)))))
|
|
399 |
(unless note (error "~A is not a note or note name." n))
|
|
400 |
(setf (note-disabled note) t)
|
|
401 |
note))
|
|
402 |
|
|
403 |
(defun enable-note (n)
|
|
404 |
(let ((note (if (note-p n) n
|
|
405 |
(setf n (gethash n *notes*)))))
|
|
406 |
(unless note (error "~A is not a note or note name." n))
|
|
407 |
(setf (note-disabled note) nil)
|
|
408 |
note))
|