Testing with ppx_expect and ppx_deriving.show

Hi,

Recently I’ve started to use ppx_expect accompanied by ppx_deriving (show plugin) for writing unit tests. Alongside with the latest test integration in dune this creates the great workflow, where I can easily review diffs and update tests.

There was one problem I encountered though, which I may need some help with. The function I’m testing returns the data structure which includes absolute file paths. Here is an example:

type resolved = (Fastpack.Module.location * string list) [@@deriving show]

let%expect_test "." =
  resolve ".";
  [%expect_exact {|
((File {
  filename = (Some "/Users/zindel/ocaml/fastpack/test/resolve/index.js");
  preprocessors = []
}),
[])
|}]

In order to make the test reproducible on other environments where the root path may be different , I am collecting the show_resolved output and replacing project root with some constant string in it. The code above becomes:

type resolved = (Fastpack.Module.location * string list) [@@deriving show]

let%expect_test "." =
  resolve ".";
  [%expect_exact {|
((File {
  filename = (Some "/.../test/resolve/index.js");
  preprocessors = []
}),
[])
|}]

But then, I’ve hit another problem, which is the fact that ppx_deriving.show uses the Format module and its output is highly dependent on the formatter’s margin. Obviously, it would work well if values in the data structure were constants known in advance, but in my case those are absolute paths which depend on the environment. So, what I ended up doing is the set of nasty hacks:

let show resolved =
  (* this is to make sure that pp_resolved will produce 1-liner*)
  Format.(pp_set_margin str_formatter 100000);
  pp_resolved Format.str_formatter resolved;
  (* make it more friendly for diffing *)
  Format.flush_str_formatter ()
  |> String.replace ~sub:"{ " ~by:"{\n  "
  |> String.replace ~sub:"; " ~by:";\n  "
  |> String.replace ~sub:" }), " ~by:"\n}),\n"
  |> String.replace ~sub:"[\"" ~by:"[\n  \""
  |> String.replace ~sub:"\"]" ~by:"\"\n]"

So, the question is: Is there idiomatic (or, at least, less hack-ish) way to make show_*/pp_* produce pretty-printed values which disregard the formatter’s margin? Or, maybe, it should be some specific formatter adapted to this use-case? I see the analogy with dumping JSON in JS/Python. It is, obviously, simpler than OCaml type-system, but still. If you specify indent, it will return the pretty-printed string. Would something similar be possible with ppx_deriving.show?

1 Like

To make the expect test idiom work well, you do indeed need to transform the output to drop information that’s not a deterministic function of the code being run. It sounds like you’ve done some of that already, which seems great.

My recommendation is that you do that transformation before calling the formatter. I’m not sure how easy that is to do in your context, but one way of approaching it would be to use s-expressions for serialization. You could then transform the s-expression before pretty-printing, which should solve the issue.

I also recommend you take a look at Expect_test_helpers_kernel, which provides many useful utilities, including a good s-expression pretty printer, that you can access by calling print_s.

1 Like

Thanks for the suggestion! It looks like Expect_test_helpers_kernel.print_s may do what I’m looking for.