summaryrefslogtreecommitdiff
path: root/test/test_temporal_ef.ml
diff options
context:
space:
mode:
Diffstat (limited to 'test/test_temporal_ef.ml')
-rw-r--r--test/test_temporal_ef.ml155
1 files changed, 155 insertions, 0 deletions
diff --git a/test/test_temporal_ef.ml b/test/test_temporal_ef.ml
index 751abd2..23a817d 100644
--- a/test/test_temporal_ef.ml
+++ b/test/test_temporal_ef.ml
@@ -167,12 +167,110 @@ let test_resumed_sundays () =
module Cel = Colitur_kernel.Celebration
module Sl = Colitur_kernel.Slug
module Colr = Colitur_kernel.Colour
+module Sub = Colitur_kernel.Subject
let office dt = (T.temporal dt).Colitur_kernel.Temporal.office
let slug_of dt = Sl.to_string (office dt).Cel.slug
let rank_of dt = V.rank_to_string (office dt).Cel.rank
let colour_of dt = Colr.to_string (office dt).Cel.colour
+let subject_of dt = (office dt).Cel.subject
let temporal_week dt = (T.temporal dt).Colitur_kernel.Temporal.week
+let la = Colitur_kernel.Lang.of_string_exn "la"
+let en = Colitur_kernel.Lang.of_string_exn "en"
+let name_la_of dt = Colitur_kernel.Names.find (office dt).Cel.names la
+
+(* RG 17(b) (Caput III, "De Dominicis"), primary text (scan-verified,
+ docs/research/rules-register.md §4): "festum S. Familiae Iesu, Mariae,
+ Ioseph, celebrandum dominica prima post Epiphaniam" -- the Holy Family is
+ celebrated on the first Sunday after Epiphany, EVERY year (unlike RG
+ 17(a)'s Holy Name, whose narrower 2-5 January window can be empty and
+ carries an explicit "vel ea deficiente, die 2 ianuarii" fallback in the
+ calendarium itself -- 7-13 January can never be empty of a Sunday, so
+ Holy Family needs, and the calendarium carries, no such fallback).
+ [T.holy_family_sunday] is [named]'s own [week_origin Time_after_epiphany]
+ formula, restated for its own citation -- not re-derived a second time.
+
+ Deliberately NOT part of [T.named]: [named] is checked before
+ [T.sunday_slug]'s own Christmastide branch in [T.temporal]'s dispatch, but
+ this feast's slug, rank and colour are EXACTLY what that branch already
+ computes for the 7-13 January Sunday (register's own pre-existing note on
+ [sunday_slug]: "the key stays lectio's") -- the only field that actually
+ needed to change is [subject] (silently always [Temporal] before this),
+ so [T.temporal] applies it as a targeted override inside that branch
+ rather than duplicating slug/rank/colour a second time through [named]'s
+ 4-tuple. *)
+let test_holy_family () =
+ (* 2026: Epiphany (6 Jan) is a Tuesday, so Holy Family falls 11 January --
+ an ordinary instance, no collision with the Baptism (13 Jan). *)
+ Alcotest.(check string) "2026: slug is the ordinary I-post-Epiphany Sunday key"
+ "ef-time-after-epiphany-sunday-1" (slug_of (d 2026 1 11));
+ Alcotest.(check string) "2026: II class" "class-2" (rank_of (d 2026 1 11));
+ Alcotest.(check string) "2026: white" "white" (colour_of (d 2026 1 11));
+ Alcotest.(check bool) "2026: subject is Lord (RG 91 entry 14, \"Festa Domini II \
+ classis\" -- the mystery of the Holy Family touches Christ)"
+ true
+ (subject_of (d 2026 1 11) = Sub.Lord);
+ (* fix round 1 (coordinator finding 3): a primary-sourced LATIN name,
+ zero circularity (the calendarium's own January table and the Mass
+ propers' own heading, both photographic scans, word for word:
+ "Sanctae Familiae Iesu, Mariae, Ioseph"). No ENGLISH name -- deliberate
+ (temporal_ef.ml's own comment), and every OTHER temporal-cycle
+ candidate still carries no name of any kind. *)
+ Alcotest.(check (option string)) "2026: Latin name from the calendarium/propers, verbatim"
+ (Some "Sanctae Familiae Iesu, Mariae, Ioseph") (name_la_of (d 2026 1 11));
+ Alcotest.(check (option string)) "2026: no English name (deliberate -- would read the oracle it is \
+ compared against)"
+ None (Colitur_kernel.Names.find (office (d 2026 1 11)).Cel.names en);
+ Alcotest.(check (option string)) "2026: an ordinary neighbouring Sunday carries no Latin name either"
+ None (name_la_of (d 2026 1 18));
+ (* 2019: Epiphany itself is a Sunday, so the first Sunday AFTER it is the
+ LATEST possible date, 13 January -- the one date Holy Family collides
+ with the fixed Commemoration of the Baptism of the Lord (data/ef/
+ sanctoral.sexp: "commemoration-of-the-baptism-of-the-lord", also 13
+ Jan, II class, subject Lord). [T.temporal]'s own resolution of WHO wins
+ that day is {!Rite_ef.Precedence_ef}'s business (band/disposition, see
+ test_precedence_ef.ml), not this function's -- this only proves
+ [T.temporal] itself still identifies 13 January 2019 as Holy Family's
+ own date, unconditionally, regardless of what else might compete for
+ the day once precedence is applied. *)
+ Alcotest.(check bool) "2019: 6 January is a Sunday" true (D.weekday (d 2019 1 6) = D.Sun);
+ Alcotest.(check string) "2019: Holy Family falls on the latest possible date, 13 Jan"
+ "2019-01-13" (D.to_iso8601 (T.holy_family_sunday 2019));
+ Alcotest.(check string) "2019: still the same slug" "ef-time-after-epiphany-sunday-1"
+ (slug_of (T.holy_family_sunday 2019));
+ Alcotest.(check bool) "2019: still subject Lord" true (subject_of (T.holy_family_sunday 2019) = Sub.Lord);
+ (* Every possible weekday for 6 January exercised, proving the formula
+ never leaves the 7-13 window empty (register's own citation above) --
+ not merely the two years already checked above. *)
+ List.iter
+ (fun y ->
+ let hf = T.holy_family_sunday y in
+ Alcotest.(check bool) (Printf.sprintf "%d: Holy Family is a Sunday" y) true
+ (D.weekday hf = D.Sun);
+ Alcotest.(check bool) (Printf.sprintf "%d: Holy Family falls 7-13 January" y) true
+ (D.month hf = 1 && D.day hf >= 7 && D.day hf <= 13))
+ [ 2019; 2020; 2021; 2022; 2023; 2024; 2025; 2026; 2027 ];
+ (* Negative control: an ORDINARY Sunday after Epiphany (the 2nd) stays
+ [subject Temporal] -- the override is scoped to exactly one Sunday a
+ year, not every Time-after-Epiphany-shaped Sunday. *)
+ Alcotest.(check bool) "2026: the following Sunday (18 Jan, II after Epiphany) is NOT Lord"
+ true
+ (subject_of (d 2026 1 18) = Sub.Temporal)
+
+(* Three of [test_holy_family]'s own exhaustive-weekday sample, pinned to
+ their literal ISO dates and independently cross-checked against
+ `date -d <iso> +%A` (the same discipline test_golden.ml's own header
+ describes for its own weekday assertions) -- the earliest possible date
+ (Epiphany a Saturday), the latest (Epiphany a Sunday), and one ordinary
+ middle case, rather than trusting the structural "always 7-13" check
+ above on its own without ever anchoring it to real calendar dates. *)
+let test_holy_family_no_gap_year () =
+ Alcotest.(check string) "2024 (6 Jan a Saturday): earliest possible date, 7 Jan" "2024-01-07"
+ (D.to_iso8601 (T.holy_family_sunday 2024));
+ Alcotest.(check string) "2019 (6 Jan a Sunday): latest possible date, 13 Jan" "2019-01-13"
+ (D.to_iso8601 (T.holy_family_sunday 2019));
+ Alcotest.(check string) "2025 (6 Jan a Monday): 12 Jan" "2025-01-12"
+ (D.to_iso8601 (T.holy_family_sunday 2025))
(* Regression for a Task 14 Validate finding, reworked (register finding 4):
[named] no longer carries its own week at all -- [temporal] computes it
@@ -432,6 +530,59 @@ let test_anchors_erosion_is_caught () =
[ "ef-ascension"; "ef-nativity" ]
(missing_from_anchors ~named_slugs ~anchors:eroded_anchors)
+(* Holy Family's own anchor coverage, kept SEPARATE from the two tests above:
+ it is genuinely NOT one of [T.named]'s outputs (test_holy_family's own
+ comment explains why -- [T.temporal] applies it as a targeted override
+ inside [T.sunday_slug]'s branch instead), so [named_slugs_for_year]'s scan
+ cannot see it, and folding it into [test_anchors_cover_all_named_days]/
+ [test_anchors_erosion_is_caught] would misrepresent what those two are
+ actually proving ("everything [named] produces"). This proves the same
+ two properties directly against [T.holy_family_sunday] instead: the entry
+ is present, AND deleting it is caught -- {!Validate}'s own production
+ anchor check (validate.ml, register/spec §5.7) calls [temporal], not
+ [named], so it is unaffected by this distinction and already covers this
+ entry regardless; this is this file's own unit-level guard against the
+ same erosion.
+
+ CORRECTED, fix round 1 (coordinator finding 5): the original version of
+ this test built [eroded] by [List.filter]-ing the exact pair it then
+ asserted [List.mem ... eroded = false] against -- tautological (removing
+ an element from a list and then checking it is not there proves nothing
+ about a DETECTOR; it proves [List.filter] works). The genuine detector
+ the OTHER erosion test above actually exercises is [missing_from_anchors]
+ itself, called with an "expected" set sourced INDEPENDENTLY of
+ [T.anchors] ([named_slugs_for_year], via [T.named] -- a different
+ function entirely) -- the interesting claim is that the SAME slug is
+ confirmed by two independent computations, not merely that a filtered
+ list lacks what was filtered out of it. Reworked to match: the
+ "expected" side here is [T.sunday_slug expected_date] -- computed via
+ season/weekday logic, not read out of [T.anchors]'s own hand-typed
+ string -- so the presence check below is a genuine cross-check between
+ two independent sources, and the erosion check reuses the real
+ [missing_from_anchors] detector rather than re-deriving a bespoke,
+ self-referential one. *)
+let test_holy_family_anchor_present_and_erosion_is_caught () =
+ let y = 2026 in
+ let expected_date = T.holy_family_sunday y in
+ let expected_slug =
+ match T.sunday_slug expected_date with
+ | Some s -> s
+ | None -> Alcotest.fail "sanity: holy_family_sunday must itself be a Sunday"
+ in
+ Alcotest.(check (list string))
+ "the anchor entry exists: T.sunday_slug's own independent computation is not missing from T.anchors" []
+ (missing_from_anchors ~named_slugs:[ expected_slug ] ~anchors:(anchor_slugs y));
+ (* Erosion: with the entry struck out of [anchors], [missing_from_anchors]
+ -- the SAME real detector, not a bespoke re-check -- must now report it
+ missing, using [expected_slug]'s own independent source ([sunday_slug])
+ as the thing being checked FOR, not derived from the erosion itself. *)
+ let eroded_anchors = List.filter (fun s -> s <> expected_slug) (anchor_slugs y) in
+ Alcotest.(check (list string)) "the erosion is caught: missing_from_anchors reports it, and only it"
+ [ expected_slug ]
+ (missing_from_anchors ~named_slugs:[ expected_slug ] ~anchors:eroded_anchors);
+ Alcotest.(check string) "sanity: temporal itself is unaffected by the anchors-list erosion"
+ expected_slug (slug_of expected_date)
+
let test_totality () =
(* Every day of 2026 yields an office without raising. Not a slug
re-validation -- Slug.t is a private string validated on construction,
@@ -453,6 +604,8 @@ let suite_extra =
Alcotest.test_case "nativity octave" `Quick test_nativity_octave;
Alcotest.test_case "week numbers" `Quick test_week_numbers;
Alcotest.test_case "sunday slugs" `Quick test_sunday_slugs;
+ Alcotest.test_case "holy family (RG 17(b))" `Quick test_holy_family;
+ Alcotest.test_case "holy family: no gap year (RG 17(b))" `Quick test_holy_family_no_gap_year;
Alcotest.test_case "week/sunday_slug agree" `Quick test_week_sunday_slug_agree;
Alcotest.test_case "resumed sundays" `Quick test_resumed_sundays;
Alcotest.test_case "ferial slugs and ranks" `Quick test_ferial_slugs_and_ranks;
@@ -465,6 +618,8 @@ let suite_extra =
Alcotest.test_case "totality" `Quick test_totality;
Alcotest.test_case "anchors cover all named days" `Quick
test_anchors_cover_all_named_days;
+ Alcotest.test_case "holy family anchor present, erosion caught" `Quick
+ test_holy_family_anchor_present_and_erosion_is_caught;
Alcotest.test_case "anchors erosion is caught" `Quick test_anchors_erosion_is_caught ]
let suite =