NAME name
  STANDARD_MAIN | MAIN somewhere/main.c
  [C_RUNTIME ...]
  [ARGS ...]


Conventionally you will place your unit tests in the tests/Units folder of your project, although you can use one or more project subfolders of your choosing.

The conventional tests/Units/CMakeLists.txt should contain:


# Use the TESTS flag to get access to your test dependencies DkSDKProject_AddPackage(AUTO_OPAM_PACKAGE TESTS)

# Use one or more unit test commands DkSDKTest_AddUnitTest(NAME first STANDARD_MAIN ...) DkSDKTest_AddUnitTest(NAME second STANDARD_MAIN ...)

Then place each unit test in a subfolder according to the following directory structure:

├── CMakeLists.txt <-- Contains DkSDKTest_AddUnitTest() commands
├── first <-- Corresponds to DkSDKTest_AddUnitTest(NAME first ...)
│   ├──
│   ├── testsuite_builtin_types.c
│   └── testsuite_capnp_c.c
└── second <-- Corresponds to DkSDKTest_AddUnitTest(NAME second ...)

The NAME "first" and the NAME "second" are just examples.

However, you must include the test entry point .ml file named <NAME>/test_<NAME> in each unit test subfolder.

You can additionally use any .c and .ml files. Those additional files do not need to be specified in the CMakeLists.txt because they are automatically discovered.

Compilation And Linking

The unit test C code and unit test OCaml code are compiled separately into a _c and a _ml target, respectively. Then the two targets are linked together into a single test executable.

Let's use the following "first" unit test as an example:

├── CMakeLists.txt <-- Has DkSDKTest_AddUnitTest(NAME first ...)
└── first
    ├── testsuite_builtin_types.c
    └── testsuite_capnp_c.c

That example would create:

  1. A first_ml STATIC library target containing the code for and Any ML_RUNTIME from DkSDKTest_AddUnitTest(NAME first ML_RUNTIME ...) would be linked into first_ml.

  2. A first_c STATIC library target containing the code for testsuite_builtin_types.c and testsuite_capnp_c.c. Any C_RUNTIME or C_COMPILE from DkSDKTest_AddUnitTest(NAME first C_RUNTIME ...) would be linked into first_c.

  3. A first executable target containing the main entry code from either DkSDKTest_AddUnitTest(NAME first STANDARD_MAIN ...) or DkSDKTest_AddUnitTest(NAME first MAIN main_entry.c ...). The first_ml and first_c library targets would be linked into the first executable target.

Several implicit libraries are included automatically in each target. The complete list of libraries for each target is:

  1. The _ml target (ex. first_ml):

    • Findlib::tezt for writing Tezt based OCaml tests

    • Findlib::lwt and Findlib::lwt.unix for OCaml cooperative threading

    • All the ML_RUNTIME from DkSDKTest_AddUnitTest(ML_RUNTIME ...)

  2. The _c target (ex. first_c) will include:

  3. The executable target (ex. first) will include:


The test executables described in DkSDKTest_AddUnitTest() Compilation And Linking are automatically added as CTest tests unless either:

  • the test executable has an incompatible ABI with the build ("host") machine

  • the BUILD_TESTING variable is OFF

Unix only: The library directories for each ML_RUNTIME and C_RUNTIME dependencies are added to the LD_LIBRARY_PATH of the test executable. The C_COMPILE dependencies are not added to the PATH.

But since the test executables will always be compiled and generated you achieve:

  1. Cross-compiling of your libraries is exercised and tested.

  2. (Advanced) The Dune build tool requires that any .opam files that have been checked into source code have matching statements in dune-project. DkSDKTest_AddUnitTest() ensures that dune-project test packages have correctly modeled dependencies, even if the tests are disabled. Said another way, we can't have dune-project mutating simply because tests are not enabled in one invocation while they are enabled in another invocation.



The name of the unit test.

The files for the unit test must be in a subfolder with the same name, and there must be an entry point <NAME>/test_<NAME>

For example, if NAME was message then the following is the minimal directory structure:

tests/Units <-- The conventional directory to place unit tests
├── CMakeLists.txt <-- Has DkSDKTest_AddUnitTest(NAME message ...)
└── message

A minimal main() C entry point will be generated for your unit test executable that runs your OCaml code and then exits.

MAIN c_file

Use your own C code in c_file that has a main() C entry point.

ML_RUNTIME dependency1 ... dependencyN

Add OCaml runtime dependencies to the <NAME>_ml target. See the above DkSDKTest_AddUnitTest() Compilation And Linking and DkSDKTest_AddUnitTest() CTest sections for complete details.

ARGS arg1 ... argN

Arguments passed to the test executable

C_COMPILE dependency1 ... dependencyN

Add C compile dependencies to the <NAME>_c target. These dependencies do not influence the runtime library path (LD_LIBRARY_PATH) for CTest. See the above DkSDKTest_AddUnitTest() Compilation And Linking and DkSDKTest_AddUnitTest() CTest sections for complete details.

C_RUNTIME dependency1 ... dependencyN

Add C runtime dependencies to the <NAME>_c target. See the above DkSDKTest_AddUnitTest() Compilation And Linking and DkSDKTest_AddUnitTest() CTest sections for complete details.


Tests the command ./dk


Template Project Requirements

The project must contain a dk and a dk.cmd script in the root of the project.

The dk scripts must recognize the task. The DkHelloWorld* projects include that task.

There must be a ci/ POSIX script that accepts the following options:


The Major.Minor version of DkSDK


The location of the CMake executable

and accepts the following environment variables:


All FETCHCONTENT_SOURCE_DIR_<name> CMake variables are set as file://<path from FETCHCONTENT>/.git if the content is a git repository.



The directory of the template project.



The name of the project that will be generated.

Defaults to TestProject.


If specified and the host is Windows, then will skip tests that require MSVC if a Visual Studio Command Prompt has not been used to launch the test.

Often an IDE like CLion will only pass MSVC environment variables to cmake but not ctest. The SKIP_MSVC_TESTS_IF_MSVC_UNAVAILABLE flag will let those IDEs still work.

Currently the Visual Studio Command Prompt (vcvarsall.bat or VsDevCmd.bat) is detected based on the presence of the VCINSTALLDIR environment variable.


When specified, the new project will invoke ctest with --verbose. That means the ctest output is not hidden.