Test interactive F# examples.
Doctest is a small program that checks examples in XML Documentation. It is similar to the popular Haskell program with the same name.
Doctest was primarily created when porting Hedgehog's Range module from Haskell to F#, so one possible example is to show Doctest in action:
(Excerpt from the Range.fs file.)
namespace Hedgehog
/// $setup
/// >>> let x = 3
/// A range describes the bounds of a number to generate, which may or may not
/// be dependent on a 'Size'.
type Range<'a> =
| Range of ('a * (Size -> 'a * 'a))
module Range =
...
//
// Combinators - Constant
//
/// Construct a range which is unaffected by the size parameter with a
/// origin point which may differ from the bounds.
///
/// A range from @-10@ to @10@, with the origin at @0@:
///
/// >>> Range.bounds x <| Range.constantFrom 0 (-10) 10
/// (-10, 10)
///
/// >>> Range.origin <| Range.constantFrom 0 (-10) 10
/// 0
///
/// A range from @1970@ to @2100@, with the origin at @2000@:
///
/// >>> Range.bounds x <| Range.constantFrom 2000 1970 2100
/// (1970, 2100)
///
/// >>> Range.origin <| Range.constantFrom 2000 1970 2100
/// 2000
///
let constantFrom (z : 'a) (x : 'a) (y : 'a) : Range<'a> =
Range (z, fun _ -> x, y)
...
//
// Combinators - Linear
//
[<AutoOpen>]
module Internal =
// The functions in this module where initially marked as internal
// but then the F# compiler complained with the following message:
//
// The value 'linearFrom' was marked inline but its implementation
// makes use of an internal or private function which is not
// sufficiently accessible.
/// Truncate a value so it stays within some range.
///
/// >>> Range.Internal.clamp 5 10 15
/// 10
///
/// >>> Range.Internal.clamp 5 10 0
/// 5
///
let clamp (x : 'a) (y : 'a) (n : 'a) =
if x > y then
min x (max y n)
else
min y (max x n)
...
/// Construct a range which scales the second bound relative to the size
/// parameter.
///
/// >>> Range.bounds 0 <| Range.linear 0 10
/// (0, 0)
///
/// >>> Range.bounds 50 <| Range.linear 0 10
/// (0, 5)
///
/// >>> Range.bounds 99 <| Range.linear 0 10
/// (0, 10)
///
let inline linear (x : 'a) : ('a -> Range<'a>) =
linearFrom x x
To highlight what Doctest does when finding a failing case, we can go ahead and change some of the above tests on purpose:
diff --git a/src/Hedgehog/Range.fs b/src/Hedgehog/Range.fs
index 060f9f8..1ef4c2d 100644
--- a/src/Hedgehog/Range.fs
+++ b/src/Hedgehog/Range.fs
@@ -82,10 +82,10 @@ module Range =
/// A range from @1970@ to @2100@, with the origin at @2000@:
///
/// >>> Range.bounds x <| Range.constantFrom 2000 1970 2100
- /// (1970, 2100)
+ /// (1970, 2101)
///
/// >>> Range.origin <| Range.constantFrom 2000 1970 2100
- /// 2000
+ /// 2001
///
let constantFrom (z : 'a) (x : 'a) (y : 'a) : Range<'a> =
Range (z, fun _ -> x, y)
@@ -137,7 +137,7 @@ module Range =
/// Truncate a value so it stays within some range.
///
/// >>> Range.Internal.clamp 5 10 15
- /// 10
+ /// 101
///
/// >>> Range.Internal.clamp 5 10 0
/// 5
@@ -191,11 +191,14 @@ module Range =
/// (0, 0)
///
/// >>> Range.bounds 50 <| Range.linear 0 10
- /// (0, 5)
+ /// (0, 51)
///
/// >>> Range.bounds 99 <| Range.linear 0 10
/// (0, 10)
///
+ /// >>> ([3; 2; 1; 0] |> List.map ((+) 1))
+ /// [1 + 3..1 + 0]
+ ///
let inline linear (x : 'a) : ('a -> Range<'a>) =
linearFrom x x
Compiling the above code and re-running Doctest will produce the following output:
(0, 51) = Range.bounds 50 <| Range.linear 0 10
Test failed:
(0, 51) = (0, 5)
false
[1 + 3..1 + 0] = ([3; 2; 1; 0] |> List.map ((+) 1))
Test failed:
[] = [4; 3; 2; 1]
false
(1970, 2101) = Range.bounds x <| Range.constantFrom 2000 1970 2100
Test failed:
(1970, 2101) = (1970, 2100)
false
2001 = Range.origin <| Range.constantFrom 2000 1970 2100
Test failed:
2001 = 2000
false
101 = Range.Internal.clamp 5 10 15
Test failed:
101 = 10
false
In order to build doctest, ensure that you have MSBuild and NuGet installed.
Clone a copy of the repo:
git clone https://github.com/moodmosaic/doctest
Change to the doctest directory:
cd doctest
Build the project:
msbuild Doctest.fsproj /p:Configuration=Release