ocamlounit

How can I use the sexp_diff library as input to the pp_diff parameter of OUnit's assert_equals?


My use-case is that I am writing tests for a parser. The parser outputs an AST which I convert to an s-expression of sexplib type. I want to compare this s-expression to an expected parse tree, also written as an s-expression. The OUnit 2 assert_equal function works for this, along with the Sexp.equal function provided by sexplib:

assert_equal expected actual ~cmp:Sexp.equal

I would also like OUnit to print the s-expression diff when this assertion fails. The assert_equal function exposes an optional parameter for this with the following type:

?pp_diff:(Format.formatter -> ('a * 'a) -> unit)

So it takes a formatter to which it prints a diff between the two 'a type instances. Sexplib doesn't quite have something like this built in; it only has the Sexp.pp family of functions which have signatures like:

val pp_hum : Format.formatter -> t -> unit

So only print out a single s-expression, not the diff between them.

The sexplib authors provide the sexp_diff package for the purpose of diffing s-expressions. I can compute the diff between two s-expressions doing something like:

let diff = Sexp_diff.Algo.diff ~original:expected ~updated:actual

(there is an extra parameter of type unit in this function which I can't exactly figure out the purpose of - but anyway,)

Unfortunately when it comes to converting this diff function into a form that would be accepted by pp_diff I'm at a loss. How can I define a new function which takes a Format.formatter instance that would do this?

let diff_tree (fmt : Format.formatter) ((expected, actual) : Sexp.t * Sexp.t) : unit =
  let diff = Sexp_diff.Algo.diff ~original:expected ~updated:actual in
  (** what goes here? *)

Alternatively, should I just give up on the pp_diff approach and serialize the diff to a string using the sexp_diff library's display functions, then attach that as a regular string message to assert_equals?


Solution

  • If you don't need to have the difference well-integrated in term of formatting, you can print the string created by Display:

    let pp_diff ppf (actual,expected) =
      let open Sexp_diff in
      let diff = Algo.diff ~original:expected ~updated:actual () in
      let options = Display.Display_options.(create Layout.Two_column) in
      let text = Display.display_with_ansi_colors options diff in
      Format.pp_print_string ppf text
    

    Otherwise, you will have to write a printer for the Diff.t type yourself. For a basic printers, this could looks like

    let rec pp_diff ppf diff =
       let sexp = Sexplib.Sexp.pp_hum in
       let open Sexp_diff.Diff in
       match diff with
       | Same x -> Fmt.pf ppf "%a@," sexp x
       | Delete x -> Fmt.pf ppf "-%a@," sexp x
       | Add x -> Fmt.pf ppf "+%a@," sexp x
       | Replace (x,y) -> Fmt.pf ppf "-%a@,+%a@," sexp x sexp y
       | Enclose x -> Fmt.pf ppf "@[<v 2>(@,%a@,)@]" Fmt.(list ~sep:cut pp_diff) x