1 ;;;; test-suite/lib.scm --- generic support for testing
2 ;;;; Copyright (C) 1999 Free Software Foundation, Inc.
4 ;;;; This program is free software; you can redistribute it and/or modify
5 ;;;; it under the terms of the GNU General Public License as published by
6 ;;;; the Free Software Foundation; either version 2, or (at your option)
7 ;;;; any later version.
9 ;;;; This program is distributed in the hope that it will be useful,
10 ;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
11 ;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 ;;;; GNU General Public License for more details.
14 ;;;; You should have received a copy of the GNU General Public License
15 ;;;; along with this software; see the file COPYING. If not, write to
16 ;;;; the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
17 ;;;; Boston, MA 02111-1307 USA
19 (define-module (test-suite lib)
20 #:use-module (test-suite paths))
24 ;; Reporting passes and failures.
27 ;; Indicating tests that are expected to fail.
28 expect-failure expect-failure-if expect-failure-if*
30 ;; Marking independent groups of tests.
31 catch-test-errors catch-test-errors*
33 ;; Naming groups of tests in a regular fashion.
34 with-test-prefix with-test-prefix* current-test-prefix
36 ;; Reporting results in various ways.
37 register-reporter unregister-reporter reporter-registered?
38 make-count-reporter print-counts
44 ;; Finding test input files.
48 ;;;; If you're using Emacs's Scheme mode:
49 ;;;; (put 'expect-failure 'scheme-indent-function 0)
50 ;;;; (put 'with-test-prefix 'scheme-indent-function 1)
55 ;;;; Every test in the test suite has a unique name, to help
56 ;;;; developers find tests that are failing (or unexpectedly passing),
57 ;;;; and to help gather statistics.
59 ;;;; A test name is a list of printable objects. For example:
60 ;;;; ("ports.scm" "file" "read and write back list of strings")
61 ;;;; ("ports.scm" "pipe" "read")
63 ;;;; Test names may contain arbitrary objects, but they always have
64 ;;;; the following properties:
65 ;;;; - Test names can be compared with EQUAL?.
66 ;;;; - Test names can be reliably stored and retrieved with the standard WRITE
67 ;;;; and READ procedures; doing so preserves their identity.
69 ;;;; The functions for reporting results (PASS, FAIL, PASS-IF, ...)
70 ;;;; take the name of the passing/failing test as an argument.
73 ;;;; (if (= 4 (+ 2 2))
74 ;;;; (pass "simple addition"))
76 ;;;; In that case, the test name is the list ("simple addition").
78 ;;;; The WITH-TEST-PREFIX syntax and WITH-TEST-PREFIX* procedure establish
79 ;;;; a prefix for the names of all tests whose results are reported
80 ;;;; within their dynamic scope. For example:
83 ;;;; (with-test-prefix "basic arithmetic"
84 ;;;; (pass-if "addition" (= (+ 2 2) 4))
85 ;;;; (pass-if "division" (= (- 4 2) 2)))
86 ;;;; (pass-if "multiplication" (= (* 2 2) 4)))
88 ;;;; In that example, the three test names are:
89 ;;;; ("basic arithmetic" "addition"),
90 ;;;; ("basic arithmetic" "division"), and
91 ;;;; ("multiplication").
93 ;;;; WITH-TEST-PREFIX can be nested. Each WITH-TEST-PREFIX postpends
94 ;;;; a new element to the current prefix:
96 ;;;; (with-test-prefix "arithmetic"
97 ;;;; (with-test-prefix "addition"
98 ;;;; (pass-if "integer" (= (+ 2 2) 4))
99 ;;;; (pass-if "complex" (= (+ 2+3i 4+5i) 6+8i)))
100 ;;;; (with-test-prefix "subtraction"
101 ;;;; (pass-if "integer" (= (- 2 2) 0))
102 ;;;; (pass-if "complex" (= (- 2+3i 1+2i) 1+1i))))
104 ;;;; The four test names here are:
105 ;;;; ("arithmetic" "addition" "integer")
106 ;;;; ("arithmetic" "addition" "complex")
107 ;;;; ("arithmetic" "subtraction" "integer")
108 ;;;; ("arithmetic" "subtraction" "complex")
110 ;;;; To print a name for a human reader, we DISPLAY its elements,
111 ;;;; separated by ": ". So, the last set of test names would be
114 ;;;; arithmetic: addition: integer
115 ;;;; arithmetic: addition: complex
116 ;;;; arithmetic: subtraction: integer
117 ;;;; arithmetic: subtraction: complex
119 ;;;; The Guile benchmarks use with-test-prefix to include the name of
120 ;;;; the source file containing the test in the test name, to help
121 ;;;; developers to find failing tests, and to provide each file with its
127 ;;;; A reporter is a function which we apply to each test outcome.
128 ;;;; Reporters can log results, print interesting results to the
129 ;;;; standard output, collect statistics, etc.
131 ;;;; A reporter function takes one argument, RESULT; its return value
132 ;;;; is ignored. RESULT has one of the following forms:
134 ;;;; (pass TEST) - The test named TEST passed.
135 ;;;; (fail TEST) - The test named TEST failed.
136 ;;;; (xpass TEST) - The test named TEST passed unexpectedly.
137 ;;;; (xfail TEST) - The test named TEST failed, as expected.
138 ;;;; (error PREFIX) - An error occurred, with TEST as the current
139 ;;;; test name prefix. Some tests were
140 ;;;; probably not executed because of this.
142 ;;;; This library provides some standard reporters for logging results
143 ;;;; to a file, reporting interesting results to the user, and
144 ;;;; collecting totals.
146 ;;;; You can use the REGISTER-REPORTER function and friends to add
147 ;;;; whatever reporting functions you like. If you don't register any
148 ;;;; reporters, the library uses FULL-REPORTER, which simply writes
149 ;;;; all results to the standard output.
152 ;;;; with-test-prefix: naming groups of tests
153 ;;;; See the discussion of TEST
155 ;;; A fluid containing the current test prefix, as a list.
156 (define prefix-fluid (make-fluid))
157 (fluid-set! prefix-fluid '())
159 ;;; Postpend PREFIX to the current name prefix while evaluting THUNK.
160 ;;; The name prefix is only changed within the dynamic scope of the
161 ;;; call to with-test-prefix*. Return the value returned by THUNK.
162 (define (with-test-prefix* prefix thunk)
163 (with-fluids ((prefix-fluid
164 (append (fluid-ref prefix-fluid) (list prefix))))
167 ;;; (with-test-prefix PREFIX BODY ...)
168 ;;; Postpend PREFIX to the current name prefix while evaluating BODY ...
169 ;;; The name prefix is only changed within the dynamic scope of the
170 ;;; with-test-prefix expression. Return the value returned by the last
172 (defmacro with-test-prefix (prefix . body)
173 `(with-test-prefix* ,prefix (lambda () ,@body)))
175 (define (current-test-prefix)
176 (fluid-ref prefix-fluid))
179 ;;;; register-reporter, etc. --- the global reporter list
181 ;;; The global list of reporters.
182 (define reporters '())
184 ;;; The default reporter, to be used only if no others exist.
185 (define default-reporter #f)
187 ;;; Add the procedure REPORTER to the current set of reporter functions.
188 ;;; Signal an error if that reporter procedure object is already registered.
189 (define (register-reporter reporter)
190 (if (memq reporter reporters)
191 (error "register-reporter: reporter already registered: " reporter))
192 (set! reporters (cons reporter reporters)))
194 ;;; Remove the procedure REPORTER from the current set of reporter
195 ;;; functions. Signal an error if REPORTER is not currently registered.
196 (define (unregister-reporter reporter)
197 (if (memq reporter reporters)
198 (set! reporters (delq! reporter reporters))
199 (error "unregister-reporter: reporter not registered: " reporter)))
201 ;;; Return true iff REPORTER is in the current set of reporter functions.
202 (define (reporter-registered? reporter)
203 (if (memq reporter reporters) #t #f))
206 ;;; Send RESULT to all currently registered reporter functions.
207 (define (report result)
208 (if (pair? reporters)
209 (for-each (lambda (reporter) (reporter result))
211 (default-reporter result)))
214 ;;;; Some useful reporter functions.
216 ;;; Return a list of the form (COUNTER RESULTS), where:
217 ;;; - COUNTER is a reporter procedure, and
218 ;;; - RESULTS is a procedure taking no arguments which returns the
219 ;;; results seen so far by COUNTER. The return value is an alist
220 ;;; mapping outcome symbols (`pass', `fail', etc.) onto counts.
221 (define (make-count-reporter)
222 (let ((counts (map (lambda (outcome) (cons outcome 0))
223 '(pass fail xpass xfail error))))
226 (let ((pair (assq (car result) counts)))
227 (if pair (set-cdr! pair (+ 1 (cdr pair)))
228 (error "count-reporter: unexpected test result: " result))))
230 (append counts '())))))
232 ;;; Print a count reporter's results nicely. Pass this function the value
233 ;;; returned by a count reporter's RESULTS procedure.
235 (let ((tags '(pass fail xpass xfail error))
239 "unexpected passes: "
240 "expected failures: "
242 (lambda (results . port?)
243 (let ((port (if (pair? port?)
245 (current-output-port))))
247 (display-line-port port "Totals for this test run:")
250 (let ((result (assq tag results)))
252 (display-line-port port label (cdr result))
253 (display-line-port port
255 "no total available for `" tag "'"))))
259 ;;; Handy functions. Should be in a library somewhere.
260 (define (display-line . objs)
261 (for-each display objs)
263 (define (display-line-port port . objs)
264 (for-each (lambda (obj) (display obj port))
268 ;;; Turn a test name into a nice human-readable string.
269 (define (format-test-name name)
270 (call-with-output-string
272 (let loop ((name name))
275 (display (car name) port)
276 (if (pair? (cdr name))
278 (loop (cdr name))))))))
280 ;;; Return a reporter procedure which prints all results to the file
281 ;;; FILE, in human-readable form. FILE may be a filename, or a port.
282 (define (make-log-reporter file)
283 (let ((port (if (output-port? file) file
284 (open-output-file file))))
286 (display (car result) port)
288 (display (format-test-name (cadr result)) port)
290 (force-output port))))
292 ;;; A reporter that reports all results to the user.
293 (define (full-reporter result)
294 (let ((label (case (car result)
302 (display-line label ": " (format-test-name (cadr result)))
303 (error "(test-suite lib) FULL-REPORTER: unrecognized result: "
306 ;;; A reporter procedure which shows interesting results (failures,
307 ;;; unexpected passes) to the user.
308 (define (user-reporter result)
310 ((fail xpass) (full-reporter result))))
312 (set! default-reporter full-reporter)
315 ;;;; Marking independent groups of tests.
317 ;;; When test code encounters an error (like "file not found" or "()
318 ;;; is not a pair"), that may mean that that particular test can't
319 ;;; continue, or that some nearby tests shouldn't be run, but it
320 ;;; doesn't mean the whole test suite must be aborted.
322 ;;; Wrap each group of interdependent tests in a CATCH-TEST-ERRORS
323 ;;; form, so that if an error occurs, that group will be aborted, but
324 ;;; control will continue after the catch-test-errors form.
326 ;;; Evaluate thunk, catching errors. If THUNK returns without
327 ;;; signalling any errors, return a list containing its value.
328 ;;; Otherwise, return #f.
329 (define (catch-test-errors* thunk)
333 (display-line "ERROR in test "
334 (format-test-name (current-test-prefix))
337 (make-stack #t handler)
340 (throw 'catch-test-errors))))
342 ;; I don't know if we should really catch everything here. If you
343 ;; find a case where an error is signalled which really should abort
344 ;; the whole test case, feel free to adjust this appropriately.
345 (catch 'catch-test-errors
348 (lambda () (list (thunk)))
351 (report (list 'error (current-test-prefix)))
354 ;;; (catch-test-errors BODY ...)
355 ;;; Evaluate the expressions BODY ... If a BODY expression signals an
356 ;;; error, record that in the test results, and return #f. Otherwise,
357 ;;; return a list containing the value of the last BODY expression.
358 (defmacro catch-test-errors body
359 `(catch-test-errors* (lambda () ,@body)))
362 ;;;; Indicating tests that are expected to fail.
364 ;;; Fluid indicating whether we're currently expecting tests to fail.
365 (define expected-failure-fluid (make-fluid))
367 ;;; Hmm. The documentation treats EXPECT-FAILURE-IF as the primitive,
368 ;;; but in the implementation, EXPECT-FAILURE-IF* is the primitive.
370 ;;; (expect-failure-if TEST BODY ...)
371 ;;; Evaluate the expression TEST, then evaluate BODY ...
372 ;;; If TEST evaluates to a true value, expect all tests whose results
373 ;;; are reported by the BODY expressions to fail.
374 ;;; Return the value of the last BODY form.
375 (defmacro expect-failure-if (test . body)
376 `(expect-failure-if* ,test (lambda () ,@body)))
378 ;;; Call THUNK; if SHOULD-FAIL is true, expect any tests whose results
379 ;;; are reported by THUNK to fail. Return the value returned by THUNK.
380 (define (expect-failure-if* should-fail thunk)
381 (with-fluids ((expected-failure-fluid (not (not should-fail))))
384 ;;; (expect-failure BODY ...)
385 ;;; Evaluate the expressions BODY ..., expecting all tests whose results
386 ;;; they report to fail.
387 (defmacro expect-failure body
388 `(expect-failure-if #t ,@body))
391 (fluid-ref expected-failure-fluid))
394 ;;;; Reporting passes and failures.
396 (define (full-name name)
397 (append (current-test-prefix) (list name)))
400 (report (list (if (pessimist?) 'xpass 'pass)
404 (report (list (if (pessimist?) 'xfail 'fail)
407 (define (pass-if name condition)
408 ((if condition pass fail) name))
411 ;;;; Helping test cases find their files
413 ;;; Returns FILENAME, relative to the directory the test suite data
414 ;;; files were installed in, and makes sure the file exists.
415 (define (data-file filename)
416 (let ((f (in-vicinity datadir filename)))
418 (error "Test suite data file does not exist: " f))