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