From 3097bb85b76ee3741bd4c51f713ce90fe799f2ca Mon Sep 17 00:00:00 2001 From: Ryan Culpepper Date: Wed, 1 Sep 2010 15:47:52 -0600 Subject: [PATCH] separated "internals & extension API" from main "RackUnit API" --- collects/rackunit/scribblings/api.scrbl | 3 - collects/rackunit/scribblings/check.scrbl | 24 -- .../scribblings/compound-testing.scrbl | 35 --- collects/rackunit/scribblings/internals.scrbl | 271 ++++++++++++++++++ collects/rackunit/scribblings/misc.scrbl | 5 +- collects/rackunit/scribblings/rackunit.scrbl | 1 + .../rackunit/scribblings/running-tests.scrbl | 195 ------------- 7 files changed, 276 insertions(+), 258 deletions(-) create mode 100644 collects/rackunit/scribblings/internals.scrbl delete mode 100644 collects/rackunit/scribblings/running-tests.scrbl diff --git a/collects/rackunit/scribblings/api.scrbl b/collects/rackunit/scribblings/api.scrbl index 75dee084f6..32665de09c 100644 --- a/collects/rackunit/scribblings/api.scrbl +++ b/collects/rackunit/scribblings/api.scrbl @@ -12,6 +12,3 @@ @include-section["control-flow.scrbl"] @include-section["misc.scrbl"] @include-section["ui.scrbl"] -@include-section["running-tests.scrbl"] - - diff --git a/collects/rackunit/scribblings/check.scrbl b/collects/rackunit/scribblings/check.scrbl index accc865a60..18895d8fd6 100644 --- a/collects/rackunit/scribblings/check.scrbl +++ b/collects/rackunit/scribblings/check.scrbl @@ -372,27 +372,3 @@ The @racket[fail-check] macro raises an @racket[exn:test:check] with the contents of the check information stack. } - - -@section{The Check Evaluation Context} - -The semantics of checks are determined by the parameters -@racket[current-check-around] and -@racket[current-check-handler]. Other testing form such as -@racket[test-begin] and @racket[test-suite] change the value -of these parameters. - -@defparam[current-check-handler handler (-> any/c any/c)]{ - -Parameter containing the function that handles exceptions -raised by check failures. The default value is @racket[raise]. } - -@defparam[current-check-around check (-> thunk any/c)]{ - -Parameter containing the function that handles the execution -of checks. The default value wraps the evaluation of -@racket[thunk] in a @racket[with-handlers] call that calls -@racket[current-check-handler] if an exception is raised and then -(when an exception is not raised) discards the result, returning -@racket[(void)]. -} diff --git a/collects/rackunit/scribblings/compound-testing.scrbl b/collects/rackunit/scribblings/compound-testing.scrbl index 084059d2da..66184543ad 100644 --- a/collects/rackunit/scribblings/compound-testing.scrbl +++ b/collects/rackunit/scribblings/compound-testing.scrbl @@ -178,38 +178,3 @@ As far I know no-one uses this macro, so it might disappear in future versions of RackUnit.} } - -@section{Compound Testing Evaluation Context} - -Just like with checks, there are several parameters that -control the semantics of compound testing forms. - -@defparam[current-test-name name (or/c string? false/c)]{ - -This parameter stores the name of the current test case. A -value of @racket[#f] indicates a test case with no name, -such as one constructed by @racket[test-begin]. } - -@defparam[current-test-case-around handler (-> (-> any/c) any/c)]{ - -This parameter handles evaluation of test cases. The value -of the parameter is a function that is passed a thunk (a -function of no arguments). The function, when applied, -evaluates the expressions within a test case. The default -value of the @racket[current-test-case-around] parameters -evaluates the thunk in a context that catches exceptions and -prints an appropriate message indicating test case failure.} - -@defproc[(test-suite-test-case-around [thunk (-> any/c)]) any/c]{ - -The @racket[current-test-case-around] parameter is -parameterized to this value within the scope of a -@racket[test-suite]. This function creates a test case -structure instead of immediately evaluating the thunk.} - -@defproc[(test-suite-check-around [thunk (-> any/c)]) any/c]{ - -The @racket[current-check-around] parameter is parameterized -to this value within the scope of a @racket[test-suite]. -This function creates a test case structure instead of -immediately evaluating a check.} diff --git a/collects/rackunit/scribblings/internals.scrbl b/collects/rackunit/scribblings/internals.scrbl new file mode 100644 index 0000000000..0e58bcd25b --- /dev/null +++ b/collects/rackunit/scribblings/internals.scrbl @@ -0,0 +1,271 @@ +#lang scribble/doc +@(require "base.rkt") + +@declare-exporting[rackunit #:use-sources (rackunit)] + +@title[#:tag "internals"]{RackUnit Internals and Extension API} + +This section describes RackUnit's facilities for customizing the +behavior of checks and tests and for creating new kinds of test +runners. + +@section{Customizing Check Evaluation} + +The semantics of checks are determined by the parameters +@racket[current-check-around] and +@racket[current-check-handler]. Other testing form such as +@racket[test-begin] and @racket[test-suite] change the value +of these parameters. + +@defparam[current-check-handler handler (-> any/c any)]{ + +Parameter containing the function that handles exceptions +raised by check failures. The default value is @racket[raise]. +} + +@defparam[current-check-around check (-> (-> any) any)]{ + +Parameter containing the function that handles the execution +of checks. The default value wraps the evaluation of +@racket[thunk] in a @racket[with-handlers] call that calls +@racket[current-check-handler] if an exception is raised and then +(when an exception is not raised) discards the result, returning +@racket[(void)]. +} + +@section{Customizing Test Evaluation} + +Just like with checks, there are several parameters that +control the semantics of compound testing forms. + +@defparam[current-test-name name (or/c string? false/c)]{ + +This parameter stores the name of the current test case. A +value of @racket[#f] indicates a test case with no name, +such as one constructed by @racket[test-begin]. +} + +@defparam[current-test-case-around handler (-> (-> any) any)]{ + +This parameter handles evaluation of test cases. The value +of the parameter is a function that is passed a thunk (a +function of no arguments). The function, when applied, +evaluates the expressions within a test case. The default +value of the @racket[current-test-case-around] parameters +evaluates the thunk in a context that catches exceptions and +prints an appropriate message indicating test case failure. +} + +@defproc[(test-suite-test-case-around [thunk (-> any)]) any]{ + +The @racket[current-test-case-around] parameter is +parameterized to this value within the scope of a +@racket[test-suite]. This function creates a test case +structure instead of immediately evaluating the thunk. +} + +@defproc[(test-suite-check-around [thunk (-> any/c)]) any/c]{ + +The @racket[current-check-around] parameter is parameterized +to this value within the scope of a @racket[test-suite]. +This function creates a test case structure instead of +immediately evaluating a check. +} + +@;{--------} + +@section[#:tag "running"]{Programmatically Running Tests and Inspecting Results} + +RackUnit provides an API for running tests, from which +custom UIs can be created. + +@subsection{Result Types} + +@defstruct[(exn:test exn) ()]{ + +The base structure for RackUnit exceptions. You should +never catch instances of this type, only the subtypes +documented below.} + +@defstruct[(exn:test:check exn:test) ([stack (listof check-info)])]{ + +A @racket[exn:test:check] is raised when an check fails, and +contains the contents of the check-info stack at the +time of failure.} + +@defstruct[test-result ([test-case-name (or/c string #f)])]{ + +A test-result is the result of running the test with +the given name (with @racket[#f] indicating no name is available).} + +@defstruct[(test-failure test-result) ([result any])]{ + +Subtype of test-result representing a test failure.} + +@defstruct[(test-error test-result) ([result exn])]{ + +Subtype of test-result representing a test error.} + +@defstruct[(test-success test-result) ([result any])]{ + +Subtype of test-result representing a test success.} + + +@subsection{Functions to Run Tests} + +@defproc[(run-test-case (name (or/c string #f)) (action (-> any))) + test-result]{ + +Runs the given test case, returning a result representing success, +failure, or error. +} + + +@defproc[(run-test (test (or/c test-case? test-suite?))) + (flat-murec-contract ([R (listof (or/c test-result? R))]) R)]{ + +Runs the given test (test case or test suite) returning a +tree (list of lists) of results} + +Example: + +@racketblock[ + (run-test + (test-suite + "Dummy" + (test-case "Dummy" (check-equal? 1 2)))) +] + +@defproc[(fold-test-results [result-fn ('b 'c ... 'a . -> . 'a)] + [seed 'a] + [test (or/c test-case? test-suite?)] + [#:run run (string (() -> any) . -> . 'b 'c ...)] + [#:fdown fdown (string 'a . -> . 'a)] + [#:fup fup (string 'a . -> . 'a)]) + 'a]{ + +Fold @racket[result-fn] pre-order left-to-right depth-first +over the results of @racket[run]. By default @racket[run] +is @racket[run-test-case] and @racket[fdown] and +@racket[fup] just return the seed, so @racket[result-fn] is +folded over the test results. + +This function is useful for writing custom folds (and hence UIs) over +test results without you having to take care of all the expected setup +and teardown. For example, @racket[fold-test-results] will run test +suite before and after actions for you. However it is still flexible +enough, via its keyword arguments, to do almost anything that +@racket[foldts] can. Hence it should be used in preference to @racket[foldts]. + +The @racket[result-fn] argument is a function from the results of +@racket[run] (defaults to a @racket[test-result]) and the seed to a +new seed. + +The @racket[seed] argument is any value. + +The @racket[test] argument is a test case or test suite. + +The @racket[run] argument is a function from a test case name (string) +and action (thunk) to any values. The values produced by @racket[run] +are fed into the @scheme[result-fn]. + +The @racket[fdown] argument is a function from a test suite name +(string) and the seed, to a new seed. + +The @racket[fup] argument is a function from a test suite name +(string) and the seed, to a new seed. +} + +Examples: + +The following code counts the number of successes: + +@racketblock[ +(define (count-successes test) + (fold-test-results + (lambda (result seed) + (if (test-success? result) + (add1 seed) + seed)) + 0 + test))] + +The following code returns the symbol @racket['burp] instead +of running test cases. Note how the @racket[result-fn] receives the +value of @racket[run]. + +@racketblock[ +(define (burp test) + (fold-test-results + (lambda (result seed) (cons result seed)) + null + test + #:run (lambda (name action) 'burp)))] + + +@defproc[(foldts [fdown (test-suite string thunk thunk 'a -> 'a)] + [fup (test-suite string thunk thunk 'a 'a -> 'a)] + [fhere(test-case string thunk 'a -> 'a)] + [seed 'a] + [test (or/c test-case? test-suite?)]) + 'a]{ + +The @racket[foldts] function is a nifty tree fold (created by Oleg +Kiselyov) that folds over a test in a useful way +(@racket[fold-test-results] isn't that useful as you can't specify +actions around test cases). + +The @racket[fdown] argument is a function of test suite, test suite +name, before action, after action, and the seed. It is run when a +test suite is encountered on the way down the tree (pre-order). + +The @racket[fup] argument is a function of test suite, test suite +name, before action, after action, the seed at the current level, and +the seed returned by the children. It is run on the way up the tree +(post-order). + +The @racket[fhere] argument is a function of the test case, test case +name, the test case action, and the seed. (Note that this might change +in the near future to just the test case. This change would be to +allow @racket[fhere] to discriminate subtypes of test-case, which in +turn would allow test cases that are, for example, ignored). +} + +Example: + +Here's the implementation of @racket[fold-test-results] in terms of +@racket[foldts]: + +@racketblock[ +(define (fold-test-results suite-fn case-fn seed test) + (foldts + (lambda (suite name before after seed) + (before) + (suite-fn name seed)) + (lambda (suite name before after seed kid-seed) + (after) + kid-seed) + (lambda (case name action seed) + (case-fn + (run-test-case name action) + seed)) + seed + test)) +] + +If you're used to folds you'll probably be a bit surprised that the +functions you pass to @racket[foldts] receive both the structure they +operate on, and the contents of that structure. This is indeed +unusual. It is done to allow subtypes of test-case and test-suite to +be run in customised ways. For example, you might define subtypes of +test case that are ignored (not run), or have their execution time +recorded, and so on. To do so the functions that run the test cases +need to know what type the test case has, and hence is is necessary to +provide this information. + +If you've made it this far you truly are a master RackUnit hacker. As +a bonus prize we'll just mention that the code in +@racketfont{hash-monad.rkt} and @racketfont{monad.rkt} might be of +interest for constructing user interfaces. The API is still in flux, +so isn't documented here. However, do look at the implementation of +@racket[run-tests] for examples of use. diff --git a/collects/rackunit/scribblings/misc.scrbl b/collects/rackunit/scribblings/misc.scrbl index 2b0c3f303a..dcd1803751 100644 --- a/collects/rackunit/scribblings/misc.scrbl +++ b/collects/rackunit/scribblings/misc.scrbl @@ -8,7 +8,10 @@ bindings that a module does not provide. It is useful for testing the private functions of modules. @defform[(require/expose module (id ...))]{ -Requires @racket[id] from @racket[module] into the current module. It doesn't matter if the source module provides the bindings or not; @racket[require/expose] can still get at them. + +Requires @racket[id] from @racket[module] into the current module. It +doesn't matter if the source module provides the bindings or not; +@racket[require/expose] can still get at them. Note that @racket[require/expose] can be a bit fragile, especially when mixed with compiled code. Use at your own risk! diff --git a/collects/rackunit/scribblings/rackunit.scrbl b/collects/rackunit/scribblings/rackunit.scrbl index fb81a8a9b9..16da247199 100644 --- a/collects/rackunit/scribblings/rackunit.scrbl +++ b/collects/rackunit/scribblings/rackunit.scrbl @@ -15,6 +15,7 @@ from novices to experts. @include-section["quick-start.scrbl"] @include-section["philosophy.scrbl"] @include-section["api.scrbl"] +@include-section["internals.scrbl"] @include-section["release-notes.scrbl"] @include-section["acknowledgements.scrbl"] diff --git a/collects/rackunit/scribblings/running-tests.scrbl b/collects/rackunit/scribblings/running-tests.scrbl deleted file mode 100644 index af37007145..0000000000 --- a/collects/rackunit/scribblings/running-tests.scrbl +++ /dev/null @@ -1,195 +0,0 @@ -#lang scribble/doc -@(require "base.rkt") - -@title[#:tag "running"]{Programmatically Running Tests and Inspecting Results} - -RackUnit provides an API for running tests, from which -custom UIs can be created. - -@section{Result Types} - -@defstruct[(exn:test exn) ()]{ - -The base structure for RackUnit exceptions. You should -never catch instances of this type, only the subtypes -documented below.} - -@defstruct[(exn:test:check exn:test) ([stack (listof check-info)])]{ - -A @racket[exn:test:check] is raised when an check fails, and -contains the contents of the check-info stack at the -time of failure.} - -@defstruct[test-result ([test-case-name (or/c string #f)])]{ - -A test-result is the result of running the test with -the given name (with @racket[#f] indicating no name is available).} - -@defstruct[(test-failure test-result) ([result any])]{ - -Subtype of test-result representing a test failure.} - -@defstruct[(test-error test-result) ([result exn])]{ - -Subtype of test-result representing a test error.} - -@defstruct[(test-success test-result) ([result any])]{ - -Subtype of test-result representing a test success.} - - -@section{Functions to Run Tests} - -@defproc[(run-test-case (name (or/c string #f)) (action (-> any))) - test-result]{ - -Runs the given test case, returning a result representing success, failure, or error.} - - -@defproc[(run-test (test (or/c test-case? test-suite?))) - (R = (listof (or/c test-result R)))]{ - -Runs the given test (test case or test suite) returning a -tree (list of lists) of results} - -Example: - -@racketblock[ - (run-test - (test-suite - "Dummy" - (test-case "Dummy" (check-equal? 1 2)))) -] - -@defproc[(fold-test-results [result-fn ('b 'c ... 'a . -> . 'a)] - [seed 'a] - [test (or/c test-case? test-suite?)] - [#:run run (string (() -> any) . -> . 'b 'c ...)] - [#:fdown fdown (string 'a . -> . 'a)] - [#:fup fup (string 'a . -> . 'a)]) - 'a]{ - -Fold @racket[result-fn] pre-order left-to-right depth-first -over the results of @racket[run]. By default @racket[run] -is @racket[run-test-case] and @racket[fdown] and -@racket[fup] just return the seed, so @racket[result-fn] is -folded over the test results. - -This function is useful for writing custom folds (and hence -UIs) over test results without you having to take care of -all the expected setup and teardown. For example, -@racket[fold-test-results] will run test suite before and -after actions for you. However it is still flexible enough, -via its keyword arguments, to do almost anything that foldts -can. Hence it should be used in preference to foldts. - -@racket[result-fn] is a function from the results of -@racket[run] (defaults to a @racket[test-result]) and the -seed to a new seed - -Seed is any value - -Test is a test-case or test-suite - -Run is a function from a test case name (string) and action -(thunk) to any values. - -FDown is a function from a test suite name (string) and the -seed, to a new seed - -FUp is a function from a test suite name (string) and the -seed, to a new seed.} - -Examples: - -The following code counts the number of successes - -@racketblock[ -(define (count-successes test) - (fold-test-results - (lambda (result seed) - (if (test-success? result) - (add1 seed) - seed)) - 0 - test))] - -The following code returns the symbol @racket['burp] instead -of running test cases. Note how the result-fn receives the -value of run. - -@racketblock[ -(define (burp test) - (fold-test-results - (lambda (result seed) (cons result seed)) - null - test - #:run (lambda (name action) 'burp)))] - - -@defproc[(foldts [fdown (test-suite string thunk thunk 'a -> 'a)] - [fup (test-suite string thunk thunk 'a 'a -> 'a)] - [fhere(test-case string thunk 'a -> 'a)] - [seed 'a] - [test (or/c test-case? test-suite?)]) - 'a]{ - -Foldts is a nifty tree fold (created by Oleg Kiselyov) that -folds over a test in a useful way (fold-test-results isn't -that useful as you can't specify actions around test cases). - -Fdown is a function of test suite, test suite name, before -action, after action, and the seed. It is run when a test -suite is encountered on the way down the tree (pre-order). - -Fup is a function of test suite, test suite name, before -action, after action, the seed at the current level, and the -seed returned by the children. It is run on the way up the -tree (post-order). - -Fhere is a function of the test case, test case name, the -test case action, and the seed. (Note that this might change -in the near future to just the test case. This change would -be to allow fhere to discriminate subtypes of test-case, -which in turn would allow test cases that are, for example, -ignored).} - -Example: - -Here's the implementation of fold-test-results in terms of -foldts: - -@racketblock[ -(define (fold-test-results suite-fn case-fn seed test) - (foldts - (lambda (suite name before after seed) - (before) - (suite-fn name seed)) - (lambda (suite name before after seed kid-seed) - (after) - kid-seed) - (lambda (case name action seed) - (case-fn - (run-test-case name action) - seed)) - seed - test)) -] - -If you're used to folds you'll probably be a bit surprised -that the functions you pass to foldts receive both the -structure they operate on, and the contents of that -structure. This is indeed unusual. It is done to allow -subtypes of test-case and test-suite to be run in customised -ways. For example, you might define subtypes of test case -that are ignored (not run), or have their execution time -recorded, and so on. To do so the functions that run the -test cases need to know what type the test case has, and -hence is is necessary to provide this information. - -If you've made it this far you truly are a master RackUnit -hacker. As a bonus prize we'll just mention that the code -in hash-monad.rkt and monad.rkt might be of interest for -constructing user interfaces. The API is still in flux, so -isn't documented here. However, do look at the -implementation of @racket[run-tests] for examples of use.