;;; Basic functions for the scsh-test-suite ;;; Author: 2001 David Frese ;; --- The list to store the tests --- (define *test-list* '()) ;; --- add-test! ------------------------------------------------ ;; This is the main function to add a test to the test-suite ;; name - a symbol naming the test uniquely ;; group - a symbol for the group of this test ;; proc - the function that does the test ;; args - the arguments for proc ;; add-test deletes all previously added tests that have the same ;; name (group is ignored)! ;; proc should return #f or signal an error, if the test failed. ;; Every other value means, that the test succeeded. (define (add-test! name group proc . args) (let ((test (make-testdt name group proc args))) (let ((other (filter (lambda (test) (equal? (testdt-name test) name)) *test-list*))) (for-each (lambda (test) (set! *test-list* (delete! test *test-list*))) other)) (set! *test-list* (cons test *test-list*)))) (define (find-test name) (find (lambda (test) (eq? (testdt-name test) name)) *test-list*)) ;; --- add-test-multiple! ---------------------------------------- ;; This function calls add-test! multiple times, with the same proc, ;; but different arguments. ;; name, group, proc see add-test! above ;; input-lists - each additional parameter has to be a list, specifying ;; alternative operands for proc. ;; Now add-test! is called for each permutation of input-lists. ;; If there's more than 1 permutation, the name is appended with ;; "-1"..."-n" respectively. ;; Example: ;; (add-test-multiple! 'test 'general proc '(a b) '(1 2)) ;; results in 4 tests, that could have been generated with ;; (add-test 'test-1 'general proc 'a 1) ;; (add-test 'test-2 'general proc 'b 1) ;; (add-test 'test-3 'general proc 'a 2) ;; (add-test 'test-4 'general proc 'b 2) ;; Note: In future versions, these tests will run simultanously ;; with multi-threading. (define (add-test-multiple! name group proc . input-lists) (let* ((permutations (permute-lists input-lists)) (single? (and (not (null? permutations)) (null? (cdr permutations))))) (let loop ((i 0) (permutations permutations)) (if (not (null? permutations)) (let ((input-params (car permutations)) (new-name (if single? name (string->symbol (string-append (symbol->string name) "-" (number->string i)))))) (apply add-test! new-name group proc input-params) (loop (+ i 1) (cdr permutations))))))) (define (permute-lists lists) (cond ((null? lists) lists) ((null? (cdr lists)) (map list (car lists))) (else (let ((first-list (car lists)) (rest-perm (permute-lists (cdr lists)))) (fold-right (lambda (elem result) (append (map (lambda (new-param) (cons new-param elem)) first-list) result)) '() rest-perm))))) ;; --- Functions for the test-datatype --- (define-record-type testdt :testdt (make-testdt name group proc args) testdt? (name testdt-name) (group testdt-group) (proc testdt-proc) (args testdt-args)) ;; --- Basic function to make a test --- (define (run-test test . rest) (let ((silent (if (null? rest) #f (car rest))) (name (testdt-name test)) (group (testdt-group test)) (proc (testdt-proc test)) (args (testdt-args test))) (let ((display-start (lambda () (display "Testing ") (display group) (display ":") (display name) (display " ... ")))) (if (not silent) (display-start)) (call-with-current-continuation (lambda (k) (if (with-handler (lambda (cond more) (display "Error: ") (display cond) (newline) (k #f)) (lambda () (apply proc args))) (begin (if silent (display ".") (display "OK\n")) #t) (begin (if silent (begin (newline) (display-start))) (display "Error! Input was ") (display args) (newline) #f))))))) ;; --- Exported functions to make a test ------------------------------- ;; The following 3 functions start the testing. They all have an ;; optional parameter >silent< with default #f. if silent is #t, ;; only those tests that signaled an error are printed on the screen. ;; test-single - runs the test with that name, returns the result of proc. ;; test-group - runs all tests that are part of that group. the result ;; is unspecified. ;; test-all - runs all tests in the test-suite. (define (test-single name . rest) (let ((test (find-test name))) (if test (apply run-test test rest) (begin (display "Test ") (display name) (display " not found") (newline))))) (define (test-single/args name . args) (let* ((test (find-test name)) (group (testdt-group test)) (proc (testdt-proc test))) (run-test (apply make-testdt name group proc args)))) (define (test-group group . rest) (let ((tests (filter (lambda (test) (eq? (testdt-group test) group)) *test-list*))) (if (null? tests) (begin (display "Group ") (display group) (display " doesn't contain any tests") (newline)) (for-each (lambda (test) (apply run-test test rest)) tests)))) (define (test-all . rest) (for-each (lambda (test) (apply run-test test rest)) *test-list*)) ;; --- Summary functions ------------------------------------------- ;; test-summary displays all registered tests in the test-suite, if ;; called with no arguments. Calling it with the additional parameter ;; group, displays only those tests that belong to that group. (define (test-summary . rest) (let ((group (if (null? rest) #f (car rest)))) (if group (begin (display "Listing group: ") (display group) (newline) (for-each (lambda (test) (if (eq? (testdt-group test) group) (begin (display (testdt-name test)) (newline)))) *test-list*)) (begin (display "Listing all tests in format: group:name") (newline) (for-each (lambda (test) (display (testdt-group test)) (display ":") (display (testdt-name test)) (newline)) *test-list*)))))