From 7e29712aadf2436b83de2d5e8d1f98b30d2f6d59 Mon Sep 17 00:00:00 2001 From: Lukasz Kasprzak Date: Tue, 11 Aug 2026 19:23:06 +0200 Subject: test(precedence): strengthen accounting test, cover order and empty sanctoral The accounting test only checked bucket lengths, which a mutant satisfies by duplicating a candidate across two buckets while dropping another entirely. Replace it with a sorted slug-set comparison (Alcotest.slist), which a duplicate-or-missing slug both fail. Add two cases the brief's three properties call for but nothing exercised: input-order independence (permuting the sanctoral list must not change the outcome) and a temporal-only day (empty sanctoral list), the case the temporal/sanctoral split exists to make safe. --- test/test_precedence.ml | 46 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 39 insertions(+), 7 deletions(-) diff --git a/test/test_precedence.ml b/test/test_precedence.ml index 39be06c..369c490 100644 --- a/test/test_precedence.ml +++ b/test/test_precedence.ml @@ -56,15 +56,45 @@ let test_commemoration_only_never_observed () = Alcotest.(check (list string)) "suppressed commemorated" [ "suppressed" ] (List.map (fun (c, _) -> slug_of c) r.P.commemorations) -(* Anything the admit limit drops is recorded in `omitted`, never dropped silently. *) +(* Anything the admit limit drops is recorded in `omitted`, never dropped silently + -- and each input candidate lands in exactly one bucket, not zero or two. *) let test_nothing_silently_lost () = let r = P.resolve rules ctx ~temporal:(cand ~origin:P.Temporal ~rank:Hi "sunday") ~sanctoral:[ cand ~rank:Lo "a"; cand ~rank:Lo "b"; cand ~rank:Lo "c" ] in - Alcotest.(check int) "two admitted" 2 (List.length r.P.commemorations); - Alcotest.(check int) "one recorded as omitted" 1 (List.length r.P.omitted); - let total = 1 + List.length r.P.commemorations + List.length r.P.deferred - + List.length r.P.omitted in - Alcotest.(check int) "every candidate accounted for" 4 total + let bucketed = + (slug_of r.P.observed + :: List.map (fun (c, _) -> slug_of c) r.P.commemorations) + @ List.map slug_of r.P.deferred + @ List.map (fun (c, _) -> slug_of c) r.P.omitted + in + Alcotest.(check (slist string compare)) "every candidate appears exactly once" + [ "a"; "b"; "c"; "sunday" ] bucketed; + Alcotest.(check int) "two admitted" 2 (List.length r.P.commemorations) + +(* The result must not depend on the order sanctoral candidates arrive in; + ties break on slug, not on list position. *) +let test_order_independent () = + let a = cand ~rank:Lo "a" and b = cand ~rank:Lo "b" and c = cand ~rank:Lo "c" in + let t = cand ~origin:P.Temporal ~rank:Hi "sunday" in + let observed_for sanctoral = slug_of (P.resolve rules ctx ~temporal:t ~sanctoral).P.observed in + let comms_for sanctoral = + List.map (fun (x, _) -> slug_of x) (P.resolve rules ctx ~temporal:t ~sanctoral).P.commemorations + in + List.iter + (fun perm -> + Alcotest.(check string) "same observed" (observed_for [ a; b; c ]) (observed_for perm); + Alcotest.(check (list string)) "same commemorations" (comms_for [ a; b; c ]) (comms_for perm)) + [ [ c; b; a ]; [ b; a; c ]; [ c; a; b ] ] + +(* The case the "pass temporal separately" design exists to make safe: a day + with no sanctoral candidate at all. *) +let test_temporal_only_day () = + let r = P.resolve rules ctx ~temporal:(cand ~origin:P.Temporal ~rank:Lo "feria") + ~sanctoral:[] in + Alcotest.(check string) "feria observed" "feria" (slug_of r.P.observed); + Alcotest.(check int) "no commemorations" 0 (List.length r.P.commemorations); + Alcotest.(check int) "nothing deferred" 0 (List.length r.P.deferred); + Alcotest.(check int) "nothing omitted" 0 (List.length r.P.omitted) let suite = ( "Precedence", @@ -73,4 +103,6 @@ let suite = Alcotest.test_case "loser dispositions" `Quick test_loser_dispositions; Alcotest.test_case "commemoration-only never observed" `Quick test_commemoration_only_never_observed; - Alcotest.test_case "nothing silently lost" `Quick test_nothing_silently_lost ] ) + Alcotest.test_case "nothing silently lost" `Quick test_nothing_silently_lost; + Alcotest.test_case "order independent" `Quick test_order_independent; + Alcotest.test_case "temporal-only day" `Quick test_temporal_only_day ] ) -- cgit v1.3