Skip to content

moodmosaic/doctest

Repository files navigation

doctest NuGet Travis

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.

Example

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

Building

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

About

Created primarily as an internal tool for the F# version of Hedgehog. Test interactive F# examples, similar to doctest for Haskell.

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages