package ppx_inline_test

  1. Overview
  2. Docs
Syntax extension for writing in-line tests in ocaml code

Install

Dune Dependency

Authors

Maintainers

Sources

ppx_inline_test-v0.16.0.tar.gz
sha256=216462f8fe988587d1e90f4a10aeb38664facb6eaeb3df60a32e9fb1a6bfbc67

Description

Part of the Jane Street's PPX rewriters collection.

Published: 02 Jun 2023

README

ppx_inline_test

Syntax extension for writing in-line tests in ocaml code.

New syntactic constructs

The following constructs are now valid structure items:

let%test "name" = <boolean expr> (* true means ok, false or exn means broken *)
let%test_unit "name" = <unit expr> (* () means ok, exn means broken *)
let%test_module "name" = (module <module-expr>)  (* to group tests (to share
                                                    some setup for instance) *)

We may write _ instead of "name" for anonymous tests. It is also possible to use [%name <string expr>] for a dynamically computed name.

When running tests, they will be executed when the control flow reaches the structure item (i.e. at toplevel for a toplevel test; when the functor is applied for a test defined in the body of a functor, etc.).

Tags

One can tag tests with the following construct:

let%test "name" [@tags "no-js"] = <expr>
let%test "name" [@tags "no-js", "other-tag"] = <expr>
let%test _ [@tags "no-js"] = <expr>
let%test _ [@tags "js-only"] = <expr>

Available tags are:

  • no-js for tests that should not run when compiling Ocaml to Javascript

  • js-only for tests that should only run in Javascript

  • 32-bits-only for tests that should only run in 32 bits architectures

  • 64-bits-only for tests that should only run in 64 bits architectures

  • fast-flambda for tests that might only pass when compiling with flambda, -O3 and cross library inlining

  • x-library-inlining-sensitive for tests that might only pass when compiling with cross library inlining switched on

  • disabled for tests that should not run (unless requested with -require-tag)

One can also tag entire test modules similarly:

let%test_module "name" [@tags "no-js"] = (module struct end)

The flags -drop-tag and -require-tag can be passed to the test runner to restrict which tests are run. We say the tags of a test are the union of the tags applied directly to that test using [@tags ...] and the tags of all enclosing modules. It is to this union that the predicates -drop-tag and -require-tag are applied.

If it is clear, from a test-module's tags, that none of the tests within will possibly match the tag predicates imposed by the command line flags, then additionally the top-level of that module will not be run.

Examples

prime.ml

let is_prime = <magic>

let%test _ = is_prime 5
let%test _ = is_prime 7
let%test _ = not (is_prime 1)
let%test _ = not (is_prime 8)

Tests in a functor.

module Make(C : S) = struct
     <magic>
     let%test _ = <some expression>
end

module M = Make(Int)

Grouping test and side-effecting initialisation.

Since the module passed as an argument to let%test_module is only initialised when we run the tests, it is ok to perform side-effects in the module-expression argument.

let%test_module _ = (module struct
    module UID = Unique_id.Int(struct end)

    let%test _ = UID.create() <> UID.create()
end)

Building and running the tests at jane street

Inline tests can only be used in libraries, not executables.

The standard build rules create an executable script inline_tests_runner which runs all tests in the directory. This script takes optional arguments (see below) to restrict which tests are run.

The full set of tests are run when building the jenga runtest alias.

jenga .runtest

Building and running the tests outside of jane street with dune

Inline tests can only be used in libraries, not executables.

To use this with dune, see dune's documentation. At the time of writing of the current document, the short version is:

  • define a library this way:

(library
  (name foo)
  (inline_tests)
  (preprocess (pps ppx_inline_test)))
  • add tests to it

  • call dune runtest

Building and running the tests outside of jane street without dune

Code using this extension must be compiled and linked using the ppx_inline_test.runtime-lib library. The ppx_inline_test syntax extension will reject any test if it wasn't passed a -inline-test-lib libname flag.

Execution

Tests are only executed when both these conditions are met:

  • the executable containing the tests is linked with ppx_inline_test.runner.lib

  • the executable containing the tests is called with command line arguments:

    your.exe inline-test-runner libname [options]

This libname is a way of restricting the tests run by the executable. The dependencies of your library (or executable) could also use ppx_inline_test, but you don't necessarily want to run their tests too. For instance, core is built by giving -inline-test-lib core and core_extended is built by giving -inline-test-lib core_extended. And now when an executable linked with both core and core_extended is run with a libname of core_extended, only the tests of core_extended are run.

Finally, after running tests, Ppx_inline_test_lib.exit () should be called (to exit with an error and a summary of the number of failed tests if there were errors or exit normally otherwise).

One can construct a dual-use binary that only runs the tests when prompted to (through the command line), by sticking the following piece of code in it, after the tests have run but before the binary starts doing non-test side effects. However be aware that Base.am_testing will be true even when not running tests, which may be undesirable.

match Ppx_inline_test_lib.testing with
| `Testing `Am_test_runner ->
  print_endline "Exiting test suite";
  Ppx_inline_test_lib.exit ()
| `Testing _ -> exit 0
| `Not_testing -> ()

Command line arguments

The executable that runs tests can take additional command line arguments. The most useful of these are:

  • -stop-on-error

    Stop running tests after the first error.

  • -verbose

    to see the tests as they run

  • -only-test location

    where location is either a filename -only-test main.ml, a filename with a line number -only-test main.ml:32, or with the syntax that the compiler uses: File "main.ml", or File "main.ml", line 32 or File "main.ml", line 32, characters 2-6 (characters are ignored). The position that matters is the position of the let%test or let%test_unit.

    The positions shown by -verbose are valid inputs for -only-test.

    If no -only-test flag is given, all the tests are run. Otherwise all the tests matching any of the locations are run.

  • -drop-tag tag

    drop all the tests tagged with tag.

These can be specified to jenga like this:

(library
  (...
   (inline_tests ((flags (-stop-on-error))))
   ...
  ))

and to dune like this:

(library
  ...
  (inline_tests (flags (-stop-on-error)))
  ...)

Parallelizing tests

If you pass arguments of the form -inline-test-lib lib:partition to ppx_inline_test, then you will be able to run tests from a given source file in parallel with tests from other source files. All the tests inside the same source file are still run sequentially.

You should pick different partition names for the different files in your library (the name of the .ml files for instance).

ppx_inline_test_lib currently requires some external system like a build system to run it multiple times in parallel, although we may make it possible to run the inline tests in parallel directly in the future.

If you do that, you can now use two new flags of the executable containing the tests:

  • -list-partitions

    lists all the partitions that contain at least one test, one per line.

  • -partition P

    only run the tests of the library that are encountered at toplevel of the source file that was preprocessed with the given partition P (the tests need not be syntactically in the file, they could be the result of applying a functor)

A build system can combine these two commands by first listing partitions, and then running one command for each partition.

Dependencies (5)

  1. ppxlib >= "0.28.0"
  2. dune >= "2.0.0"
  3. time_now >= "v0.16" & < "v0.17"
  4. base >= "v0.16" & < "v0.17"
  5. ocaml >= "4.14.0"

Dev Dependencies

None

  1. alba >= "0.4.1"
  2. autofonce
  3. autofonce_config
  4. autofonce_core
  5. autofonce_lib
  6. autofonce_m4
  7. autofonce_misc
  8. autofonce_patch
  9. autofonce_share
  10. aws-s3 >= "4.0.0" & != "4.5.0"
  11. bio_io >= "0.2.1"
  12. bitpack_serializer
  13. bitwuzla >= "1.0.0"
  14. bitwuzla-c
  15. bitwuzla-cxx
  16. caisar >= "0.2.1"
  17. caisar-ir
  18. colibri2
  19. coq-lsp >= "0.1.9+8.17"
  20. core >= "v0.16.0" & < "v0.17.0"
  21. drom
  22. drom_lib
  23. drom_toml
  24. dune >= "3.17.0"
  25. electrod >= "0.1.6" & != "0.2.1" & < "0.5"
  26. embedded_ocaml_templates >= "0.6"
  27. encoding < "0.0.4"
  28. extism
  29. extism-manifest
  30. ez_cmdliner >= "0.2.0"
  31. ez_config >= "0.2.0"
  32. ez_file >= "0.2.0"
  33. ez_hash < "0.5.3"
  34. ez_opam_file
  35. ez_search
  36. ez_subst
  37. fiber-lwt
  38. fmlib < "0.5.0"
  39. fmlib_browser
  40. fmlib_js < "0.5.0"
  41. fmlib_parse
  42. fmlib_pretty
  43. fmlib_std
  44. GT >= "0.5.0"
  45. guardian < "0.1.0"
  46. hdf5 >= "0.1.5"
  47. header-check
  48. hexstring
  49. idds
  50. knights_tour
  51. lablqml >= "0.7"
  52. learn-ocaml >= "0.16.0"
  53. learn-ocaml-client >= "0.16.0"
  54. little_logger
  55. module-graph
  56. mula
  57. mysql8
  58. nuscr < "2.0.0"
  59. OCanren >= "0.3.0~alpha1"
  60. OCanren-ppx >= "0.3.0~alpha1"
  61. ocaml-protoc-plugin >= "1.0.0"
  62. ocp-search
  63. ocplib_stuff >= "0.3.0"
  64. opam-bin >= "0.9.5"
  65. opam-check-npm-deps
  66. opam_bin_lib >= "0.9.5"
  67. patricia-tree
  68. pp-binary-ints
  69. ppx_bench = "v0.16.0"
  70. ppx_deriving_cad
  71. ppx_deriving_decoders
  72. ppx_deriving_scad
  73. ppx_expect = "v0.16.0"
  74. ppx_jane = "v0.16.0"
  75. ppx_partial
  76. ppx_protocol_conv_json >= "5.0.0"
  77. ppx_ts
  78. psmt2-frontend >= "0.3.0"
  79. pyml_bindgen
  80. randoml
  81. res_tailwindcss
  82. sel
  83. serde < "0.0.2"
  84. serde_json
  85. serde_sexpr
  86. sexp_decode >= "0.5"
  87. simple63
  88. solidity-alcotest
  89. solidity-common
  90. solidity-parser
  91. solidity-test
  92. solidity-typechecker
  93. splittable_random = "v0.16.0"
  94. sqlite3 >= "5.0.1"
  95. tezos-client-009-PsFLoren >= "10.2" & < "14.0"
  96. tezos-client-010-PtGRANAD < "14.0"
  97. tezos-client-011-PtHangz2 < "14.0"
  98. tezos-client-012-Psithaca < "14.0"
  99. tezos-client-013-PtJakart < "14.0"
  100. tezos-client-alpha >= "10.2" & < "14.0"
  101. tezos-micheline >= "9.0" & < "14.0"
  102. tezos-stdlib >= "9.0" & < "14.0"
  103. tezos-test-helpers = "13.0"
  104. toplevel_expect_test = "v0.16.0"
  105. torch = "v0.16.0"
  106. vscoq-language-server
  107. zanuda

Conflicts

None

OCaml

Innovation. Community. Security.