Skip to content

About

Lightweight ppxes for conditional compilation and expect tests

Resources

Code of conduct

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

ppx_devtools

Two lightweight ppx rewriters, extracted from js_of_ocaml:

  • ppx_devtools-optcomp: compile-time gating of code with [@if] attributes.
  • ppx_devtools-expect: inline expect tests in the style of ppx_expect, with conditional snapshots. Tests run natively, and under js_of_ocaml and wasm_of_ocaml.

They are released as two opam packages, ppx_devtools-optcomp and ppx_devtools-expect. ppx_devtools-optcomp only depends on ppxlib; ppx_devtools-expect also uses out-channel-redirect to capture the output of tests. Both support OCaml 4.13 and later, including OxCaml.

ppx_devtools-optcomp

(preprocess (pps ppx_devtools-optcomp))

An item carrying [@if COND] (or [@@if COND]) is removed when COND is false:

let input_all = In_channel.input_all [@@if ocaml_version >= (5, 1, 0)]

let input_all ic = really_input_string ic (in_channel_length ic)
[@@if ocaml_version < (5, 1, 0)]

[@if] can be placed on toplevel bindings, externals, modules, module types, exceptions and type declarations, on values in signatures, on variant and extension constructors, on toplevel extension nodes such as let%expect_test, and on the patterns of match cases (or on the alternatives of an or-pattern):

type open_flag =
  | O_RDONLY
  | O_NOFOLLOW [@if os_type = "Unix"]

let to_string = function
  | O_RDONLY -> "O_RDONLY"
  | O_NOFOLLOW [@if os_type = "Unix"] -> "O_NOFOLLOW"

A floating [@@@if COND] removes all the items that follow it in the structure or signature when COND is false. [@if] anywhere else is an error.

The available atoms are:

  • ocaml_version, compared with a version tuple: ocaml_version >= (5, 1, 0);
  • ast_version, the ppxlib AST version;
  • os_type: "Unix", "Win32" or "Cygwin";
  • arch_sixtyfour;
  • oxcaml: whether the compiler is OxCaml;
  • introspect: whether the compiler was built with introspection support.

They can be combined with not, &&, || and the comparison operators.

ppx_devtools-optcomp is not a replacement for ppx_optcomp: there is no [%%if], [%%define] or .mlh import. Both use the [@if] attribute, so they should not be used together.

ppx_devtools-expect

(library
 (name my_tests)
 (inline_tests
  (modes best js wasm))
 (preprocess
  (pps ppx_devtools-expect)))
let%expect_test "addition" =
  Printf.printf "%d" (1 + 2);
  [%expect {| 3 |}]

[%expect {| ... |}] checks the output printed since the previous assertion point. [%expect.output] returns it as a string instead. When the output does not match, dune promote updates the test.

Output printed after the last assertion point is expected to be empty: when it is not, promotion adds an [%expect] at the end of the test. An assertion point that runs several times, for instance in a loop, must see the same output each time, otherwise the test fails.

Conditional snapshots and tests

A [@when COND] attribute on an expectation makes it apply only when COND holds at runtime. The plain [%expect] of the group is the default; without one, the output is expected to be empty, and promotion adds it:

let%expect_test "float32 marshalling" =
  print_string (Marshal.to_string 1.0s []);
  [%expect ({| <js bytes> |} [@when js])];
  [%expect {| <native/wasm bytes> |}]

On the test name, [@when COND] skips the whole test when COND is false, and [@tags "a", "b"] attaches inline-test tags: disabled, js-only / no-js, wasm-only / no-wasm, native-only, 64-bits-only / 32-bits-only.

The identifiers of runtime predicates are axes. The predefined ones are backend ("native", "js" or "wasm", with shorthands native, js, wasm), os_type (with shorthands unix, win32, cygwin), ocaml_version and oxcaml.

Adding axes

A ppx library can add axes with Ppx_devtools_expect.register_axes, mapping an identifier to the fully qualified name of its runtime value. The values come from a library listed in its ppx_runtime_libraries:

(library
 (name my_axes_ppx)
 (kind ppx_rewriter)
 (libraries ppx_devtools-expect)
 (ppx_runtime_libraries my_axes))
let () =
  Ppx_devtools_expect.register_axes (function
    | "fancy" -> Some "My_axes.fancy"
    | _ -> None)

Users then just add the ppx: (pps ppx_devtools-expect my_axes_ppx). An axis is a bool when used on its own, and otherwise has the type of the literal it is compared with. Axes registered later take precedence over earlier ones and over the predefined axes.

License

LGPL-2.1-or-later with the OCaml linking exception. See LICENSE.

About

Lightweight ppxes for conditional compilation and expect tests

Resources

Code of conduct

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages