package windtrap

  1. Overview
  2. Docs

Module WindtrapSource

Windtrap - One library for all your OCaml tests.

Unit tests, property-based tests, snapshot tests, and expect tests in a single library. Tests are organized as a tree of cases and groups, run with run.

Quick Start

  open Windtrap

  let () =
    run "MyTests"
      [
        group "Calculator"
          [
            test "addition" (fun () -> equal int 5 (add 2 3));
            test "subtraction" (fun () -> equal int 1 (sub 3 2));
          ];
        test "standalone" (fun () -> is_true true);
      ]

Modules

Sourcemodule Testable : sig ... end

Type-safe equality witnesses. See Testable.

Sourcemodule Tag : sig ... end

Test metadata for filtering and categorization.

Sourcemodule Pp : sig ... end

Lightweight pretty-printing with ANSI styling.

Sourcemodule Ppx_runtime : sig ... end

Internal. Runtime support for ppx_windtrap. Used by PPX-generated code; not intended for direct use.

Testable Constructors

Type-safe equality witnesses used by assertions (equal, not_equal) and property tests (prop). Compose them freely: pair int string, list (option int), etc.

These are re-exported from Testable for convenience. Use the Testable module directly for construction helpers like Testable.make and accessors like Testable.pp.

Sourcetype 'a testable = 'a Testable.t

Alias for Testable.t.

Primitives

Sourceval unit : unit testable
Sourceval bool : bool testable
Sourceval int : int testable
Sourceval small_int : int testable

Like int but generates integers in [-10_000, 10_000]. Useful for properties involving arithmetic that would overflow with full-range integers.

Sourceval nat : int testable

Like int but generates non-negative integers in [0, 10_000], biased towards small values. Useful for indices, sizes, and counts.

Sourceval int32 : int32 testable
Sourceval int64 : int64 testable
Sourceval nativeint : nativeint testable
Sourceval char : char testable
Sourceval string : string testable
Sourceval bytes : bytes testable
Sourceval float : float -> float testable

float eps creates a testable with absolute tolerance eps.

Sourceval float_rel : rel:float -> abs:float -> float testable

float_rel ~rel ~abs creates a testable with both relative and absolute tolerance.

Containers

Sourceval option : 'a testable -> 'a option testable
Sourceval result : 'a testable -> 'b testable -> ('a, 'b) result testable
Sourceval either : 'a testable -> 'b testable -> ('a, 'b) Either.t testable

Testable for Either.t values.

Sourceval list : 'a testable -> 'a list testable

Testable for lists. Produces highlighted diffs on failure.

Sourceval array : 'a testable -> 'a array testable

Testable for arrays. Produces highlighted diffs on failure.

Sourceval pair : 'a testable -> 'b testable -> ('a * 'b) testable
Sourceval triple : 'a testable -> 'b testable -> 'c testable -> ('a * 'b * 'c) testable
Sourceval quad : 'a testable -> 'b testable -> 'c testable -> 'd testable -> ('a * 'b * 'c * 'd) testable

Combinators

Sourceval pass : 'a testable

Always considers values equal. Useful for ignoring parts of a structure.

Sourceval slist : 'a testable -> ('a -> 'a -> int) -> 'a list testable

slist t cmp compares lists as sets, ignoring order.

Sourceval of_equal : ('a -> 'a -> bool) -> 'a testable

of_equal eq creates a testable from an equality function.

Sourceval contramap : ('a -> 'b) -> 'b testable -> 'a testable

contramap f t transforms values with f before comparing.

Sourceval seq : 'a testable -> 'a Seq.t testable

Testable for sequences.

Sourceval lazy_t : 'a testable -> 'a Lazy.t testable

Testable for lazy values.

Sourceval testable : pp:'a Pp.t -> ?equal:('a -> 'a -> bool) -> ?gen:'a Windtrap_prop.Gen.t -> ?check:('a -> 'a -> 'a Testable.check_result) -> unit -> 'a testable

testable ~pp () creates a custom testable. Shorthand for Testable.make.

  let point = testable ~pp:pp_point ~equal:equal_point ()
  let tensor = testable ~pp:pp_tensor ~gen:gen_tensor ()

Types

Sourcetype test

An abstract test or group of tests.

Sourcetype pos = string * int * int * int

Source position as produced by __POS__.

Source position as produced by [%here].

Test Creation

Sourceval test : ?pos:pos -> ?tags:Tag.t -> ?timeout:float -> ?retries:int -> string -> (unit -> 'a) -> test

test name fn creates a test case. The return value of fn is ignored.

  • parameter timeout

    Maximum time in seconds for the test to complete. If exceeded, the test fails with a timeout error.

  • parameter retries

    Number of retry attempts on failure (default: 0). Use sparingly for flaky tests.

Sourceval group : ?pos:pos -> ?tags:Tag.t -> ?setup:(unit -> unit) -> ?teardown:(unit -> unit) -> ?before_each:(unit -> unit) -> ?after_each:(unit -> unit) -> string -> test list -> test

group name children creates a test group. Tags are inherited by children.

  • parameter setup

    Runs once before any child test.

  • parameter teardown

    Runs once after all child tests, even on failure.

  • parameter before_each

    Runs before every individual test in this group. Hooks from nested groups stack: outer before_each runs first.

  • parameter after_each

    Runs after every individual test in this group (even on failure). Hooks from nested groups stack: inner after_each runs first.

Sourceval ftest : ?pos:pos -> ?tags:Tag.t -> ?timeout:float -> ?retries:int -> string -> (unit -> 'a) -> test

ftest name fn creates a focused test case. When any focused test or group exists, only focused tests run. A warning is printed to remind you to remove focus markers before committing.

Sourceval fgroup : ?pos:pos -> ?tags:Tag.t -> ?setup:(unit -> unit) -> ?teardown:(unit -> unit) -> ?before_each:(unit -> unit) -> ?after_each:(unit -> unit) -> string -> test list -> test

fgroup name children creates a focused test group. All tests inside a focused group are treated as focused.

Sourceval slow : ?pos:pos -> ?tags:Tag.t -> ?timeout:float -> ?retries:int -> string -> (unit -> 'a) -> test

slow name fn creates a test tagged with Tag.speed.Slow, skipped when ~quick:true is passed to run. The return value of fn is ignored.

Sourceval bracket : ?pos:pos -> ?tags:Tag.t -> ?timeout:float -> ?retries:int -> setup:(unit -> 'a) -> teardown:('a -> unit) -> string -> ('a -> 'b) -> test

bracket ~setup ~teardown name fn creates a test with setup and teardown. setup runs before the test to acquire a resource, which is passed to fn. teardown runs after the test (even on failure) to release the resource.

Partial application creates reusable test constructors:

  let with_db = bracket ~setup:connect_db ~teardown:close_db

  group "Database" [
    with_db "query works" (fun db -> ...);
    with_db "insert works" (fun db -> ...);
  ]
Sourceval cases : 'a testable -> 'a list -> string -> ('a -> unit) -> test

cases testable cases name fn creates a group with one test per case.

Test names are generated as "name (case_value)" using the testable's pretty-printer.

  cases (triple int int int)
    [ (2, 3, 5); (0, 0, 0); (-1, 1, 0) ]
    "addition"
    (fun (a, b, expected) -> equal int expected (a + b))
Sourceval fixture : (unit -> 'a) -> unit -> 'a

fixture create returns a lazy accessor. The resource is created on first call and cached for subsequent calls.

  let get_db = fixture (fun () -> Db.connect ())

  group "Database"
    [ test "query" (fun () -> let db = get_db () in ...);
      test "insert" (fun () -> let db = get_db () in ...);
    ]

Running Tests

Sourcetype format =
  1. | Compact
    (*

    One character per test (dot reporter, default).

    *)
  2. | Verbose
    (*

    Tree-style hierarchical output.

    *)
  3. | Tap
    (*

    TAP (Test Anything Protocol).

    *)
  4. | Junit
    (*

    JUnit XML to stdout.

    *)
Sourceval run : ?quick:bool -> ?bail:int -> ?fail_fast:bool -> ?output_dir:string -> ?stream:bool -> ?update:bool -> ?snapshot_dir:string -> ?filter:string -> ?exclude:string -> ?failed:bool -> ?list_only:bool -> ?format:format -> ?junit:string -> ?seed:int -> ?timeout:float -> ?prop_count:int -> ?tags:string list -> ?exclude_tags:string list -> ?argv:string array -> string -> test list -> unit

run name tests runs the test suite and exits the process.

  • Exit 0: all tests passed (at least one ran).
  • Exit 1: at least one test failed.
  • Exit 2: no tests ran (all filtered out). Helps catch typos in filters.

Tests with the "disabled" label are automatically skipped.

Configuration is resolved with the following priority (highest first): programmatic arguments, CLI flags, environment variables, defaults.

  • parameter quick

    Skip tests tagged as slow (default: false)

  • parameter bail

    Stop the suite after N failures. CLI: --bail N. Useful for large suites where you want to stop early without aborting on the very first failure.

  • parameter fail_fast

    Stop after first test failure (default: false). Sugar for ~bail:1. CLI: -x, --fail-fast.

  • parameter output_dir

    Directory for test logs (default: "_build/_tests")

  • parameter stream

    Stream test output to console instead of capturing to files. Useful for debugging. CLI: -s, env: WINDTRAP_STREAM=1.

  • parameter update

    Update snapshots instead of comparing (default: false)

  • parameter snapshot_dir

    Overrides snapshot root directory

  • parameter filter

    Filter tests by name (substring match). CLI: -f PATTERN or positional.

  • parameter exclude

    Exclude tests by name (substring match). CLI: -e PATTERN, --exclude PATTERN, env: WINDTRAP_EXCLUDE.

  • parameter failed

    Rerun only tests that failed in the last run. CLI: --failed. Reads from a .last-failed file in the output directory.

  • parameter list_only

    List test names without running. CLI: -l.

  • parameter format

    Output format: Compact, Verbose, Tap, or Junit (default: Compact)

  • parameter junit

    Path to write JUnit XML report

  • parameter seed

    Random seed for property tests. CLI: --seed N, env: WINDTRAP_SEED.

  • parameter timeout

    Default timeout in seconds for tests without a per-test timeout. CLI: --timeout N, env: WINDTRAP_TIMEOUT.

  • parameter prop_count

    Number of test cases per property test (default: 100). CLI: --prop-count N, env: WINDTRAP_PROP_COUNT.

  • parameter tags

    Required labels. Only tests with all listed labels will run. CLI: --tag LABEL (repeatable), env: WINDTRAP_TAG (comma-separated).

  • parameter exclude_tags

    Excluded labels. Tests with any listed label will be skipped. CLI: --exclude-tag LABEL (repeatable), env: WINDTRAP_EXCLUDE_TAG (comma-separated).

  • parameter argv

    Command line arguments to parse (default: Sys.argv)

Equality Assertions

Sourceval equal : ?here:here -> ?pos:pos -> ?msg:string -> 'a testable -> 'a -> 'a -> unit

equal testable expected actual asserts that expected equals actual.

Sourceval not_equal : ?here:here -> ?pos:pos -> ?msg:string -> 'a testable -> 'a -> 'a -> unit

not_equal testable a b asserts that a does not equal b.

Boolean Assertions

Sourceval is_true : ?here:here -> ?pos:pos -> ?msg:string -> bool -> unit

is_true b asserts that b is true.

Sourceval is_false : ?here:here -> ?pos:pos -> ?msg:string -> bool -> unit

is_false b asserts that b is false.

Option Assertions

Sourceval is_some : ?here:here -> ?pos:pos -> ?msg:string -> 'a option -> unit

is_some opt asserts that opt is Some _.

Sourceval is_none : ?here:here -> ?pos:pos -> ?msg:string -> 'a option -> unit

is_none opt asserts that opt is None.

Sourceval some : ?here:here -> ?pos:pos -> ?msg:string -> 'a testable -> 'a -> 'a option -> unit

some testable expected opt asserts that opt is Some expected.

Result Assertions

Sourceval is_ok : ?here:here -> ?pos:pos -> ?msg:string -> ('a, 'b) result -> unit

is_ok r asserts that r is Ok _.

Sourceval is_error : ?here:here -> ?pos:pos -> ?msg:string -> ('a, 'b) result -> unit

is_error r asserts that r is Error _.

Sourceval ok : ?here:here -> ?pos:pos -> ?msg:string -> 'a testable -> 'a -> ('a, 'b) result -> unit

ok testable expected r asserts that r is Ok expected.

Sourceval error : ?here:here -> ?pos:pos -> ?msg:string -> 'b testable -> 'b -> ('a, 'b) result -> unit

error testable expected r asserts that r is Error expected.

Exception Assertions

Sourceval raises : ?here:here -> ?pos:pos -> ?msg:string -> exn -> (unit -> 'a) -> unit

raises exn fn asserts that fn () raises exn.

Uses structural equality (the = operator) to compare exceptions. This works for most exceptions but may not behave as expected for exceptions carrying mutable data, closures, or abstract types. Use raises_match for such cases.

Sourceval raises_match : ?here:here -> ?pos:pos -> ?msg:string -> (exn -> bool) -> (unit -> 'a) -> unit

raises_match pred fn asserts that fn () raises an exception matching pred.

Sourceval no_raise : ?here:here -> ?pos:pos -> ?msg:string -> (unit -> 'a) -> 'a

no_raise fn asserts that fn () does not raise, and returns its result.

Sourceval raises_invalid_arg : ?here:here -> ?pos:pos -> ?msg:string -> string -> (unit -> 'a) -> unit

raises_invalid_arg expected_msg fn asserts that fn () raises Invalid_argument with message expected_msg. On failure, shows the expected vs actual exception message.

  test "negative size" (fun () ->
      raises_invalid_arg "size must be positive" (fun () ->
          create ~size:(-1) ()))
Sourceval raises_failure : ?here:here -> ?pos:pos -> ?msg:string -> string -> (unit -> 'a) -> unit

raises_failure expected_msg fn asserts that fn () raises Failure with message expected_msg. On failure, shows the expected vs actual exception message.

Custom Failures

Sourceval fail : ?here:here -> ?pos:pos -> string -> 'a

fail msg fails the test with message msg.

Sourceval failf : ?here:here -> ?pos:pos -> ('a, Format.formatter, unit, 'b) format4 -> 'a

failf fmt ... fails the test with a formatted message.

Test Control

Sourceval skip : ?reason:string -> unit -> 'a

skip ?reason () skips the current test. Use when a test cannot run due to environment constraints.

  test "windows only" (fun () ->
      if not Sys.win32 then skip ~reason:"not on Windows" ()
        (* ... windows-specific test ... *))

Snapshot Testing

Sourceval snapshot : ?here:here -> ?pos:pos -> ?name:string -> string -> unit

snapshot ~pos:__POS__ actual compares actual against a stored snapshot. Create snapshots with WINDTRAP_UPDATE=1 or ~update:true.

Sourceval snapshot_pp : ?here:here -> ?pos:pos -> ?name:string -> 'a Pp.t -> 'a -> unit

snapshot_pp ~pos:__POS__ pp value pretty-prints value and snapshots the result.

Sourceval snapshotf : ?here:here -> ?pos:pos -> ?name:string -> ('a, Format.formatter, unit, unit) format4 -> 'a

snapshotf ~pos:__POS__ "format" ... formats and snapshots the result.

Output Testing

Sourceval output : unit -> string

output () returns and consumes stdout since last expect/output call. Use for post-processing non-deterministic output before comparison.

  test "timing" (fun () ->
      slow_operation ();
      let out = output () in
      let masked = mask_timing out in
      expect masked "Done in XXms")
Sourceval expect : string -> unit

expect expected compares stdout (normalized) against expected.

Normalization strips trailing whitespace per line and removes leading/trailing blank lines. This matches ppx_expect's default behavior.

  test "greet" (fun () ->
      print_endline "  Hello  ";
      expect "Hello")

Multiple expects consume output incrementally:

  test "multi" (fun () ->
      print_endline "First";
      expect "First";
      print_endline "Second";
      expect "Second")
Sourceval expect_exact : string -> unit

expect_exact expected compares stdout exactly, with no normalization. Use when whitespace is semantically significant.

Sourceval capture : (unit -> 'a) -> string -> 'a

capture fn expected runs fn, captures stdout, compares (normalized).

  test "compute" (fun () ->
      let x =
        capture
          (fun () ->
            print_string "ok";
            42)
          "ok"
      in
      equal int 42 x)
Sourceval capture_exact : (unit -> 'a) -> string -> 'a

capture_exact fn expected is like capture but with no normalization.

Property-Based Testing

Property tests generate random inputs and check that properties hold for all generated values. On failure, counterexamples are automatically shrunk to find minimal failing cases.

Testables with generators (like int, list, etc.) can be used directly for property testing:

  open Windtrap

  let () =
    run "Properties"
      [
        prop "reverse is involutive" (list int) (fun l ->
            List.rev (List.rev l) = l);
        prop "append length"
          (pair (list int) (list int))
          (fun (l1, l2) ->
            List.length (l1 @ l2) = List.length l1 + List.length l2);
      ]

Random value generators with integrated shrinking. For advanced use cases where you need to create custom generators.

Sourceval prop : ?config:Windtrap_prop.Prop.config -> ?pos:pos -> ?tags:Tag.t -> ?timeout:float -> string -> 'a testable -> ('a -> bool) -> test

prop name testable law creates a property test.

Generates random values using testable's generator and checks that law holds for all. If a counterexample is found, it is automatically shrunk to find a minimal failing case.

  prop "reverse is involutive" (list int) (fun l ->
      List.rev (List.rev l) = l)
  • parameter config

    Property test configuration (count, seed, etc.)

  • parameter timeout

    Maximum time for all test cases.

  • raises Invalid_argument

    if testable has no generator. Built-in testables like int always have generators; custom testables need Testable.make ~gen.

Sourceval prop' : ?config:Windtrap_prop.Prop.config -> ?pos:pos -> ?tags:Tag.t -> ?timeout:float -> string -> 'a testable -> ('a -> unit) -> test

prop' name testable fn is like prop but uses Windtrap assertions.

The property passes if fn completes without raising. Use Windtrap assertions like equal inside fn.

  prop' "sorted list is sorted" (list int) (fun l ->
      let sorted = List.sort Int.compare l in
      (* Use Windtrap assertions *)
      is_true (is_sorted sorted))
Sourceval prop2 : ?config:Windtrap_prop.Prop.config -> ?pos:pos -> ?tags:Tag.t -> ?timeout:float -> string -> 'a testable -> 'b testable -> ('a -> 'b -> bool) -> test

prop2 name t1 t2 law is a convenience for two-argument properties.

Sourceval prop3 : ?config:Windtrap_prop.Prop.config -> ?pos:pos -> ?tags:Tag.t -> ?timeout:float -> string -> 'a testable -> 'b testable -> 'c testable -> ('a -> 'b -> 'c -> bool) -> test

prop3 name t1 t2 t3 law is a convenience for three-argument properties.

Sourceval prop4 : ?config:Windtrap_prop.Prop.config -> ?pos:pos -> ?tags:Tag.t -> ?timeout:float -> string -> 'a testable -> 'b testable -> 'c testable -> 'd testable -> ('a -> 'b -> 'c -> 'd -> bool) -> test

prop4 name t1 t2 t3 t4 law is a convenience for four-argument properties.

Sourceval assume : bool -> unit

assume b discards the current test case if b is false.

Use to filter out invalid inputs. Prefer constrained generators when possible, as excessive discarding can cause tests to give up.

  prop "division" (pair int int) (fun (a, b) ->
      assume (b <> 0);
      (a / b * b) + (a mod b) = a)
Sourceval reject : unit -> 'a

reject () unconditionally discards the current test case.

Sourceval collect : string -> unit

collect label records label for the current property case.

Labels are reported as a distribution over successful (non-discarded) property cases. Calling collect multiple times with the same label in one case counts once.

Sourceval classify : string -> bool -> unit

classify label cond is if cond then collect label.

Sourceval cover : label:string -> at_least:float -> bool -> unit

cover ~label ~at_least cond declares a required minimum coverage for label, and records a hit when cond is true.

Coverage is measured over successful (non-discarded) cases and checked once the property has generated all required cases. If unmet, the property fails with a coverage report.

  • raises Invalid_argument

    if at_least is not in [0.0, 100.0], or if the same label is used with conflicting thresholds in one property run.