diff options
Diffstat (limited to 'test/test_golden_of.ml')
| -rw-r--r-- | test/test_golden_of.ml | 332 |
1 files changed, 332 insertions, 0 deletions
diff --git a/test/test_golden_of.ml b/test/test_golden_of.ml new file mode 100644 index 0000000..f40b62a --- /dev/null +++ b/test/test_golden_of.ml @@ -0,0 +1,332 @@ +(* Task 6 (2026-08-26-colitur-of-phases-3-5): golden pins, the OF + counterpart of test_golden.ml -- validation layer 5, one rite down. + + *** THE ONE RULE THIS FILE IS BUILT AROUND (test_golden.ml's own, + restated for this file) *** + + A golden test blesses whatever it is given. Every other layer this + branch built derives its expectation from something independent of + colitur's own output -- a property (test_validate_of.ml), a second + implementation (test_litcal_of.ml). This layer's expectation comes from + ME, so every literal string below was checked BY HAND, against the + primary-source citations already carried by temporal_of.ml/ + precedence_of.ml/the amendment files (each pin's own comment names + which), BEFORE it was typed in here -- never copied from a `colitur + day`/`readings` run and then rationalised. Every date and weekday + claim below was independently cross-checked against + {!Colitur_kernel.Computus.gregorian_easter} arithmetic done BY HAND + (Easter date + a day-offset, or a `date -d` cross-check for a fixed + date), the same discipline test_golden.ml's own header states for its + own pins. + + Six landmark/tricky dates, per this task's own brief, at minimum: + 1. A year Ordinary Time skips a week (2026 -- already independently + hand-verified, twice over, by test_temporal_of.ml's own + [test_ordinary_time_resumption]; reused here, not re-derived, since + re-deriving the SAME arithmetic a second time would not add + confidence, only a second literal to keep in sync). + 2. A year 24 December falls on a Sunday (2028; the Advent IV case -- + commit a2fba71d, "fix(of): Nativity Vigil no longer suppresses a + Sunday of Advent"). + 3. A year the Annunciation falls in Holy Week (2027; Normae n. 60's + fixed Easter+8 transfer). + 4. Mary, Mother of the Church (Easter+50). + 5. All Souls (2 November). + 6. The Sacred Heart (Easter+68). + Plus 17 December's own date-keyed reading (Gen 49:2,8-10 / Mt 1:1-17, + fixed earlier on this branch, commit b6d0eba0). *) + +module Cal = Colitur_kernel.Calendar +module Layer = Colitur_kernel.Layer +module Overlay = Colitur_kernel.Overlay +module LD = Colitur_kernel.Liturgical_day +module Slug = Colitur_kernel.Slug +module Date = Colitur_kernel.Date +module Cel = Colitur_kernel.Celebration +module Colour = Colitur_kernel.Colour +module Citation = Colitur_kernel.Citation +module Temporal = Colitur_kernel.Temporal +module Subject = Colitur_kernel.Subject +module V = Rite_of.Vocab_of + +let calendar_path = "../data/of/calendar-2002.sexp" +let amendments_dir = "../data/of/amendments/" +let of_lectionary_path = "../data/of/lectionary.sexp" + +let amendment_files = + [ "001-padre-pio.sexp"; "002-juan-diego-cuauhtlatoatzin.sexp"; "003-our-lady-of-guadalupe.sexp"; + "004-john-xxiii-john-paul-ii.sexp"; "005-mary-magdalene-rank.sexp"; "006-mary-mother-of-the-church.sexp"; + "007-paul-vi.sexp"; "008-our-lady-of-loreto.sexp"; "009-faustina-kowalska.sexp"; + "010-narek-avila-hildegard.sexp"; "011-martha-mary-lazarus.sexp"; "012-teresa-of-calcutta.sexp"; + "013-john-henry-newman.sexp" ] + +(* Duplicated verbatim from test_rite_of.ml/test_validate_of.ml -- see + either file's own header for why (no .mli any of them could share it + through). *) +let real_of_layer = + let base = + match Layer.load V.rank_of_sexp calendar_path with + | Ok l -> l + | Error e -> failwith (Printf.sprintf "%s: failed to load: %s" calendar_path e) + in + let overlays = + List.map + (fun name -> + let path = amendments_dir ^ name in + match Overlay.load V.rank_of_sexp path with + | Ok o -> o + | Error e -> failwith (Printf.sprintf "%s: failed to load: %s" path e)) + amendment_files + in + let layer, diagnostics = Overlay.merge base overlays in + if diagnostics <> [] then + failwith + (Printf.sprintf "unexpected amendment diagnostics: %s" + (String.concat "; " (List.map Overlay.diagnostic_to_string diagnostics))); + layer + +let real_of_lectionary = + match Colitur_kernel.Lectionary.load of_lectionary_path with + | Ok l -> l + | Error e -> failwith (Printf.sprintf "%s: failed to load: %s" of_lectionary_path e) + +let real_of_rite = Rite_of.context ~lectionary:real_of_lectionary + +let mk y m d = match Date.make ~year:y ~month:m ~day:d with Ok t -> t | Error e -> failwith e +let fetch y m d = Cal.day real_of_rite real_of_layer (mk y m d) + +let slug_s (c : V.rank Cel.t) = Slug.to_string c.Cel.slug +let rank_s (c : V.rank Cel.t) = V.rank_to_string c.Cel.rank +let colour_s (c : V.rank Cel.t) = Colour.to_string c.Cel.colour +let subject_s (c : V.rank Cel.t) = Subject.to_string c.Cel.subject + +(* [describe]'s own five components mirror {!Colitur_kernel.Liturgical_day + .t}'s own promise never to lose a field, the same discipline + test_golden.ml's header argues for at length -- one string per day, not + five separate checks, so a one-line diff on failure still names the day + and shows exactly which field changed. + + [comms] is included even though it is STRUCTURALLY ALWAYS "[]" for this + rite ({!Rite_of.Precedence_of.admit} never returns anything else, + Precedence_of.mli's own citation) -- kept in the format rather than + dropped, so a future rite-level change that somehow populated it would + still show up in every existing pin's own diff, not silently pass. *) +let describe (day : (V.season, V.rank) LD.t) = + let t = day.LD.temporal in + let week = match t.Temporal.week with Some n -> string_of_int n | None -> "-" in + let comms = + List.map (fun (c, _) -> slug_s c) day.LD.commemorations |> List.sort compare |> String.concat "," + in + let tin = match day.LD.transferred_in with None -> "-" | Some c -> slug_s c in + let tout = + List.map (fun (c, d) -> Printf.sprintf "%s->%s" (slug_s c) (Date.to_iso8601 d)) day.LD.transferred_out + |> List.sort compare |> String.concat "," + in + Printf.sprintf "%s %s season=%s week=%s slug=%s rank=%s colour=%s subject=%s comms=[%s] in=%s out=[%s]" + (Date.to_iso8601 day.LD.date) + (Date.weekday_to_string t.Temporal.weekday) + (V.season_to_string t.Temporal.season) + week (slug_s day.LD.observed) (rank_s day.LD.observed) (colour_s day.LD.observed) (subject_s day.LD.observed) + comms tin tout + +let check ~msg y m d expected = Alcotest.(check string) msg expected (describe (fetch y m d)) + +let describe_readings (day : (V.season, V.rank) LD.t) = + let part_ref p = + match List.find_opt (fun (c : Citation.t) -> c.Citation.part = p) day.LD.citations with + | Some c -> c.Citation.reference + | None -> "-" + in + Printf.sprintf "%s slug=%s first=%s gospel=%s" + (Date.to_iso8601 day.LD.date) + (slug_s day.LD.observed) (part_ref Citation.First) (part_ref Citation.Gospel) + +let check_readings ~msg y m d expected = Alcotest.(check string) msg expected (describe_readings (fetch y m d)) + +(* ------------------------------------------------------------------ *) +(* 1. Ordinary Time skips a week: 2026. Pentecost is 24 May 2026 (Easter *) +(* 5 April + 49); block A tops out at week 6 (Shrove Tuesday, 17 Feb, *) +(* independently hand-verified against Ash Wednesday 18 Feb); Normae *) +(* n. 43-44's own two-block arithmetic resumes the Monday after Pentecost *) +(* (25 May) at week 8, not 7 -- week 7 never occurs anywhere in Ordinary *) +(* Time that civil year (test_temporal_of.ml's own *) +(* [test_ordinary_time_resumption], which checks this exhaustively over *) +(* the WHOLE civil year, not merely this one pinned Monday). Pinned here *) +(* through the ASSEMBLED pipeline (Rite_of.context/Calendar.day), not *) +(* Temporal_of directly -- the same "genuinely different code path" *) +(* argument test_validate_of.ml's own [ot_and_coverage_of_year] makes. *) +(* ------------------------------------------------------------------ *) + +let test_ordinary_time_skips_a_week_2026 () = + (* subject=temporal, not =lord: {!Rite_of.Temporal_of.named}'s own tuple + shape (season, slug, colour, rank) carries no subject field at all, + so every day it builds (Nativity, Epiphany, Ascension, Pentecost, Ash + Wednesday, Palm Sunday, Easter, the Easter Octave, Trinity, Corpus + Christi, Christ the King) defaults to {!Colitur_kernel.Subject + .Temporal} -- {!Rite_of.Precedence_of.band}'s own entry-2 branch + routes these by SLUG membership ([entry_2_named]), never by subject, + so this is not a gap, just a different (and correct) tagging + convention from the DATA-origin solemnities pinned elsewhere in this + file (Sacred Heart, the Annunciation, All Souls), which carry a real + [subject] field in their own calendar-2002.sexp/amendment record. *) + check ~msg:"2026-05-24 Pentecost: Easter+49, red, block A's own last Sunday" 2026 5 24 + "2026-05-24 sunday season=easter week=8 slug=of-pentecost rank=sollemnitas colour=red subject=temporal \ + comms=[] in=- out=[]"; + check ~msg:"2026-05-25 (Mon after Pentecost) resumes Ordinary Time at week 8, SKIPPING week 7" 2026 5 25 + "2026-05-25 monday season=ordinary-time week=8 slug=mary-mother-of-the-church rank=memoria-obligatoria \ + colour=white subject=bvm comms=[] in=- out=[]" + +(* ------------------------------------------------------------------ *) +(* 2. 24 December falls on a Sunday: 2028 (and, symmetrically, 2034 -- *) +(* both inside commit a2fba71d's own worked examples). Before that fix, *) +(* the Vigil Mass of the Nativity silently displaced the Fourth Sunday of *) +(* Advent (Tabula I.2/Normae n. 5 -- a privileged Sunday outranks *) +(* everything but a HIGHER Tabula entry, and the Vigil is Tabula I.3). *) +(* After it, 24 December on a Sunday resolves as the Fourth Sunday of *) +(* Advent itself, violet (Advent's own colour, not Gaudete's rose -- *) +(* that is week 3 only, IGMR 346(c)/[V.season]'s own [is_rose_sunday]). *) +(* ------------------------------------------------------------------ *) + +let test_advent_iv_on_christmas_eve_2028 () = + check ~msg:"2028-12-24 (a Sunday): the Fourth Sunday of Advent, not the Vigil (Tabula I.2 over I.3)" 2028 12 24 + "2028-12-24 sunday season=advent week=4 slug=of-advent-sunday-4 rank=sollemnitas colour=violet \ + subject=temporal comms=[] in=- out=[]"; + (* The day immediately before it: still the Third Week of Advent, an + ordinary (non-rose) Saturday feria -- confirms the season/week + boundary itself sits in the right place, not merely that 24 December + alone reads correctly. *) + check ~msg:"2028-12-23: still Advent week 3, an ordinary violet feria" 2028 12 23 + "2028-12-23 saturday season=advent week=3 slug=of-advent-3-saturday rank=feria colour=violet subject=temporal \ + comms=[] in=- out=[]"; + (* And the day after: Christmas Day itself, entirely unaffected by which + office 24 December carried. *) + check ~msg:"2028-12-25: the Nativity, unaffected" 2028 12 25 + "2028-12-25 monday season=christmas week=- slug=of-nativity rank=sollemnitas colour=white subject=temporal \ + comms=[] in=- out=[]" + +(* The symmetric 2034 witness -- a second, independent year, not a + re-check of the same arithmetic (a fix that happened to work for one + specific weekday alignment but not the general rule would show up as a + difference here). *) +let test_advent_iv_on_christmas_eve_2034 () = + check ~msg:"2034-12-24 (a Sunday): the Fourth Sunday of Advent" 2034 12 24 + "2034-12-24 sunday season=advent week=4 slug=of-advent-sunday-4 rank=sollemnitas colour=violet \ + subject=temporal comms=[] in=- out=[]" + +(* ------------------------------------------------------------------ *) +(* 3. The Annunciation in Holy Week: 2027 (Easter 28 March 2027, so 25 *) +(* March is Easter-3, the Thursday of Holy Week). Normae n. 60's own *) +(* FIRST, fixed-destination rule (not the general "nearest later free *) +(* day" search rule 3): "Sollemnitas... Annuntiationis Domini, *) +(* quotiescumque occurrit aliquo die Hebdomadae sanctae, semper ad feriam *) +(* II post dominicam II Paschae erit transferenda" -- ALWAYS to the *) +(* Monday after the Second Sunday of Easter, Easter+8, here 5 April 2027. *) +(* Pinned as THREE dates, not one: 25 March itself (Holy Thursday's own *) +(* office wins outright, IGMR 346(a)'s "celebrationibus Domini, quae non *) +(* sint de eius Passione" -- white, not Passiontide violet/red), the *) +(* departure recorded in [out], and the arrival on 5 April recorded in *) +(* [in] -- Calendar's own transfer bookkeeping, both ends. *) +(* ------------------------------------------------------------------ *) + +let test_annunciation_holy_week_2027 () = + check ~msg:"2027-03-25 (Holy Thursday, Easter-3): the day's own office wins, white, and records the \ + Annunciation's own departure" + 2027 3 25 + "2027-03-25 thursday season=lent week=6 slug=of-lent-6-thursday rank=feria colour=white subject=temporal \ + comms=[] in=- out=[annunciation-of-the-lord->2027-04-05]"; + check ~msg:"2027-04-05 (Easter+8, the Monday after the Second Sunday of Easter): the Annunciation arrives" 2027 4 + 5 + "2027-04-05 monday season=easter week=2 slug=annunciation-of-the-lord rank=sollemnitas colour=white \ + subject=lord comms=[] in=annunciation-of-the-lord out=[]" + +(* ------------------------------------------------------------------ *) +(* 4. Mary, Mother of the Church: Easter+50, the Monday after Pentecost *) +(* (data/of/amendments/006-mary-mother-of-the-church.sexp's own citation, *) +(* CDW decree 11 February 2018, Prot. N. 1037/2017, AAS 110 (2018) *) +(* 437-438). ALREADY the day this file's own [test_ordinary_time_skips_a_ *) +(* week_2026] pins (2026-05-25) -- not re-pinned a second time under a *) +(* different date; that pin's own [msg] is worded to name both facts it *) +(* carries (the week-8 resumption AND Mother of the Church herself), the *) +(* SAME "one string, several independently-true facts" economy *) +(* test_golden.ml's own header argues for, rather than a second literal *) +(* asserting the identical field values under a different label. *) +(* ------------------------------------------------------------------ *) + +(* ------------------------------------------------------------------ *) +(* 5. All Souls: 2 November, every year, unconditionally (a fixed date, *) +(* not movable) -- Tabula I.3 ("Commemoratio omnium fidelium *) +(* defunctorum"), tagged Sollemnitas on the Tabula's own authority *) +(* despite the calendarium page itself printing no grade word *) +(* (precedence_of.ml's own [band] entry-3 citation has the full *) +(* argument). Violet (not white, not black) -- shipped *) +(* data/of/calendar-2002.sexp's own colour, already independently *) +(* exercised by test_precedence_of.ml's own *) +(* [test_all_souls_beats_an_ordinary_sunday] against a DIFFERENT year *) +(* (2025-11-02, itself a Sunday); 2026 is chosen here instead so this *) +(* file's own pin is not a byte-identical duplicate of that one. ------- *) + +let test_all_souls_2026 () = + check ~msg:"2026-11-02: All Souls, violet, every year unconditionally" 2026 11 2 + "2026-11-02 monday season=ordinary-time week=31 slug=all-souls rank=sollemnitas colour=violet subject=saint \ + comms=[] in=- out=[]" + +(* ------------------------------------------------------------------ *) +(* 6. The Sacred Heart: Easter+68 (calendar-2002.sexp's own *) +(* [Easter_offset 68] entry) -- Easter 2026 is 5 April, so Easter+68 is *) +(* 12 June 2026 (25 days left in April + 31 in May + 12 = 68, hand- *) +(* verified against {!Colitur_kernel.Computus.gregorian_easter} directly, *) +(* not merely trusted from the offset). White, Sollemnitas, subject Lord *) +(* -- shipped data, matching the Missal's own title ("SACRATISSIMI *) +(* CORDIS IESU"). ------------------------------------------------------ *) + +let test_sacred_heart_2026 () = + Alcotest.(check string) "Easter 2026 is really 5 April (the Sacred Heart's own Easter+68 anchor)" "2026-04-05" + (Date.to_iso8601 (Colitur_kernel.Computus.gregorian_easter 2026)); + check ~msg:"2026-06-12 (Easter+68): the Sacred Heart of Jesus" 2026 6 12 + "2026-06-12 friday season=ordinary-time week=10 slug=sacred-heart-of-jesus rank=sollemnitas colour=white \ + subject=lord comms=[] in=- out=[]" + +(* ------------------------------------------------------------------ *) +(* 7. 17 December's own date-keyed reading (OLM n. 69.3, commit b6d0eba0, *) +(* "fix(of-lectionary): stop serving drifting readings for O-Antiphon and *) +(* Christmas-season dates"): Genesis 49:2,8-10 / Matthew 1:1-17, fixed by *) +(* CIVIL DATE, not by the day's own weekday-keyed ferial slug -- this pin *) +(* is what would have caught the original bug (a citation that silently *) +(* drifted year to year on this date before the fix), pinned through the *) +(* full resolved pipeline rather than test_lectionary_of.ml's own more *) +(* narrowly-scoped unit test for the same fact. Raw reference strings *) +(* (data/of/lectionary.sexp's own literal text, "Genesis"/"Matthew" in *) +(* full), not a rendered siglum -- the same discipline test_golden.ml's *) +(* own [describe_readings] follows for EF. *) +(* ------------------------------------------------------------------ *) + +let test_december_17_reading () = + check_readings ~msg:"2026-12-17: the O-Antiphon date-keyed reading, Genesis 49:2,8-10 / Matthew 1:1-17" 2026 12 + 17 "2026-12-17 slug=of-advent-3-thursday first=Genesis 49:2,8-10 gospel=Matthew 1:1-17"; + (* A second, independent civil year -- OLM n. 69.4's weekday-cycle + letter flips on the liturgical year's own parity (Lectionary_of + .weekday_cycle), so a date-keyed reading that accidentally still + routed through the weekday-keyed path would likely only fail in + roughly half of all years -- this second year is chosen from the + OTHER cycle letter than 2026's own (test_lectionary_of.ml's own + [test_date_keyed_no_drift] already proves this pair directly at the + Lectionary_of level; repeated here through the full pipeline for the + same reason [test_advent_iv_on_christmas_eve_2034] repeats 2028's own + shape in a second year). *) + check_readings ~msg:"2027-12-17: the same date-keyed reading, a second civil year, the other weekday-cycle \ + letter" + 2027 12 17 "2027-12-17 slug=of-advent-3-friday first=Genesis 49:2,8-10 gospel=Matthew 1:1-17" + +let suite = + ( "golden-of", + [ Alcotest.test_case "Ordinary Time skips a week (2026); Mary, Mother of the Church" `Quick + test_ordinary_time_skips_a_week_2026; + Alcotest.test_case "Advent IV on Christmas Eve (2028)" `Quick test_advent_iv_on_christmas_eve_2028; + Alcotest.test_case "Advent IV on Christmas Eve, a second year (2034)" `Quick + test_advent_iv_on_christmas_eve_2034; + Alcotest.test_case "the Annunciation in Holy Week, transferred to Easter+8 (2027)" `Quick + test_annunciation_holy_week_2027; + Alcotest.test_case "All Souls (2026)" `Quick test_all_souls_2026; + Alcotest.test_case "the Sacred Heart, Easter+68 (2026)" `Quick test_sacred_heart_2026; + Alcotest.test_case "17 December's own date-keyed reading, two civil years" `Quick test_december_17_reading + ] ) |
