diff options
Diffstat (limited to 'test')
| -rw-r--r-- | test/dune | 8 | ||||
| -rw-r--r-- | test/test_calendar.ml | 15 | ||||
| -rw-r--r-- | test/test_differential.ml | 14 | ||||
| -rw-r--r-- | test/test_golden.ml | 15 | ||||
| -rw-r--r-- | test/test_lectionary_ef.ml | 323 | ||||
| -rw-r--r-- | test/test_oracle.ml | 14 | ||||
| -rw-r--r-- | test/test_rite_ef.ml | 14 | ||||
| -rw-r--r-- | test/test_validate.ml | 15 |
8 files changed, 409 insertions, 9 deletions
@@ -7,10 +7,16 @@ ../data/ef/expected-divergences.sexp ../data/ef/expected-divergences-missalemeum.sexp ../data/ef/lectionary.sexp + ../data/ef/commons.sexp fixtures/lectio-ef-2005-2050.txt fixtures/missalemeum-ef-2026-2027.txt) (preprocess (pps ppx_sexp_conv))) (cram - (deps %{bin:colitur} ../data/ef/sanctoral.sexp ../data/ef/adjustments.sexp)) + (deps + %{bin:colitur} + ../data/ef/sanctoral.sexp + ../data/ef/adjustments.sexp + ../data/ef/lectionary.sexp + ../data/ef/commons.sexp)) diff --git a/test/test_calendar.ml b/test/test_calendar.ml index aeaeea0..a533ab2 100644 --- a/test/test_calendar.ml +++ b/test/test_calendar.ml @@ -471,6 +471,18 @@ let real_ef_lectionary_for_transfer_probes = | Error e -> failwith ("../data/ef/lectionary.sexp: " ^ e) | Ok l -> l +(* The Commons (data/ef/commons.sexp) travel the same caller-supplied seam + as the lectionary above, and [~commons] is required rather than defaulted + so that no caller can silently run with none -- nothing in layers 3-5 + compares reading citations, so a rite quietly missing its Commons would + be invisible. Loaded here even where this file asserts nothing about + readings, so that the rite under test is the same one bin/main.ml + assembles. *) +let real_ef_commons_for_transfer_probes = + match Rite_ef.Lectionary_ef.Commons.load "../data/ef/commons.sexp" with + | Error e -> failwith ("../data/ef/commons.sexp: " ^ e) + | Ok c -> c + let test_transferred_commemoration_only_capped_out_at_target_settles_cleanly () = let probe_date = match Colitur_kernel.Date_spec.fixed ~month:4 ~day:26 with Ok d -> d | Error e -> failwith e @@ -488,7 +500,8 @@ let test_transferred_commemoration_only_capped_out_at_target_settles_cleanly () 25 and 26 April 2011. *) let year = C.year - (Rite_ef.context ~lectionary:real_ef_lectionary_for_transfer_probes) + (Rite_ef.context ~lectionary:real_ef_lectionary_for_transfer_probes + ~commons:real_ef_commons_for_transfer_probes) augmented_layer 2010 in let find_date target = diff --git a/test/test_differential.ml b/test/test_differential.ml index 0b68f90..acbb559 100644 --- a/test/test_differential.ml +++ b/test/test_differential.ml @@ -214,6 +214,18 @@ let real_lectionary () = | Ok l -> l | Error e -> Alcotest.failf "../data/ef/lectionary.sexp: failed to load: %s" e +(* The Commons (data/ef/commons.sexp) travel the same caller-supplied seam + as the lectionary above, and [~commons] is required rather than defaulted + so that no caller can silently run with none -- nothing in layers 3-5 + compares reading citations, so a rite quietly missing its Commons would + be invisible. Loaded here even where this file asserts nothing about + readings, so that the rite under test is the same one bin/main.ml + assembles. *) +let real_commons () = + match Rite_ef.Lectionary_ef.Commons.load "../data/ef/commons.sexp" with + | Ok c -> c + | Error e -> Alcotest.failf "../data/ef/commons.sexp: failed to load: %s" e + (* --- The seven-column row both streams share (commemorations excluded, --- *) (* limit 1 above). *) type row = { @@ -262,7 +274,7 @@ let lectio_rows () = List.map row_of_line (read_lines fixture_path) test_rite_ef.ml already made for [real_layer] above. *) let colitur_rows_2005_2050 () = let layer = real_layer () in - let rite = Rite_ef.context ~lectionary:(real_lectionary ()) in + let rite = Rite_ef.context ~lectionary:(real_lectionary ()) ~commons:(real_commons ()) in let by_rata : (int, (V.season, V.rank) LD.t) Hashtbl.t = Hashtbl.create 20000 in for y = 2004 to 2050 do let days = Cal.year rite layer y in diff --git a/test/test_golden.ml b/test/test_golden.ml index 188e37f..2f8930f 100644 --- a/test/test_golden.ml +++ b/test/test_golden.ml @@ -64,6 +64,7 @@ module V = Rite_ef.Vocab_ef let sanctoral_path = "../data/ef/sanctoral.sexp" let adjustments_path = "../data/ef/adjustments.sexp" let lectionary_path = "../data/ef/lectionary.sexp" +let commons_path = "../data/ef/commons.sexp" (* Loaded once at module init, same convention test_validate.ml's own [real_ef_layer] uses (not test_oracle.ml/test_differential.ml's @@ -91,7 +92,19 @@ let real_ef_lectionary = | Error e -> failwith (Printf.sprintf "%s: failed to load: %s" lectionary_path e) | Ok l -> l -let real_ef_rite = Rite_ef.context ~lectionary:real_ef_lectionary +(* The Commons (data/ef/commons.sexp) travel the same caller-supplied seam + as the lectionary above, and [~commons] is required rather than defaulted + so that no caller can silently run with none -- nothing in layers 3-5 + compares reading citations, so a rite quietly missing its Commons would + be invisible. Loaded here even where this file asserts nothing about + readings, so that the rite under test is the same one bin/main.ml + assembles. *) +let real_ef_commons = + match Rite_ef.Lectionary_ef.Commons.load commons_path with + | Error e -> failwith (Printf.sprintf "%s: failed to load: %s" commons_path e) + | Ok c -> c + +let real_ef_rite = Rite_ef.context ~lectionary:real_ef_lectionary ~commons:real_ef_commons let mk y m d = match Date.make ~year:y ~month:m ~day:d with Ok t -> t | Error e -> failwith e diff --git a/test/test_lectionary_ef.ml b/test/test_lectionary_ef.ml index 8af57f6..e28476d 100644 --- a/test/test_lectionary_ef.ml +++ b/test/test_lectionary_ef.ml @@ -11,6 +11,7 @@ open Rite_ef let sanctoral_path = "../data/ef/sanctoral.sexp" let adjustments_path = "../data/ef/adjustments.sexp" let lectionary_path = "../data/ef/lectionary.sexp" +let commons_path = "../data/ef/commons.sexp" let real_layer () = let layer = @@ -40,6 +41,17 @@ let real_lectionary () = | Ok l -> l | Error e -> Alcotest.failf "%s: failed to load: %s" lectionary_path e +(* The Commons travel the same caller-supplied seam (Task 6). Note that + [Commons.load]'s own validation runs here on the committed file: a + duplicate common id, a duplicate assignment, an empty formulary, or an + assignment naming a common that does not exist all come back as [Error] + and fail every test in this file rather than degrading silently to "this + saint has no Common". *) +let real_commons () = + match Lectionary_ef.Commons.load commons_path with + | Ok c -> c + | Error e -> Alcotest.failf "%s: failed to load: %s" commons_path e + (* [Calendar.day] (not [year]): the liturgical year "opening in civil year y" is Advent-anchored (RG 61), so [Calendar.year _ _ 2030] covers Advent 2030 through November 2031 -- it would never contain 13 January 2030, which @@ -48,7 +60,9 @@ let real_lectionary () = let day y m d = let date = match Date.make ~year:y ~month:m ~day:d with | Ok x -> x | Error e -> Alcotest.fail e in - Calendar.day (Rite_ef.context ~lectionary:(real_lectionary ())) (real_layer ()) date + Calendar.day + (Rite_ef.context ~lectionary:(real_lectionary ()) ~commons:(real_commons ())) + (real_layer ()) date let refs (ld : _ Liturgical_day.t) = List.map (fun c -> c.Citation.reference) ld.citations @@ -164,6 +178,289 @@ let test_step3_sunday_does_not_recurse () = let d = day 2026 6 14 in Alcotest.(check int) "a Sunday resolves without looping" 2 (List.length (refs d)) +(* ---------------------------------------------------------------------- *) +(* Chain step 4: the Commons (Task 6). *) +(* *) +(* Step 4 EXECUTES SECOND, between step 1 and step 2, not last as the task *) +(* brief sketched -- see lectionary_ef.ml's own branch comment for the *) +(* measurement and the primary-source warrant. The step NUMBER is kept as *) +(* the plan wrote it so that every "step 3" already recorded elsewhere *) +(* still names the same branch. *) +(* *) +(* Every expected value below is quoted from data/ef/commons.sexp, which in *) +(* turn cites the Missal page it was read from. There is NO oracle for any *) +(* of it: lectio has no readings for these feasts (that is why Task 6 *) +(* exists) and missalemeum's 2026-2027 window never observes one of them. *) +(* These pins and the golden ones are the entire regression net. *) +(* ---------------------------------------------------------------------- *) + +(* 2038-03-06 observes Sts Perpetua and Felicity: a III-class feast that + beats the Septuagesima II Saturday feria. The Missal sends 6 March to + "Missa Me exspectaverunt, de Communi non Virginum I loco", whose printed + Epistle and Gospel are these. + + This test is also the ORDERING pin, and it is the reason it uses explicit + values rather than a length check: with step 4 placed last (the brief's + sketch) this day resolves through step 2 to the feria's own + "2 Cor. 11:19-33; 12:1-9" / "Luke 8:4-15" instead -- a green + two-citation answer that a [List.length = 2] assertion would not + distinguish from the right one. *) +let test_step4_commons_perpetua_and_felicity () = + Alcotest.(check (list string)) + "6 March 2038: Sts Perpetua and Felicity take the Common of non-Virgins I" + [ "Ecclus 51:1-8, 12"; "Matt 13:44-52" ] + (refs (day 2038 3 6)) + +(* 2038-03-09, St Frances of Rome: "Missa Cognovi, de Communi non Virginum + II loco". Same year, three days later, and a DIFFERENT Common -- so this + pin also proves the assignment table is consulted per saint rather than + one Common being handed to everything that reaches step 4. Its temporal + slug (ef-septuagesima-3-tuesday) resolves to 1 Cor. 13:1-13 / Luke + 18:31-43, so the wrong-order failure is again a plausible-looking green. *) +let test_step4_commons_frances_of_rome () = + Alcotest.(check (list string)) + "9 March 2038: St Frances of Rome takes the Common of non-Virgins II" + [ "Prov 31:10-31"; "Matt 13:44-52" ] + (refs (day 2038 3 9)) + +(* 2008-04-04, St Isidore: "Missa In medio, de Communi Doctorum". A + Paschaltide witness, where step 2 would otherwise have supplied the + Easter-week feria's 1 John 5:4-10 / John 20:19-31. *) +let test_step4_commons_isidore () = + Alcotest.(check (list string)) + "4 April 2008: St Isidore takes the Common of Doctors" + [ "2 Tim 4:1-8"; "Matt 5:13-19" ] + (refs (day 2008 4 4)) + +(* 2005-04-05, St Vincent Ferrer: "Missa Os iusti, de Communi Confessoris + non Pontificis I loco". *) +let test_step4_commons_vincent_ferrer () = + Alcotest.(check (list string)) + "5 April 2005: St Vincent Ferrer takes the Common of a Confessor not a Bishop I" + [ "Ecclus 31:8-11"; "Luke 12:35-40" ] + (refs (day 2005 4 5)) + +(* Step 1 still wins over step 4 for a saint the Missal prints a full Mass + for. St John of God (8 March) is the sharp case: his Epistle IS the + Common of a Confessor not a Bishop's own (Ecclus 31:8-11), but the Missal + assigns him a different GOSPEL (Mt 22:34-46, not that Common's Luke + 12:35-40), so a chain that reached for a Common here would be caught by + the Gospel alone. He has no assignment in commons.sexp at all -- this + asserts the proper arrived via adjustments.sexp and step 1. *) +let test_step1_proper_beats_any_common_john_of_god () = + Alcotest.(check (list string)) + "8 March 2038: St John of God's own printed Mass, not the Common that shares its Epistle" + [ "Ecclus 31:8-11"; "Matt 22:34-46" ] + (refs (day 2038 3 8)); + Alcotest.(check bool) + "and he is deliberately absent from the Commons assignment table" true + (Lectionary_ef.commons_for ~commons:(real_commons ()) + (Slug.of_string_exn "john-of-god") + = None) + +(* The other two proper-Mass saints a real year ever observes. Both would + otherwise silently emit their feria's Mass. *) +let test_step1_proper_thomas_aquinas () = + Alcotest.(check (list string)) + "7 March 2011: St Thomas Aquinas's own proper" + [ "Wis 7:7-14"; "Matt 5:13-19" ] + (refs (day 2011 3 7)) + +let test_step1_proper_francis_of_paola () = + Alcotest.(check (list string)) + "2 April 2008: St Francis of Paola's own proper" + [ "Phil 3:7-12"; "Luke 12:32-34" ] + (refs (day 2008 4 2)) + +(* Step 4 must never divert a day whose observed office IS its temporal + office. The guard in [readings] makes that structural; this pins it + behaviourally on the case most at risk -- the RG 78 Saturday Office of + the BVM, which deliberately REUSES the ordinary ferial slug (see + temporal_ef.ml's [bvm_saturday_names], "Slug" paragraph). If that shared + slug were ever assigned a Common, every feria sharing it would change + Mass. 4 July 2026 is such a Saturday; it keeps its own ferial Mass. *) +let test_step4_never_diverts_a_temporal_office () = + Alcotest.(check (list string)) + "4 July 2026 (a BVM Saturday) keeps its ferial Mass, unaffected by step 4" + [ "1 Pet 3:8-15."; "Matt 5:20-24." ] + (refs (day 2026 7 4)) + +(* THE DATA ASSERTION, and the one that cannot drift: after Task 6, every + sanctoral entry that can ever BE the observed office -- i.e. every + [status Feast] entry -- either carries its own proper or has a Common + assigned. Stated over the real loaded layer rather than as a hard-coded + list of fifteen names, so that a future re-bootstrap adding a + readingless class-3 feast fails here instead of silently emitting its + feria's Mass. + + [Commemoration_only] entries are excluded deliberately and not as an + oversight: they are never the observed celebration (Precedence never + returns one as [observed]), and [readings] only ever consults + [observed], so their empty citations are unreachable. 104 of the 119 + entries with no citations are of that kind. *) +let test_every_observable_sanctoral_feast_has_readings () = + let commons = real_commons () in + let layer = real_layer () in + let gaps = + List.filter_map + (fun (entry : Vocab_ef.rank Layer.entry) -> + let cel = entry.Layer.cel in + if cel.Celebration.status <> Celebration.Feast then None + else if cel.Celebration.citations <> [] then None + else if Lectionary_ef.commons_for ~commons cel.Celebration.slug <> None then None + else Some (Slug.to_string cel.Celebration.slug)) + layer.Layer.entries + in + Alcotest.(check (list string)) + "every sanctoral Feast has either a proper or an assigned Common" [] + (List.sort_uniq compare gaps) + +(* The seven saints the Missal sends to a Common, and the exact Common each + one is sent to -- quoted per saint, with its Missal citation, in + data/ef/commons.sexp. Named explicitly (in addition to the generic + invariant above) because three of them -- Benedict, Gregory the Great, + Patrick -- are NEVER the observed office anywhere in 1583-9999 (12, 17 + and 21 March always fall to a Lenten or Passiontide feria that outranks a + III-class feast), so no end-to-end pin can reach them and this table is + their only coverage. + + The plan expected Benedict, Patrick and Gregory the Great to have PROPERS + and Thomas Aquinas likewise; the Missal gives a proper only to Thomas + Aquinas. All three reversals were verified on the page images + (pp. 488, 492) as well as in both OCR text layers. *) +let test_step4_assignment_table () = + let commons = real_commons () in + let assigned = + List.map + (fun (saint, common) -> (Slug.to_string saint, Slug.to_string common)) + (Lectionary_ef.Commons.assignments commons) + in + Alcotest.(check (list (pair string string))) + "the Commons assignment table, exactly" + [ ("benedict", "common-of-abbots"); + ("frances-rome", "common-of-non-virgins-2"); + ("gregory-the-great", "common-of-supreme-pontiffs"); + ("isidore-of-seville", "common-of-doctors"); + ("patrick", "common-of-a-confessor-bishop-1"); + ("sts-felicitas-perpetua", "common-of-non-virgins-1"); + ("vincent-ferrer", "common-of-a-confessor-not-a-bishop-1") ] + assigned + +(* The three Commons no real year reaches, resolved through [commons_for] so + that the assignment AND the formulary behind it are both exercised. *) +let test_step4_unreachable_commons_still_resolve () = + let commons = real_commons () in + let for_saint s = Lectionary_ef.commons_for ~commons (Slug.of_string_exn s) in + let refs_of = function + | None -> [ "<no common>" ] + | Some cs -> List.map (fun c -> c.Citation.reference) cs + in + Alcotest.(check (list string)) + "St Benedict (21 March), Common of Abbots" + [ "Ecclus 45:1-6"; "Matt 19:27-29" ] + (refs_of (for_saint "benedict")); + Alcotest.(check (list string)) + "St Gregory the Great (12 March), Common of Supreme Pontiffs" + [ "1 Pet 5:1-4, 10-11"; "Matt 16:13-19" ] + (refs_of (for_saint "gregory-the-great")); + Alcotest.(check (list string)) + "St Patrick (17 March), Common of a Confessor Bishop I" + [ "Ecclus 44:16-27; 45:3-20"; "Matt 25:14-23" ] + (refs_of (for_saint "patrick")) + +(* The five proper-Mass saints no real year reaches, asserted on the layer + rather than end-to-end, for the same reason. *) +let test_step4_unreachable_propers_are_present () = + let layer = real_layer () in + let refs_of slug = + match + List.find_opt + (fun (e : Vocab_ef.rank Layer.entry) -> + Slug.equal e.Layer.cel.Celebration.slug (Slug.of_string_exn slug)) + layer.Layer.entries + with + | None -> [ "<absent from the layer>" ] + | Some e -> List.map (fun c -> c.Citation.reference) e.Layer.cel.Celebration.citations + in + Alcotest.(check (list string)) + "Forty Holy Martyrs of Sebaste (10 March)" + [ "Heb 11:33-39"; "Luke 6:17-23" ] + (refs_of "forty-holy-martyrs-of-sebaste"); + Alcotest.(check (list string)) + "St Cyril of Jerusalem (18 March)" + [ "Ecclus 39:6-14"; "Matt 10:23-28" ] + (refs_of "cyril-of-jerusalem"); + (* The one genuine disagreement between the two printings: scan1's edition + misprints this Gospel as "Luc. 2, 26-38" (confirmed on its own page + image, so a typographical error, not OCR). Luke 1:26-38 is settled by + scan2's edition, by the pericope text in both, and by the same edition + citing the identical pericope correctly two pages later at the + Annunciation. See data/ef/adjustments.sexp for the full account. *) + Alcotest.(check (list string)) + "St Gabriel the Archangel (24 March)" + [ "Dan 9:21-26"; "Luke 1:26-38" ] + (refs_of "gabriel-the-archangel"); + (* Wis 10:10-17 here versus 10:10-14 for John of Capistrano below: a real + difference, agreed by both scans and both page images. *) + Alcotest.(check (list string)) + "St John Damascene (27 March)" + [ "Wis 10:10-17"; "Luke 6:6-11" ] + (refs_of "john-damascene"); + Alcotest.(check (list string)) + "St John of Capistrano (28 March)" + [ "Wis 10:10-14"; "Luke 9:1-6" ] + (refs_of "john-of-capistrano") + +(* [Commons.load] must reject, not silently degrade, the four data defects + that are indistinguishable downstream from "this saint has no Common". + Written against strings rather than the committed file so the committed + file stays clean. *) +let test_commons_load_rejects_bad_data () = + let write name contents = + let path = Filename.concat (Filename.get_temp_dir_name ()) name in + let oc = open_out path in + output_string oc contents; + close_out oc; + path + in + let err name contents = + match Lectionary_ef.Commons.load (write name contents) with + | Ok _ -> Alcotest.failf "%s: expected Error, got Ok" name + | Error e -> e + in + let good_common = "(c-a (((part First) (reference \"A 1:1\")) ((part Gospel) (reference \"B 2:2\"))))" in + let contains needle haystack = + let n = String.length needle and h = String.length haystack in + let rec go i = i + n <= h && (String.sub haystack i n = needle || go (i + 1)) in + go 0 + in + let check_msg label needle msg = + Alcotest.(check bool) (Printf.sprintf "%s names the culprit (%s)" label needle) true + (contains needle msg) + in + check_msg "duplicate common" "duplicate common" + (err "commons-dup-common.sexp" + (Printf.sprintf "((commons (%s %s)) (assigned ()))" good_common good_common)); + check_msg "duplicate assignment" "duplicate assignment" + (err "commons-dup-assign.sexp" + (Printf.sprintf "((commons (%s)) (assigned ((s c-a) (s c-a))))" good_common)); + check_msg "empty formulary" "no citations" + (err "commons-empty.sexp" "((commons ((c-a ()))) (assigned ()))"); + check_msg "unknown common" "unknown common" + (err "commons-unknown.sexp" + (Printf.sprintf "((commons (%s)) (assigned ((s c-missing))))" good_common)); + (* A missing file is an [Error] too, never an exception -- the contract + [Lectionary.load] already makes and the reason neither is read at + module-initialisation time. *) + (match Lectionary_ef.Commons.load "../data/ef/no-such-commons.sexp" with + | Ok _ -> Alcotest.fail "a missing commons file must not load" + | Error _ -> ()); + (* [empty] is the identity: every lookup [None], nothing invented. *) + Alcotest.(check bool) "Commons.empty resolves nothing" true + (Lectionary_ef.commons_for ~commons:Lectionary_ef.Commons.empty + (Slug.of_string_exn "benedict") + = None) + let suite = [ ("step 1: sanctoral proper", `Quick, test_step1_sanctoral_proper); ("step 2: own temporal proper", `Quick, test_step2_lenten_feria_has_its_own); @@ -178,4 +475,26 @@ let suite = ("step 3: 3 Feb 2025 takes the Sunday's temporal Mass, not the Purification's", `Quick, test_step3_uses_temporal_not_observed); ("step 3: a Sunday does not recurse into itself", `Quick, - test_step3_sunday_does_not_recurse) ] + test_step3_sunday_does_not_recurse); + ("step 4: Perpetua and Felicity take the Common of non-Virgins I", `Quick, + test_step4_commons_perpetua_and_felicity); + ("step 4: Frances of Rome takes the Common of non-Virgins II", `Quick, + test_step4_commons_frances_of_rome); + ("step 4: Isidore takes the Common of Doctors", `Quick, test_step4_commons_isidore); + ("step 4: Vincent Ferrer takes the Common of a Confessor not a Bishop I", `Quick, + test_step4_commons_vincent_ferrer); + ("step 1 beats step 4: John of God's proper, not the Common sharing its Epistle", `Quick, + test_step1_proper_beats_any_common_john_of_god); + ("step 1: Thomas Aquinas's proper", `Quick, test_step1_proper_thomas_aquinas); + ("step 1: Francis of Paola's proper", `Quick, test_step1_proper_francis_of_paola); + ("step 4 never diverts a temporal office (BVM Saturday)", `Quick, + test_step4_never_diverts_a_temporal_office); + ("every observable sanctoral Feast has a proper or a Common", `Quick, + test_every_observable_sanctoral_feast_has_readings); + ("the Commons assignment table, exactly", `Quick, test_step4_assignment_table); + ("the three Commons no real year reaches still resolve", `Quick, + test_step4_unreachable_commons_still_resolve); + ("the five propers no real year reaches are present", `Quick, + test_step4_unreachable_propers_are_present); + ("Commons.load rejects the four silent-degradation defects", `Quick, + test_commons_load_rejects_bad_data) ] diff --git a/test/test_oracle.ml b/test/test_oracle.ml index e1e4184..f1ab9f1 100644 --- a/test/test_oracle.ml +++ b/test/test_oracle.ml @@ -227,6 +227,18 @@ let real_lectionary () = | Ok l -> l | Error e -> Alcotest.failf "../data/ef/lectionary.sexp: failed to load: %s" e +(* The Commons (data/ef/commons.sexp) travel the same caller-supplied seam + as the lectionary above, and [~commons] is required rather than defaulted + so that no caller can silently run with none -- nothing in layers 3-5 + compares reading citations, so a rite quietly missing its Commons would + be invisible. Loaded here even where this file asserts nothing about + readings, so that the rite under test is the same one bin/main.ml + assembles. *) +let real_commons () = + match Rite_ef.Lectionary_ef.Commons.load "../data/ef/commons.sexp" with + | Ok c -> c + | Error e -> Alcotest.failf "../data/ef/commons.sexp: failed to load: %s" e + (* ---------------------------------------------------------------------- *) (* The oracle side: one line per day, as tools/extract_missalemeum_oracle *) (* .py's own header documents. *) @@ -328,7 +340,7 @@ let en = Lang.of_string_exn "en" let colitur_rows_2026_2027 () = let layer = real_layer () in - let rite = Rite_ef.context ~lectionary:(real_lectionary ()) in + let rite = Rite_ef.context ~lectionary:(real_lectionary ()) ~commons:(real_commons ()) in let by_rata : (int, (V.season, V.rank) LD.t) Hashtbl.t = Hashtbl.create 800 in for y = 2025 to 2027 do let days = Cal.year rite layer y in diff --git a/test/test_rite_ef.ml b/test/test_rite_ef.ml index 7680545..a014977 100644 --- a/test/test_rite_ef.ml +++ b/test/test_rite_ef.ml @@ -56,7 +56,19 @@ let real_lectionary () = | Ok l -> l | Error e -> Alcotest.failf "../data/ef/lectionary.sexp: failed to load: %s" e -let context () = Rite_ef.context ~lectionary:(real_lectionary ()) +(* The Commons (data/ef/commons.sexp) travel the same caller-supplied seam + as the lectionary above, and [~commons] is required rather than defaulted + so that no caller can silently run with none -- nothing in layers 3-5 + compares reading citations, so a rite quietly missing its Commons would + be invisible. Loaded here even where this file asserts nothing about + readings, so that the rite under test is the same one bin/main.ml + assembles. *) +let real_commons () = + match Rite_ef.Lectionary_ef.Commons.load "../data/ef/commons.sexp" with + | Ok c -> c + | Error e -> Alcotest.failf "../data/ef/commons.sexp: failed to load: %s" e + +let context () = Rite_ef.context ~lectionary:(real_lectionary ()) ~commons:(real_commons ()) let slug_of (c : V.rank Cel.t) = Slug.to_string c.Cel.slug diff --git a/test/test_validate.ml b/test/test_validate.ml index 8e63a1b..8c96f26 100644 --- a/test/test_validate.ml +++ b/test/test_validate.ml @@ -16,6 +16,7 @@ module T = Rite_ef.Temporal_ef let sanctoral_path = "../data/ef/sanctoral.sexp" let adjustments_path = "../data/ef/adjustments.sexp" let lectionary_path = "../data/ef/lectionary.sexp" +let commons_path = "../data/ef/commons.sexp" (* Loaded once at module init, not per call: [run] below is called by every test and by the 200-sample property, and Calendar.year's own resolution @@ -42,7 +43,19 @@ let real_ef_lectionary = | Error e -> failwith (Printf.sprintf "%s: failed to load: %s" lectionary_path e) | Ok l -> l -let real_ef_rite = Rite_ef.context ~lectionary:real_ef_lectionary +(* The Commons (data/ef/commons.sexp) travel the same caller-supplied seam + as the lectionary above, and [~commons] is required rather than defaulted + so that no caller can silently run with none -- nothing in layers 3-5 + compares reading citations, so a rite quietly missing its Commons would + be invisible. Loaded here even where this file asserts nothing about + readings, so that the rite under test is the same one bin/main.ml + assembles. *) +let real_ef_commons = + match Rite_ef.Lectionary_ef.Commons.load commons_path with + | Error e -> failwith (Printf.sprintf "%s: failed to load: %s" commons_path e) + | Ok c -> c + +let real_ef_rite = Rite_ef.context ~lectionary:real_ef_lectionary ~commons:real_ef_commons let run year = Val.run real_ef_rite real_ef_layer ~year |
