Test interactive F# examples, similar to doctest for Haskell. Built primarily as an internal tool for F# Hedgehog.
Switch branches/tags
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
.gitignore
.travis.yml
App.config
Doctest.fs
Doctest.fsproj
Doctest.nuspec
LICENSE
README.md
packages.config

README.md

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