-
-
Notifications
You must be signed in to change notification settings - Fork 35
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add meta checks #59
base: master
Are you sure you want to change the base?
Add meta checks #59
Changes from all commits
ea4302b
c3ef801
621821b
d7f18bf
d8075e7
cf773ca
01865d3
896596a
73d97fd
a74c8fe
8d42cd9
1c1be48
e0b8bfb
d845bb1
07a5df9
6c8300f
81075ed
fabfad0
70725ac
0ef33c3
fc439c8
052d24c
370edf6
c7da6ad
c571f3c
80a6f34
48edd6d
925a3a7
98d1718
2063233
a01c91d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
#lang racket/base | ||
|
||
(provide check-fail) | ||
|
||
(require (for-syntax racket/base) | ||
racket/function | ||
racket/list | ||
rackunit/log | ||
syntax/parse/define | ||
rackunit | ||
(only-in rackunit/private/check-info | ||
current-check-info | ||
pretty-info) | ||
"private/util-list.rkt") | ||
|
||
|
||
(define-check (check-fail tree chk-thnk) | ||
(contract-tree! 'check-fail tree) | ||
(contract-thunk! 'check-fail chk-thnk) | ||
(define failure (check-raise-value chk-thnk)) | ||
(unless (exn:test:check? failure) | ||
(with-actual failure | ||
(fail-check "Check raised error instead of calling fail-check"))) | ||
(check-tree-assert tree failure)) | ||
|
||
;; Shorthands for adding infos | ||
|
||
(define-simple-macro (with-actual act:expr body:expr ...) | ||
(with-check-info* (error-info act) (λ () body ...))) | ||
|
||
(define (error-info raised) | ||
(define (exn-info) (make-check-info 'exn (pretty-info raised))) | ||
(define (msg-info) (make-check-info 'message (exn-message raised))) | ||
(define (info-info) | ||
(make-check-info 'info (nested-info (exn:test:check-stack raised)))) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wish the
Because, what if I'd like to do There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you mean what if There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I didn't know that about Okay though, my real complaint is that |
||
(define info-value | ||
(cond [(exn:test:check? raised) | ||
(nested-info (list (exn-info) (msg-info) (info-info)))] | ||
[(exn? raised) (nested-info (list (exn-info) (msg-info)))] | ||
[else (pretty-info raised)])) | ||
(list (make-check-info 'actual info-value))) | ||
|
||
;; Pseudo-contract helpers, to be replaced with real check contracts eventually | ||
|
||
(define (contract-thunk! name thnk) | ||
(unless (and (procedure? thnk) | ||
(procedure-arity-includes? thnk 0)) | ||
(raise-argument-error name "(-> any)" thnk))) | ||
|
||
(define (contract-tree! name tree) | ||
(for ([v (in-list (flatten tree))]) | ||
(unless (or (and (procedure? v) | ||
(procedure-arity-includes? v 1)) | ||
(regexp? v) | ||
(check-info? v)) | ||
(define ctrct "(or/c (-> any/c any/c) regexp? check-info?)") | ||
(raise-argument-error name ctrct v)))) | ||
|
||
;; Extracting raised values from checks | ||
|
||
(define (check-raise-value chk-thnk) | ||
;; Checks called inside other checks raise their values normally and don't | ||
;; log test failures, so all we have to do is ensure the check is executed | ||
;; with an info stack that is independent of the outer check. | ||
(or (parameterize ([current-check-info (list)]) | ||
(with-handlers ([(negate exn:break?) values]) (chk-thnk) #f)) | ||
(fail-check "Check passed unexpectedly"))) | ||
|
||
;; Assertion helpers | ||
|
||
(struct failure (type expected) #:transparent) | ||
|
||
(define (assert-pred raised pred) | ||
(and (not (pred raised)) | ||
(failure 'predicate pred))) | ||
|
||
(define (assert-regexp exn rx) | ||
(and (not (regexp-match? rx (exn-message exn))) | ||
(failure 'message rx))) | ||
|
||
(define (assert-info exn info) | ||
(and (not (member info (exn:test:check-stack exn))) | ||
(failure 'info info))) | ||
|
||
(define (assert assertion raised) | ||
((cond [(procedure? assertion) assert-pred] | ||
[(regexp? assertion) assert-regexp] | ||
[(check-info? assertion) assert-info]) | ||
raised assertion)) | ||
|
||
(define (assertions-adjust assertions raised) | ||
(define is-exn? (exn? raised)) | ||
(define has-regexps? (ormap regexp? assertions)) | ||
(define adjust-regexps? (and has-regexps? (not is-exn?))) | ||
(if adjust-regexps? | ||
(cons exn? (filter-not regexp? assertions)) | ||
assertions)) | ||
|
||
(define (assertion-tree-apply tree raised) | ||
(define assertions (assertions-adjust (flatten tree) raised)) | ||
(filter-map (λ (a) (assert a raised)) assertions)) | ||
|
||
(define (failure-sort-key f) | ||
(case (failure-type f) [(predicate) 0] [(message) 1] [(info) 2])) | ||
|
||
(define (failure-list->info failures) | ||
(define failures* (sort failures < #:key failure-sort-key)) | ||
(define vs | ||
(if (equal? (length failures*) 1) | ||
(pretty-info (failure-expected (first failures*))) | ||
(nested-info (for/list ([f (in-list failures*)]) | ||
(make-check-info (failure-type f) | ||
(pretty-info (failure-expected f))))))) | ||
(make-check-info 'expected vs)) | ||
|
||
(define (check-tree-assert tree raised) | ||
(with-actual raised | ||
(define failures (assertion-tree-apply tree raised)) | ||
(unless (empty? failures) | ||
(with-check-info* (list (failure-list->info failures)) | ||
fail-check)))) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
#lang racket/base | ||
|
||
(provide list/if) | ||
|
||
(define (list/if . vs) (filter values vs)) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
#lang racket/base | ||
|
||
(module+ test | ||
(require rackunit | ||
rackunit/meta | ||
(only-in rackunit/private/check-info pretty-info)) | ||
|
||
(define foo-info (make-check-info 'foo 'foo)) | ||
(define-check (check-raise) (raise 'foo)) | ||
|
||
;; We test the meta checks using the meta checks themselves (essentially | ||
;; testing them twice). Direct uses of the checks assume they work, while | ||
;; individual test cases verify specific scenarios. This can be a bit | ||
;; confusing, but given the self-referential nature of testing a testing | ||
;; framework we've got to tie the knot somewhere. | ||
|
||
(define (accepts-no-args) (void)) | ||
(define-check (fail/raise) (raise 'foo)) | ||
(define-check (fail-foo) (fail-check "foo")) | ||
(define-check (fail-not-foo) (fail-check "bar")) | ||
(define some-info (make-check-info 'random 'info)) | ||
(define-check (fail/info) (with-check-info* (list some-info) fail-check)) | ||
(define (foo-fail? e) (equal? (exn-message e) "foo")) | ||
|
||
(test-case "check-fail" | ||
(check-fail '() fail-check) | ||
(check-fail foo-fail? fail-foo) | ||
(check-fail #rx"foo" fail-foo) | ||
(check-fail some-info fail/info) | ||
(check-fail #rx"Check passed unexpectedly" (λ () (check-fail '() void))) | ||
(check-fail #rx"Check raised error instead of calling fail-check" | ||
(λ () (check-fail '() fail/raise))) | ||
(check-fail (make-check-info 'expected (pretty-info foo-fail?)) | ||
(λ () (check-fail foo-fail? fail-not-foo))) | ||
(check-fail (make-check-info 'expected (pretty-info #rx"foo")) | ||
(λ () (check-fail #rx"foo" fail-not-foo))) | ||
(check-fail (make-check-info 'expected (pretty-info some-info)) | ||
(λ () (check-fail some-info fail-check))) | ||
(check-exn exn:fail:contract? | ||
(λ () (check-fail 'nonsense fail-check))) | ||
(check-exn exn:fail:contract? | ||
(λ () (check-fail (list #rx"foo" 'partial-nonsense) | ||
fail-check))) | ||
(check-exn exn:fail:contract? | ||
(λ () (check-fail accepts-no-args fail-check))) | ||
(define (hello-world-exn? e) (equal? (exn-message e) "hello world")) | ||
(define-check (fail/hello-world) | ||
(with-check-info (['hello 'world]) | ||
(fail-check "hello world"))) | ||
(check-fail (list hello-world-exn? | ||
(list '(#rx"hello") '() (make-check-info 'hello 'world)) | ||
#rx"world") | ||
fail/hello-world))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think these thunks are more time/space expensive than just doing
(define exn-info (make....
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The optimizer should be able to inline them, so I don't think that's worth worrying about.