#lang scheme
(require (planet cce/scheme:4:1/planet))
(require (only-in srfi/13 string-contains)
(this-package-in private/command-line))
(provide plai-error test test/pred test/regexp test/exn)
(provide/contract
[exn:plai? (any/c . -> . boolean?)]
[abridged-test-output (parameter/c boolean?)]
[plai-ignore-exn-strings (parameter/c boolean?)]
[plai-catch-test-exn (parameter/c boolean?)]
[test-inspector (parameter/c inspector?)]
[test-inexact-epsilon (parameter/c number?)])
(define thunk (-> any))
(define test-inspector (make-parameter (current-inspector)))
(define test-inexact-epsilon (make-parameter 0.01))
(define-struct (exn:plai exn:fail) () #:transparent)
(define (plai-error . args)
(with-handlers
[(exn:fail? (λ (exn)
(raise
(make-exn:plai (exn-message exn)
(exn-continuation-marks exn)))))]
(apply error args)))
(define-struct (exn:test exn:fail) ())
(define (install-test-inspector)
(test-inspector (current-inspector))
(current-inspector (make-inspector))
(print-struct #t))
(define (maybe-command-line arg)
(and (member arg (vector->list (current-command-line-arguments))) true))
(define halt-on-errors? (maybe-command-line "--plai-halt-on-errors"))
(define print-only-errors? (maybe-command-line "--plai-print-only-errors"))
(provide/contract (halt-on-errors (() (boolean?) . ->* . void?)))
(define (halt-on-errors [halt? true])
(set! halt-on-errors? halt?))
(provide/contract (print-only-errors (() (boolean?) . ->* . void?)))
(define (print-only-errors [print? true])
(set! print-only-errors? print?))
(provide plai-all-test-results)
(define plai-all-test-results empty)
(define plai-ignore-exn-strings (make-parameter false))
(define (may-print-result result)
(parameterize ([current-inspector (test-inspector)]
[print-struct #t])
(define error?
(not (eq? (first result) 'good)))
(define print?
(if print-only-errors?
(if error?
#t
#f)
#t))
(set! plai-all-test-results (cons result plai-all-test-results))
(when print?
(write result) (newline))
(when (and halt-on-errors? error?)
(raise (make-exn:test (string->immutable-string (format "test failed: ~s" result))
(current-continuation-marks))))))
(define plai-catch-test-exn (make-parameter true))
(define (exn+catching? x)
(and (exn? x) (plai-catch-test-exn)))
(define-syntax (return-exception stx)
(syntax-case stx ()
[(_ expr)
#'(with-handlers
([exn+catching? (λ (exn) exn)])
expr)]))
(provide generic-test)
(define (abridged v)
(if (abridged-test-output)
empty
(list v)))
(define (print-error case test-sexp test-result expected-val loc)
`(,case
,@(abridged test-sexp)
,test-result
,expected-val
,@(abridged loc)))
(define (generic-test test-thunk pred test-sexp loc)
(unless (disable-tests)
(may-print-result
(with-handlers
([exn+catching? (λ (exn)
(print-error
'pred-exception
test-sexp
(exn-message exn)
'<no-expected-value>
loc))])
(let ([test-result (return-exception (test-thunk))])
(if (or (exn:plai? test-result)
(not (exn? test-result)))
(local [(define-values (test-value expected-val)
(pred test-result))]
(print-error
(cond
[(exn:plai? test-value) 'exception]
[test-value 'good]
[else 'bad])
test-sexp
(if (exn:plai? test-result)
(exn-message test-result)
test-result)
expected-val loc))
(print-error
'exception
test-sexp
(exn-message test-result)
'<no-expected-value>
loc)))))))
(define (equal~? x y)
(or (parameterize ([current-inspector (test-inspector)])
(equal? x y))
(and (number? x) (number? y)
(or (inexact? x) (inexact? y))
(< (abs (- x y)) (test-inexact-epsilon)))))
(define-syntax (test stx)
(syntax-case stx ()
[(_ result-expr expected-expr)
#`(generic-test
(λ () result-expr)
(λ (result-value)
(define expected-val expected-expr)
(values
(cond
[(exn:plai? result-value) result-value]
[(equal~? result-value expected-val) true]
[else false])
expected-val))
(quote #,(syntax->datum #'result-expr))
(format "at line ~a" #,(syntax-line stx)))]))
(define-syntax (test/pred stx)
(syntax-case stx ()
[(_ test-expr pred)
#`(generic-test
(λ () test-expr)
(λ (val)
(values
(cond
[(exn:plai? val) val]
[else (pred val)])
(quote #,(syntax->datum #'pred))))
(quote #,(syntax->datum #'test-expr))
(format "at line ~a" #,(syntax-line stx)))]))
(define-syntax (test/exn stx)
(syntax-case stx ()
[(_ test-expr exception-substring)
#`(generic-test
(λ () test-expr)
(λ (val)
(define exception-substring-val exception-substring)
(values
(and (exn:plai? val)
(or (plai-ignore-exn-strings)
(string-contains (exn-message val) exception-substring-val)))
exception-substring-val))
(quote #,(syntax->datum #'test-expr))
(format "at line ~a" #,(syntax-line stx)))]))
(define-syntax (test/regexp stx)
(syntax-case stx ()
[(_ test-expr regexp)
#`(generic-test
(λ () test-expr)
(λ (val)
(define regexp-val regexp)
(values
(and (exn:plai? val)
(or (plai-ignore-exn-strings)
(regexp-match regexp (exn-message val))))
regexp-val))
(quote #,(syntax->datum #'test-expr))
(format "at line ~a" #,(syntax-line stx)))]))
(install-test-inspector)