Merge branch 'master' of ssh://git.b9.com/home/gitpub/rt
[rt.git] / rt.lisp
1 ;-*-syntax:COMMON-LISP;Package:(RT :use "COMMON-LISP" :colon-mode :external)-*-
2
3 #|----------------------------------------------------------------------------|
4  | Copyright 1990 by the Massachusetts Institute of Technology, Cambridge MA. |
5  |                                                                            |
6  | Permission  to  use,  copy, modify, and distribute this software  and  its |
7  | documentation for any purpose  and without fee is hereby granted, provided |
8  | that this copyright  and  permission  notice  appear  in  all  copies  and |
9  | supporting  documentation,  and  that  the  name  of M.I.T. not be used in |
10  | advertising or  publicity  pertaining  to  distribution  of  the  software |
11  | without   specific,   written   prior   permission.      M.I.T.  makes  no |
12  | representations  about  the  suitability of this software for any purpose. |
13  | It is provided "as is" without express or implied warranty.                |
14  |                                                                            |
15  |  M.I.T. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,  INCLUDING  |
16  |  ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL  |
17  |  M.I.T. BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL  DAMAGES  OR  |
18  |  ANY  DAMAGES  WHATSOEVER  RESULTING  FROM  LOSS OF USE, DATA OR PROFITS,  |
19  |  WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER  TORTIOUS  ACTION,  |
20  |  ARISING  OUT  OF  OR  IN  CONNECTION WITH THE USE OR PERFORMANCE OF THIS  |
21  |  SOFTWARE.                                                                 |
22  |----------------------------------------------------------------------------|#
23
24 (defpackage #:regression-test
25   (:nicknames #:rtest #-lispworks #:rt)
26   (:use #:cl)
27   (:export #:*do-tests-when-defined* #:*test* #:continue-testing
28            #:deftest #:do-test #:do-tests #:get-test #:pending-tests
29            #:rem-all-tests #:rem-test)
30   (:documentation "The MIT regression tester with pfdietz's modifications"))
31
32 ;;This was the December 19, 1990 version of the regression tester, but
33 ;;has since been modified.
34
35 (in-package :regression-test)
36
37 (declaim (ftype (function (t) t) get-entry expanded-eval do-entries))
38 (declaim (type list *entries*))
39 (declaim (ftype (function (t &rest t) t) report-error))
40 (declaim (ftype (function (t &optional t) t) do-entry))
41
42 (defvar *test* nil "Current test name")
43 (defvar *do-tests-when-defined* nil)
44 (defvar *entries* '(nil) "Test database.  Has a leading dummy cell that does not contain an entry.")
45 (defvar *entries-tail* *entries* "Tail of the *entries* list")
46 (defvar *entries-table* (make-hash-table :test #'equal)
47     "Map the names of entries to the cons cell in *entries* that precedes the one whose car is the entry.")
48 (defvar *in-test* nil "Used by TEST")
49 (defvar *debug* nil "For debugging")
50 (defvar *catch-errors* t "When true, causes errors in a test to be caught.")
51 (defvar *print-circle-on-failure* nil
52   "Failure reports are printed with *PRINT-CIRCLE* bound to this value.")
53
54 (defvar *compile-tests* nil "When true, compile the tests before running them.")
55 (defvar *expanded-eval* nil "When true, convert the tests into a form that is less likely to have compiler optimizations.")
56 (defvar *optimization-settings* '((safety 3)))
57
58 (defvar *expected-failures* nil
59   "A list of test names that are expected to fail.")
60
61 (defvar *notes* (make-hash-table :test 'equal)
62   "A mapping from names of notes to note objects.")
63
64 (defstruct (entry (:conc-name nil))
65   pend name props form vals)
66
67 ;;; Note objects are used to attach information to tests.
68 ;;; A typical use is to mark tests that depend on a particular
69 ;;; part of a set of requirements, or a particular interpretation
70 ;;; of the requirements.
71
72 (defstruct note
73   name
74   contents
75   disabled ;; When true, tests with this note are considered inactive
76   )
77
78 ;; (defmacro vals (entry) `(cdddr ,entry))
79
80 (defmacro defn (entry)
81   (let ((var (gensym)))
82     `(let ((,var ,entry))
83        (list* (name ,var) (form ,var) (vals ,var)))))
84
85 (defun entry-notes (entry)
86   (let* ((props (props entry))
87          (notes (getf props :notes)))
88     (if (listp notes)
89         notes
90       (list notes))))
91
92 (defun has-disabled-note (entry)
93   (let ((notes (entry-notes entry)))
94     (loop for n in notes
95           for note = (if (note-p n) n
96                        (gethash n *notes*))
97           thereis (and note (note-disabled note)))))
98
99 (defun pending-tests ()
100   (loop for entry in (cdr *entries*)
101         when (and (pend entry) (not (has-disabled-note entry)))
102         collect (name entry)))
103
104 (defun rem-all-tests ()
105   (setq *entries* (list nil))
106   (setq *entries-tail* *entries*)
107   (clrhash *entries-table*)
108   nil)
109
110 (defun rem-test (&optional (name *test*))
111   (let ((pred (gethash name *entries-table*)))
112     (when pred
113       (if (null (cddr pred))
114           (setq *entries-tail* pred)
115         (setf (gethash (name (caddr pred)) *entries-table*) pred))
116       (setf (cdr pred) (cddr pred))
117       (remhash name *entries-table*)
118       name)))
119
120 (defun get-test (&optional (name *test*))
121   (defn (get-entry name)))
122
123 (defun get-entry (name)
124   (let ((entry ;; (find name (the list (cdr *entries*))
125                ;;     :key #'name :test #'equal)
126          (cadr (gethash name *entries-table*))
127          ))
128     (when (null entry)
129       (report-error t
130         "~%No test with name ~:@(~S~)."
131         name))
132     entry))
133
134 (defmacro deftest (name &rest body)
135   (let* ((p body)
136          (properties
137           (loop while (keywordp (first p))
138                 unless (cadr p)
139                 do (error "Poorly formed deftest: ~A~%"
140                           (list* 'deftest name body))
141                 append (list (pop p) (pop p))))
142          (form (pop p))
143          (vals p))
144     `(add-entry (make-entry :pend t
145                             :name ',name
146                             :props ',properties
147                             :form ',form
148                             :vals ',vals))))
149
150 (defun add-entry (entry)
151   (setq entry (copy-entry entry))
152   (let* ((pred (gethash (name entry) *entries-table*)))
153     (cond
154      (pred
155       (setf (cadr pred) entry)
156       (report-error nil
157         "Redefining test ~:@(~S~)"
158         (name entry)))
159      (t
160       (setf (gethash (name entry) *entries-table*) *entries-tail*)
161       (setf (cdr *entries-tail*) (cons entry nil))
162       (setf *entries-tail* (cdr *entries-tail*))
163       )))
164   (when *do-tests-when-defined*
165     (do-entry entry))
166   (setq *test* (name entry)))
167
168 (defun report-error (error? &rest args)
169   (cond (*debug*
170          (apply #'format t args)
171          (if error? (throw '*debug* nil)))
172         (error? (apply #'error args))
173         (t (apply #'warn args)))
174   nil)
175
176 (defun do-test (&optional (name *test*))
177   #-sbcl (do-entry (get-entry name))
178   #+sbcl (handler-bind ((sb-ext:code-deletion-note #'muffle-warning))
179                        (do-entry (get-entry name))))
180
181 (defun my-aref (a &rest args)
182   (apply #'aref a args))
183
184 (defun my-row-major-aref (a index)
185   (row-major-aref a index))
186
187 (defun equalp-with-case (x y)
188   "Like EQUALP, but doesn't do case conversion of characters.
189    Currently doesn't work on arrays of dimension > 2."
190   (cond
191    ((eq x y) t)
192    ((consp x)
193     (and (consp y)
194          (equalp-with-case (car x) (car y))
195          (equalp-with-case (cdr x) (cdr y))))
196    ((and (typep x 'array)
197          (= (array-rank x) 0))
198     (equalp-with-case (my-aref x) (my-aref y)))
199    ((typep x 'vector)
200     (and (typep y 'vector)
201          (let ((x-len (length x))
202                (y-len (length y)))
203            (and (eql x-len y-len)
204                 (loop
205                  for i from 0 below x-len
206                  for e1 = (my-aref x i)
207                  for e2 = (my-aref y i)
208                  always (equalp-with-case e1 e2))))))
209    ((and (typep x 'array)
210          (typep y 'array)
211          (not (equal (array-dimensions x)
212                      (array-dimensions y))))
213     nil)
214
215    ((typep x 'array)
216     (and (typep y 'array)
217          (let ((size (array-total-size x)))
218            (loop for i from 0 below size
219                  always (equalp-with-case (my-row-major-aref x i)
220                                           (my-row-major-aref y i))))))
221
222    (t (eql x y))))
223
224 (defun do-entry (entry &optional
225                        (s *standard-output*))
226   (catch '*in-test*
227     (setq *test* (name entry))
228     (setf (pend entry) t)
229     (let* ((*in-test* t)
230            ;; (*break-on-warnings* t)
231            (aborted nil)
232            r)
233       ;; (declare (special *break-on-warnings*))
234
235       (block aborted
236         (setf r
237               (flet ((%do
238                       ()
239                       (cond
240                        (*compile-tests*
241                         (multiple-value-list
242                          (funcall (compile
243                                    nil
244                                    `(lambda ()
245                                       (declare
246                                        (optimize ,@*optimization-settings*))
247                                       ,(form entry))))))
248                        (*expanded-eval*
249                         (multiple-value-list
250                          (expanded-eval (form entry))))
251                        (t
252                         (multiple-value-list
253                          (eval (form entry)))))))
254                 (if *catch-errors*
255                     (handler-bind
256                      (#-ecl (style-warning #'muffle-warning)
257                             (error #'(lambda (c)
258                                        (setf aborted t)
259                                        (setf r (list c))
260                                        (return-from aborted nil))))
261                      (%do))
262                   (%do)))))
263
264       (setf (pend entry)
265             (or aborted
266                 (not (equalp-with-case r (vals entry)))))
267
268       (when (pend entry)
269         (let ((*print-circle* *print-circle-on-failure*))
270           (format s "~&Test ~:@(~S~) failed~
271                    ~%Form: ~S~
272                    ~%Expected value~P: ~
273                       ~{~S~^~%~17t~}~%"
274                   *test* (form entry)
275                   (length (vals entry))
276                   (vals entry))
277           (handler-case
278            (let ((st (format nil "Actual value~P: ~
279                       ~{~S~^~%~15t~}.~%"
280                             (length r) r)))
281              (format s "~A" st))
282            (error () (format s "Actual value: #<error during printing>~%")
283                   ))
284           (finish-output s)
285           ))))
286   (when (not (pend entry)) *test*))
287
288 (defun expanded-eval (form)
289   "Split off top level of a form and eval separately.  This reduces the chance that
290    compiler optimizations will fold away runtime computation."
291   (if (not (consp form))
292       (eval form)
293    (let ((op (car form)))
294      (cond
295       ((eq op 'let)
296        (let* ((bindings (loop for b in (cadr form)
297                               collect (if (consp b) b (list b nil))))
298               (vars (mapcar #'car bindings))
299               (binding-forms (mapcar #'cadr bindings)))
300          (apply
301           (the function
302             (eval `(lambda ,vars ,@(cddr form))))
303           (mapcar #'eval binding-forms))))
304       ((and (eq op 'let*) (cadr form))
305        (let* ((bindings (loop for b in (cadr form)
306                               collect (if (consp b) b (list b nil))))
307               (vars (mapcar #'car bindings))
308               (binding-forms (mapcar #'cadr bindings)))
309          (funcall
310           (the function
311             (eval `(lambda (,(car vars) &aux ,@(cdr bindings)) ,@(cddr form))))
312           (eval (car binding-forms)))))
313       ((eq op 'progn)
314        (loop for e on (cdr form)
315              do (if (null (cdr e)) (return (eval (car e)))
316                   (eval (car e)))))
317       ((and (symbolp op) (fboundp op)
318             (not (macro-function op))
319             (not (special-operator-p op)))
320        (apply (symbol-function op)
321               (mapcar #'eval (cdr form))))
322       (t (eval form))))))
323
324 (defun continue-testing ()
325   (if *in-test*
326       (throw '*in-test* nil)
327       (do-entries *standard-output*)))
328
329 (defun do-tests (&optional
330                  (out *standard-output*))
331   (dolist (entry (cdr *entries*))
332     (setf (pend entry) t))
333   (if (streamp out)
334       (do-entries out)
335       (with-open-file
336           (stream out :direction :output)
337         (do-entries stream))))
338
339 (defun do-entries* (s)
340   (format s "~&Doing ~A pending test~:P ~
341              of ~A tests total.~%"
342           (count t (the list (cdr *entries*)) :key #'pend)
343           (length (cdr *entries*)))
344   (finish-output s)
345   (dolist (entry (cdr *entries*))
346     (when (and (pend entry)
347                (not (has-disabled-note entry)))
348       (format s "~@[~<~%~:; ~:@(~S~)~>~]"
349               (do-entry entry s))
350       (finish-output s)
351       ))
352   (let ((pending (pending-tests))
353         (expected-table (make-hash-table :test #'equal)))
354     (dolist (ex *expected-failures*)
355       (setf (gethash ex expected-table) t))
356     (let ((new-failures
357            (loop for pend in pending
358                  unless (gethash pend expected-table)
359                  collect pend)))
360       (if (null pending)
361           (format s "~&No tests failed.")
362         (progn
363           (format s "~&~A out of ~A ~
364                    total tests failed: ~
365                    ~:@(~{~<~%   ~1:;~S~>~
366                          ~^, ~}~)."
367                   (length pending)
368                   (length (cdr *entries*))
369                   pending)
370           (if (null new-failures)
371               (format s "~&No unexpected failures.")
372             (when *expected-failures*
373               (format s "~&~A unexpected failures: ~
374                    ~:@(~{~<~%   ~1:;~S~>~
375                          ~^, ~}~)."
376                     (length new-failures)
377                     new-failures)))
378           ))
379       (finish-output s)
380       (null pending))))
381
382 (defun do-entries (s)
383   #-sbcl (do-entries* s)
384   #+sbcl (handler-bind ((sb-ext:code-deletion-note #'muffle-warning))
385                        (do-entries* s)))
386
387 ;;; Note handling functions and macros
388
389 (defmacro defnote (name contents &optional disabled)
390   `(eval-when (:load-toplevel :execute)
391      (let ((note (make-note :name ',name
392                             :contents ',contents
393                             :disabled ',disabled)))
394        (setf (gethash (note-name note) *notes*) note)
395        note)))
396
397 (defun disable-note (n)
398   (let ((note (if (note-p n) n
399                 (setf n (gethash n *notes*)))))
400     (unless note (error "~A is not a note or note name." n))
401     (setf (note-disabled note) t)
402     note))
403
404 (defun enable-note (n)
405   (let ((note (if (note-p n) n
406                 (setf n (gethash n *notes*)))))
407     (unless note (error "~A is not a note or note name." n))
408     (setf (note-disabled note) nil)
409     note))