1 ;;;-*- Mode: Lisp; Syntax: ANSI-Common-Lisp -*-
4 Copyright
(c) 2004-
2005 Christopher K. Riesbeck
6 Permission is hereby granted
, free of charge
, to any person obtaining
7 a copy of this software and associated documentation files
(the "Software"),
8 to deal in the Software without restriction
, including without limitation
9 the rights to use
, copy
, modify
, merge
, publish
, distribute
, sublicense
,
10 and
/or sell copies of the Software
, and to permit persons to whom the
11 Software is furnished to do so
, subject to the following conditions
:
13 The above copyright notice and this permission notice shall be included
14 in all copies or substantial portions of the Software.
16 THE SOFTWARE IS PROVIDED
"AS IS", WITHOUT WARRANTY OF ANY KIND
, EXPRESS
17 OR IMPLIED
, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY
,
18 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
19 THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM
, DAMAGES OR
20 OTHER LIABILITY
, WHETHER IN AN ACTION OF CONTRACT
, TORT OR OTHERWISE
,
21 ARISING FROM
, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 OTHER DEALINGS IN THE SOFTWARE.
26 ;;; A test suite package, modelled after JUnit.
27 ;;; Author: Chris Riesbeck
37 1. Read the documentation in lisp-unit.html.
39 2. Make a file of DEFINE-TEST
's. See exercise-tests.lisp for many
40 examples. If you want
, start your test file with
(REMOVE-TESTS) to
41 clear any previously defined tests.
45 2.
(use-package :lisp-unit
)
47 3. Load your code file and your file of tests.
49 4. Test your code with
(RUN-TESTS test-name1 test-name2 ...
) -- no quotes
! --
50 or simply
(RUN-TESTS) to run all defined tests.
52 A summary of how many tests passed and failed will be printed
,
53 with details on the failures.
55 Note
: Nothing is compiled until RUN-TESTS is expanded. Redefining
56 functions or even macros does not require reloading any tests.
58 For more information
, see lisp-unit.html.
62 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
64 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
68 (defpackage :lisp-unit
70 ;; Forms for assertions
72 :assert-eq
:assert-eql
:assert-equal
:assert-equalp
73 :assert-equality
:assert-prints
:assert-expands
74 :assert-true
:assert-false
:assert-error
)
75 ;; Functions for managing tests
78 :get-tests
:get-test-code
79 :remove-tests
:remove-all-tests
80 :run-tests
:run-all-tests
85 :logically-equal
:set-equal
))
87 (in-package :lisp-unit
)
91 (defparameter *pass
* 0
92 "The number of passed assertions.")
94 (defparameter *fail
* 0
95 "The number of failed assertions.")
99 (defparameter *report-results
* nil
100 "If not NIL, report the results of the assertions.")
102 ;;; Global unit test database
104 (defparameter *test-db
* (make-hash-table :test
#'eq
)
105 "The unit test database is simply a hash table.")
107 (defun package-table (package &optional create
)
109 ((gethash (find-package package
) *test-db
*))
111 (setf (gethash package
*test-db
*) (make-hash-table)))))
113 (defmacro define-test
(name &body body
)
114 "Store the test in the test database."
117 (gethash ',name
(package-table *package
* t
))
119 ;; Return the name of the test
124 (defmacro assert-eq
(expected form
&rest extras
)
125 "Assert whether expected and form are EQ."
126 `(expand-assert :equal
,form
,form
,expected
,extras
:test
#'eq
))
128 (defmacro assert-eql
(expected form
&rest extras
)
129 "Assert whether expected and form are EQL."
130 `(expand-assert :equal
,form
,form
,expected
,extras
:test
#'eql
))
132 (defmacro assert-equal
(expected form
&rest extras
)
133 "Assert whether expected and form are EQUAL."
134 `(expand-assert :equal
,form
,form
,expected
,extras
:test
#'equal
))
136 (defmacro assert-equalp
(expected form
&rest extras
)
137 "Assert whether expected and form are EQUALP."
138 `(expand-assert :equal
,form
,form
,expected
,extras
:test
#'equalp
))
140 (defmacro assert-error
(condition form
&rest extras
)
141 "Assert whether form signals condition."
142 `(expand-assert :error
,form
(expand-error-form ,form
)
145 (defmacro assert-expands
(expansion form
&rest extras
)
146 "Assert whether form expands to expansion."
147 `(expand-assert :macro
,form
148 (expand-macro-form ,form nil
)
151 (defmacro assert-false
(form &rest extras
)
152 "Assert whether the form is false."
153 `(expand-assert :result
,form
,form nil
,extras
))
155 (defmacro assert-equality
(test expected form
&rest extras
)
156 "Assert whether expected and form are equal according to test."
157 `(expand-assert :equal
,form
,form
,expected
,extras
:test
,test
))
159 (defmacro assert-prints
(output form
&rest extras
)
160 "Assert whether printing the form generates the output."
161 `(expand-assert :output
,form
(expand-output-form ,form
)
164 (defmacro assert-true
(form &rest extras
)
165 "Assert whether the form is true."
166 `(expand-assert :result
,form
,form t
,extras
))
168 (defmacro expand-assert
(type form body expected extras
&key
(test '#'eql
))
169 "Expand the assertion to the internal format."
170 `(internal-assert ,type
',form
172 (lambda () ,expected
)
173 (expand-extras ,extras
)
176 (defmacro expand-error-form
(form)
177 "Wrap the error assertion in HANDLER-CASE."
179 (condition (error) error
)))
181 (defmacro expand-output-form
(form)
182 "Capture the output of the form in a string."
183 (let ((out (gensym)))
184 `(let* ((,out
(make-string-output-stream))
186 (make-broadcast-stream *standard-output
* ,out
)))
188 (get-output-stream-string ,out
))))
190 (defmacro expand-macro-form
(form env
)
191 "Expand the macro form once."
192 `(macroexpand-1 ',form
,env
))
194 (defmacro expand-extras
(extras)
195 "Expand extra forms."
197 (list ,@(mapcan (lambda (form) (list `',form form
)) extras
))))
199 (defun internal-assert
200 (type form code-thunk expected-thunk extras test
)
201 (let ((expected (multiple-value-list (funcall expected-thunk
)))
202 (actual (multiple-value-list (funcall code-thunk
)))
204 ;; Count the assertion
205 (if (setq passed
(test-passed-p type expected actual test
))
208 ;; Report the assertion
209 (when (and *report-results
* (not passed
))
210 (report-failure type form expected actual extras
))
214 ;;; Test passed predicate.
216 (defgeneric test-passed-p
(type expected actual test
)
218 "Return the result of the test."))
220 (defmethod test-passed-p ((type (eql :error
)) expected actual test
)
221 "Return the result of the error assertion."
223 (eql (car actual
) (car expected
))
224 (typep (car actual
) (car expected
))))
226 (defmethod test-passed-p ((type (eql :equal
)) expected actual test
)
227 "Return the result of the equality assertion."
229 (<= (length expected
) (length actual
))
230 (every test expected actual
)))
232 (defmethod test-passed-p ((type (eql :macro
)) expected actual test
)
233 "Return the result of the macro expansion."
234 (equal (car actual
) (car expected
)))
236 (defmethod test-passed-p ((type (eql :output
)) expected actual test
)
237 "Return the result of the printed output."
239 (string-trim '(#\newline
#\return
#\space
) (car actual
))
242 (defmethod test-passed-p ((type (eql :result
)) expected actual test
)
243 "Return the result of the assertion."
244 (logically-equal (car actual
) (car expected
)))
246 ;;; Failure control strings for reports.
248 (defgeneric failure-control-string
(type)
250 "~&Expected ~{~S~^; ~} ~<~%~:;but saw ~{~S~^; ~}~>")
252 "Return the FORMAT control string for the failure type."))
254 (defmethod failure-control-string ((type (eql :error
)))
255 "~&~@[Should have signalled ~{~S~^; ~} but saw~] ~{~S~^; ~}")
257 (defmethod failure-control-string ((type (eql :macro
)))
258 "~&Should have expanded to ~{~S~^; ~} ~<~%~:;but saw ~{~S~^; ~}~>")
260 (defmethod failure-control-string ((type (eql :output
)))
261 "~&Should have printed ~{~S~^; ~} ~<~%~:;but saw ~{~S~^; ~}~>")
265 (defun report-failure (type form expected actual extras
)
266 "Report the details of the failure assertion."
267 (format t
"Failed Form: ~S" form
)
268 (format t
(failure-control-string type
) expected actual
)
270 (format t
"~{~& ~S => ~S~}~%" (funcall extras
)))
273 (defun report-summary (name pass fail
&optional exerr
)
274 (format t
"~&~A: ~S assertions passed," name pass
)
275 (format t
" ~S failed~@[, ~S execution errors~]." fail exerr
))
279 (defclass test-results
()
284 (successful-assertions
287 :accessor successful-assertions
)
291 :accessor failed-assertions
)
295 :accessor execution-errors
)
299 :reader failed-tests
)
307 :reader missing-tests
))
308 (:default-initargs
:test-names
())
310 "Store the results of the tests for further evaluation."))
312 (defmacro run-code
(code)
313 "Wrap the code in a lambda and FUNCALL it."
314 `(funcall (lambda () ,@code
)))
316 (defun use-debugger-p (e)
318 (or (not (eql *use-debugger
* :ask
))
319 (y-or-n-p "~A -- debug?" e
))))
321 (defun run-test-thunk (code)
324 (declare (special *pass
* *fail
*))
325 (handler-case (run-code code
)
327 (if (use-debugger-p condition
)
329 (return (values *pass
* *fail
* :error
)))))
330 ;; Return the result count
331 (values *pass
* *fail
* nil
)))
333 (defun %run-all-thunks
(&optional
(package *package
*))
334 "Run all of the test thunks in the package."
336 with results
= (make-instance 'test-results
)
337 for test-name being each hash-key in
(package-table package
)
338 using
(hash-value code
)
340 (multiple-value-bind (pass fail exerr
)
341 (run-test-thunk test-name code
)
342 (push test-name
(test-names results
))
343 ;; Count passed tests
345 (incf (successful-assertions results
) pass
))
346 ;; Count failed tests and record name
348 (incf (failed-assertions results
) fail
)
349 (push test-name
(failed-tests results
)))
350 ;; Count errors and record name
351 (when (eq :error exerr
)
352 (incf (execution-errors results
))
353 (push test-name
(error-tests results
))))
355 (push test-name
(missing-tests results
))
356 ;; Return the test results
357 finally
(return results
)))
359 (defun run-tests (test-names &optional
(package *package
*))
360 "Run the specified tests in package."
361 (if (eq :all test-names
)
362 (%run-all-thunks package
)
363 (%run-thunks test-names package
)))
365 (defun get-test-thunks (names &optional
(package *package
*))
366 (loop for name in names collect
367 (get-test-thunk name package
)))
369 (defun get-test-thunk (name package
)
370 (assert (get-test-code name package
) (name package
)
371 "No test defined for ~S in package ~S" name package
)
372 (list name
(coerce `(lambda () ,@(get-test-code name
)) 'function
)))
374 (defun use-debugger (&optional
(flag t
))
375 (setq *use-debugger
* flag
))
377 (defmacro with-test-listener
(listener &body body
)
378 `(let ((*test-listener
* #',listener
)) ,@body
))
380 ;;; RUN-TESTS support
382 (defun run-test-thunks (test-thunks)
383 (unless (null test-thunks
)
384 (let ((total-test-count 0)
386 (total-error-count 0))
387 (dolist (test-thunk test-thunks
)
388 (multiple-value-bind (test-count pass-count error-count
)
389 (run-test-thunk (car test-thunk
) (cadr test-thunk
))
390 (incf total-test-count test-count
)
391 (incf total-pass-count pass-count
)
392 (incf total-error-count error-count
)))
393 (unless (null (cdr test-thunks
))
394 (show-summary 'total total-test-count total-pass-count total-error-count
))
397 (defun run-test-thunk (*test-name
* thunk
)
399 (format t
"~& Test ~S not found" *test-name
*)
400 (prog ((*test-count
* 0)
405 (let ((*print-escape
* nil
))
407 (format t
"~& ~S: ~W" *test-name
* e
))
408 (if (use-debugger-p e
) e
(go exit
)))))
410 (show-summary *test-name
* *test-count
* *pass-count
*))
412 (return (values *test-count
* *pass-count
* error-count
)))))
414 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
416 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
418 (defun get-test-code (name &optional
(package *package
*))
419 (let ((table (get-package-table package
)))
421 (code (gethash name table
)))))
423 (defun get-tests (&optional
(package *package
*))
424 (loop for key being each hash-key in
(get-package-table package
)
427 (defun remove-tests (names &optional
(package *package
*))
428 (let ((table (get-package-table package
)))
432 (loop for name in names always
433 (remhash name table
))))))
435 (defun remove-all-tests (&optional
(package *package
*))
438 (remhash (find-package package
) *tests
*)))
440 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
441 ;;; Private functions
442 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
444 ;;; DEFINE-TEST support
446 ;;; ASSERTION support
448 (defclass test-results
()
456 :accessor total-assertions
)
457 (successful-assertions
460 :accessor successful-assertions
)
464 :accessor failed-assertions
)
468 :accessor execution-errors
)
472 :reader failed-tests
)
476 :reader error-tests
))
478 "Store the results of the tests for further evaluation."))
480 (defun record-result (passed type form expected actual extras
)
481 (funcall (or *test-listener
* 'default-listener
)
482 passed type
*test-name
* form expected actual
483 (and extras
(funcall extras
))
484 *test-count
* *pass-count
*))
486 (defun default-listener
487 (passed type name form expected actual extras test-count pass-count
)
488 (declare (ignore test-count pass-count
))
490 (show-failure type
(get-failure-message type
)
491 name form expected actual extras
)))
495 (defun collect-form-values (form values
)
496 (mapcan (lambda (form-arg value
)
497 (if (constantp form-arg
)
499 (list form-arg value
)))
503 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
504 ;;; Useful equality predicates for tests
505 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
507 ;;; (LOGICALLY-EQUAL x y) => true or false
508 ;;; Return true if x and y both false or both true
509 (defun logically-equal (x y
)
510 (eql (not x
) (not y
)))
512 ;;; (SET-EQUAL l1 l2 :test) => true or false
513 ;;; Return true if every element of l1 is an element of l2
515 (defun set-equal (l1 l2
&key
(test #'equal
))
518 (subsetp l1 l2
:test test
)
519 (subsetp l2 l1
:test test
)))
521 (pushnew :lisp-unit common-lisp
:*features
*)