3 (:export
#:with-test
#:report-test-status
#:*failures
*
4 #:really-invoke-debugger
5 #:*break-on-failure
* #:*break-on-expected-failure
*
6 #:make-kill-thread
#:make-join-thread
9 (in-package :test-util
)
11 (defvar *test-count
* 0)
12 (defvar *test-file
* nil
)
13 (defvar *failures
* nil
)
14 (defvar *break-on-failure
* nil
)
15 (defvar *break-on-expected-failure
* nil
)
17 (defvar *threads-to-kill
*)
18 (defvar *threads-to-join
*)
20 (eval-when (:compile-toplevel
:load-toplevel
:execute
)
23 (sb-posix:putenv
(format nil
"SBCL_MACHINE_TYPE=~A" (machine-type)))
24 (sb-posix:putenv
(format nil
"SBCL_SOFTWARE_TYPE=~A" (software-type)))
27 (defun make-kill-thread (&rest args
)
28 (let ((thread (apply #'sb-thread
:make-thread args
)))
29 (when (boundp '*threads-to-kill
*)
30 (push thread
*threads-to-kill
*))
34 (defun make-join-thread (&rest args
)
35 (let ((thread (apply #'sb-thread
:make-thread args
)))
36 (when (boundp '*threads-to-join
*)
37 (push thread
*threads-to-join
*))
40 (defun log-msg (&rest args
)
41 (format *trace-output
* "~&::: ")
42 (apply #'format
*trace-output
* args
)
43 (terpri *trace-output
*)
44 (force-output *trace-output
*))
46 (defmacro with-test
((&key fails-on broken-on skipped-on name
)
48 (let ((block-name (gensym))
49 #+sb-thread
(threads (gensym "THREADS")))
53 (symbol (let ((package (symbol-package x
)))
55 (eql package
(find-package "CL"))
56 (eql package
(find-package "KEYWORD"))
57 (eql (mismatch "SB-" (package-name package
)) 3))))
59 (unless (tree-equal name name
:test
#'name-ok
)
60 (error "test name must be all-keywords: ~S" name
)))
64 ((broken-p ,broken-on
)
65 (fail-test :skipped-broken
',name
"Test broken on this platform"))
66 ((skipped-p ,skipped-on
)
67 (fail-test :skipped-disabled
',name
"Test disabled for this combination of platform and features"))
69 (let (#+sb-thread
(,threads
(sb-thread:list-all-threads
))
70 (*threads-to-join
* nil
)
71 (*threads-to-kill
* nil
))
73 (handler-bind ((error (lambda (error)
74 (if (expected-failure-p ,fails-on
)
75 (fail-test :expected-failure
',name error
)
76 (fail-test :unexpected-failure
',name error
))
77 (return-from ,block-name
))))
79 (log-msg "Running ~S" ',name
)
82 (let ((any-leftover nil
))
83 (dolist (thread *threads-to-join
*)
84 (ignore-errors (sb-thread:join-thread thread
)))
85 (dolist (thread *threads-to-kill
*)
86 (ignore-errors (sb-thread:terminate-thread thread
)))
87 (setf ,threads
(union (union *threads-to-kill
*
90 #+(and sb-safepoint-strictly
(not win32
))
91 (dolist (thread (sb-thread:list-all-threads
))
92 (when (typep thread
'sb-thread
:signal-handling-thread
)
93 (ignore-errors (sb-thread:join-thread thread
))))
94 (dolist (thread (sb-thread:list-all-threads
))
95 (unless (or (not (sb-thread:thread-alive-p thread
))
96 (eql (the sb-thread
:thread thread
)
97 sb-thread
:*current-thread
*)
98 (member thread
,threads
)
99 (sb-thread:thread-ephemeral-p thread
))
100 (setf any-leftover thread
)
101 (ignore-errors (sb-thread:terminate-thread thread
))))
103 (fail-test :leftover-thread
',name any-leftover
)
104 (return-from ,block-name
)))
105 (if (expected-failure-p ,fails-on
)
106 (fail-test :unexpected-success
',name nil
)
107 (log-msg "Success ~S" ',name
)))))))))))
109 (defun report-test-status ()
110 (with-standard-io-syntax
111 (with-open-file (stream "test-status.lisp-expr"
113 :if-exists
:supersede
)
114 (format stream
"~s~%" *failures
*))))
117 (unless (eq *test-file
* *load-pathname
*)
118 (setf *test-file
* *load-pathname
*)
119 (setf *test-count
* 0))
122 (defun really-invoke-debugger (condition)
123 (with-simple-restart (continue "Continue")
124 (let ((*invoke-debugger-hook
* *invoke-debugger-hook
*))
126 (invoke-debugger condition
))))
128 (defun fail-test (type test-name condition
)
129 (if (stringp condition
)
130 (log-msg "~@<~A ~S ~:_~A~:>"
131 type test-name condition
)
132 (log-msg "~@<~A ~S ~:_due to ~S: ~4I~:_\"~A\"~:>"
133 type test-name condition condition
))
134 (push (list type
*test-file
* (or test-name
*test-count
*))
136 (unless (stringp condition
)
137 (when (or (and *break-on-failure
*
138 (not (eq type
:expected-failure
)))
139 *break-on-expected-failure
*)
140 (really-invoke-debugger condition
))))
142 (defun expected-failure-p (fails-on)
143 (sb-impl::featurep fails-on
))
145 (defun broken-p (broken-on)
146 (sb-impl::featurep broken-on
))
148 (defun skipped-p (skipped-on)
149 (sb-impl::featurep skipped-on
))
151 ;;; Repeat calling THUNK until its cumulated runtime, measured using
152 ;;; GET-INTERNAL-RUN-TIME, is larger than PRECISION. Repeat this
153 ;;; REPETITIONS many times and return the time one call to THUNK took
154 ;;; in seconds as a float, according to the minimum of the cumulated
155 ;;; runtimes over the repetitions.
156 ;;; This allows to easily measure the runtime of expressions that take
157 ;;; much less time than one internal time unit. Also, the results are
158 ;;; unaffected, modulo quantization effects, by changes to
159 ;;; INTERNAL-TIME-UNITS-PER-SECOND.
160 ;;; Taking the minimum is intended to reduce the error introduced by
161 ;;; garbage collections occurring at unpredictable times. The inner
162 ;;; loop doubles the number of calls to THUNK each time before again
163 ;;; measuring the time spent, so that the time measurement overhead
164 ;;; doesn't distort the result if calling THUNK takes very little time.
165 (defun runtime* (thunk repetitions precision
)
166 (loop repeat repetitions
168 (loop with start
= (get-internal-run-time)
170 for n
= 1 then
(* n
2)
171 for total-runs
= n then
(+ total-runs n
)
174 (setf duration
(- (get-internal-run-time) start
))
175 when
(> duration precision
)
176 return
(/ (float duration
) (float total-runs
)))
177 into min-internal-time-units-per-call
178 finally
(return (/ min-internal-time-units-per-call
179 (float internal-time-units-per-second
)))))
181 (defmacro runtime
(form &key
(repetitions 3) (precision 10))
182 `(runtime* (lambda () ,form
) ,repetitions
,precision
))