Ppx_expect#
Ppx_expect is a framework for writing tests for OCaml code printing textual output.
- There is pretty comprehensive documentation about
Inline expectation tests
and theppx_expect
tool for writing them:
Here, we will just cover enough to get started using them inside the Octez codebase.
How to run tests#
If you want to run tests and see whether they succeed, build the standard runtest
alias:
dune runtest src/lib_stdlib
# or dune build @@src/lib_stdlib/runtest
The result would look like this:
File "src/lib_stdlib/bloomer.ml", line 97, characters 0-817: random_read_writes (0.006 sec)
File "src/lib_stdlib/bloomer.ml", line 123, characters 0-1339: peek and poke work with bits = [1 .. Sys.int_size - 7] (0.012 sec)
File "src/lib_stdlib/bloomer.ml", line 163, characters 0-739: sequential_read_writes (0.021 sec)
File "src/lib_stdlib/bloomer.ml", line 182, characters 0-705: read_over_write (0.001 sec)
File "src/lib_stdlib/bloomer.ml", line 282, characters 0-835: consistent_add_mem_countdown (0.275 sec)
File "src/lib_stdlib/bloomer.ml", line 309, characters 0-1531: consistent_add_countdown_count (2.079 sec)
File "src/lib_stdlib/bloomer.ml", line 456, characters 4-137 (11.966 sec)
File "src/lib_stdlib/bloomer.ml", line 461, characters 4-1695: <<match Sys.getenv_opt "BLOOMER_TEST_GNUPLOT_PA[...]>> (0.000 sec)
A test can fail because it generates an output different from what is expected or because it raises an exception. Failures are reported as a diff in both cases.
Different output:
File "src/proto_alpha/lib_client/limit.ml", line 1, characters 0-0:
------ src/proto_alpha/lib_client/limit.ml
++++++ src/proto_alpha/lib_client/limit.ml.corrected
File "src/proto_alpha/lib_client/limit.ml", line 57, characters 0-1:
| if eq x y then Result.return_some x
| else error_with "Limit.join: error (%s)" where
|
|let%expect_test "join" =
| let pp_print_err fmt = function
| | Result.Error _ -> Format.pp_print_string fmt "error"
| | Ok x ->
| Format.(
| pp_print_option
| ~none:(fun fmt () -> pp_print_string fmt "None")
| pp_print_bool)
| fmt
| x
| in
| let print x = Format.fprintf Format.std_formatter "%a" pp_print_err x in
| print (join ~where:__LOC__ Bool.equal (Some true) (Some true)) ;
-| [%expect {| false |}] ;
+| [%expect {| true |}] ;
| print (join ~where:__LOC__ Bool.equal None None) ;
| [%expect {| None |}] ;
| print (join ~where:__LOC__ Bool.equal None (Some true)) ;
| [%expect {| true |}] ;
| print (join ~where:__LOC__ Bool.equal (Some true) None) ;
| [%expect {| true |}] ;
| print (join ~where:__LOC__ Bool.equal (Some true) (Some false)) ;
| [%expect {| error |}]
|
|let get ~when_unknown = function
| | None -> error_with "Limit.get: %s" when_unknown
| | Some x -> ok x
|
|let%expect_test "get" =
| let pp_print_err fmt = function
| | Result.Error _ -> Format.fprintf fmt "error"
Exception raised:
File "src/lib_stdlib/bloomer.ml", line 1, characters 0-0:
------ src/lib_stdlib/bloomer.ml
++++++ src/lib_stdlib/bloomer.ml.corrected
File "src/lib_stdlib/bloomer.ml", line 309, characters 0-1:
| let bloomer = create ~hash ~index_bits ~hashes ~countdown_bits in
| let rec init n acc =
| if n = 0 then acc
| else
| let x = Random.int (1 lsl 29) in
| add bloomer x ;
| assert (mem bloomer x) ;
| init (n - 1) (x :: acc)
| in
| let all = init 1000 [] in
| for _ = 0 to (1 lsl countdown_bits) - 2 do
| List.iter (fun x -> assert (mem bloomer x)) all ;
| countdown bloomer
| done ;
| List.iter (fun x -> assert (not (mem bloomer x))) all
| done
+|[@@expect.uncaught_exn {|
+| (* CR expect_test_collector: This test expectation appears to contain a backtrace.
+| This is strongly discouraged as backtraces are fragile.
+| Please change this test to not include a backtrace. *)
+|
+| "Assert_failure src/lib_stdlib/bloomer.ml:287:4"
+| Raised at Tezos_stdlib__Bloomer.(fun) in file "src/lib_stdlib/bloomer.ml", line 287, characters 4-18
+| Called from Expect_test_collector.Make.Instance_io.exec in file "collector/expect_test_collector.ml", line 262, characters 12-19 |}]
How to add tests#
Ppx_expect is based on ppx_inline_test
machinery. That is, it
collects tests automatically for you.
To add a new test, make sure you have the following in your library stanza in the dune file:
(inline_tests)
(preprocess (pps ppx_expect))
In the manifest, just add the following argument to the library containing expect tests:
~inline_tests:ppx_expect
Adding new tests is then just a matter of adding let%expect_test
at top level:
let%expect_test "optional name" =
print_endline "hello world";
[%expect {||}]
Running test with the example above will fail and show you a diff between your source and the corrected one:
|let%expect_test "optional name" =
| print_endline "hello world";
-| [%expect {||}]
+| [%expect {| hello world |}]
If you agree with the diff, just ask dune to promote the source and you’re done:
dune runtest --auto-promote
Where to put the tests#
Expect tests can live next to the implementation or in a different library dedicated to tests (e.g. if you don’t want to polute your source/binary or if your want to only test the exposed API).
Integration with Lwt#
Ppx_expect can be used in combination with Lwt, see the package description. This integration has not been tested on the Octez codebase yet, hence some work will be needed to a have specific support for the codebase.