The (aux unittest) module

Bootstrapping a unittest framework

This test suite drove the implementation of the unittest framework itself.

(module
  (aux unittest)
  *
  (import
    scheme
    (chicken base)
    (chicken condition)
    (chicken pretty-print)
    (chicken port)
    (chicken string)
    (chicken syntax)
    (chicken flonum)
    (chicken time)
    (chicken gc)
    srfi-1
    srfi-19
    (aux sxml))
  (define-record unittest/testcase name log)
  (define (unittest/wasrun name) (make-unittest/testcase name '()))
  (define (unittest/testcase-logcons! testcase msg)
    (unittest/testcase-log-set!
      testcase
      (cons msg (unittest/testcase-log testcase))))
  (define (unittest/testcase-run testcase result sut)
    (let* ((methods (cdr sut))
           (setup (alist-ref 'setup methods))
           (teardown (alist-ref 'teardown methods))
           (testcase-name (unittest/testcase-name testcase)))
      (unittest/result-started! result)
      (let-values
        ((args (if setup ((car setup) testcase) (values)))
         ((f code) (apply values (alist-ref testcase-name methods))))
        (let* ((witness (gensym))
               (no-outsrt "")
               (eta-time 0)
               (pair (condition-case
                       (let* ((res (void))
                              (outstr no-outsrt)
                              (errstr
                                (with-error-output-to-string
                                  (lambda ()
                                    (set! outstr
                                      (with-output-to-string
                                        (lambda ()
                                          (let-values
                                            (((u s) (cpu-time)))
                                            (set! res (apply f testcase args))
                                            (let-values
                                              (((uu ss) (cpu-time)))
                                              (set! eta-time (- (+ uu ss) (+ u s))))))))))))
                         (list res outstr errstr))
                       (c (exn unittest-assert-equal)
                          (begin
                            (unittest/result-failed!
                              result
                              (cons testcase-name
                                    (get-condition-property
                                      c
                                      'unittest-assert-equal
                                      'comparison)))
                            (list witness no-outsrt no-outsrt)))
                       (c (exn)
                          (begin
                            (unittest/result-failed!
                              result
                              (list testcase-name
                                    (call-with-output-string
                                      (lambda (port) (print-error-message c port)))))
                            (list witness no-outsrt no-outsrt)))
                       (c ()
                          (begin
                            (unittest/result-failed!
                              result
                              (list testcase-name c))
                            (list witness no-outsrt no-outsrt)))))
               (v (first pair))
               (outstr (second pair))
               (errstr (third pair))
               (hasdoc (and (pair? v) (eq? (car v) 'doc))))
          (when teardown (apply (car teardown) testcase args))
          `((structure/section
              (code ,testcase-name)
              ": "
              ,(if (eq? v witness)
                 '(span (@ (class "w3-text-red")) fail)
                 '(span (@ (class "w3-text-green")) pass)))
            ,@(if hasdoc (cdr v) '())
            (code/scheme ,(if hasdoc (butlast code) code))
            (code/scheme
              ((eta ,(exact->inexact (/ eta-time 1000)))
               (memory ,(memory-statistics))
               (stdout ,outstr)
               (stderr ,errstr))))))))
  (define-syntax
    define-suite
    (syntax-rules
      ()
      ((_ sutname ((casename formal ...) body ...) ...)
       (define sutname
         `(sutname
            (casename
              ,(lambda (formal ...) body ...)
              ,'(define (casename formal ...) body ...))
            ...)))))
  (define-syntax
    lettest
    (syntax-rules
      ()
      ((_ ((test nameexp) ...) body ...)
       (let ((test (unittest/wasrun nameexp)) ...) body ...))))
  (define-record unittest/result ran failed)
  (define (unittest/result-summary result)
    `((ran ,(unittest/result-ran result))
      (failed
        ,(length (unittest/result-failed result))
        ,@(unittest/result-failed result))))
  (define (unittest/result-started! result)
    (unittest/result-ran-set! result (add1 (unittest/result-ran result))))
  (define (unittest/result-failed! result exn)
    (unittest/result-failed-set!
      result
      (cons exn (unittest/result-failed result))))
  (define (equal-approx? p)
    (letrec ((? (lambda (a b)
                  (cond ((and (real? a) (real? b)) (< (abs (- a b)) p))
                        ((and (pair? a) (pair? b))
                         (and (? (car a) (car b)) (? (cdr a) (cdr b))))
                        (else (equal=? a b))))))
      ?))
  (define (⊦ pred? a b)
    (unless (pred? a b) (signal (unittest/condition-expected-actual a b))))
  (define (⊦= a b) (⊦ (equal-approx? 1e-06) a b))
  (define (⊦≠ a b) (⊦ (complement equal?) a b))
  (define (⊨ a) (⊦= #t a))
  (define (⊭ a) (⊦= #f a))
  (define-syntax
    letsuite
    (syntax-rules
      ()
      ((_ ((name '(method ...)) ...) body ...)
       (letrec ((name (lettest ((method 'method) ...) (list method ...))) ...)
         body
         ...))))
  (define (unittest/testsuite-run suite r sut)
    (map (lambda (testcase) (unittest/testcase-run testcase r sut)) suite))
  (define (unittest/✓ sut)
    (let* ((r (make-unittest/result 0 '()))
           (sut-name (car sut))
           (sut-methods (cdr sut))
           (F (lambda (x)
                (let ((name (car x)))
                  (and (not (eq? name 'setup))
                       (not (eq? name 'teardown))
                       (not (eq? name 'doc))))))
           (methods (filter F sut-methods))
           (s (map (lambda (pair) (lettest ((t (car pair))) t)) methods)))
      (let ((docs (unittest/testsuite-run s r sut))
            (res (unittest/result-summary r))
            (sxml (alist-ref 'doc sut-methods)))
        (SXML->HTML->file!
          (sxml-tree sut-name `(,@(if sxml ((car sxml) r) '()) ,@docs))
          (symbol-append 'testsuite- sut-name))
        (pretty-print res)
        r)))
  (define (unittest/condition-expected-actual a b)
    (condition
      `(exn message "assert-equal failed")
      `(unittest-assert-equal comparison ((expected ,a) (got ,b)))))
  (define-syntax
    ⊦⧳
    (syntax-rules
      ()
      ((_ ((exn ...) ...) body ...)
       (condition-case (begin body ...) ((exn ...) (void)) ...)))))

test-running: pass

hello

(define (test-running tc t r)
  (⊦= '() (unittest/testcase-log t))
  (unittest/testcase-run t r wasrun-sut)
  (⊦= '(teardown test-method setup) (unittest/testcase-log t)))
((eta 0.003) (memory #(6291456 1796152 1048576)) (stdout "") (stderr ""))

test-result: pass

(define (test-result tc t r)
  (unittest/testcase-run t r wasrun-sut)
  (⊦= '((ran 1) (failed 0)) (unittest/result-summary r)))
((eta 0.001) (memory #(6291456 1797184 1048576)) (stdout "") (stderr ""))

test-failed: pass

(define (test-failed tc _ r)
  (lettest
    ((t 'test-broken))
    (unittest/testcase-run t r wasrun-sut)
    (⊦= '((ran 1) (failed 1 (test-broken (expected useless) (got _))))
          (unittest/result-summary r))))
((eta 0.001) (memory #(6291456 1798056 1048576)) (stdout "") (stderr ""))

test-unbound-variable: pass

(define (test-unbound-variable tc _ r)
  (lettest
    ((t 'test-unbound-variable))
    (unittest/testcase-run t r wasrun-sut)
    (⊦= '((ran 1)
            (failed
              1
              (test-unbound-variable
                "\nError: unbound variable: unbound-v\n")))
          (unittest/result-summary r))))
((eta 0.002) (memory #(6291456 1798808 1048576)) (stdout "") (stderr ""))

test-failed-result: pass

(define (test-failed-result tc _ r)
  (unittest/result-started! r)
  (unittest/result-failed! r 'no-reason)
  (⊦= '((ran 1) (failed 1 no-reason)) (unittest/result-summary r)))
((eta 0.0) (memory #(6291456 1799096 1048576)) (stdout "") (stderr ""))

test-suite: pass

(define (test-suite tc _ r)
  (letsuite
    ((suite '(test-running test-failed)))
    (unittest/testsuite-run suite r bootstrap-sut)
    (⊦= '((ran 2) (failed 0)) (unittest/result-summary r))))
((eta 0.003) (memory #(6291456 1800128 1048576)) (stdout "") (stderr ""))

test-⊨: pass

(define (test-⊨ tc _ _) (⊨ #t))
((eta 0.0) (memory #(6291456 1800800 1048576)) (stdout "") (stderr ""))

test-⊭: pass

(define (test-⊭ tc _ _) (⊭ #f))
((eta 0.0) (memory #(6291456 1801584 1048576)) (stdout "") (stderr ""))

test-should-signal: pass

(define (test-should-signal tc _ _)
  (⊦⧳ ((exn)) (signal (condition '(exn message "useless"))))
  (⊦⧳ ((exn-a)) (signal (condition '(exn-a message "useless"))))
  (⊦⧳ ((exn) (exn-a)) (signal (condition '(exn-a message "useless"))))
  (condition-case
    (⊦⧳ ((exn)) (signal (condition '(exn-a message "useless"))))
    ((exn-a) (void)))
  (condition-case (⊦⧳ ((exn)) (signal 99)) (() (void)))
  (⊦⧳ () 99))
((eta 0.0) (memory #(6291456 1804168 1048576)) (stdout "") (stderr ""))

See also