blob: 20b2f1a3233caaf8d30d37d7103f26b1aac6b1b7 (
plain) (
tree)
|
|
let label s =
match Sol.Label.of_string s with
| Ok l -> l
| Error (`Invalid_label s) -> Alcotest.failf "invalid label in test: %s" s
let term = Alcotest.testable Sol.Term.pp Sol.Term.equal
let rational num den =
match Sol.Term.rational ~num:(Z.of_int num) ~den:(Z.of_int den) with
| Ok t -> t
| Error `Zero_denominator -> Alcotest.fail "unexpected zero denominator"
let record bindings =
match Sol.Term.record bindings with
| Ok t -> t
| Error `Empty_record -> Alcotest.fail "unexpected empty record"
| Error (`Duplicate_label _) -> Alcotest.fail "unexpected duplicate label"
let int n = Sol.Term.integer (Z.of_int n)
(* Canonical forms: Decision 12, minimal shape. *)
let test_rational_reduces () =
Alcotest.check term "2/4 = 1/2" (rational 1 2) (rational 2 4)
let test_rational_collapses_to_integer () =
Alcotest.check term "4/2 = 2" (int 2) (rational 4 2);
Alcotest.check term "2/2 = 1" (int 1) (rational 2 2);
Alcotest.check term "0/5 = 0" (int 0) (rational 0 5)
let test_rational_sign_normalizes () =
Alcotest.check term "1/-2 = -1/2" (rational (-1) 2) (rational 1 (-2));
Alcotest.check term "-1/-2 = 1/2" (rational 1 2) (rational (-1) (-2))
let test_rational_zero_denominator () =
match Sol.Term.rational ~num:Z.one ~den:Z.zero with
| Error `Zero_denominator -> ()
| Ok _ -> Alcotest.fail "1/0 must be rejected"
(* Records: label order is irrelevant, duplicates are rejected. *)
let test_record_label_order_irrelevant () =
let ab = record [ (label "a", int 1); (label "b", int 2) ] in
let ba = record [ (label "b", int 2); (label "a", int 1) ] in
Alcotest.check term "{a: 1 b: 2} = {b: 2 a: 1}" ab ba
let test_record_duplicate_rejected () =
match Sol.Term.record [ (label "a", int 1); (label "a", int 2) ] with
| Error (`Duplicate_label l) ->
Alcotest.(check string) "offending label" "a" (Sol.Label.to_string l)
| Ok _ -> Alcotest.fail "duplicate label must be rejected"
| Error `Empty_record -> Alcotest.fail "wrong error"
let test_record_empty_rejected () =
match Sol.Term.record [] with
| Error `Empty_record -> ()
| Ok _ | Error (`Duplicate_label _) ->
Alcotest.fail "empty record must be rejected"
(* Sequences: the published elaboration into head/tail cells. *)
let test_sequence_elaboration () =
let expected =
Sol.Term.cons ~head:(int 1)
~tail:(Sol.Term.cons ~head:(int 2) ~tail:Sol.Term.nil)
in
Alcotest.check term "#(1 2) elaborates to nested cells" expected
(Sol.Term.sequence [ int 1; int 2 ])
let test_empty_sequence_is_nil () =
Alcotest.check term "#() is Nil" Sol.Term.nil (Sol.Term.sequence [])
(* Printing: Sol notation back out. *)
let print t = Format.asprintf "%a" Sol.Term.pp t
let test_printing () =
Alcotest.(check string) "integer" "42" (print (int 42));
Alcotest.(check string) "rational" "1/4" (print (rational 1 4));
Alcotest.(check string) "string" {|"Sun"|} (print (Sol.Term.string "Sun"));
Alcotest.(check string) "empty sequence" "#()" (print Sol.Term.nil);
Alcotest.(check string) "sequence" "#(1 2 3)"
(print (Sol.Term.sequence [ int 1; int 2; int 3 ]));
Alcotest.(check string) "record" "{a: 1 b: 2}"
(print (record [ (label "b", int 2); (label "a", int 1) ]))
let test_label_validation () =
let ok s = Result.is_ok (Sol.Label.of_string s) in
Alcotest.(check bool) "dept_id" true (ok "dept_id");
Alcotest.(check bool) "empty" false (ok "");
Alcotest.(check bool) "capitalized" false (ok "Name");
Alcotest.(check bool) "leading digit" false (ok "1a")
let () =
Alcotest.run "sol"
[
( "canonical forms",
[
Alcotest.test_case "rational reduces" `Quick test_rational_reduces;
Alcotest.test_case "integer-valued rational is an integer" `Quick
test_rational_collapses_to_integer;
Alcotest.test_case "sign normalizes" `Quick
test_rational_sign_normalizes;
Alcotest.test_case "zero denominator rejected" `Quick
test_rational_zero_denominator;
] );
( "records",
[
Alcotest.test_case "label order irrelevant" `Quick
test_record_label_order_irrelevant;
Alcotest.test_case "duplicate label rejected" `Quick
test_record_duplicate_rejected;
Alcotest.test_case "empty record rejected" `Quick
test_record_empty_rejected;
] );
( "sequences",
[
Alcotest.test_case "elaboration" `Quick test_sequence_elaboration;
Alcotest.test_case "empty is nil" `Quick test_empty_sequence_is_nil;
] );
( "notation",
[
Alcotest.test_case "printing" `Quick test_printing;
Alcotest.test_case "label validation" `Quick test_label_validation;
] );
]
|