(defpackage :test-util (:use :cl :sb-ext) (:export #:with-test #:report-test-status #:*failures* #:really-invoke-debugger #:*break-on-failure* #:*break-on-expected-failure*)) (in-package :test-util) (defvar *test-count* 0) (defvar *test-file* nil) (defvar *failures* nil) (defvar *break-on-failure* nil) (defvar *break-on-expected-failure* nil) (defmacro with-test ((&key fails-on name) &body body) (let ((block-name (gensym))) `(block ,block-name (handler-bind ((error (lambda (error) (if (expected-failure-p ,fails-on) (fail-test :expected-failure ',name error) (fail-test :unexpected-failure ',name error)) (return-from ,block-name)))) (progn (start-test) ,@body (when (expected-failure-p ,fails-on) (fail-test :unexpected-success ',name nil))))))) (defun report-test-status () (with-standard-io-syntax (with-open-file (stream "test-status.lisp-expr" :direction :output :if-exists :supersede) (format stream "~s~%" *failures*)))) (defun start-test () (unless (eq *test-file* *load-pathname*) (setf *test-file* *load-pathname*) (setf *test-count* 0)) (incf *test-count*)) (defun fail-test (type test-name condition) (push (list type *test-file* (or test-name *test-count*)) *failures*) (when (or (and *break-on-failure* (not (eq type :expected-failure))) *break-on-expected-failure*) (really-invoke-debugger condition))) (defun expected-failure-p (fails-on) (sb-impl::featurep fails-on)) (defun really-invoke-debugger (condition) (with-simple-restart (continue "Continue") (let ((*invoke-debugger-hook* *invoke-debugger-hook*)) (enable-debugger) (invoke-debugger condition))))