(defparameter *debug-on-error* nil
"T if we should drop into a debugger on error, NIL otherwise.")
+(defparameter *debug-on-failure* nil
+ "T if we should drop into a debugger on a failing check, NIL otherwise.")
+
(defun import-testing-symbols (package-designator)
(import '(5am::is 5am::is-true 5am::is-false 5am::signals 5am::finishes)
package-designator))
(and (satisfies-depends-p #'every))
(or (satisfies-depends-p #'some))
(not (satisfies-depends-p #'notany))))))
-
-(defun handle-unexpected-error (test error)
- "Handler for unexpected conditions raised during test
- execution."
- (when (not *debug-on-error*)
- (format *test-dribble* "F")
- (with-run-state (result-list)
- (push (make-instance 'unexpected-test-failure
- :test-case test
- :reason (format nil "Unexpected Error: ~S." error)
- :condition test)
- result-list)
- (throw 'run-block result-list))))
(defun results-status (result-list)
"Given a list of test results (generated while running a test)
(defmethod run-test-lambda ((test test-case))
(with-run-state (result-list)
- (catch 'run-block
- (bind-run-state ((current-test test))
- (handler-bind ((error (lambda (e) (handle-unexpected-error test e))))
- (restart-case
- (let ((results (return-result-list (test-lambda test))))
- (setf (status test) (results-status results)
- result-list (nconc result-list results)))
- (retest ()
- :report (lambda (stream)
- (format stream "~@<Rerun the test ~S~@:>" test))
- (%run test))
- (ignore ()
- :report (lambda (stream)
- (format stream "~@<Signal a test failure and abort the test ~S.~@:>" test))
- (push (make-instance 'test-failure :test-case test
- :reason "Failure restart.")
- result-list))))))))
+ (bind-run-state ((current-test test))
+ (labels ((abort-test (e)
+ (add-result 'unexpected-test-failure
+ :test-expr nil
+ :test-case test
+ :reason (format nil "Unexpected Error: ~S~%~A." e e)
+ :condition e))
+ (run-it ()
+ (let ((result-list '()))
+ (declare (special result-list))
+ (handler-bind ((check-failure (lambda (e)
+ (declare (ignore e))
+ (unless *debug-on-failure*
+ (invoke-restart
+ (find-restart 'ignore-failure)))))
+ (error (lambda (e)
+ (unless (or *debug-on-error*
+ (typep e 'check-failure))
+ (abort-test e)
+ (return-from run-it result-list)))))
+ (restart-case
+ (let ((*readtable* (copy-readtable))
+ (*package* (runtime-package test)))
+ (if (collect-profiling-info test)
+ (setf (profiling-info test)
+ (arnesi:collect-timing (test-lambda test)))
+ (funcall (test-lambda test))))
+ (retest ()
+ :report (lambda (stream)
+ (format stream "~@<Rerun the test ~S~@:>" test))
+ (return-from run-it (run-it)))
+ (ignore ()
+ :report (lambda (stream)
+ (format stream "~@<Signal an exceptional test failure and abort the test ~S.~@:>" test))
+ (abort-test (make-instance 'test-failure :test-case test
+ :reason "Failure restart."))))
+ result-list))))
+ (let ((results (run-it)))
+ (setf (status test) (results-status results)
+ result-list (nconc result-list results)))))))
(defgeneric %run (test-spec)
(:documentation "Internal method for running a test. Does not
(defmethod %run ((test test-case))
(run-resolving-dependencies test))
+(defmethod %run ((tests list))
+ (mapc #'%run tests))
+
(defmethod %run ((suite test-suite))
(let ((suite-results '()))
- (bind-run-state ((result-list '()))
- (loop for test being the hash-values of (tests suite)
- do (%run test)
- finally (setf suite-results result-list)))
- (setf (status suite) (every (lambda (res)
- (typep res 'test-passed))
- suite-results))
- (with-run-state (result-list)
- (setf result-list (nconc result-list suite-results)))))
+ (flet ((run-tests ()
+ (loop
+ for test being the hash-values of (tests suite)
+ do (%run test))))
+ (unwind-protect
+ (bind-run-state ((result-list '()))
+ (unwind-protect
+ (if (collect-profiling-info suite)
+ (setf (profiling-info suite) (collect-timing #'run-tests))
+ (run-tests)))
+ (setf suite-results result-list
+ (status suite) (every (lambda (res)
+ (typep res 'test-passed))
+ suite-results)))
+ (with-run-state (result-list)
+ (setf result-list (nconc result-list suite-results)))))))
(defmethod %run ((test-name symbol))
(when-bind test (get-test test-name)
;;;; ** Public entry points
-(defun run! (test-spec)
+(defun run! (&optional (test-spec *suite*))
"Equivalent to (explain (run TEST-SPEC))."
(explain! (run test-spec)))
detailed-text-explainer with output going to *test-dribble*"
(explain (make-instance 'detailed-text-explainer) result-list *test-dribble*))
+(defun debug! (&optional (test-spec *suite*))
+ "Calls (run! test-spec) but enters the debugger if any kind of error happens."
+ (let ((*debug-on-error* t)
+ (*debug-on-failure* t))
+ (run! test-spec)))
+
(defun run (test-spec)
- "Run the test specified by TEST-SPEC.
+ "Run the test specified by TEST-SPEC.
TEST-SPEC can be either a symbol naming a test or test suite, or
a testable-object object. This function changes the operations
performed by the !, !! and !!! functions."
- (psetf *!* (lambda ()
- (loop for test being the hash-keys of *test*
- do (setf (status (get-test test)) :unknown))
- (bind-run-state ((result-list '()))
- (%run test-spec)
- result-list))
- *!!* *!*
- *!!!* *!!*)
- (funcall *!*))
+ (psetf *!* (lambda ()
+ (loop for test being the hash-keys of *test*
+ do (setf (status (get-test test)) :unknown))
+ (bind-run-state ((result-list '()))
+ (with-simple-restart (explain "Ignore the rest of the tests and explain current results")
+ (%run test-spec))
+ result-list))
+ *!!* *!*
+ *!!!* *!!*)
+ (funcall *!*))
(defun ! ()
"Rerun the most recently run test and explain the results."