375 lines
11 KiB
Racket
375 lines
11 KiB
Racket
#lang scribble/doc
|
|
@(require "base.rkt")
|
|
|
|
@title{Checks}
|
|
|
|
Checks are the basic building block of RackUnit. A check
|
|
checks some condition. If the condition holds the check
|
|
evaluates to @racket[(void)]. If the condition doesn't hold the
|
|
check raises an instance of @racket[exn:test:check] with
|
|
information detailing the failure.
|
|
|
|
Although checks are implemented as macros, which is
|
|
necessary to grab source location, they are conceptually
|
|
functions. This means, for instance, checks always evaluate
|
|
their arguments. You can use check as first class
|
|
functions, though you will lose precision in the reported
|
|
source locations if you do so.
|
|
|
|
The following are the basic checks RackUnit provides. You
|
|
can create your own checks using @racket[define-check].
|
|
|
|
@defproc*[([(check-eq? (v1 any) (v2 any) (message string? "")) void?]
|
|
[(check-not-eq? (v1 any) (v2 any) (message string? "")) void?]
|
|
[(check-eqv? (v1 any) (v2 any) (message string? "")) void?]
|
|
[(check-not-eqv? (v1 any) (v2 any) (message string? "")) void?]
|
|
[(check-equal? (v1 any) (v2 any) (message string? "")) void?]
|
|
[(check-not-equal? (v1 any) (v2 any) (message string? "")) void?])]{
|
|
|
|
Checks that @racket[v1] is equal (or not equal) to @racket[v2], using
|
|
@racket[eq?], @racket[eqv?], or @racket[equal?], respectively. The
|
|
optional @racket[message] is included in the output if the check
|
|
fails.
|
|
|
|
For example, the following checks all fail:
|
|
|
|
@racketblock[
|
|
(check-eq? (list 1) (list 1) "allocated data not eq?")
|
|
(check-not-eq? 1 1 "fixnums are eq?")
|
|
(check-eqv? 1 1.0 "not eqv?")
|
|
(check-not-eqv? 1 1 "integers are eqv?")
|
|
(check-equal? 1 1.0 "not equal?")
|
|
(check-not-equal? (list 1) (list 1) "equal?")
|
|
]
|
|
}
|
|
|
|
@defproc[(check-pred (pred (-> any any)) (v any) (message string? ""))
|
|
void?]{
|
|
|
|
Checks that @racket[pred] returns a value that is not @racket[#f] when
|
|
applied to @racket[v]. The optional @racket[message] is included in
|
|
the output if the check fails. The value returned by a successful
|
|
check is the value returned by @racket[pred].
|
|
|
|
For example, the following check passes:
|
|
@racketblock[
|
|
(check-pred string? "I work")
|
|
]
|
|
The following check fails:
|
|
@racketblock[
|
|
(check-pred number? "I fail")
|
|
]
|
|
}
|
|
|
|
@defproc[(check-= (v1 any) (v2 any) (epsilon number?) (message string? ""))
|
|
void?]{
|
|
|
|
Checks that @racket[v1] and @racket[v2] are within
|
|
@racket[epsilon] of one another. The optional
|
|
@racket[message] is included in the output if the check
|
|
fails.
|
|
|
|
For example, the following check passes:
|
|
|
|
@racketblock[
|
|
(check-= 1.0 1.01 0.01 "I work")
|
|
]
|
|
The following check fails:
|
|
@racketblock[
|
|
(check-= 1.0 1.01 0.005 "I fail")
|
|
]
|
|
}
|
|
|
|
@defproc*[([(check-true (v any) (message string? "")) void?]
|
|
[(check-false (v any) (message string? "")) void?]
|
|
[(check-not-false (v any) (message string? "")) void?])]{
|
|
|
|
Checks that @racket[v] is @racket[#t], is @racket[#f], or is not
|
|
@racket[#f], respectively. The optional @racket[message] is included
|
|
in the output if the check fails.
|
|
|
|
For example, the following checks all fail:
|
|
|
|
@racketblock[
|
|
(check-true 1)
|
|
(check-false 1)
|
|
(check-not-false #f)
|
|
]
|
|
}
|
|
|
|
@defproc[(check-exn (exn-predicate (or/c (-> any boolean?) regexp?))
|
|
(thunk (-> any)) (message string? ""))
|
|
void?]{
|
|
|
|
Checks that @racket[thunk] raises an exception and that either
|
|
@racket[exn-predicate] returns @racket[#t] if it is a function, or
|
|
that it matches the message in the exception if @racket[exn-predicate]
|
|
is a regexp. In the latter case, the exception raised must be an
|
|
@racket[exn:fail?]. The optional @racket[message] is included in the
|
|
output if the check fails. A common error is to use an expression
|
|
instead of a function of no arguments for @racket[thunk]. Remember
|
|
that checks are conceptually functions.
|
|
|
|
For example, the following check succeeds:
|
|
|
|
@racketblock[
|
|
(check-exn exn:fail?
|
|
(lambda ()
|
|
(raise (make-exn "Hi there"
|
|
(current-continuation-marks)))))
|
|
]
|
|
|
|
The following check fails:
|
|
|
|
@racketblock[
|
|
(check-exn exn:fail?
|
|
(lambda ()
|
|
(break-thread (current-thread))))
|
|
]
|
|
|
|
The following example is a common mistake. The call to @racket[error]
|
|
is not within a @racket[lambda], so it bypasses @racket[check-exn]
|
|
entirely.
|
|
|
|
@racketblock[
|
|
(code:comment "Forgot to wrap the expression in a thunk. Don't do this!")
|
|
(check-exn exn:fail?
|
|
(error 'hi "there"))
|
|
]
|
|
}
|
|
|
|
@defproc[(check-not-exn (thunk (-> any)) (message string? "")) void?]{
|
|
|
|
Checks that @racket[thunk] does not raise any exceptions.
|
|
The optional @racket[message] is included in the output if
|
|
the check fails.
|
|
}
|
|
|
|
@defproc[(check-regexp-match (regexp regexp?)
|
|
(string string?))
|
|
void?]{
|
|
|
|
Checks that @racket[regexp] matches the @racket[string].
|
|
|
|
|
|
For example, the following check succeeds:
|
|
|
|
@racketblock[
|
|
(check-regexp-match "a+bba" "aaaaaabba")
|
|
]
|
|
|
|
The following check fails:
|
|
|
|
@racketblock[
|
|
(check-regexp-match "a+bba" "aaaabbba")
|
|
]
|
|
}
|
|
|
|
|
|
@defproc[(check (op (-> any any any))
|
|
(v1 any)
|
|
(v2 any)
|
|
(message string? ""))
|
|
void?]{
|
|
|
|
The most generic check. Succeeds if @racket[op] applied to
|
|
@racket[v1] and @racket[v2] is not @racket[#f], otherwise raises an
|
|
exception of type @racket[exn:test:check]. The optional
|
|
@racket[message] is included in the output if the check fails.
|
|
|
|
For example, the following check succeeds:
|
|
|
|
@racketblock[
|
|
(check < 2 3)
|
|
]
|
|
|
|
The following check fails:
|
|
|
|
@racketblock[
|
|
(check memq 'pine '(apple orange pear))
|
|
]
|
|
}
|
|
|
|
@defproc[(fail (message string? ""))
|
|
void?]{
|
|
|
|
This check fails unconditionally. Good for creating test stubs that
|
|
you intend to fill out later. The optional @racket[message] is
|
|
included in the output.
|
|
}
|
|
|
|
@section{Augmenting Information on Check Failure}
|
|
|
|
When a check fails it stores information including the name
|
|
of the check, the location and message (if available), the
|
|
expression the check is called with, and the parameters to
|
|
the check. Additional information can be stored by using
|
|
the @racket[with-check-info*] function, and the
|
|
@racket[with-check-info] macro.
|
|
|
|
@defstruct[check-info ([name symbol?] [value any])]{
|
|
|
|
A check-info structure stores information associated
|
|
with the context of execution of a check.}
|
|
|
|
The are several predefined functions that create check
|
|
information structures with predefined names. This avoids
|
|
misspelling errors:
|
|
|
|
@defproc*[([(make-check-name (name string?)) check-info?]
|
|
[(make-check-params (params (listof any))) check-info?]
|
|
[(make-check-location (loc (list/c any (or/c number? #f) (or/c number? #f)
|
|
(or/c number? #f) (or/c number? #f))))
|
|
check-info?]
|
|
[(make-check-expression (msg any)) check-info?]
|
|
[(make-check-message (msg string?)) check-info?]
|
|
[(make-check-actual (param any)) check-info?]
|
|
[(make-check-expected (param any)) check-info?])]{}
|
|
|
|
@defproc[(with-check-info* (info (listof check-info?)) (thunk (-> any))) any]{
|
|
|
|
Stores the given @racket[info] on the check-info stack for
|
|
the duration (the dynamic extent) of the execution of
|
|
@racket[thunk]}
|
|
|
|
Example:
|
|
|
|
@racketblock[
|
|
(with-check-info*
|
|
(list (make-check-info 'time (current-seconds)))
|
|
(lambda () (check = 1 2)))
|
|
]
|
|
|
|
When this check fails the message
|
|
|
|
@verbatim{time: <current-seconds-at-time-of-running-check>}
|
|
|
|
is printed along with the usual information on an check failure.
|
|
|
|
@defform[(with-check-info ((name val) ...) body ...)]{
|
|
|
|
The @racket[with-check-info] macro stores the given
|
|
information in the check information stack for the duration
|
|
of the execution of the body expressions. @racket[Name] is
|
|
a quoted symbol and @racket[val] is any value.}
|
|
|
|
Example:
|
|
|
|
@racketblock[
|
|
(for-each
|
|
(lambda (elt)
|
|
(with-check-info
|
|
(('current-element elt))
|
|
(check-pred odd? elt)))
|
|
(list 1 3 5 7 8))
|
|
]
|
|
|
|
When this test fails the message
|
|
|
|
@verbatim{current-element: 8}
|
|
|
|
is displayed along with the usual information on an check failure.
|
|
|
|
|
|
|
|
@section{Custom Checks}
|
|
|
|
Custom checks can be defined using @racket[define-check] and
|
|
its variants. To effectively use these macros it is useful
|
|
to understand a few details about a check's evaluation
|
|
model.
|
|
|
|
Firstly, a check should be considered a function, even
|
|
though most uses are actually macros. In particular, checks
|
|
always evaluate their arguments exactly once before
|
|
executing any expressions in the body of the checks. Hence
|
|
if you wish to write checks that evaluate user defined code
|
|
that code must be wrapped in a thunk (a function of no
|
|
arguments) by the user. The predefined @racket[check-exn]
|
|
is an example of this type of check.
|
|
|
|
It is also useful to understand how the check information stack
|
|
operates. The stack is stored in a parameter and the
|
|
@racket[with-check-info] forms evaluate to calls to
|
|
@racket[parameterize]. For this reason simple checks (see below)
|
|
cannot usefully contain calls to @racket[with-check-info] to report
|
|
additional information. All checks created using
|
|
@racket[define-simple-check] or @racket[define-check] grab some
|
|
information by default: the name of the checks and the values of the
|
|
parameters. Additionally the macro forms of checks grab location
|
|
information and the expressions passed as parameters.
|
|
|
|
@defform[(define-simple-check (name param ...) expr ...)]{
|
|
|
|
The @racket[define-simple-check] macro constructs a check
|
|
called @racket[name] that takes the params and an optional
|
|
message as arguments and evaluates the @racket[expr]s. The
|
|
check fails if the result of the @racket[expr]s is
|
|
@racket[#f]. Otherwise the check succeeds. Note that
|
|
simple checks cannot report extra information using
|
|
@racket[with-check-info].}
|
|
|
|
For example, the following code defines a check @racket[check-odd?]
|
|
|
|
@racketblock[
|
|
(define-simple-check (check-odd? number)
|
|
(odd? number))
|
|
]
|
|
|
|
We can use these checks in the usual way:
|
|
|
|
@racketblock[
|
|
(check-odd? 3) (code:comment "Success")
|
|
(check-odd? 2) (code:comment "Failure")
|
|
]
|
|
|
|
@defform*[[(define-binary-check (name pred actual expected))
|
|
(define-binary-check (name actual expected) expr ...)]]{
|
|
|
|
The @racket[define-binary-check] macro constructs a check
|
|
that tests a binary predicate. It's benefit over
|
|
@racket[define-simple-check] is in better reporting on check
|
|
failure. The first form of the macro accepts a binary
|
|
predicate and tests if the predicate holds for the given
|
|
values. The second form tests if @racket[expr] non-false.
|
|
}
|
|
|
|
Examples:
|
|
|
|
Here's the first form, where we use a predefined predicate
|
|
to construct a binary check:
|
|
|
|
@racketblock[
|
|
(define-binary-check (check-char=? char=? actual expected))
|
|
]
|
|
|
|
In use:
|
|
|
|
@racketblock[
|
|
(check-char=? (read-char a-port) #\a)
|
|
]
|
|
|
|
If the expression is more complicated the second form should
|
|
be used. For example, below we define a binary check that
|
|
tests a number if within 0.01 of the expected value:
|
|
|
|
@racketblock[
|
|
(define-binary-check (check-in-tolerance actual expected)
|
|
(< (abs (- actual expected)) 0.01))
|
|
]
|
|
|
|
@defform[(define-check (name param ...) expr ...)]{
|
|
|
|
The @racket[define-check] macro acts in exactly the same way
|
|
as @racket[define-simple-check], except the check only fails
|
|
if the macro @racket[fail-check] is called in the body of
|
|
the check. This allows more flexible checks, and in
|
|
particular more flexible reporting options.}
|
|
|
|
@defform[(fail-check)]{
|
|
|
|
The @racket[fail-check] macro raises an @racket[exn:test:check] with
|
|
the contents of the check information stack.
|
|
|
|
}
|