As our codebase grows, maintaining clarity and organization becomes more important. It’s time to move beyond inline tests and switch to Alcotest.
Alcotest Link to heading
To use Alcotest, you need to provide a pp
function for pretty-printing and an equals
function for equality checks. The testable
function in Alcotest handles wrapping your type for testing, requiring only these two functions.
(* unification.mli *)
module Env : sig
(* ... *)
val pp : Format.formatter -> t -> unit
(** Prints the env [t] using the format output function [fmt].
@param fmt The formatter to which the formatted env is output.
@param t The env to be formatted. *)
val equal : t -> t -> bool
(** Returns [true] if two environments are equal. *)
end
To implement pretty-printing we’ll use the Format
module.
Equality is just an alias for Map.equal Term.equal
.
(* unification.ml *)
module Env = struct
(* ... *)
let pp fmt env =
let pp_binding fmt (key, term) =
Format.fprintf fmt "%s -> %s" key (Term.to_string term)
in
let bindings = Map.to_alist env in
Format.fprintf fmt "{%a}"
(Format.pp_print_list
~pp_sep:(fun fmt () -> Format.fprintf fmt ", ")
pp_binding)
bindings
let equal = Map.equal Term.equal
end
Testsuite Link to heading
We need a a couple of helper functions and testables as well:
(* helpers *)
let pp_error ppf err = Format.pp_print_string ppf err
let error = Alcotest.testable pp_error ( = )
(* testables *)
let term_testable = Alcotest.testable Term.pp Term.equal
let env_testable = Alcotest.testable Env.pp Env.equal
Here is the new testsuite and a few example tests for unification module:
(* unification_test.ml *)
let test_unify_occurs_check_failure () =
(* equation: P(x) = P(f(x))
expected: Error "Occurs check failed for variable x" *)
let env = Env.empty in
let f1 = Formula.Pred ("P", [ Term.Var "x" ]) in
let f2 = Formula.Pred ("P", [ Term.Function ("f", [ Term.Var "x" ]) ]) in
let result_env = unify env (f1, f2) in
let expected_error = "Occurs check failed for variable x" in
Alcotest.(check (result env_testable error))
"unify: occurs check failure" (Error expected_error) result_env
Notice how we test the negative case above ^
(* unification_test.ml *)
let test_unify_unifies_nested_functions () =
(* equation: P(f(x)) = P(f(g()))
expected: x -> g() *)
let env = Env.empty in
let f1 = Formula.Pred ("P", [ Term.Function ("f", [ Term.Var "x" ]) ]) in
let f2 =
Formula.Pred ("P", [ Term.Function ("f", [ Term.Function ("g", []) ]) ])
in
let result_env = unify env (f1, f2) in
let expected_env = Env.add Env.empty ("x", Term.Function ("g", [])) in
Alcotest.(check (result env_testable error))
"unify: unifies nested functions" (Ok expected_env) result_env
(* unification_test.ml *)
let test_unify_unifies_nested_functions_with_variables () =
(* equation: P(f(x, g(y)), z) = P(f(a, g(b)), h(c))
expected: x -> a, y -> b, z -> h(c) *)
let env = Env.empty in
let f1 =
Formula.Pred
( "P",
[
Term.Function
("f", [ Term.Var "x"; Term.Function ("g", [ Term.Var "y" ]) ]);
Term.Var "z";
] )
in
let f2 =
Formula.Pred
( "P",
[
Term.Function
( "f",
[
Term.Param ("a", []);
Term.Function ("g", [ Term.Param ("b", []) ]);
] );
Term.Function ("h", [ Term.Param ("c", []) ]);
] )
in
let result_env = unify env (f1, f2) in
let expected_env =
let e = Env.empty in
let e = Env.add e ("x", Term.Param ("a", [])) in
let e = Env.add e ("y", Term.Param ("b", [])) in
let e = Env.add e ("z", Term.Function ("h", [ Term.Param ("c", []) ])) in
e
in
Alcotest.(check (result env_testable error))
"unify: nested function unification with variables" (Ok expected_env)
result_env
You can find the remaining tests in the repository.
Next time, as we continue through Paulson’s paper, I want to focus on term instantiation and inference.