8.17.0.4
recspecs: Expect Testing for Racket🔗ℹ
The expect form captures anything printed to the current output
port while evaluating an expression and compares it to a string literal
stored directly in the source file. Each use expands to a
RackUnit test-case. When the environment variable
RECSPECS_UPDATE is set and the expectation does not match, the file
is rewritten with the new output instead of failing the test. When
RECSPECS_UPDATE is not set and the expectation fails, a colorized
diff is printed to help understand the mismatch. Updating can be
restricted to a single test case by setting
RECSPECS_UPDATE_TEST to the name shown for that case.
Verbose mode can be enabled by setting RECSPECS_VERBOSE or by
parameterizing recspecs-verbose?. When enabled, captured
output is echoed to the real output port as it is produced.
For example:
RECSPECS_VERBOSE=1 raco test my-test.rkt |
For Emacs users, the accompanying "emacs/recspecs.el" file
provides recspecs-update-at-point, which runs the current
file under racket-test with those environment variables set for
the expectation at the cursor position. After the test finishes the
buffer is automatically reverted so that any updated expectations are
reloaded from disk.
Use #:port 'stderr with expect, expect-file,
expect-exn, or capture-output to record output written
to the current error port instead of the output port. Pass
'both to capture from both ports simultaneously.
Output can be transformed before it is compared by parameterizing
recspecs-output-filter. The parameter holds a procedure that
receives the captured string and returns a new string used for the
comparison and when updating:
(parameterize ([recspecs-output-filter |
(lambda (s) (regexp-replace* #px"[0-9]+" s ""))]) |
(expect (display "v1.2") "v.")) |
(parameterize ([recspecs-output-filter string-upcase]) |
(expect (display "ok") "OK")) |
(parameterize ([recspecs-output-filter string-trim]) |
(expect (display " hi ") "hi")) |
The thunk that performs the test is executed via the procedure stored in
recspecs-runner. The default simply calls the thunk, but it can
be replaced to control the runtime context. For example, limit memory
usage with a new custodian and redirect the error port:
(parameterize ([recspecs-runner |
(lambda (th) |
(call-in-nested-thread |
(lambda () |
(custodian-limit-memory (current-custodian) (* 1024 1024)) |
(parameterize ([current-error-port (current-output-port)]) |
(th)))))]) |
(expect (begin |
(display "oops" (current-error-port)) |
(make-bytes (* 2 1024 1024))) |
"oops")) |
(expect expr expected-str ...)
|
Evaluates expr and checks that the captured output is equal to
the concatenation of expected-strs. If they differ and
RECSPECS_UPDATE is set, the expectation string in the source file
is replaced with the new value. Otherwise the test case fails.
(require recspecs) |
(expect (displayln "hello") "hello\n") |
It can be convenient to use at-exp for multi-line
expectations:
#:lang at-exp racket |
(require recspecs) |
|
(expect (begin (displayln "hello") (displayln (+ 1 2))) |
"hello""\n" |
"3") |
(expect-file expr path-str)
|
Reads the expectation from path-str instead of embedding it in the
source. The file is replaced with new output when RECSPECS_UPDATE is set.
(expect-file |
(begin |
(displayln "hello") |
(displayln "world")) |
"expected.txt") |
(expect-exn expr expected-str ...)
|
Checks that expr raises an exception whose message matches the
concatenation of expected-strs. The message is updated when
update mode is enabled.
(expect-exn (raise-user-error "bad") |
"bad") |
(expect-unreachable expr)
|
Fails the enclosing test if expr evaluates. When update mode is
enabled, the form is replaced with expr in the source instead of
failing.
(when #f |
(expect-unreachable (displayln "never"))) |
(capture-output thunk [#:port port]) → string?
|
thunk : (-> any/c) |
port : any/c = 'stdout |
Runs thunk and returns everything printed to the selected port(s).
When port is 'stderr, the current error port is captured
instead of the output port. Pass 'both to capture from both ports.
When recspecs-verbose? is true, the output is also echoed to the
original port(s).
(capture-output (lambda () (display "hi")))
(capture-output (lambda () (display "err" (current-error-port))) |
#:port 'stderr) |
(capture-output (lambda () |
(display "warn" (current-error-port)) |
(display "out")) |
#:port 'both) |
(struct | | expectation (out committed? skip?) |
|
#:extra-constructor-name make-expectation) |
out : string? |
committed? : boolean? |
skip? : boolean? |
Represents recorded output that can be committed or skipped. The
structure is mutable so repeated with-expectation blocks can
append to out.
(make-expectation) → expectation?
|
Create a fresh expectation.
(commit-expectation! e) → void?
|
e : expectation? |
Mark e as committed.
(reset-expectation! e) → void?
|
e : expectation? |
Reset the output and flags of e.
(skip-expectation! e) → void?
|
e : expectation? |
Mark e as skipped.
(with-expectation e expr ...)
|
Evaluates the exprs and appends anything printed to
e’s out field.
You can wrap any of the expectation forms with with-expectation and
access the captured output with expectation-out:
(define log (make-expectation)) |
(with-expectation log |
(expect (display "hi") "hi")) |
(commit-expectation! log) |
(displayln (expectation-out log)) |
(run-expect | | thunk | | | | | | | expected | | | | | | | path | | | | | | | pos | | | | | | | span | | | | | | [ | #:strict strict? | | | | | | | #:port port]) | | → | | void? |
|
thunk : (-> any/c) |
expected : string? |
path : (or/c path-string? #f) |
pos : exact-nonnegative-integer? |
span : exact-nonnegative-integer? |
strict? : boolean? = #f |
port : (symbols 'stdout 'stderr 'both) = 'stdout |
Runs thunk and checks that the captured output matches
expected. The path, pos and span
identify the source location used when updating.
(run-expect-exn | | thunk | | | | | | | expected | | | | | | | path | | | | | | | pos | | | | | | | span | | | | | | [ | #:strict strict? | | | | | | | #:port port]) | | → | | void? |
|
thunk : (-> any/c) |
expected : string? |
path : (or/c path-string? #f) |
pos : exact-nonnegative-integer? |
span : exact-nonnegative-integer? |
strict? : boolean? = #f |
port : (symbols 'stdout 'stderr 'both) = 'stdout |
Like run-expect but expects thunk to raise an
exception whose message matches expected.
(update-file-entire path pos span new-str) → void?
|
path : path-string? |
pos : exact-nonnegative-integer? |
span : exact-nonnegative-integer? |
new-str : string? |
Replace the entire file at path with new-str.