* Removed old system to check for exceptions.
[bpt/guile.git] / test-suite / lib.scm
CommitLineData
000ee07f 1;;;; test-suite/lib.scm --- generic support for testing
57e7f270 2;;;; Copyright (C) 1999, 2000 Free Software Foundation, Inc.
000ee07f
JB
3;;;;
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.
8;;;;
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.
13;;;;
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
18
6b4113af
DH
19(define-module (test-suite lib)
20 :use-module (ice-9 regex))
000ee07f
JB
21
22(export
23
6b4113af
DH
24 ;; Exceptions which are commonly being tested for.
25 exception:out-of-range exception:wrong-type-arg
26
000ee07f 27 ;; Reporting passes and failures.
6b4113af
DH
28 run-test
29 pass-if expect-fail
30 pass-if-exception expect-fail-exception
000ee07f
JB
31
32 ;; Naming groups of tests in a regular fashion.
33 with-test-prefix with-test-prefix* current-test-prefix
34
35 ;; Reporting results in various ways.
36 register-reporter unregister-reporter reporter-registered?
37 make-count-reporter print-counts
38 make-log-reporter
087dab1c 39 full-reporter
000ee07f 40 user-reporter
88f9ab70 41 format-test-name)
000ee07f
JB
42
43
44;;;; If you're using Emacs's Scheme mode:
000ee07f
JB
45;;;; (put 'with-test-prefix 'scheme-indent-function 1)
46
47\f
57e7f270
DH
48;;;; CORE FUNCTIONS
49;;;;
50;;;; The function (run-test name expected-result thunk) is the heart of the
51;;;; testing environment. The first parameter NAME is a unique name for the
52;;;; test to be executed (for an explanation of this parameter see below under
53;;;; TEST NAMES). The second parameter EXPECTED-RESULT is a boolean value
54;;;; that indicates whether the corresponding test is expected to pass. If
55;;;; EXPECTED-RESULT is #t the test is expected to pass, if EXPECTED-RESULT is
56;;;; #f the test is expected to fail. Finally, THUNK is the function that
57;;;; actually performs the test. For example:
58;;;;
59;;;; (run-test "integer addition" #t (lambda () (= 2 (+ 1 1))))
60;;;;
61;;;; To report success, THUNK should either return #t or throw 'pass. To
62;;;; report failure, THUNK should either return #f or throw 'fail. If THUNK
63;;;; returns a non boolean value or throws 'unresolved, this indicates that
64;;;; the test did not perform as expected. For example the property that was
65;;;; to be tested could not be tested because something else went wrong.
66;;;; THUNK may also throw 'untested to indicate that the test was deliberately
67;;;; not performed, for example because the test case is not complete yet.
68;;;; Finally, if THUNK throws 'unsupported, this indicates that this test
69;;;; requires some feature that is not available in the configured testing
70;;;; environment. All other exceptions thrown by THUNK are considered as
71;;;; errors.
72;;;;
6b4113af
DH
73;;;;
74;;;; Convenience macros for tests expected to pass or fail
75;;;;
57e7f270
DH
76;;;; * (pass-if name body) is a short form for
77;;;; (run-test name #t (lambda () body))
78;;;; * (expect-fail name body) is a short form for
79;;;; (run-test name #f (lambda () body))
80;;;;
81;;;; For example:
82;;;;
83;;;; (pass-if "integer addition" (= 2 (+ 1 1)))
6b4113af
DH
84;;;;
85;;;;
86;;;; Convenience macros to test for exceptions
87;;;;
88;;;; The following macros take exception parameters which are pairs
89;;;; (type . message), where type is a symbol that denotes an exception type
90;;;; like 'wrong-type-arg or 'out-of-range, and message is a string holding a
91;;;; regular expression that describes the error message for the exception
92;;;; like "Argument .* out of range".
93;;;;
94;;;; * (pass-if-exception name exception body) will pass if the execution of
95;;;; body causes the given exception to be thrown. If no exception is
96;;;; thrown, the test fails. If some other exception is thrown, is is an
97;;;; error.
98;;;; * (expect-fail-exception name exception body) will pass unexpectedly if
99;;;; the execution of body causes the given exception to be thrown. If no
100;;;; exception is thrown, the test fails expectedly. If some other
101;;;; exception is thrown, it is an error.
57e7f270
DH
102
103\f
000ee07f
JB
104;;;; TEST NAMES
105;;;;
106;;;; Every test in the test suite has a unique name, to help
107;;;; developers find tests that are failing (or unexpectedly passing),
108;;;; and to help gather statistics.
109;;;;
110;;;; A test name is a list of printable objects. For example:
111;;;; ("ports.scm" "file" "read and write back list of strings")
112;;;; ("ports.scm" "pipe" "read")
113;;;;
114;;;; Test names may contain arbitrary objects, but they always have
115;;;; the following properties:
116;;;; - Test names can be compared with EQUAL?.
117;;;; - Test names can be reliably stored and retrieved with the standard WRITE
118;;;; and READ procedures; doing so preserves their identity.
119;;;;
000ee07f
JB
120;;;; For example:
121;;;;
57e7f270 122;;;; (pass-if "simple addition" (= 4 (+ 2 2)))
000ee07f
JB
123;;;;
124;;;; In that case, the test name is the list ("simple addition").
125;;;;
126;;;; The WITH-TEST-PREFIX syntax and WITH-TEST-PREFIX* procedure establish
127;;;; a prefix for the names of all tests whose results are reported
128;;;; within their dynamic scope. For example:
129;;;;
130;;;; (begin
131;;;; (with-test-prefix "basic arithmetic"
132;;;; (pass-if "addition" (= (+ 2 2) 4))
05c4ba00 133;;;; (pass-if "subtraction" (= (- 4 2) 2)))
000ee07f
JB
134;;;; (pass-if "multiplication" (= (* 2 2) 4)))
135;;;;
136;;;; In that example, the three test names are:
137;;;; ("basic arithmetic" "addition"),
05c4ba00 138;;;; ("basic arithmetic" "subtraction"), and
000ee07f
JB
139;;;; ("multiplication").
140;;;;
141;;;; WITH-TEST-PREFIX can be nested. Each WITH-TEST-PREFIX postpends
142;;;; a new element to the current prefix:
143;;;;
144;;;; (with-test-prefix "arithmetic"
145;;;; (with-test-prefix "addition"
146;;;; (pass-if "integer" (= (+ 2 2) 4))
147;;;; (pass-if "complex" (= (+ 2+3i 4+5i) 6+8i)))
148;;;; (with-test-prefix "subtraction"
149;;;; (pass-if "integer" (= (- 2 2) 0))
150;;;; (pass-if "complex" (= (- 2+3i 1+2i) 1+1i))))
151;;;;
152;;;; The four test names here are:
153;;;; ("arithmetic" "addition" "integer")
154;;;; ("arithmetic" "addition" "complex")
155;;;; ("arithmetic" "subtraction" "integer")
156;;;; ("arithmetic" "subtraction" "complex")
157;;;;
158;;;; To print a name for a human reader, we DISPLAY its elements,
159;;;; separated by ": ". So, the last set of test names would be
160;;;; reported as:
161;;;;
162;;;; arithmetic: addition: integer
163;;;; arithmetic: addition: complex
164;;;; arithmetic: subtraction: integer
165;;;; arithmetic: subtraction: complex
166;;;;
167;;;; The Guile benchmarks use with-test-prefix to include the name of
168;;;; the source file containing the test in the test name, to help
169;;;; developers to find failing tests, and to provide each file with its
170;;;; own namespace.
171
172\f
173;;;; REPORTERS
57e7f270 174;;;;
000ee07f
JB
175;;;; A reporter is a function which we apply to each test outcome.
176;;;; Reporters can log results, print interesting results to the
177;;;; standard output, collect statistics, etc.
178;;;;
57e7f270
DH
179;;;; A reporter function takes two mandatory arguments, RESULT and TEST, and
180;;;; possibly additional arguments depending on RESULT; its return value
000ee07f
JB
181;;;; is ignored. RESULT has one of the following forms:
182;;;;
57e7f270
DH
183;;;; pass - The test named TEST passed.
184;;;; Additional arguments are ignored.
185;;;; upass - The test named TEST passed unexpectedly.
186;;;; Additional arguments are ignored.
187;;;; fail - The test named TEST failed.
188;;;; Additional arguments are ignored.
189;;;; xfail - The test named TEST failed, as expected.
190;;;; Additional arguments are ignored.
191;;;; unresolved - The test named TEST did not perform as expected, for
192;;;; example the property that was to be tested could not be
193;;;; tested because something else went wrong.
194;;;; Additional arguments are ignored.
195;;;; untested - The test named TEST was not actually performed, for
196;;;; example because the test case is not complete yet.
197;;;; Additional arguments are ignored.
198;;;; unsupported - The test named TEST requires some feature that is not
199;;;; available in the configured testing environment.
200;;;; Additional arguments are ignored.
201;;;; error - An error occurred while the test named TEST was
202;;;; performed. Since this result means that the system caught
203;;;; an exception it could not handle, the exception arguments
204;;;; are passed as additional arguments.
000ee07f
JB
205;;;;
206;;;; This library provides some standard reporters for logging results
207;;;; to a file, reporting interesting results to the user, and
208;;;; collecting totals.
087dab1c
JB
209;;;;
210;;;; You can use the REGISTER-REPORTER function and friends to add
211;;;; whatever reporting functions you like. If you don't register any
212;;;; reporters, the library uses FULL-REPORTER, which simply writes
213;;;; all results to the standard output.
000ee07f
JB
214
215\f
57e7f270
DH
216;;;; MISCELLANEOUS
217;;;;
218
6b4113af
DH
219;;; Define some exceptions which are commonly being tested for.
220(define exception:out-of-range
221 (cons 'out-of-range "^Argument .*out of range"))
222(define exception:wrong-type-arg
223 (cons 'wrong-type-arg "^Wrong type argument"))
224
57e7f270
DH
225;;; Display all parameters to the default output port, followed by a newline.
226(define (display-line . objs)
227 (for-each display objs)
228 (newline))
229
230;;; Display all parameters to the given output port, followed by a newline.
231(define (display-line-port port . objs)
232 (for-each (lambda (obj) (display obj port)) objs)
233 (newline port))
234
235\f
236;;;; CORE FUNCTIONS
237;;;;
238
239;;; The central testing routine.
240;;; The idea is taken from Greg, the GNUstep regression test environment.
241(define run-test #f)
242(let ((test-running #f))
243 (define (local-run-test name expect-pass thunk)
244 (if test-running
245 (error "Nested calls to run-test are not permitted.")
246 (let ((test-name (full-name name)))
247 (set! test-running #t)
248 (catch #t
249 (lambda ()
250 (let ((result (thunk)))
251 (if (eq? result #t) (throw 'pass))
252 (if (eq? result #f) (throw 'fail))
253 (throw 'unresolved)))
254 (lambda (key . args)
255 (case key
256 ((pass)
257 (report (if expect-pass 'pass 'upass) test-name))
258 ((fail)
259 (report (if expect-pass 'fail 'xfail) test-name))
260 ((unresolved untested unsupported)
261 (report key test-name))
262 ((quit)
263 (report 'unresolved test-name)
264 (quit))
265 (else
266 (report 'error test-name (cons key args))))))
267 (set! test-running #f))))
268 (set! run-test local-run-test))
269
270;;; A short form for tests that are expected to pass, taken from Greg.
6ad9007a 271(defmacro pass-if (name body . rest)
5c96bc39 272 `(run-test ,name #t (lambda () ,body ,@rest)))
57e7f270
DH
273
274;;; A short form for tests that are expected to fail, taken from Greg.
6ad9007a
DH
275(defmacro expect-fail (name body . rest)
276 `(run-test ,name #f (lambda () ,body ,@rest)))
57e7f270 277
6b4113af
DH
278;;; A helper function to implement the macros that test for exceptions.
279(define (run-test-exception name exception expect-pass thunk)
280 (run-test name expect-pass
281 (lambda ()
282 (catch (car exception)
283 (lambda () (thunk) #f)
284 (lambda (key proc message . rest)
285 (if (not (string-match (cdr exception) message))
286 (apply throw key proc message rest)
287 #t))))))
288
289;;; A short form for tests that expect a certain exception to be thrown.
290(defmacro pass-if-exception (name exception body . rest)
291 `(,run-test-exception ,name ,exception #t (lambda () ,body ,@rest)))
292
293;;; A short form for tests expected to fail to throw a certain exception.
294(defmacro expect-fail-exception (name exception body . rest)
295 `(,run-test-exception ,name ,exception #f (lambda () ,body ,@rest)))
296
57e7f270
DH
297\f
298;;;; TEST NAMES
299;;;;
300
301;;;; Turn a test name into a nice human-readable string.
302(define (format-test-name name)
303 (call-with-output-string
304 (lambda (port)
305 (let loop ((name name)
306 (separator ""))
307 (if (pair? name)
308 (begin
309 (display separator port)
310 (display (car name) port)
311 (loop (cdr name) ": ")))))))
312
313;;;; For a given test-name, deliver the full name including all prefixes.
314(define (full-name name)
315 (append (current-test-prefix) (list name)))
000ee07f
JB
316
317;;; A fluid containing the current test prefix, as a list.
318(define prefix-fluid (make-fluid))
319(fluid-set! prefix-fluid '())
57e7f270
DH
320(define (current-test-prefix)
321 (fluid-ref prefix-fluid))
000ee07f
JB
322
323;;; Postpend PREFIX to the current name prefix while evaluting THUNK.
324;;; The name prefix is only changed within the dynamic scope of the
325;;; call to with-test-prefix*. Return the value returned by THUNK.
326(define (with-test-prefix* prefix thunk)
327 (with-fluids ((prefix-fluid
328 (append (fluid-ref prefix-fluid) (list prefix))))
329 (thunk)))
330
331;;; (with-test-prefix PREFIX BODY ...)
332;;; Postpend PREFIX to the current name prefix while evaluating BODY ...
333;;; The name prefix is only changed within the dynamic scope of the
334;;; with-test-prefix expression. Return the value returned by the last
335;;; BODY expression.
336(defmacro with-test-prefix (prefix . body)
337 `(with-test-prefix* ,prefix (lambda () ,@body)))
338
000ee07f 339\f
57e7f270
DH
340;;;; REPORTERS
341;;;;
000ee07f
JB
342
343;;; The global list of reporters.
344(define reporters '())
345
087dab1c
JB
346;;; The default reporter, to be used only if no others exist.
347(define default-reporter #f)
348
000ee07f
JB
349;;; Add the procedure REPORTER to the current set of reporter functions.
350;;; Signal an error if that reporter procedure object is already registered.
351(define (register-reporter reporter)
352 (if (memq reporter reporters)
353 (error "register-reporter: reporter already registered: " reporter))
354 (set! reporters (cons reporter reporters)))
355
356;;; Remove the procedure REPORTER from the current set of reporter
357;;; functions. Signal an error if REPORTER is not currently registered.
358(define (unregister-reporter reporter)
359 (if (memq reporter reporters)
360 (set! reporters (delq! reporter reporters))
361 (error "unregister-reporter: reporter not registered: " reporter)))
362
363;;; Return true iff REPORTER is in the current set of reporter functions.
364(define (reporter-registered? reporter)
365 (if (memq reporter reporters) #t #f))
366
000ee07f 367;;; Send RESULT to all currently registered reporter functions.
57e7f270 368(define (report . args)
087dab1c 369 (if (pair? reporters)
57e7f270 370 (for-each (lambda (reporter) (apply reporter args))
087dab1c 371 reporters)
57e7f270 372 (apply default-reporter args)))
000ee07f
JB
373
374\f
57e7f270
DH
375;;;; Some useful standard reporters:
376;;;; Count reporters count the occurrence of each test result type.
377;;;; Log reporters write all test results to a given log file.
378;;;; Full reporters write all test results to the standard output.
379;;;; User reporters write interesting test results to the standard output.
380
381;;; The complete list of possible test results.
382(define result-tags
383 '((pass "PASS" "passes: ")
384 (fail "FAIL" "failures: ")
385 (upass "UPASS" "unexpected passes: ")
386 (xfail "XFAIL" "expected failures: ")
387 (unresolved "UNRESOLVED" "unresolved test cases: ")
388 (untested "UNTESTED" "untested test cases: ")
389 (unsupported "UNSUPPORTED" "unsupported test cases: ")
390 (error "ERROR" "errors: ")))
391
392;;; The list of important test results.
393(define important-result-tags
394 '(fail upass unresolved error))
395
396;;; Display a single test result in formatted form to the given port
397(define (print-result port result name . args)
398 (let* ((tag (assq result result-tags))
399 (label (if tag (cadr tag) #f)))
400 (if label
401 (begin
402 (display label port)
403 (display ": " port)
404 (display (format-test-name name) port)
405 (if (pair? args)
406 (begin
407 (display " - arguments: " port)
408 (write args port)))
409 (newline port))
410 (error "(test-suite lib) FULL-REPORTER: unrecognized result: "
411 result))))
000ee07f
JB
412
413;;; Return a list of the form (COUNTER RESULTS), where:
414;;; - COUNTER is a reporter procedure, and
415;;; - RESULTS is a procedure taking no arguments which returns the
416;;; results seen so far by COUNTER. The return value is an alist
417;;; mapping outcome symbols (`pass', `fail', etc.) onto counts.
418(define (make-count-reporter)
57e7f270 419 (let ((counts (map (lambda (tag) (cons (car tag) 0)) result-tags)))
000ee07f 420 (list
57e7f270
DH
421 (lambda (result name . args)
422 (let ((pair (assq result counts)))
423 (if pair
424 (set-cdr! pair (+ 1 (cdr pair)))
425 (error "count-reporter: unexpected test result: "
426 (cons result (cons name args))))))
000ee07f
JB
427 (lambda ()
428 (append counts '())))))
429
430;;; Print a count reporter's results nicely. Pass this function the value
431;;; returned by a count reporter's RESULTS procedure.
57e7f270
DH
432(define (print-counts results . port?)
433 (let ((port (if (pair? port?)
434 (car port?)
435 (current-output-port))))
436 (newline port)
437 (display-line-port port "Totals for this test run:")
438 (for-each
439 (lambda (tag)
440 (let ((result (assq (car tag) results)))
441 (if result
442 (display-line-port port (caddr tag) (cdr result))
443 (display-line-port port
444 "Test suite bug: "
445 "no total available for `" (car tag) "'"))))
446 result-tags)
447 (newline port)))
000ee07f
JB
448
449;;; Return a reporter procedure which prints all results to the file
450;;; FILE, in human-readable form. FILE may be a filename, or a port.
451(define (make-log-reporter file)
452 (let ((port (if (output-port? file) file
453 (open-output-file file))))
57e7f270
DH
454 (lambda args
455 (apply print-result port args)
000ee07f
JB
456 (force-output port))))
457
087dab1c 458;;; A reporter that reports all results to the user.
57e7f270
DH
459(define (full-reporter . args)
460 (apply print-result (current-output-port) args))
087dab1c
JB
461
462;;; A reporter procedure which shows interesting results (failures,
57e7f270
DH
463;;; unexpected passes etc.) to the user.
464(define (user-reporter result name . args)
465 (if (memq result important-result-tags)
466 (apply full-reporter result name args)))
087dab1c
JB
467
468(set! default-reporter full-reporter)