source: trunk/abcl/test/lisp/abcl/rt.lisp

Last change on this file was 11599, checked in by Mark Evenson, 15 years ago

Use HANDLER-CASE for ANSI tests to quit invoking Lisp if an error in generated.

Further incremental work on ABCL-TEST-LISP (aka the internal ABCL
tests) necessitated by the fact that both it and the ANSI tests use
the REGRESSION-TEST framework which doesn't work well in the same Lisp
instances. Trying to repackage this correctly, but it needs more work.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 15.0 KB
Line 
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;This was the December 19, 1990 version of the regression tester, but
25;has since been modified.
26
27(in-package :abcl-regression-test)
28
29(declaim (ftype (function (t) t) get-entry expanded-eval do-entries))
30(declaim (type list *entries*))
31(declaim (ftype (function (t &rest t) t) report-error))
32(declaim (ftype (function (t &optional t) t) do-entry))
33
34(defvar *test* nil "Current test name")
35(defvar *do-tests-when-defined* nil)
36(defvar *entries* '(nil) "Test database.  Has a leading dummy cell that does not contain an entry.")
37(defvar *entries-tail* *entries* "Tail of the *entries* list")
38(defvar *entries-table* (make-hash-table :test #'equal)
39    "Map the names of entries to the cons cell in *entries* that precedes the one whose car is the entry.")
40(defvar *in-test* nil "Used by TEST")
41(defvar *debug* nil "For debugging")
42(defvar *catch-errors* t "When true, causes errors in a test to be caught.")
43(defvar *print-circle-on-failure* nil
44  "Failure reports are printed with *PRINT-CIRCLE* bound to this value.")
45
46(defvar *compile-tests* nil "When true, compile the tests before running them.")
47(defvar *expanded-eval* nil "When true, convert the tests into a form that is less likely to have compiler optimizations.")
48(defvar *optimization-settings* '((safety 3)))
49
50(defvar *failed-tests* nil "After DO-TESTS, becomes the list of names of tests that have failed")
51(defvar *passed-tests* nil "After DO-TESTS, becomes the list of names of tests that have passed")
52
53(defvar *expected-failures* nil
54  "A list of test names that are expected to fail.")
55
56(defvar *notes* (make-hash-table :test 'equal)
57  "A mapping from names of notes to note objects.")
58
59(defstruct (entry (:conc-name nil))
60  pend name props form vals)
61
62;;; Note objects are used to attach information to tests.
63;;; A typical use is to mark tests that depend on a particular
64;;; part of a set of requirements, or a particular interpretation
65;;; of the requirements.
66
67(defstruct note
68  name
69  contents
70  disabled ;; When true, tests with this note are considered inactive
71  )
72
73;; (defmacro vals (entry) `(cdddr ,entry))
74
75(defmacro defn (entry)
76  (let ((var (gensym)))
77    `(let ((,var ,entry))
78       (list* (name ,var) (form ,var) (vals ,var)))))
79
80(defun entry-notes (entry)
81  (let* ((props (props entry))
82   (notes (getf props :notes)))
83    (if (listp notes)
84  notes
85      (list notes))))
86
87(defun has-disabled-note (entry)
88  (let ((notes (entry-notes entry)))
89    (loop for n in notes
90    for note = (if (note-p n) n
91           (gethash n *notes*))
92    thereis (and note (note-disabled note)))))
93
94(defun has-note (entry note)
95  (unless (note-p note)
96    (let ((new-note (gethash note *notes*)))
97      (setf note new-note)))
98  (and note (not (not (member note (entry-notes entry))))))
99
100(defun pending-tests ()
101  (loop for entry in (cdr *entries*)
102  when (and (pend entry) (not (has-disabled-note entry)))
103  collect (name entry)))
104
105(defun rem-all-tests ()
106  (setq *entries* (list nil))
107  (setq *entries-tail* *entries*)
108  (clrhash *entries-table*)
109  nil)
110
111(defun rem-test (&optional (name *test*))
112  (let ((pred (gethash name *entries-table*)))
113    (when pred
114      (if (null (cddr pred))
115    (setq *entries-tail* pred)
116  (setf (gethash (name (caddr pred)) *entries-table*) pred))
117      (setf (cdr pred) (cddr pred))
118      (remhash name *entries-table*)
119      name)))
120
121(defun get-test (&optional (name *test*))
122  (defn (get-entry name)))
123
124(defun get-entry (name)
125  (let ((entry ;; (find name (the list (cdr *entries*))
126         ;;     :key #'name :test #'equal)
127   (cadr (gethash name *entries-table*))
128   ))
129    (when (null entry)
130      (report-error t
131        "~%No test with name ~:@(~S~)."
132  name))
133    entry))
134
135(defmacro deftest (name &rest body)
136  (let* ((p body)
137   (properties
138    (loop while (keywordp (first p))
139    unless (cadr p)
140    do (error "Poorly formed deftest: ~A~%"
141        (list* 'deftest name body))
142    append (list (pop p) (pop p))))
143   (form (pop p))
144   (vals p))
145    `(add-entry (make-entry :pend t
146          :name ',name
147          :props ',properties
148          :form ',form
149          :vals ',vals))))
150
151(defun add-entry (entry)
152  (setq entry (copy-entry entry))
153  (let* ((pred (gethash (name entry) *entries-table*)))
154    (cond
155     (pred
156      (setf (cadr pred) entry)
157      (report-error nil
158        "Redefining test ~:@(~S~)"
159        (name entry)))
160     (t
161      (setf (gethash (name entry) *entries-table*) *entries-tail*)
162      (setf (cdr *entries-tail*) (cons entry nil))
163      (setf *entries-tail* (cdr *entries-tail*))
164      )))
165  (when *do-tests-when-defined*
166    (do-entry entry))
167  (setq *test* (name entry)))
168
169(defun report-error (error? &rest args)
170  (cond (*debug*
171   (apply #'format t args)
172   (if error? (throw '*debug* nil)))
173  (error? (apply #'error args))
174  (t (apply #'warn args)))
175  nil)
176
177(defun do-test (&optional (name *test*) &rest key-args)
178  (flet ((%parse-key-args
179    (&key
180     ((:catch-errors *catch-errors*) *catch-errors*)
181     ((:compile *compile-tests*) *compile-tests*))
182    (do-entry (get-entry name))))
183    (apply #'%parse-key-args key-args)))
184
185(defun my-aref (a &rest args)
186  (apply #'aref a args))
187
188(defun my-row-major-aref (a index)
189  (row-major-aref a index))
190
191(defun equalp-with-case (x y)
192  "Like EQUALP, but doesn't do case conversion of characters.
193   Currently doesn't work on arrays of dimension > 2."
194  (cond
195   ((eq x y) t)
196   ((consp x)
197    (and (consp y)
198   (equalp-with-case (car x) (car y))
199   (equalp-with-case (cdr x) (cdr y))))
200   ((and (typep x 'array)
201   (= (array-rank x) 0))
202    (equalp-with-case (my-aref x) (my-aref y)))
203   ((typep x 'vector)
204    (and (typep y 'vector)
205   (let ((x-len (length x))
206         (y-len (length y)))
207     (and (eql x-len y-len)
208    (loop
209     for i from 0 below x-len
210     for e1 = (my-aref x i)
211     for e2 = (my-aref y i)
212     always (equalp-with-case e1 e2))))))
213   ((and (typep x 'array)
214   (typep y 'array)
215   (not (equal (array-dimensions x)
216         (array-dimensions y))))
217    nil)
218
219   ((typep x 'array)
220    (and (typep y 'array)
221   (let ((size (array-total-size x)))
222     (loop for i from 0 below size
223     always (equalp-with-case (my-row-major-aref x i)
224            (my-row-major-aref y i))))))
225   ((typep x 'pathname)
226    (equal x y))
227   (t (eql x y))))
228
229(defun do-entry (entry &optional
230           (s *standard-output*))
231  (catch '*in-test*
232    (setq *test* (name entry))
233    (setf (pend entry) t)
234    (let* ((*in-test* t)
235     ;; (*break-on-warnings* t)
236     (aborted nil)
237     r)
238      ;; (declare (special *break-on-warnings*))
239
240      (block aborted
241  (setf r
242        (flet ((%do ()
243        (handler-bind
244         #-sbcl nil
245         #+sbcl ((sb-ext:code-deletion-note #'(lambda (c)
246                  (if (has-note entry :do-not-muffle)
247                      nil
248                    (muffle-warning c)))))
249         (cond
250          (*compile-tests*
251           (multiple-value-list
252            (funcall (compile
253          nil
254          `(lambda ()
255             (declare
256              (optimize ,@*optimization-settings*))
257             ,(form entry))))))
258          (*expanded-eval*
259           (multiple-value-list
260            (expanded-eval (form entry))))
261          (t
262           (multiple-value-list
263            (eval (form entry))))))))
264    (if *catch-errors*
265        (handler-bind
266         (#-ecl (style-warning #'(lambda (c) (if (has-note entry :do-not-muffle-warnings)
267                   c
268                 (muffle-warning c))))
269          (error #'(lambda (c)
270               (setf aborted t)
271               (setf r (list c))
272               (return-from aborted nil))))
273         (%do))
274      (%do)))))
275
276      (setf (pend entry)
277      (or aborted
278    (not (equalp-with-case r (vals entry)))))
279
280      (when (pend entry)
281  (let ((*print-circle* *print-circle-on-failure*))
282          #+xcl
283          (progn
284            (fresh-line)
285            (format t "Test ~S failed~%" *test*)
286            (format t "Form: ~S~%" (form entry))
287            (format t "Expected value: ~S~%"
288                    (if (= (length (vals entry)) 1)
289                        (car (vals entry))
290                        (vals entry))))
291          #-xcl
292    (format s "~&Test ~:@(~S~) failed~
293                   ~%Form: ~S~
294                   ~%Expected value~P: ~
295                      ~{~S~^~%~17t~}~%"
296      *test* (form entry)
297      (length (vals entry))
298      (vals entry))
299    (handler-case
300           #+xcl
301           (let ((r (if (= (length r) 1) (car r) r)))
302             (format t "Actual value: ~S" r)
303             (when (typep r 'condition)
304               (format t " [\"~A\"]" r))
305             (terpri))
306           #-xcl
307     (let ((st (format nil "Actual value~P: ~
308                      ~{~S~^~%~15t~}.~%"
309           (length r) r)))
310       (format s "~A" st))
311     (error () (format s "Actual value: #<error during printing>~%")))
312    (finish-output s)))))
313  (when (not (pend entry)) *test*))
314
315(defun expanded-eval (form)
316  "Split off top level of a form and eval separately.  This reduces the chance that
317   compiler optimizations will fold away runtime computation."
318  (if (not (consp form))
319      (eval form)
320   (let ((op (car form)))
321     (cond
322      ((eq op 'let)
323       (let* ((bindings (loop for b in (cadr form)
324            collect (if (consp b) b (list b nil))))
325        (vars (mapcar #'car bindings))
326        (binding-forms (mapcar #'cadr bindings)))
327   (apply
328    (the function
329      (eval `(lambda ,vars ,@(cddr form))))
330    (mapcar #'eval binding-forms))))
331      ((and (eq op 'let*) (cadr form))
332       (let* ((bindings (loop for b in (cadr form)
333            collect (if (consp b) b (list b nil))))
334        (vars (mapcar #'car bindings))
335        (binding-forms (mapcar #'cadr bindings)))
336   (funcall
337    (the function
338      (eval `(lambda (,(car vars) &aux ,@(cdr bindings)) ,@(cddr form))))
339    (eval (car binding-forms)))))
340      ((eq op 'progn)
341       (loop for e on (cdr form)
342       do (if (null (cdr e)) (return (eval (car e)))
343      (eval (car e)))))
344      ((and (symbolp op) (fboundp op)
345      (not (macro-function op))
346      (not (special-operator-p op)))
347       (apply (symbol-function op)
348        (mapcar #'eval (cdr form))))
349      (t (eval form))))))
350
351(defun continue-testing ()
352  (if *in-test*
353      (throw '*in-test* nil)
354      (do-entries *standard-output*)))
355
356(defun do-tests (&key (out *standard-output*)
357          ((:catch-errors *catch-errors*) *catch-errors*)
358          ((:compile *compile-tests*) *compile-tests*))
359  (setq *failed-tests* nil
360  *passed-tests* nil)
361  (dolist (entry (cdr *entries*))
362    (setf (pend entry) t))
363  (if (streamp out)
364      (do-entries out)
365      (with-open-file
366    (stream out :direction :output)
367  (do-entries stream))))
368
369(defun do-entries (s)
370  (format s "~&Doing ~A pending test~:P ~
371             of ~A tests total.~%"
372          (count t (the list (cdr *entries*)) :key #'pend)
373    (length (cdr *entries*)))
374  (finish-output s)
375  (dolist (entry (cdr *entries*))
376    (when (and (pend entry)
377         (not (has-disabled-note entry)))
378      (let ((success? (do-entry entry s)))
379  (if success?
380    (push (name entry) *passed-tests*)
381    (push (name entry) *failed-tests*))
382        #+xcl
383        (progn
384          (fresh-line s)
385          (when success?
386            (format s "Test ~S~%" (name entry))))
387        #-xcl
388  (format s "~@[~<~%~:; ~:@(~S~)~>~]" success?))
389      (finish-output s)
390      ))
391  (let ((pending (pending-tests))
392  (expected-table (make-hash-table :test #'equal)))
393    (dolist (ex *expected-failures*)
394      (setf (gethash ex expected-table) t))
395    (let ((new-failures
396     (loop for pend in pending
397     unless (gethash pend expected-table)
398     collect pend)))
399      (if (null pending)
400          #+xcl
401          (progn
402            (fresh-line s)
403            (format s "No tests failed."))
404          #-xcl
405    (format s "~&No tests failed.")
406  (progn
407          #+xcl
408          (progn
409            (fresh-line s)
410            (format s "~D out of ~D total tests failed"
411                    (length pending)
412                    (length (cdr *entries*))))
413          #-xcl
414    (format s "~&~A out of ~A ~
415                   total tests failed: ~
416                   ~:@(~{~<~%   ~1:;~S~>~
417                         ~^, ~}~)."
418      (length pending)
419      (length (cdr *entries*))
420      pending)
421    (if (null new-failures)
422        (format s "~&No unexpected failures.")
423      (when *expected-failures*
424        (format s "~&~A unexpected failures: ~
425                   ~:@(~{~<~%   ~1:;~S~>~
426                         ~^, ~}~)."
427        (length new-failures)
428        new-failures)))
429    ))
430      (finish-output s)
431      (null pending))))
432
433;;; Note handling functions and macros
434
435(defmacro defnote (name contents &optional disabled)
436  `(eval-when (:load-toplevel :execute)
437     (let ((note (make-note :name ',name
438          :contents ',contents
439          :disabled ',disabled)))
440       (setf (gethash (note-name note) *notes*) note)
441       note)))
442
443(defun disable-note (n)
444  (let ((note (if (note-p n) n
445    (setf n (gethash n *notes*)))))
446    (unless note (error "~A is not a note or note name." n))
447    (setf (note-disabled note) t)
448    note))
449
450(defun enable-note (n)
451  (let ((note (if (note-p n) n
452    (setf n (gethash n *notes*)))))
453    (unless note (error "~A is not a note or note name." n))
454    (setf (note-disabled note) nil)
455    note))
456
457;;; Extended random regression
458
459(defun do-extended-tests (&key (tests *passed-tests*) (count nil)
460             ((:catch-errors *catch-errors*) *catch-errors*)
461             ((:compile *compile-tests*) *compile-tests*))
462  "Execute randomly chosen tests from TESTS until one fails or until
463   COUNT is an integer and that many tests have been executed."
464  (let ((test-vector (coerce tests 'simple-vector)))
465    (let ((n (length test-vector)))
466      (when (= n 0) (error "Must provide at least one test."))
467      (loop for i from 0
468      for name = (svref test-vector (random n))
469      until (eql i count)
470      do (print name)
471      unless (do-test name) return (values name (1+ i))))))
Note: See TracBrowser for help on using the repository browser.