summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/test_colitur.ml1
-rw-r--r--test/test_rubrics_of.ml307
2 files changed, 308 insertions, 0 deletions
diff --git a/test/test_colitur.ml b/test/test_colitur.ml
index 98e08fb..3e16566 100644
--- a/test/test_colitur.ml
+++ b/test/test_colitur.ml
@@ -18,6 +18,7 @@ let () =
Test_calendar.suite; Test_precedence_ef.suite; Test_sanctoral_ef.suite; Test_calendar_of_data.suite;
Test_rite_ef.suite;
Test_rubrics_ef.suite;
+ Test_rubrics_of.suite;
Test_differential.suite; Test_oracle.suite; Test_oracle.suite_2038; Test_oracle.suite_2035;
Test_lms_ordo.suite_2023_2024; Test_lms_ordo.suite_2024_2025; Test_lms_ordo.suite_2025_2026;
Test_lms_ordo.suite_allow_list; Test_fiuv_ordo.suite;
diff --git a/test/test_rubrics_of.ml b/test/test_rubrics_of.ml
new file mode 100644
index 0000000..db8744a
--- /dev/null
+++ b/test/test_rubrics_of.ml
@@ -0,0 +1,307 @@
+(* IGMR n. 53 (Gloria), n. 67-68 (Symbolum/Credo), n. 364-365 and the
+ Missale's own INDEX PRAEFATIONUM -- see lib/rites/rite_of/rubrics_of.ml
+ for every rubric quoted in full and every branch's own citation. The OF
+ counterpart of test/test_rubrics_ef.ml; one Alcotest case per rule.
+
+ NO FULL OF [Colitur_kernel.Rite.t]/[Calendar] EXISTS YET (this task
+ supplies three of its four remaining fields; wiring them into a bundle
+ is a later task) -- so, unlike test_rubrics_ef.ml, there is no
+ `Cal.day`-style pipeline here to resolve a real SANCTORAL winner for a
+ date. Every TEMPORAL-origin row below therefore builds [temporal] (and,
+ where the temporal cycle's own office IS what is observed -- an ordinary
+ Sunday, a feria, a named solemnity like the Nativity) also [observed],
+ via [Temporal_of.temporal] on a real calendar date -- genuine Phase 1
+ output, per the task brief. Every SANCTORAL-origin row (a saint's
+ solemnity/feast/memorial outranking the day's own temporal office)
+ builds [temporal] the same way, via a real date, but necessarily builds
+ [observed] directly -- there is no pipeline yet to derive it. It is
+ NOT fabricated: every field is copied verbatim from the matching real
+ entry in data/of/calendar-2002.sexp (cited at each site), the same
+ "grounded in shipped data, not invented" discipline
+ test/test_precedence_of.ml's own [mk_cel] already establishes for this
+ rite. *)
+
+module RO = Rite_of.Rubrics_of
+module T = Rite_of.Temporal_of
+module D = Colitur_kernel.Date
+module Cel = Colitur_kernel.Celebration
+module Colour = Colitur_kernel.Colour
+module Subject = Colitur_kernel.Subject
+module Slug = Colitur_kernel.Slug
+module Vocab_of = Rite_of.Vocab_of
+
+let mk y m d = match D.make ~year:y ~month:m ~day:d with Ok t -> t | Error e -> failwith e
+
+(* A real, shipped sanctoral entry (data/of/calendar-2002.sexp), reused
+ directly as [observed] where no full precedence pipeline exists yet to
+ derive it. Status defaults to [Feast] (every entry cited below is). *)
+let mk_saint ~slug ~rank ~colour ~subject () =
+ Cel.make ~slug:(Slug.of_string_exn slug) ~rank ~colour ~subject ~layer:"of-universal" ()
+
+(* ---- Gloria (IGMR n. 53) ---- *)
+
+let gloria_on_temporal y m d =
+ let temporal = T.temporal (mk y m d) in
+ RO.gloria ~temporal ~observed:temporal.Colitur_kernel.Temporal.office ~date:(mk y m d)
+
+let check_gloria name expected actual = Alcotest.(check bool) name expected actual
+
+let test_gloria_advent_sunday () =
+ (* 2026-12-06: the Second Sunday of Advent -- confirmed via `date -d`,
+ [Temporal_of.temporal] tags it [Vocab_of.Sollemnitas] (the privileged-
+ season convention, temporal_of.ml's own [build]), which is exactly the
+ trap n. 53's own "extra tempus Adventus" clause exists to catch: a
+ naive [observed.rank = Sollemnitas] test would wrongly say [true]. *)
+ check_gloria "no Gloria on a Sunday of Advent" false (gloria_on_temporal 2026 12 6)
+
+let test_gloria_lent_sunday () =
+ (* 2026-03-01: the First Sunday of Lent 2026 (Ash Wednesday 2026-02-18,
+ Easter 2026-04-05). Same trap as Advent, the other of n. 53's two
+ named exceptions. *)
+ check_gloria "no Gloria on a Sunday of Lent" false (gloria_on_temporal 2026 3 1)
+
+let test_gloria_palm_sunday () =
+ (* 2026-03-29: Palm Sunday, [Vocab_of.Lent], [Sollemnitas] (named's own
+ entry) -- confirms the Advent/Lent exclusion above also correctly
+ reaches a NAMED solemnity, not only a plain numbered Sunday of Lent,
+ matching real, uncontested practice (Palm Sunday's Mass has no
+ Gloria). *)
+ check_gloria "no Gloria on Palm Sunday" false (gloria_on_temporal 2026 3 29)
+
+let test_gloria_ordinary_sunday () =
+ (* 2026-01-25: an Ordinary Time Sunday (Baptism of the Lord 2026-01-11,
+ so Ordinary Time begins 2026-01-12) -- [Festum]-ranked
+ (temporal_of.ml's own [build]), reaching [true] via n. 53's "diebus
+ dominicis" clause, not its "et festis" one (see the .ml's own
+ citation for why [temporal.weekday], not [observed.rank], is what
+ this must be read off). *)
+ check_gloria "Gloria on a Sunday per annum" true (gloria_on_temporal 2026 1 25)
+
+let test_gloria_christmas_sunday () =
+ (* 2026-12-27: a Sunday within the Octave of the Nativity (temporal_of.ml's
+ own [holy_family]/generic-Sunday dispatch), [Vocab_of.Christmas]
+ season, outside Advent/Lent -- a second, independent witness to the
+ same "diebus dominicis extra Adventum/Quadragesimam" clause as the
+ Ordinary Time row above, from the other season n. 53 does not
+ exclude. *)
+ check_gloria "Gloria on a Sunday of Christmas time" true (gloria_on_temporal 2026 12 27)
+
+let test_gloria_nativity () =
+ (* 2026-12-25: the Nativity itself -- [Vocab_of.Christmas], [Sollemnitas],
+ a Friday (never a Sunday that year), reaching [true] via n. 53's "in
+ sollemnitatibus" clause on its own, independent of the Sunday
+ clauses. Pure [Temporal_of.temporal] output; no sanctoral data
+ needed, since the Nativity IS the temporal cycle's own named office
+ (temporal_of.ml's own [named]). *)
+ check_gloria "Gloria on the Nativity" true (gloria_on_temporal 2026 12 25)
+
+let test_gloria_holy_thursday () =
+ (* 2026-04-02: the Mass of the Lord's Supper (Easter-3, Easter 2026-04-05)
+ -- Missale Romanum, "FERIA V IN CENA DOMINI" n. 7's own explicit
+ "Dicitur Gloria in excelsis" (see the .ml's own citation), overriding
+ what would otherwise be a Lenten-feria [false]. *)
+ check_gloria "Gloria at the Mass of the Lord's Supper" true (gloria_on_temporal 2026 4 2)
+
+let test_gloria_easter_vigil () =
+ (* 2026-04-04: the Easter Vigil (Easter-1) -- the same Missale rubric's
+ own "usque ad Gloria in excelsis Vigiliae paschalis", confirming this
+ Mass also has one. *)
+ check_gloria "Gloria at the Easter Vigil Mass" true (gloria_on_temporal 2026 4 4)
+
+let test_gloria_solemnity_in_lent () =
+ (* Saint Joseph, Husband of the Blessed Virgin Mary, 19 March --
+ data/of/calendar-2002.sexp: (rank Sollemnitas) (colour White)
+ (subject Saint). 2026-03-19 is a Thursday (Easter 2026-04-05, so Holy
+ Week is 2026-03-29..04-04 -- this date sits safely inside ordinary
+ Lent, not impeded) -- confirmed, not assumed, via `date -d`. 19 March
+ is ALWAYS inside Lent by construction (Gregorian Easter's earliest
+ possible date is 22 March, so Ash Wednesday can never fall later than
+ 10 March; see the .ml's own citation). Exercises n. 53's "in
+ sollemnitatibus" clause on a genuine sanctoral winner, the one row in
+ this suite [observed] cannot come from [Temporal_of.temporal] alone
+ -- see this file's own header for why. *)
+ let temporal = T.temporal (mk 2026 3 19) in
+ let observed =
+ mk_saint ~slug:"joseph-husband-of-the-blessed-virgin-mary" ~rank:Vocab_of.Sollemnitas
+ ~colour:Colour.White ~subject:Subject.Saint ()
+ in
+ check_gloria "Gloria: a solemnity falling in Lent" true
+ (RO.gloria ~temporal ~observed ~date:(mk 2026 3 19))
+
+let test_gloria_festum () =
+ (* Chair of Saint Peter, apostle, 22 February -- data/of/calendar-2002.sexp:
+ (rank Festum) (colour White) (subject Saint). 2027-02-22 is a Monday
+ (checked via `date -d`, so this date is not itself impeded by an
+ Ordinary Time Sunday outranking a Festum, Tabula entries 6 vs 7).
+ Exercises n. 53's "et festis" clause specifically -- the one member of
+ n. 53's list that n. 68 (Credo, below) does NOT share. *)
+ let temporal = T.temporal (mk 2027 2 22) in
+ let observed =
+ mk_saint ~slug:"chair-of-saint-peter-apostle" ~rank:Vocab_of.Festum ~colour:Colour.White
+ ~subject:Subject.Saint ()
+ in
+ check_gloria "Gloria on an ordinary Festum" true (RO.gloria ~temporal ~observed ~date:(mk 2027 2 22))
+
+let test_gloria_obligatory_memorial () =
+ (* Saint Aloysius Gonzaga, religious, 21 June -- data/of/calendar-2002.sexp:
+ (rank Memoria_obligatoria) (colour White) (subject Saint). 2027-06-21
+ is a Monday. Neither n. 53's "sollemnitatibus et festis" nor its
+ "dominicis" clause names a memorial. *)
+ let temporal = T.temporal (mk 2027 6 21) in
+ let observed =
+ mk_saint ~slug:"aloysius-gonzaga-religious" ~rank:Vocab_of.Memoria_obligatoria
+ ~colour:Colour.White ~subject:Subject.Saint ()
+ in
+ check_gloria "no Gloria on a weekday obligatory memorial" false
+ (RO.gloria ~temporal ~observed ~date:(mk 2027 6 21))
+
+let test_gloria_ordinary_feria () =
+ (* 2026-01-27: an Ordinary Time Tuesday feria, no memorial -- pure
+ [Temporal_of.temporal] output, [Vocab_of.Feria], [Subject.Temporal].
+ Named by neither n. 53 clause. *)
+ check_gloria "no Gloria on a plain feria" false (gloria_on_temporal 2026 1 27)
+
+let test_gloria_all_souls () =
+ (* All Souls, 2 November -- data/of/calendar-2002.sexp: (rank Sollemnitas)
+ (colour Violet) (subject Saint) -- the ONE Requiem-shaped entity this
+ engine's shipped OF data can construct as [observed]
+ ({!Rite_of.Rubrics_of.requiem_slugs}). Its own [rank] is tagged
+ [Sollemnitas] by the extractor's own override (precedence_of.ml's own
+ citation), so WITHOUT the requiem guard n. 53's "in sollemnitatibus"
+ clause would wrongly grant it [true] -- exactly the case this guard
+ exists for. 2026-11-02 is a Monday. *)
+ let temporal = T.temporal (mk 2026 11 2) in
+ let observed =
+ mk_saint ~slug:"all-souls" ~rank:Vocab_of.Sollemnitas ~colour:Colour.Violet ~subject:Subject.Saint ()
+ in
+ check_gloria "no Gloria on All Souls" false (RO.gloria ~temporal ~observed ~date:(mk 2026 11 2))
+
+(* ---- Credo / Symbolum (IGMR n. 67-68) ---- *)
+
+let credo_on_temporal y m d =
+ let temporal = T.temporal (mk y m d) in
+ RO.credo ~temporal ~observed:temporal.Colitur_kernel.Temporal.office ~date:(mk y m d)
+
+let check_credo name expected actual = Alcotest.(check bool) name expected actual
+
+let test_credo_advent_sunday () =
+ (* Same date as [test_gloria_advent_sunday], deliberately: n. 68 carries
+ no Advent/Lent exception at all, unlike n. 53 -- this is [true] here
+ where the identical day was [false] for Gloria, proving the two rules
+ genuinely diverge rather than one silently mirroring the other. *)
+ check_credo "Credo on a Sunday of Advent (unlike Gloria)" true (credo_on_temporal 2026 12 6)
+
+let test_credo_ordinary_sunday () =
+ check_credo "Credo on a Sunday per annum" true (credo_on_temporal 2026 1 25)
+
+let test_credo_solemnity () =
+ (* Same St Joseph data as [test_gloria_solemnity_in_lent]. *)
+ let temporal = T.temporal (mk 2026 3 19) in
+ let observed =
+ mk_saint ~slug:"joseph-husband-of-the-blessed-virgin-mary" ~rank:Vocab_of.Sollemnitas
+ ~colour:Colour.White ~subject:Subject.Saint ()
+ in
+ check_credo "Credo: a solemnity" true (RO.credo ~temporal ~observed ~date:(mk 2026 3 19))
+
+let test_credo_festum () =
+ (* Same Chair-of-Peter data as [test_gloria_festum] -- [false] here,
+ unlike Gloria's [true] on the identical day: n. 68 has no "et festis"
+ clause at all (see the .ml's own citation), the single sharpest
+ divergence between this function and [gloria]. *)
+ let temporal = T.temporal (mk 2027 2 22) in
+ let observed =
+ mk_saint ~slug:"chair-of-saint-peter-apostle" ~rank:Vocab_of.Festum ~colour:Colour.White
+ ~subject:Subject.Saint ()
+ in
+ check_credo "no Credo on an ordinary Festum (unlike Gloria)" false
+ (RO.credo ~temporal ~observed ~date:(mk 2027 2 22))
+
+let test_credo_weekday_obligatory_memorial () =
+ let temporal = T.temporal (mk 2027 6 21) in
+ let observed =
+ mk_saint ~slug:"aloysius-gonzaga-religious" ~rank:Vocab_of.Memoria_obligatoria
+ ~colour:Colour.White ~subject:Subject.Saint ()
+ in
+ check_credo "no Credo on a weekday obligatory memorial" false
+ (RO.credo ~temporal ~observed ~date:(mk 2027 6 21))
+
+let test_credo_ordinary_feria () =
+ check_credo "no Credo on a plain feria" false (credo_on_temporal 2026 1 27)
+
+let test_credo_all_souls () =
+ let temporal = T.temporal (mk 2026 11 2) in
+ let observed =
+ mk_saint ~slug:"all-souls" ~rank:Vocab_of.Sollemnitas ~colour:Colour.Violet ~subject:Subject.Saint ()
+ in
+ check_credo "no Credo on All Souls" false (RO.credo ~temporal ~observed ~date:(mk 2026 11 2))
+
+(* ---- Preface -- always [None]; see rubrics_of.ml's own header for the
+ full INDEX-PRAEFATIONUM census this rests on. One row per major class,
+ proving the function was deliberately built to reject every shape
+ rather than merely left unimplemented. ---- *)
+
+let check_preface_none name y m d =
+ let temporal = T.temporal (mk y m d) in
+ Alcotest.(check bool) name true
+ (RO.preface ~temporal ~observed:temporal.Colitur_kernel.Temporal.office ~date:(mk y m d) = None)
+
+let test_preface_none_nativity () = check_preface_none "preface: Nativity is None" 2026 12 25
+let test_preface_none_ordinary_sunday () = check_preface_none "preface: Sunday per annum is None" 2026 1 25
+let test_preface_none_feria () = check_preface_none "preface: a plain feria is None" 2026 1 27
+
+let test_preface_none_solemnity_with_own_single_text () =
+ (* Trinity Sunday -- exactly the "single OF text, same feast as an EF
+ constructor" near-miss the .ml's own header explains rejecting: still
+ [None], on purpose. *)
+ let easter = Colitur_kernel.Computus.gregorian_easter 2026 in
+ let trinity = D.add_days easter 56 in
+ let temporal = T.temporal trinity in
+ Alcotest.(check bool) "preface: Trinity Sunday is None" true
+ (RO.preface ~temporal ~observed:temporal.Colitur_kernel.Temporal.office ~date:trinity = None)
+
+let test_preface_none_all_souls () =
+ let temporal = T.temporal (mk 2026 11 2) in
+ let observed =
+ mk_saint ~slug:"all-souls" ~rank:Vocab_of.Sollemnitas ~colour:Colour.Violet ~subject:Subject.Saint ()
+ in
+ Alcotest.(check bool) "preface: All Souls is None" true
+ (RO.preface ~temporal ~observed ~date:(mk 2026 11 2) = None)
+
+(* Population guard, mirroring test_rubrics_ef.ml's own
+ [test_colour_black_population_is_exactly_two]: fails loudly the day a
+ second Requiem-shaped slug is added anywhere in the shipped OF data
+ without this list being revisited. *)
+let test_requiem_slugs_population () =
+ Alcotest.(check (list string)) "requiem_slugs is exactly {all-souls}" [ "all-souls" ] RO.requiem_slugs
+
+let suite =
+ ( "rubrics-of",
+ [ Alcotest.test_case "Gloria: no Gloria on a Sunday of Advent" `Quick test_gloria_advent_sunday;
+ Alcotest.test_case "Gloria: no Gloria on a Sunday of Lent" `Quick test_gloria_lent_sunday;
+ Alcotest.test_case "Gloria: no Gloria on Palm Sunday" `Quick test_gloria_palm_sunday;
+ Alcotest.test_case "Gloria: Gloria on a Sunday per annum" `Quick test_gloria_ordinary_sunday;
+ Alcotest.test_case "Gloria: Gloria on a Sunday of Christmas time" `Quick test_gloria_christmas_sunday;
+ Alcotest.test_case "Gloria: Gloria on the Nativity" `Quick test_gloria_nativity;
+ Alcotest.test_case "Gloria: Gloria at the Mass of the Lord's Supper" `Quick test_gloria_holy_thursday;
+ Alcotest.test_case "Gloria: Gloria at the Easter Vigil Mass" `Quick test_gloria_easter_vigil;
+ Alcotest.test_case "Gloria: a solemnity falling in Lent" `Quick test_gloria_solemnity_in_lent;
+ Alcotest.test_case "Gloria: an ordinary Festum" `Quick test_gloria_festum;
+ Alcotest.test_case "Gloria: no Gloria on a weekday obligatory memorial" `Quick
+ test_gloria_obligatory_memorial;
+ Alcotest.test_case "Gloria: no Gloria on a plain feria" `Quick test_gloria_ordinary_feria;
+ Alcotest.test_case "Gloria: no Gloria on All Souls" `Quick test_gloria_all_souls;
+ Alcotest.test_case "Credo: Credo on a Sunday of Advent" `Quick test_credo_advent_sunday;
+ Alcotest.test_case "Credo: Credo on a Sunday per annum" `Quick test_credo_ordinary_sunday;
+ Alcotest.test_case "Credo: a solemnity" `Quick test_credo_solemnity;
+ Alcotest.test_case "Credo: no Credo on an ordinary Festum" `Quick test_credo_festum;
+ Alcotest.test_case "Credo: no Credo on a weekday obligatory memorial" `Quick
+ test_credo_weekday_obligatory_memorial;
+ Alcotest.test_case "Credo: no Credo on a plain feria" `Quick test_credo_ordinary_feria;
+ Alcotest.test_case "Credo: no Credo on All Souls" `Quick test_credo_all_souls;
+ Alcotest.test_case "Preface: Nativity is None" `Quick test_preface_none_nativity;
+ Alcotest.test_case "Preface: Sunday per annum is None" `Quick test_preface_none_ordinary_sunday;
+ Alcotest.test_case "Preface: a plain feria is None" `Quick test_preface_none_feria;
+ Alcotest.test_case "Preface: Trinity Sunday is None" `Quick test_preface_none_solemnity_with_own_single_text;
+ Alcotest.test_case "Preface: All Souls is None" `Quick test_preface_none_all_souls;
+ Alcotest.test_case "requiem_slugs population is exactly {all-souls}" `Quick
+ test_requiem_slugs_population ] )