4 ;; full output for all tests on separate pages per suite? whatever.
5 ;; maybe lift should have the option to print test suite names and test case names
9 For
*standard-input
*: an input stream
11 For
*error-output
*, *standard-output
*, and
*trace-output
*: an output stream.
13 For
*debug-io
*, *query-io
*: a bidirectional stream.
18 (setf (test-result-property *test-result
* :style-sheet
) "style.css")
19 (setf (test-result-property *test-result
* :title
) "Test Results X")
20 (setf (test-result-property *test-result
* :if-exists
) :supersede
)
21 (test-result-report *test-result
* #p
"/tmp/report.html" :html
))
23 (run-tests :suite
'(lift-test test-cursors
))
25 (run-tests :suite
'lift-test-ensure
)
27 (test-result-property *test-result
* :title
)
31 in start-test
(result test name
)
32 (push `(,name
,(current-values test
)) (tests-run result
))
34 if fails
/ errors
, will get problem appended
36 current-values comes from prototype stuff
38 use property-list format
55 <fn
> (= testsuite-run
)
59 foreach method in suite
, run-test-internal
60 if children
, foreach direct-subclass
, run-tests-internal
63 start-test - push
, name
, value onto test-placeholder
*
67 end-test - setf
:end-time
*
68 (add test-data to tests-run of result
)
71 ;; env variables need to be part saved in result
73 (defun test-result-report (result output format
)
74 (cond ((or (stringp output
)
76 (with-open-file (stream
79 :if-does-not-exist
:create
80 :if-exists
(or (test-result-property
83 (%test-result-report-stream result stream format
)))
85 (%test-result-report-stream result output format
))
87 (error "Don't know how to send a report to ~s" output
))))
89 (defun %test-result-report-stream
(result stream format
)
90 (start-report-output result stream format
)
91 (summarize-test-result result stream format
)
92 (summarize-test-environment result stream format
)
93 (when (or (failures result
) (errors result
)
94 (expected-failures result
) (expected-errors result
))
95 (summarize-test-problems result stream format
))
96 (summarize-tests-run result stream format
)
97 (end-report-output result stream format
)
98 (generate-detailed-reports result stream format
))
100 (defmethod start-report-output (result stream format
)
101 (declare (ignore result stream format
))
104 (defmethod summarize-test-result (result stream format
)
105 (declare (ignore format
))
106 (format stream
"~&Test results for: ~a~%"
107 (results-for result
))
108 (let ((complete-success?
(and (null (errors result
))
109 (null (failures result
)))))
110 (cond (complete-success?
111 (format stream
"~&~A Successful test~:P~%"
112 (length (tests-run result
))))
114 (format stream
"~&~A Test~:P~[~:;, ~:*~A Failure~:P~]~[~:;, ~:*~A Error~:P~].~%"
115 (length (tests-run result
))
116 (length (failures result
))
117 (length (errors result
)))))))
119 (defmethod summarize-test-environment (result stream format
)
120 (declare (ignore result stream format
))
123 (defmethod summarize-test-problems (result stream format
)
124 (declare (ignore result stream format
))
127 (defmethod generate-detailed-reports (result stream format
)
128 (declare (ignore result stream format
))
131 (defmethod summarize-tests-run (result stream format
)
132 (declare (ignore result stream format
)))
134 (defmethod end-report-output (result stream format
)
135 (declare (ignore result stream format
))
139 (defun summarize-test-environment (result stream format
)
140 (loop for symbol in
(sort `((*lift-dribble-pathname
*)
141 (*lift-debug-output
* interpret-lift-stream
)
142 (*lift-standard-output
* interpret-lift-stream
)
143 (*test-break-on-errors?
*)
144 (*test-do-children?
*)
145 (*lift-equality-test
*)
146 (*test-print-length
*)
148 (*lift-if-dribble-exists
*))
149 'string-lessp
:key
'first
) do
154 ;; some cruft stolen from cl-markdown
156 '((name (:author
:description
:copyright
:keywords
:date
))
157 (http-equiv (:refresh
:expires
))))
159 (defmethod start-report-output (result stream
(format (eql :html
)))
162 (test-result-property result
:title
)
163 (test-result-property result
:style-sheet
)))
165 (defmethod html-header (stream title style-sheet
)
166 (format stream
"~&<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\"
167 \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">")
168 (format stream
"~&<html>~&<head>")
170 (format stream
"~&<title>~a</title>" title
))
172 (unless (search ".css" style-sheet
)
173 (setf style-sheet
(concatenate 'string style-sheet
".css")))
174 (format stream
"~&<link type='text/css' href='~a' rel='stylesheet' />"
176 (format stream
"~&</head>~&<body>"))
178 (defmethod summarize-test-result (result stream
(format (eql :html
)))
179 (format stream
"~&<div id=\"summary\">")
180 (format stream
"~&<h1>Test results for: ~a</h1>~%"
181 (results-for result
))
182 (let ((complete-success?
(and (null (errors result
))
183 (null (failures result
)))))
184 (cond (complete-success?
185 (format stream
"~&<h2>~A Successful test~:P</h2>~%"
186 (length (tests-run result
))))
189 "~&<h2>~A Test~:P~[~:;, ~:*~A Failure~:P~]~[~:;, ~:*~A Error~:P~].</h2>~%"
190 (length (tests-run result
))
191 (length (failures result
))
192 (length (errors result
)))))
195 (print (start-time-universal result
) stream
)
196 (print (end-time-universal result
) stream
)
197 (print (real-start-time result
) stream
)
198 (print (real-end-time result
) stream
)
200 (when (and (numberp (end-time-universal result
))
201 (numberp (start-time-universal result
)))
202 (format stream
"~&<h3>Testing took: ~:d seconds</h3>"
203 (- (end-time-universal result
)
204 (start-time-universal result
))))
206 (when (and (numberp (real-end-time result
))
207 (numberp (real-start-time result
)))
208 (format stream
"~&Time: ~,2f real-time"
209 (/ (- (real-end-time result
) (real-start-time result
))
210 internal-time-units-per-second
))))
211 (format stream
"~&</div>"))
213 (defmethod summarize-test-environment (result stream
(format (eql :html
)))
214 (declare (ignore result
))
215 (format stream
"~&<div id=\"environment\">")
217 (format stream
"~&</div>"))
219 (defmethod summarize-test-problems (result stream
(format (eql :html
)))
220 (format stream
"~&<div id=\"problem-summary\">")
221 (format stream
"~&<h2>Problem Summary:</h2>")
222 (when (failures result
)
223 (summarize-test-problems-of-type
224 (failures result
) stream
"failure-summary" "Failures"))
225 (when (errors result
)
226 (summarize-test-problems-of-type
227 (errors result
) stream
"error-summary" "Errors"))
228 (when (expected-failures result
)
229 (summarize-test-problems-of-type
230 (expected-failures result
)
231 stream
"expected-failure-summary" "Expected Failures"))
232 (when (expected-errors result
)
233 (summarize-test-problems-of-type
234 (expected-errors result
) stream
"expected-failure-summary"
236 (format stream
"~&</div>"))
238 (defmethod summarize-test-problems-of-type
239 (problems stream id heading
)
240 (format stream
"~&<div id=\"id\">" id
)
241 (format stream
"~&<h3>~a</h3>" heading
)
242 (report-tests-by-suite
243 (mapcar (lambda (problem)
244 `(,(type-of (testsuite problem
))
245 ,(test-method problem
)
246 (:problem
,problem
)))
248 (format stream
"~&</div>"))
250 (defmethod summarize-tests-run (result stream
(format (eql :html
)))
251 (format stream
"~&<div id=\"results\">")
252 (format stream
"~&<h2>Tests Run:</h2>")
253 (report-tests-by-suite (tests-run result
) stream
)
254 (format stream
"~&</div>"))
256 (defun report-tests-by-suite (tests stream
)
257 (let ((current-suite nil
))
258 (loop for rest
= (sort
259 ;; FIXME - this is a hack intended to show tests
260 ;; in the order they were run (even if it works, it's
261 ;; bound to be fragile)
263 #+(or) (nreverse (copy-list tests
))
264 'string-lessp
:key
'first
) then
(rest rest
)
266 for
(suite test-name datum
) = (first rest
) do
267 (unless (eq current-suite suite
)
269 (format stream
"</div>"))
270 (setf current-suite suite
)
271 (format stream
"~&<div class=\"testsuite\">")
272 (let* ((this-suite-end (or
275 (not (eq current-suite
(first datum
))))
278 (error-count (count-if
280 (and (getf (third datum
) :problem
)
281 (typep (getf (third datum
) :problem
)
284 :end this-suite-end
))
285 (failure-count (count-if
287 (and (getf (third datum
) :problem
)
288 (typep (getf (third datum
) :problem
)
291 :end this-suite-end
))
292 (extra-class (cond ((and (= error-count
0) (= failure-count
0))
293 'testsuite-all-passed
)
295 'testsuite-some-errors
)
297 'testsuite-some-failures
))))
298 (format stream
"~&<div class=\"testsuite-title\"><table class=\"~a\"><tr><td>~a</td>" extra-class suite
)
299 (format stream
"<td class=\"testsuite-test-count\">~:d test~:p</td>"
301 (format stream
"<td class=\"testsuite-summary\">")
302 (cond ((and (= error-count
0) (= failure-count
0))
303 (format stream
"all passed"))
305 (format stream
"~[~:;~:*~:d failure~:p~]"
307 (when (and (> error-count
0) (> failure-count
0))
308 (format stream
", "))
309 (format stream
"~[~:;~:*~a error~:p~]"
311 (format stream
"</td></tr></table>")
312 (format stream
"</div>")))
313 (format stream
"~&<div class=\"test-case\">")
314 (let ((problem (getf datum
:problem
)))
315 (cond ((typep problem
'test-failure
)
316 (format stream
"~&<span class=\"test-name\">~a <a href=\"~a\" title=\"details\">[*]</a></span>"
318 (details-link stream test-name
))
320 "~&<span class=\"test-failure\">failure</span>" ))
321 ((typep problem
'test-error
)
322 (format stream
"~&<span class=\"test-name\">~a <a href=\"~a\" title=\"details\">[during ~a]</a></span>"
324 (details-link stream test-name
)
326 (format stream
"~&<span class=\"test-error\">error</span>"))
328 (format stream
"~&<span class=\"test-name\">~a</span>"
330 (let ((seconds (getf datum
:seconds
))
331 (conses (getf datum
:conses
)))
333 (format stream
"<span class=\"test-time\">~,3f</span>"
336 (format stream
"<span class=\"test-space\">~:d</span>"
338 (format stream
"~&</div>")))
340 (format stream
"</div>"))))
342 (defun details-link (stream name
)
343 (declare (ignore stream
))
344 (make-pathname :name
(format nil
"details-~a" name
)
347 (defmethod end-report-output (result stream
(format (eql :html
)))
348 (let ((style-sheet (test-result-property result
:style-sheet
)))
351 (copy-file (asdf:system-relative-pathname
'lift
"resources/style.css")
353 :name
(pathname-name style-sheet
)
354 :type
(pathname-type style-sheet
)
355 :defaults
(pathname stream
))
356 :if-exists
:supersede
))))
357 (html-footer stream
))
359 (defun html-footer (stream)
360 (format stream
"<div id=\"footer\">")
361 (format stream
"~&generated on ~a"
363 (excl:locale-print-time
365 :fmt
"%B %d, %Y %T GMT%z" :stream nil
)
367 (get-universal-time))
368 (format stream
"</div>")
369 (format stream
"~&</body></html>"))
371 (defmethod generate-detailed-reports (result stream
(format (eql :html
)))
372 (loop for
(nil test-name datum
) in
(tests-run result
)
373 when
(getf datum
:problem
) do
374 (with-open-file (out (merge-pathnames
375 (details-link stream test-name
)
378 :if-does-not-exist
:create
379 :if-exists
:supersede
)
382 (format nil
"Test ~a details | ~a"
383 test-name
(test-result-property result
:title
))
384 (test-result-property result
:style-sheet
))
385 (format out
"~&<h2>Test ~a details</h2>" test-name
)
386 (format out
"~&<a href=\"~a\">Back</a>"
387 (namestring (make-pathname :name
(pathname-name stream
)
388 :type
(pathname-type stream
))))
389 (format out
"~&<pre>")
392 (with-output-to-string (s)
393 (print-test-problem "" (getf datum
:problem
) s
))))
394 (format out
"~&</pre>")
399 (defmethod summarize-test-environment (result stream format
)
400 (loop for symbol in
(sort `((*lift-dribble-pathname
*)
401 (*lift-debug-output
* interpret-lift-stream
)
402 (*lift-standard-output
* interpret-lift-stream
)
403 (*test-break-on-errors?
*)
404 (*test-do-children?
*)
405 (*lift-equality-test
*)
406 (*test-print-length
*)
408 (*lift-if-dribble-exists
*))
409 'string-lessp
:key
'first
) do
413 (defun encode-pre (string)
414 ;; Copied from CL-Markdown
415 ;; Copied from HTML-Encode
416 ;;?? this is very consy
418 (declare (simple-string string
))
419 (let ((output (make-array (truncate (length string
) 2/3)
420 :element-type
'character
423 (with-output-to-string (out output
)
424 (loop for char across string
426 ((#\
&) (write-string "&" out
))
427 ((#\
<) (write-string "<" out
))
428 ((#\
>) (write-string ">" out
))
429 (t (write-char char out
)))))
430 (coerce output
'simple-string
)))
434 (defmethod summarize-test-result (result stream
(format (eql :describe
)))
435 (describe result stream
))
437 (defmethod summarize-tests-run (result stream
(format (eql :describe
)))
438 (declare (ignore result stream
))
443 (defmethod summarize-test-result (result stream
(format (eql :save
)))
444 (flet ((add-property (name)
445 (format stream
"~&\(~s ~a\)"
446 (intern (symbol-name name
) :keyword
)
447 (slot-value result name
))))
448 (format stream
"\(~%")
449 (add-property 'results-for
)
450 (format stream
"~&\(:date-time ~a\)" (get-universal-time))
451 (add-property 'real-start-time-universal
)
452 (add-property 'start-time-universal
)
453 (add-property 'end-time-universal
)
454 (add-property 'real-end-time-universal
)
455 (format stream
"~&\(:tests-run ")
456 (loop for
(suite name data
) in
457 ;; FIXME - this is a hack intended to show tests
458 ;; in the order they were run (even if it works, it's
459 ;; bound to be fragile)
460 (copy-list (tests-run result
))
462 (nreverse (copy-list (tests-run result
))) do
463 (labels ((out (name &key
(source data
)
465 (let* ((key (intern (symbol-name name
) :keyword
))
466 (value (getf source key
)))
467 (when (or value print-if-nil?
)
468 (format stream
"~&\(~s ~a\)" key value
))))
470 (out name
:source
(getf data
:properties
))))
471 (format stream
"\(~%")
472 (format stream
"~&\(:suite ~a\)" suite
)
473 (format stream
"~&\(:name ~a\)" name
)
474 ;; FIXME - we could make these extensible
475 (out 'start-time-universal
)
476 (out 'end-time-universal
)
480 (loop for stuff in
(getf data
:properties
) by
#'cddr do
482 (format stream
"~&\)")))
483 (format stream
"~&\)")
484 (format stream
"~&\)")))
487 (compile 'summarize-test-result
)
491 (setf (test-result-property *test-result
* :if-exists
) :supersede
)
492 (test-result-report *test-result
* #p
"/tmp/report.save" :save
))
494 (defun symbol->turtle
(symbol)
497 (loop for char across
(string-downcase (symbol-name symbol
))
498 when
(char= char
#\-
) do
(setf upcase? t
)
499 else collect
(if upcase?
500 (prog1 (char-upcase char
)
505 (defun turtlefy (thing)
508 (pathname (namestring thing
))
511 (integer (format nil
"\"~a\"^^xsd:integer" thing
))
512 (double-float (format nil
"\"~f\"^^xsd:double" thing
))
513 (single-float (format nil
"\"~f\"^^xsd:single" thing
))))
514 (symbol (symbol-name thing
))
515 (t (format nil
"\"~a\"" thing
))))
517 (defun ensure-symbol (thing)
520 (string (intern thing
))))
523 (symbol->turtle
'real-start-time-universal
)
525 (defun date->turtle
(&key
(datetime (get-universal-time)) (include-time? nil
))
527 (second minute hour day month year day-of-the-week
)
528 (decode-universal-time datetime
)
529 (declare (ignore day-of-the-week
))
530 (let ((date-part (format nil
"~d-~2,'0d-~2,'0d" year month day
))
531 (time-part (and include-time?
532 (format nil
"T-~2,'0d:~2,'0d:~2,'0d"
533 hour minute second
)))
534 (data-type (if include-time?
535 "xsd:dateTime" "xsd:date")))
536 (concatenate 'string
"\"" date-part time-part
"\"" "^^" data-type
))))
538 ;; http://www.dajobe.org/2004/01/turtle/
539 (defmethod summarize-test-result (result stream
(format (eql :turtle
)))
540 (labels ((convert-value (value type
)
542 (string (turtlefy value
))
543 (symbol (ensure-symbol value
))
544 (date (date->turtle
:datetime value
))
545 (dateTime (date->turtle
:datetime value
:include-time? t
))))
546 (add-property (name type
)
547 (let ((value (slot-value result name
)))
549 (format stream
"~&:~a ~s ;"
550 (symbol->turtle name
)
551 (convert-value value type
))))))
553 "~&@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .~%")
555 "~&@prefix : <http://www.metabang.com/2007/04/lift#> .~%")
556 (format stream
"\[~%")
557 (add-property 'results-for
'string
)
558 (add-property 'real-start-time
'dateTime
)
559 (add-property 'start-time
'dateTime
)
560 (add-property 'end-time
'dateTime
)
561 (add-property 'real-end-time
'dateTime
)
562 (format stream
"~&\:testsRun (")
563 (loop for
(suite name data
) in
564 ;; FIXME - this is a hack intended to show tests
565 ;; in the order they were run (even if it works, it's
566 ;; bound to be fragile)
567 (copy-list (tests-run result
))
569 (nreverse (copy-list (tests-run result
))) do
570 (labels ((out (name type
&key
(source data
))
571 (let* ((key (intern (symbol-name name
) :keyword
))
572 (value (getf source key
)))
574 (format stream
"~& :~a ~a ;"
575 (symbol->turtle name
)
576 (convert-value value type
)))))
578 (out name type
:source
(getf data
:properties
))))
579 (format stream
"~&\[ ")
580 (format stream
":testSuite ~s ;" (symbol-name suite
))
581 (format stream
"~& :testName ~s ;" (symbol-name name
))
582 ;; FIXME - we could make these extensible
583 (out 'start-time
'dateTime
)
584 (out 'end-time
'dateTime
)
585 (out 'result
'string
)
586 (out 'seconds
'string
)
587 (out 'conses
'string
)
588 (loop for stuff in
(getf data
:properties
) by
#'cddr do
589 (prop stuff
'string
))
590 (format stream
" \]")))
591 (format stream
" ) ~&\] . ")))
595 (setf (test-result-property *test-result
* :if-exists
) :supersede
)
596 (test-result-report *test-result
* #p
"/tmp/report.n3" :turtle
))