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