OCaml Dune Cram Example

Posted on Mar 26, 2026

I have Dune version 3.21.0. I want to create a simple OCaml executable and test it with dune’s built in cram tests.

I write in bin/main.ml (see the full source code for this example on GitHub):

let () = print_endline "Hello world!"

and in bin/dune:

(executable
 (public_name hello_world_public)
 (name hello_world))

and run dune build in the project root:

$ dune build
$ tree _build/install/
_build/install/
└── default
    ├── bin
    │   └── hello_world_public -> ../../../default/bin/hello_world.exe
    ...

Now, to test it I put in test/cram.t:

  $ hello_world_public

Note the leading whitespace, which is significant in cram tests. It serves to distinguish commands to be executed from comments and expected output.

The test works as expected:

$ dune runtest
File "test/test.t", line 1, characters 0-0:
diff --git 1/_build/default/test/test.t 2/_build/default/test/test.t.corrected
index 7d73fcf..446aa8d 100644
--- 1/_build/default/test/test.t
+++ 2/_build/default/test/test.t.corrected
@@ -1,2 +1,3 @@

   $ hello_world_public
+  Hello world!

However, I now change public name from hello_world_public to hello_world_public_oups. Now, dune runtest runs with the same result – I expected that it would fail at this point, since the public name of the binary changed. If I change hello_world.ml to output something else than Hello world! then dune runtest will not rebuild the binary. So the test still runs successfully, which is unexpected!

Cram tests run in an isolated environment and do not automatically depend on executables defined elsewhere in the project. Unless explicitly declared, Dune will not rebuild or track those binaries when running dune runtest. The above works because dune makes all public binaries available in the $PATH of cram tests – not because dune knows that the test uses hello_world_public.

A simple solution is to create a dune file in same directory as the cram test (here, test/dune), and add the following stanza:

(cram (deps %{bin:hello_world_public}))

This makes the test’s dependency on the public binary explicit, and running dune runtest will ensure that the binary is rebuilt. If the binary’s public name changes, the build will fail, as is expected.

The full source code of this example is available on GitHub.