2021-08-21 15:29:43 +02:00
|
|
|
#|----------------------------------------------------------------------------|
|
|
|
|
| Copyright 1990 by the Massachusetts Institute of Technology, Cambridge MA. |
|
2023-05-09 19:08:50 +02:00
|
|
|
| Copyright 2023 by the TVL Authors |
|
2021-08-21 15:29:43 +02:00
|
|
|
| |
|
|
|
|
| Permission to use, copy, modify, and distribute this software and its |
|
|
|
|
| documentation for any purpose and without fee is hereby granted, provided |
|
|
|
|
| that this copyright and permission notice appear in all copies and |
|
|
|
|
| supporting documentation, and that the name of M.I.T. not be used in |
|
|
|
|
| advertising or publicity pertaining to distribution of the software |
|
|
|
|
| without specific, written prior permission. M.I.T. makes no |
|
|
|
|
| representations about the suitability of this software for any purpose. |
|
|
|
|
| It is provided "as is" without express or implied warranty. |
|
|
|
|
| |
|
|
|
|
| M.I.T. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING |
|
|
|
|
| ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL |
|
|
|
|
| M.I.T. BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR |
|
|
|
|
| ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, |
|
|
|
|
| WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, |
|
|
|
|
| ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS |
|
|
|
|
| SOFTWARE. |
|
|
|
|
|----------------------------------------------------------------------------|#
|
|
|
|
|
|
|
|
(defpackage #:regression-test
|
2023-05-09 19:08:50 +02:00
|
|
|
(:nicknames #:rtest #-lispworks #:rt)
|
2021-08-21 15:29:43 +02:00
|
|
|
(:use #:cl)
|
|
|
|
(:export #:*do-tests-when-defined* #:*test* #:continue-testing
|
2023-05-09 19:08:50 +02:00
|
|
|
#:deftest #:add-test #:do-test #:do-tests #:get-test #:pending-tests
|
2022-01-19 14:39:58 +01:00
|
|
|
#:rem-all-tests #:rem-test)
|
2021-08-21 15:29:43 +02:00
|
|
|
(:documentation "The MIT regression tester with pfdietz's modifications"))
|
|
|
|
|
|
|
|
(in-package :regression-test)
|
|
|
|
|
|
|
|
(defvar *test* nil "Current test name")
|
|
|
|
(defvar *do-tests-when-defined* nil)
|
|
|
|
(defvar *entries* '(nil) "Test database")
|
|
|
|
(defvar *in-test* nil "Used by TEST")
|
|
|
|
(defvar *debug* nil "For debugging")
|
|
|
|
(defvar *catch-errors* t
|
|
|
|
"When true, causes errors in a test to be caught.")
|
|
|
|
(defvar *print-circle-on-failure* nil
|
|
|
|
"Failure reports are printed with *PRINT-CIRCLE* bound to this value.")
|
|
|
|
(defvar *compile-tests* nil
|
|
|
|
"When true, compile the tests before running them.")
|
|
|
|
(defvar *optimization-settings* '((safety 3)))
|
|
|
|
(defvar *expected-failures* nil
|
|
|
|
"A list of test names that are expected to fail.")
|
|
|
|
|
|
|
|
(defstruct (entry (:conc-name nil)
|
2022-01-19 14:39:58 +01:00
|
|
|
(:type list))
|
2021-08-21 15:29:43 +02:00
|
|
|
pend name form)
|
|
|
|
|
|
|
|
(defmacro vals (entry) `(cdddr ,entry))
|
|
|
|
|
|
|
|
(defmacro defn (entry) `(cdr ,entry))
|
|
|
|
|
|
|
|
(defun pending-tests ()
|
|
|
|
(do ((l (cdr *entries*) (cdr l))
|
|
|
|
(r nil))
|
|
|
|
((null l) (nreverse r))
|
|
|
|
(when (pend (car l))
|
|
|
|
(push (name (car l)) r))))
|
|
|
|
|
|
|
|
(defun rem-all-tests ()
|
|
|
|
(setq *entries* (list nil))
|
|
|
|
nil)
|
|
|
|
|
|
|
|
(defun rem-test (&optional (name *test*))
|
|
|
|
(do ((l *entries* (cdr l)))
|
|
|
|
((null (cdr l)) nil)
|
|
|
|
(when (equal (name (cadr l)) name)
|
|
|
|
(setf (cdr l) (cddr l))
|
|
|
|
(return name))))
|
|
|
|
|
|
|
|
(defun get-test (&optional (name *test*))
|
|
|
|
(defn (get-entry name)))
|
|
|
|
|
|
|
|
(defun get-entry (name)
|
|
|
|
(let ((entry (find name (cdr *entries*)
|
2022-01-19 14:39:58 +01:00
|
|
|
:key #'name
|
|
|
|
:test #'equal)))
|
2021-08-21 15:29:43 +02:00
|
|
|
(when (null entry)
|
|
|
|
(report-error t
|
|
|
|
"~%No test with name ~:@(~S~)."
|
2022-01-19 14:39:58 +01:00
|
|
|
name))
|
2021-08-21 15:29:43 +02:00
|
|
|
entry))
|
|
|
|
|
|
|
|
(defmacro deftest (name form &rest values)
|
|
|
|
`(add-entry '(t ,name ,form .,values)))
|
|
|
|
|
2023-05-09 19:08:50 +02:00
|
|
|
(defun add-test (name form &rest values)
|
|
|
|
(funcall #'add-entry (append (list 't name form) values)))
|
|
|
|
|
2021-08-21 15:29:43 +02:00
|
|
|
(defun add-entry (entry)
|
|
|
|
(setq entry (copy-list entry))
|
|
|
|
(do ((l *entries* (cdr l))) (nil)
|
|
|
|
(when (null (cdr l))
|
|
|
|
(setf (cdr l) (list entry))
|
|
|
|
(return nil))
|
2023-05-09 19:08:50 +02:00
|
|
|
(when (equal (name (cadr l))
|
2022-01-19 14:39:58 +01:00
|
|
|
(name entry))
|
2021-08-21 15:29:43 +02:00
|
|
|
(setf (cadr l) entry)
|
|
|
|
(report-error nil
|
2023-05-09 19:08:50 +02:00
|
|
|
"Redefining test ~:@(~S~)"
|
|
|
|
(name entry))
|
2021-08-21 15:29:43 +02:00
|
|
|
(return nil)))
|
|
|
|
(when *do-tests-when-defined*
|
|
|
|
(do-entry entry))
|
|
|
|
(setq *test* (name entry)))
|
|
|
|
|
|
|
|
(defun report-error (error? &rest args)
|
2023-05-09 19:08:50 +02:00
|
|
|
(cond (*debug*
|
2022-01-19 14:39:58 +01:00
|
|
|
(apply #'format t args)
|
|
|
|
(if error? (throw '*debug* nil)))
|
|
|
|
(error? (apply #'error args))
|
|
|
|
(t (apply #'warn args))))
|
2021-08-21 15:29:43 +02:00
|
|
|
|
|
|
|
(defun do-test (&optional (name *test*))
|
|
|
|
(do-entry (get-entry name)))
|
|
|
|
|
|
|
|
(defun equalp-with-case (x y)
|
|
|
|
"Like EQUALP, but doesn't do case conversion of characters."
|
|
|
|
(cond
|
|
|
|
((eq x y) t)
|
|
|
|
((consp x)
|
|
|
|
(and (consp y)
|
2022-01-19 14:39:58 +01:00
|
|
|
(equalp-with-case (car x) (car y))
|
|
|
|
(equalp-with-case (cdr x) (cdr y))))
|
2021-08-21 15:29:43 +02:00
|
|
|
((and (typep x 'array)
|
2022-01-19 14:39:58 +01:00
|
|
|
(= (array-rank x) 0))
|
2021-08-21 15:29:43 +02:00
|
|
|
(equalp-with-case (aref x) (aref y)))
|
|
|
|
((typep x 'vector)
|
|
|
|
(and (typep y 'vector)
|
2022-01-19 14:39:58 +01:00
|
|
|
(let ((x-len (length x))
|
|
|
|
(y-len (length y)))
|
|
|
|
(and (eql x-len y-len)
|
|
|
|
(loop
|
|
|
|
for e1 across x
|
|
|
|
for e2 across y
|
|
|
|
always (equalp-with-case e1 e2))))))
|
2021-08-21 15:29:43 +02:00
|
|
|
((and (typep x 'array)
|
2022-01-19 14:39:58 +01:00
|
|
|
(typep y 'array)
|
|
|
|
(not (equal (array-dimensions x)
|
|
|
|
(array-dimensions y))))
|
2021-08-21 15:29:43 +02:00
|
|
|
nil)
|
|
|
|
((typep x 'array)
|
|
|
|
(and (typep y 'array)
|
2022-01-19 14:39:58 +01:00
|
|
|
(let ((size (array-total-size x)))
|
|
|
|
(loop for i from 0 below size
|
|
|
|
always (equalp-with-case (row-major-aref x i)
|
|
|
|
(row-major-aref y i))))))
|
2021-08-21 15:29:43 +02:00
|
|
|
(t (eql x y))))
|
|
|
|
|
|
|
|
(defun do-entry (entry &optional
|
2022-01-19 14:39:58 +01:00
|
|
|
(s *standard-output*))
|
2021-08-21 15:29:43 +02:00
|
|
|
(catch '*in-test*
|
|
|
|
(setq *test* (name entry))
|
|
|
|
(setf (pend entry) t)
|
|
|
|
(let* ((*in-test* t)
|
2022-01-19 14:39:58 +01:00
|
|
|
;; (*break-on-warnings* t)
|
|
|
|
(aborted nil)
|
|
|
|
r)
|
2021-08-21 15:29:43 +02:00
|
|
|
;; (declare (special *break-on-warnings*))
|
|
|
|
|
|
|
|
(block aborted
|
2022-01-19 14:39:58 +01:00
|
|
|
(setf r
|
|
|
|
(flet ((%do
|
|
|
|
()
|
|
|
|
(if *compile-tests*
|
|
|
|
(multiple-value-list
|
|
|
|
(funcall (compile
|
|
|
|
nil
|
|
|
|
`(lambda ()
|
|
|
|
(declare
|
|
|
|
(optimize ,@*optimization-settings*))
|
|
|
|
,(form entry)))))
|
|
|
|
(multiple-value-list
|
|
|
|
(eval (form entry))))))
|
|
|
|
(if *catch-errors*
|
|
|
|
(handler-bind
|
|
|
|
((style-warning #'muffle-warning)
|
|
|
|
(error #'(lambda (c)
|
|
|
|
(setf aborted t)
|
|
|
|
(setf r (list c))
|
|
|
|
(return-from aborted nil))))
|
|
|
|
(%do))
|
|
|
|
(%do)))))
|
2021-08-21 15:29:43 +02:00
|
|
|
|
|
|
|
(setf (pend entry)
|
2022-01-19 14:39:58 +01:00
|
|
|
(or aborted
|
|
|
|
(not (equalp-with-case r (vals entry)))))
|
2023-05-09 19:08:50 +02:00
|
|
|
|
2021-08-21 15:29:43 +02:00
|
|
|
(when (pend entry)
|
2022-01-19 14:39:58 +01:00
|
|
|
(let ((*print-circle* *print-circle-on-failure*))
|
|
|
|
(format s "~&Test ~:@(~S~) failed~
|
2021-08-21 15:29:43 +02:00
|
|
|
~%Form: ~S~
|
|
|
|
~%Expected value~P: ~
|
|
|
|
~{~S~^~%~17t~}~%"
|
2022-01-19 14:39:58 +01:00
|
|
|
*test* (form entry)
|
|
|
|
(length (vals entry))
|
|
|
|
(vals entry))
|
|
|
|
(format s "Actual value~P: ~
|
2021-08-21 15:29:43 +02:00
|
|
|
~{~S~^~%~15t~}.~%"
|
2022-01-19 14:39:58 +01:00
|
|
|
(length r) r)))))
|
2021-08-21 15:29:43 +02:00
|
|
|
(when (not (pend entry)) *test*))
|
|
|
|
|
|
|
|
(defun continue-testing ()
|
|
|
|
(if *in-test*
|
|
|
|
(throw '*in-test* nil)
|
|
|
|
(do-entries *standard-output*)))
|
|
|
|
|
|
|
|
(defun do-tests (&optional
|
2022-01-19 14:39:58 +01:00
|
|
|
(out *standard-output*))
|
2021-08-21 15:29:43 +02:00
|
|
|
(dolist (entry (cdr *entries*))
|
|
|
|
(setf (pend entry) t))
|
|
|
|
(if (streamp out)
|
|
|
|
(do-entries out)
|
2023-05-09 19:08:50 +02:00
|
|
|
(with-open-file
|
2022-01-19 14:39:58 +01:00
|
|
|
(stream out :direction :output)
|
|
|
|
(do-entries stream))))
|
2021-08-21 15:29:43 +02:00
|
|
|
|
|
|
|
(defun do-entries (s)
|
|
|
|
(format s "~&Doing ~A pending test~:P ~
|
|
|
|
of ~A tests total.~%"
|
|
|
|
(count t (cdr *entries*)
|
2022-01-19 14:39:58 +01:00
|
|
|
:key #'pend)
|
|
|
|
(length (cdr *entries*)))
|
2021-08-21 15:29:43 +02:00
|
|
|
(dolist (entry (cdr *entries*))
|
|
|
|
(when (pend entry)
|
|
|
|
(format s "~@[~<~%~:; ~:@(~S~)~>~]"
|
2022-01-19 14:39:58 +01:00
|
|
|
(do-entry entry s))))
|
2021-08-21 15:29:43 +02:00
|
|
|
(let ((pending (pending-tests))
|
2022-01-19 14:39:58 +01:00
|
|
|
(expected-table (make-hash-table :test #'equal)))
|
2021-08-21 15:29:43 +02:00
|
|
|
(dolist (ex *expected-failures*)
|
|
|
|
(setf (gethash ex expected-table) t))
|
|
|
|
(let ((new-failures
|
2022-01-19 14:39:58 +01:00
|
|
|
(loop for pend in pending
|
|
|
|
unless (gethash pend expected-table)
|
|
|
|
collect pend)))
|
2021-08-21 15:29:43 +02:00
|
|
|
(if (null pending)
|
2022-01-19 14:39:58 +01:00
|
|
|
(format s "~&No tests failed.")
|
|
|
|
(progn
|
|
|
|
(format s "~&~A out of ~A ~
|
2021-08-21 15:29:43 +02:00
|
|
|
total tests failed: ~
|
|
|
|
~:@(~{~<~% ~1:;~S~>~
|
|
|
|
~^, ~}~)."
|
2022-01-19 14:39:58 +01:00
|
|
|
(length pending)
|
|
|
|
(length (cdr *entries*))
|
|
|
|
pending)
|
|
|
|
(if (null new-failures)
|
|
|
|
(format s "~&No unexpected failures.")
|
|
|
|
(when *expected-failures*
|
|
|
|
(format s "~&~A unexpected failures: ~
|
2021-08-21 15:29:43 +02:00
|
|
|
~:@(~{~<~% ~1:;~S~>~
|
|
|
|
~^, ~}~)."
|
2022-01-19 14:39:58 +01:00
|
|
|
(length new-failures)
|
|
|
|
new-failures)))
|
|
|
|
))
|
2021-08-21 15:29:43 +02:00
|
|
|
(null pending))))
|