summaryrefslogtreecommitdiff
path: root/test/test_litcal_of.ml
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-08-26 23:45:16 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-08-26 23:45:16 +0200
commit9ebb06983b7a26db5564302253f2551dfbcf834e (patch)
treef73de5c193a13239258d062ca5f4caf64ba64dbe /test/test_litcal_of.ml
parent2f6d25aff88b4c76c61e36889f1c017b3cc8faee (diff)
downloadcolitur-9ebb06983b7a26db5564302253f2551dfbcf834e.tar.gz
colitur-9ebb06983b7a26db5564302253f2551dfbcf834e.zip
fix(of): dispatch Holy Family's Normae n.35(a) fallback (W2)
temporal_of.ml's temporal only ever reached holy_family's own correct 26-31 December / 30 December fallback logic through sunday_slug, which returns None immediately for a non-Sunday date. In a year 25 December is itself a Sunday (26-31 December then holding no Sunday of its own), the feast vanished entirely and fell through to an ordinary Christmas ferial slug -- confirmed on 2022, 2033, 2039, 2044, 2050 (and roughly 1583..9999 domain-wide, one year in seven). temporal's dispatch now consults holy_family directly, independent of weekday, between the sunday_slug and christmas_feria_slug branches, carrying the identical identity the Sunday case already builds (subject Lord, holy_family_names, rank Festum). holy_family and anchors were already correct and needed no change. Added a direct example-based test for the fallback across five affected years plus a control for the unaffected Sunday case, and a domain-wide property (Holy Family is observed in every liturgical year 1583-9999) folded into both the 200-sample QCheck run and the exhaustive sweep -- the property that would have caught this directly. Un-pinned the now-fixed behaviour everywhere it was recorded as known-wrong: test_rite_of.ml's own 1583 pin (rewritten to assert the fix), test_validate_of.ml's own independent exhaustive-sweep counter and landmark-year filter (found via make check, not the initial survey), and data/of/expected-divergences-litcal.sexp's L2/L3 allow-list entries against the litcal oracle (closed and removed, with a dedicated regression test replacing the allow-list's own silence).
Diffstat (limited to 'test/test_litcal_of.ml')
-rw-r--r--test/test_litcal_of.ml64
1 files changed, 48 insertions, 16 deletions
diff --git a/test/test_litcal_of.ml b/test/test_litcal_of.ml
index 0c53899..6a638a7 100644
--- a/test/test_litcal_of.ml
+++ b/test/test_litcal_of.ml
@@ -532,16 +532,22 @@ let test_unwitnessed_ordinary_time_is_counted () =
none is a loose pattern that could silently absorb an unrelated future
mismatch. *)
-(* L2/L3 -- Normae n. 35(a), the Holy Family fallback: KNOWN WRONG, pinned
- (not fixed) by test_rite_of.ml's own
- [test_holy_family_fallback_1583_known_wrong_ferial]/
- [is_known_holy_family_fallback_gap]. 30 December 2033 is this fixture's
- own live witness (2033: 25 December is a Sunday, so 26-31 December's own
- window has no Sunday of its own and {!Rite_of.Temporal_of.temporal}
- never reaches [holy_family]'s own fixed 30-December fallback branch).
- litcal correctly names ["HolyFamily"]; colitur observes an ordinary
- Nativity-octave feria (Tabula II.9, band 90) instead. *)
-let is_l2_l3_holy_family_2033 (l : litcal_row) = String.equal l.event_key "HolyFamily" && String.equal l.date "2033-12-30"
+(* L2/L3 -- CLOSED, REMOVED (W2 fix, CLAUDE.md, temporal_of.ml). This
+ paragraph used to describe a KNOWN WRONG, pinned-not-fixed divergence:
+ Normae n. 35(a)'s "vel, ea deficiente, die 30 decembris" Holy Family
+ fallback was reached by {!Rite_of.Temporal_of.holy_family} but never by
+ {!Rite_of.Temporal_of.temporal} itself (its Holy-Family test lived only
+ inside [sunday_slug]'s own Sunday-only dispatch arm), so a year 25
+ December is itself a Sunday (26-31 December then holding no Sunday of
+ its own) left the fallback unreached. 30 December 2033 was this
+ fixture's own live witness. [Temporal_of.temporal] now consults
+ [Temporal_of.holy_family] directly, independent of weekday, so litcal
+ and colitur now agree on this date on both axes -- both the L2 (grade)
+ and L3 (identity) allow-list entries in
+ data/of/expected-divergences-litcal.sexp are removed (their own
+ `expected_rows` would now be 0, not 1), and [test_no_holy_family_2033_
+ divergence] below asserts this directly rather than merely by omission. *)
+let is_holy_family_2033 (l : litcal_row) = String.equal l.event_key "HolyFamily" && String.equal l.date "2033-12-30"
(* L4 -- litcal's own [grade_lcl] "celebration with precedence over
solemnities" text covers Trinity Sunday and Corpus Christi too (24
@@ -653,7 +659,6 @@ let test_grade_matches_or_is_explained () =
let day = resolved_of l.date in
let band = band_of day in
if List.mem band bands then ()
- else if is_l2_l3_holy_family_2033 l then bump "L2"
else if is_l4_trinity_corpus_christi l then bump "L4"
else if is_l5_mary_magdalene l then bump "L5"
else if is_l6_2033_tie_grade l then bump "L6"
@@ -674,7 +679,7 @@ let test_grade_matches_or_is_explained () =
match List.assoc_opt id by_id with
| None -> Alcotest.failf "allow-list entry %s is used by the comparator but not declared in %s" id allow_list_path
| Some e -> Alcotest.(check int) (Printf.sprintf "%s expected_rows (grade)" id) e.expected_rows actual)
- [ "L2"; "L4"; "L5"; "L6"; "L8" ]
+ [ "L4"; "L5"; "L6"; "L8" ]
(* The complement, mirroring [test_unwitnessed_ordinary_time_is_counted]:
every row [expected_bands] returns [None] for, classified by which of
@@ -717,7 +722,6 @@ let test_identity_matches_or_is_explained () =
let day = resolved_of l.date in
let actual_slug = Slug.to_string day.LD.observed.Cel.slug in
if String.equal actual_slug expected_slug then ()
- else if is_l2_l3_holy_family_2033 l then bump "L3"
else if is_l7_2033_tie_identity l then bump "L7"
else if is_l8_l9_joseph_2035 l then bump "L9"
else
@@ -735,7 +739,7 @@ let test_identity_matches_or_is_explained () =
match List.assoc_opt id by_id with
| None -> Alcotest.failf "allow-list entry %s is used by the comparator but not declared in %s" id allow_list_path
| Some e -> Alcotest.(check int) (Printf.sprintf "%s expected_rows (identity)" id) e.expected_rows actual)
- [ "L3"; "L7"; "L9" ]
+ [ "L7"; "L9" ]
(* The complement: every row [identity_map] has no entry for, whether
because litcal names an event_key outside the closed FIXED/NAMED set
@@ -758,15 +762,41 @@ let test_identity_unresolved_is_counted () =
Alcotest.(check int) "unresolved + the 508 checked = 4346 non-Triduum rows" 4346
(List.length unresolved + 508)
+(* W2 fix, direct regression coverage: L2/L3's own former witness (2033-12-30,
+ HolyFamily) now matches on BOTH axes, checked here explicitly rather than
+ only by the allow-list's own [expected_rows] falling to 0 (see
+ [is_holy_family_2033]'s own updated header for the full history). Also
+ confirms the predicate is not vacuous: the fixture really does carry this
+ row. *)
+let test_no_holy_family_2033_divergence () =
+ let litcal = litcal_rows () in
+ let rows = List.filter is_holy_family_2033 litcal in
+ Alcotest.(check int) "the fixture really does carry a HolyFamily row on 2033-12-30" 1 (List.length rows);
+ let row = List.hd rows in
+ let day = resolved_of row.date in
+ let band = band_of day in
+ (match expected_bands row.grade_lcl with
+ | None -> Alcotest.fail "2033-12-30: HolyFamily's own grade_lcl is no longer a comparable band"
+ | Some bands ->
+ Alcotest.(check bool) "2033-12-30: colitur's band matches litcal's expected grade" true (List.mem band bands));
+ let actual_slug = Slug.to_string day.LD.observed.Cel.slug in
+ Alcotest.(check string) "2033-12-30: colitur observes of-holy-family" "of-holy-family" actual_slug;
+ match Hashtbl.find_opt identity_tbl row.event_key with
+ | None -> Alcotest.fail "HolyFamily is no longer in identity_tbl"
+ | Some expected_slug ->
+ Alcotest.(check string) "2033-12-30: identity_tbl's own expected slug agrees" expected_slug actual_slug
+
(* ---------------------------------------------------------------------- *)
(* The whole allow-list file, taken as a whole: every id it declares is *)
-(* recognised by exactly one comparator above (L1 season, L2 grade, L3 *)
+(* recognised by exactly one comparator above (L1 season, L4 grade, L7 *)
(* identity) -- no orphan entry a comparator no longer references, and no *)
(* comparator silently reading an id this file does not declare (each *)
(* comparator's own [List.assoc_opt] already fails loudly for that half). *)
+(* L2/L3 (Holy Family) are gone -- CLOSED, REMOVED, W2 fix -- see *)
+(* [is_holy_family_2033]'s own header. *)
(* ---------------------------------------------------------------------- *)
-let recognized_allow_ids = [ "L1"; "L2"; "L3"; "L4"; "L5"; "L6"; "L7"; "L8"; "L9" ]
+let recognized_allow_ids = [ "L1"; "L4"; "L5"; "L6"; "L7"; "L8"; "L9" ]
let test_allow_list_has_no_orphan_entries () =
let allow_list = load_allow_list () in
@@ -785,5 +815,7 @@ let suite =
Alcotest.test_case "grade-unresolved rows are counted, not skipped" `Quick test_grade_unresolved_is_counted;
Alcotest.test_case "identity matches or is explained" `Quick test_identity_matches_or_is_explained;
Alcotest.test_case "identity-unresolved rows are counted, not skipped" `Quick test_identity_unresolved_is_counted;
+ Alcotest.test_case "W2 fix: no more Holy Family divergence on 2033-12-30" `Quick
+ test_no_holy_family_2033_divergence;
Alcotest.test_case "allow-list has no orphan entries" `Quick test_allow_list_has_no_orphan_entries
] )