summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-08-26 00:18:16 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-08-26 00:18:16 +0200
commit85c1f194c03634eaa905f0bfd630fe23b5f53e59 (patch)
treeb003bd26bebd44c61514fc652250889ce56c1bef /test
parent0994587b3694bb87d9a8ee32d60babf25c784acf (diff)
downloadcolitur-85c1f194c03634eaa905f0bfd630fe23b5f53e59.tar.gz
colitur-85c1f194c03634eaa905f0bfd630fe23b5f53e59.zip
feat(of): post-2002 GRC amendment overlays, one file per decree
Adds data/of/amendments/*.sexp: 13 overlay files, one per decree, carrying the General Roman Calendar's amendments since the 2002 typical edition (docs/research/of/PROVENANCE-amendments.md's own 15-item inventory, items 10-12 sharing one decree; items 2 and 3, though issued the same day, carry different protocol numbers and are counted as two decrees). 14 Add directives (Padre Pio, Juan Diego, Guadalupe, John XXIII, John Paul II, Mary Mother of the Church, Paul VI, Loreto, Faustina Kowalska, Gregory of Narek, John of Avila, Hildegard of Bingen, Teresa of Calcutta, John Henry Newman) and 2 Edit directives (Mary Magdalene's rank memoria->festum; Martha/Mary/Lazarus's name widened, same slug, same date and rank -- a continuation of the existing memorial per the decree's own text, not a suppression and replacement). Mary, Mother of the Church is the one movable entry (Easter_offset 50, the Monday after Pentecost) -- verified against the decree's own text and against Colitur_kernel.Computus.gregorian_easter in three real years, no kernel change needed. data/of/calendar-2002.sexp is untouched, per spec sec4.1. Adds test/test_amendments_of.ml: every file parses, each has a pinned SHA-256, applying all 13 in decree order yields 222 entries (208 base + 14 Add) with an empty Overlay.merge diagnostics list, and Mary Mother of the Church resolves to the Monday after Pentecost in three real years. Two Precedence_of interactions are recorded, not fixed (out of scope for a data-only task): a movable Memoria_obligatoria can land on an existing fixed one (verified for 13 June 2011), a genuine band tie Normae n. 14 does not address and Precedence_of.ml does not implement; and adding Hildegard of Bingen changes which of two ad-libitum memorials on 17 September Precedence.resolve reports as observed, by alphabetical tie-break alone.
Diffstat (limited to 'test')
-rw-r--r--test/dune13
-rw-r--r--test/test_amendments_of.ml217
-rw-r--r--test/test_colitur.ml1
3 files changed, 231 insertions, 0 deletions
diff --git a/test/dune b/test/dune
index 9af9900..c5bfe07 100644
--- a/test/dune
+++ b/test/dune
@@ -5,6 +5,19 @@
../data/ef/sanctoral.sexp
../data/ef/adjustments.sexp
../data/of/calendar-2002.sexp
+ ../data/of/amendments/001-padre-pio.sexp
+ ../data/of/amendments/002-juan-diego-cuauhtlatoatzin.sexp
+ ../data/of/amendments/003-our-lady-of-guadalupe.sexp
+ ../data/of/amendments/004-john-xxiii-john-paul-ii.sexp
+ ../data/of/amendments/005-mary-magdalene-rank.sexp
+ ../data/of/amendments/006-mary-mother-of-the-church.sexp
+ ../data/of/amendments/007-paul-vi.sexp
+ ../data/of/amendments/008-our-lady-of-loreto.sexp
+ ../data/of/amendments/009-faustina-kowalska.sexp
+ ../data/of/amendments/010-narek-avila-hildegard.sexp
+ ../data/of/amendments/011-martha-mary-lazarus.sexp
+ ../data/of/amendments/012-teresa-of-calcutta.sexp
+ ../data/of/amendments/013-john-henry-newman.sexp
../lang/la.ini
../lang/en.ini
../lang/traditions.ini
diff --git a/test/test_amendments_of.ml b/test/test_amendments_of.ml
new file mode 100644
index 0000000..1ed21d9
--- /dev/null
+++ b/test/test_amendments_of.ml
@@ -0,0 +1,217 @@
+(* Task 2 (2026-08-25-colitur-of-phases-3-5): the post-2002 decree overlays.
+ data/of/calendar-2002.sexp (Task 1) transcribes the 2002 typical edition
+ and is never edited again -- every subsequent Calendarium Romanum Generale
+ change is its own overlay file under data/of/amendments/, one per decree,
+ per docs/research/of/PROVENANCE-amendments.md (13 decrees, 16
+ celebration-level changes -- the research inventory's own "15 items,
+ numbered 1-15, where items 10-12 are three entries carried by a single
+ decree" collapses to 13 distinct decrees once items 2/3, both dated 28
+ September 2002 but carrying DIFFERENT protocol numbers, are counted as the
+ two separate juridical instruments they are).
+
+ This suite asserts: every file parses; each has a pinned SHA-256;
+ applying all 13 in their own decree-chronological order to the base
+ calendar yields a pinned entry count; Overlay.merge reports NO diagnostic
+ (a diagnostic would mean a directive was transcribed against a slug that
+ does not exist in the base -- the exact silent failure one-file-per-decree
+ exists to prevent); and Mary, Mother of the Church resolves to the Monday
+ after Pentecost in three real, independently-computed years. *)
+
+module L = Colitur_kernel.Layer
+module O = Colitur_kernel.Overlay
+module Cel = Colitur_kernel.Celebration
+module S = Colitur_kernel.Slug
+module DS = Colitur_kernel.Date_spec
+module D = Colitur_kernel.Date
+module Computus = Colitur_kernel.Computus
+module Names = Colitur_kernel.Names
+module Lang = Colitur_kernel.Lang
+module V = Rite_of.Vocab_of
+
+let base_path = "../data/of/calendar-2002.sexp"
+let amendments_dir = "../data/of/amendments/"
+
+(* Decree-chronological order, matching docs/research/of/
+ PROVENANCE-amendments.md's own "Ordered by decree date" -- the numeric
+ filename prefixes already encode this, spelled out here explicitly so the
+ intended application order is visible in the test itself, not merely
+ inferred from a directory listing. Each paired with its SHA-256, pinned
+ 2026-08-26 against the shipped files themselves (`sha256sum
+ data/of/amendments/*.sexp`), not copied from any tool's own stdout. *)
+let files =
+ [ ("001-padre-pio.sexp",
+ "8ddd92f1820a736c7a4f3c27b3181579d6280ed82f0983f80219d523782731f6");
+ ("002-juan-diego-cuauhtlatoatzin.sexp",
+ "01000fe5615894ff82b89a099e1539d6556beccfe1d07d5664362b0126724b09");
+ ("003-our-lady-of-guadalupe.sexp",
+ "a19100769786d110d462805cc4533867c9d56308f8a058f46b257b2b0d6cb3ef");
+ ("004-john-xxiii-john-paul-ii.sexp",
+ "ae24ac41935600d0d11a81072bedce2ba9790b79e2f8192c5d4a08a9a7db4e6e");
+ ("005-mary-magdalene-rank.sexp",
+ "08a19050c21391ee565bc4d5a3ba228e3162875188db4bbc3b7f4b066a60a0c1");
+ ("006-mary-mother-of-the-church.sexp",
+ "8a1dda10c1014bc59c22779f3686c6cce0bf9ff1ec4e504cb912cf2c89319ff9");
+ ("007-paul-vi.sexp",
+ "d7b442b6b6dd6c8e7aca5f7126f7b3e65104191ae84c158e07e9afd0f70edcfc");
+ ("008-our-lady-of-loreto.sexp",
+ "772366fb6c04d1c779109d8b56fa1c0a71ff0129f92349564e1a353b253c2e13");
+ ("009-faustina-kowalska.sexp",
+ "a6bf8a1ab8e3f4aa65bb22d4ff9ba863e5f65865180444f4b143f36aa99e45b3");
+ ("010-narek-avila-hildegard.sexp",
+ "6ca11a7c2e02491b9b96349282c7b1d6fc28c9e4729f678bd54d11a70e43f568");
+ ("011-martha-mary-lazarus.sexp",
+ "c4ede10b24f22def5fd557a3627d1ecaeb5de583f263372a67bccf9ab838f023");
+ ("012-teresa-of-calcutta.sexp",
+ "5646597472f6170a2b56e1f1c4a2ef7e7917cb5c87eb5e3d1f98b8ee1aaae646");
+ ("013-john-henry-newman.sexp",
+ "f6c12d1ead043a609b0086a130ae1a9fc680a8926fe77ccd97756ee747105d7c") ]
+
+let sha256_of_file path =
+ let tmp = Filename.temp_file "colitur_amendments_of_sha256" ".txt" in
+ let cmd = Printf.sprintf "sha256sum %s > %s" (Filename.quote path) (Filename.quote tmp) in
+ (match Sys.command cmd with
+ | 0 -> ()
+ | n -> Alcotest.failf "sha256sum exited %d" n);
+ let ic = open_in tmp in
+ let line = input_line ic in
+ close_in ic;
+ Sys.remove tmp;
+ match String.split_on_char ' ' line with
+ | hash :: _ -> hash
+ | [] -> Alcotest.fail "sha256sum produced no output"
+
+let load_overlay name =
+ let path = amendments_dir ^ name in
+ match O.load V.rank_of_sexp path with
+ | Ok o -> o
+ | Error e -> Alcotest.failf "%s: failed to load: %s" path e
+
+let load_base () =
+ match L.load V.rank_of_sexp base_path with
+ | Ok l -> l
+ | Error e -> Alcotest.failf "%s: failed to load: %s" base_path e
+
+(* Every file parses. *)
+let test_files_parse () =
+ List.iter
+ (fun (name, _) ->
+ let (_ : V.rank O.t) = load_overlay name in
+ ())
+ files
+
+(* Each has a pinned SHA-256. *)
+let test_sha256_pinned () =
+ List.iter
+ (fun (name, expected) ->
+ Alcotest.(check string) (name ^ " SHA-256") expected
+ (sha256_of_file (amendments_dir ^ name)))
+ files
+
+(* Applying all 13, in decree-chronological order, to the base calendar
+ yields a pinned entry count, AND Overlay.merge reports no diagnostic --
+ the silent-no-op check the whole one-file-per-decree discipline exists
+ to enforce (a diagnostic here would mean a directive was transcribed
+ against a slug the base calendar does not actually have). *)
+let test_apply_in_order () =
+ let base = load_base () in
+ let overlays = List.map (fun (name, _) -> load_overlay name) files in
+ let merged, diagnostics = O.merge base overlays in
+ Alcotest.(check (list string)) "no diagnostics -- every directive matched something" []
+ (List.map O.diagnostic_to_string diagnostics);
+ (* 208 base entries + 14 Adds (1+1+1+2+1+1+1+1+3+1+1, files
+ 001/002/003/004/006/007/008/009/010x3/012/013) + 2 Edits (005, 011,
+ no count change) = 222. *)
+ Alcotest.(check int) "208 base + 14 adds = 222 entries" 222 (List.length merged.L.entries)
+
+(* Every new/edited slug this task introduces is actually present after
+ the merge, and the two Edits did not accidentally become Adds (no
+ "slug not present; edit ignored" diagnostic already ruled that out
+ above, but a direct spot-check on the merged layer is cheap insurance
+ against a slug typo that happened to also dodge the diagnostic, e.g. by
+ matching a DIFFERENT, wrong existing slug). *)
+let test_new_slugs_present () =
+ let base = load_base () in
+ let overlays = List.map (fun (name, _) -> load_overlay name) files in
+ let merged, _ = O.merge base overlays in
+ let has slug = L.mem merged (S.of_string_exn slug) in
+ List.iter
+ (fun slug -> Alcotest.(check bool) (slug ^ " present after merge") true (has slug))
+ [ "pio-of-pietrelcina-padre-pio-priest"; "juan-diego"; "our-lady-of-guadalupe";
+ "john-xxiii-pope"; "john-paul-ii-pope"; "mary-mother-of-the-church"; "paul-vi-pope";
+ "our-lady-of-loreto"; "faustina-kowalska-virgin"; "gregory-of-narek-abbot-and-doctor";
+ "john-of-avila-priest-and-doctor"; "hildegard-of-bingen-virgin-and-doctor";
+ "teresa-of-calcutta-virgin"; "john-henry-newman-priest-and-doctor" ];
+ (* The two Edits kept their original slugs. *)
+ match L.find merged (S.of_string_exn "mary-magdalene") with
+ | None -> Alcotest.fail "mary-magdalene: slug lost after Edit"
+ | Some e -> Alcotest.(check bool) "mary-magdalene is now Festum" true (e.L.cel.Cel.rank = V.Festum)
+
+let test_martha_mary_lazarus_renamed () =
+ let base = load_base () in
+ let overlays = List.map (fun (name, _) -> load_overlay name) files in
+ let merged, _ = O.merge base overlays in
+ match L.find merged (S.of_string_exn "martha") with
+ | None -> Alcotest.fail "martha: slug lost after Edit"
+ | Some e ->
+ Alcotest.(check (option string)) "en name widened" (Some "Saints Martha, Mary and Lazarus")
+ (Names.find e.L.cel.Cel.names (Lang.of_string_exn "en"));
+ Alcotest.(check bool) "date unchanged, 29 July" true
+ (e.L.date = (match DS.fixed ~month:7 ~day:29 with Ok d -> d | Error err -> failwith err));
+ Alcotest.(check bool) "rank unchanged, Memoria_obligatoria" true
+ (e.L.cel.Cel.rank = V.Memoria_obligatoria)
+
+(* Mary, Mother of the Church resolves to the Monday after Pentecost --
+ Easter+50 -- in three real, independently-computed years, not merely
+ trusted from the decree's own arithmetic. Pentecost is Easter+49 (the
+ 50th day of the Paschal season inclusive of Easter); the day after is
+ Easter+50. Checked structurally (weekday = Mon, and the resolved date is
+ exactly 50 days after that year's real Gregorian Easter, computed via
+ Colitur_kernel.Computus.gregorian_easter), not by hand-typing expected
+ calendar dates. *)
+let test_mater_ecclesiae_monday_after_pentecost () =
+ let base = load_base () in
+ let overlays = List.map (fun (name, _) -> load_overlay name) files in
+ let merged, _ = O.merge base overlays in
+ let entry =
+ match L.find merged (S.of_string_exn "mary-mother-of-the-church") with
+ | Some e -> e
+ | None -> Alcotest.fail "mary-mother-of-the-church: not found after merge"
+ in
+ Alcotest.(check bool) "date spec is Easter_offset 50" true (entry.L.date = DS.Easter_offset 50);
+ Alcotest.(check bool) "Memoria_obligatoria" true (entry.L.cel.Cel.rank = V.Memoria_obligatoria);
+ Alcotest.(check bool) "white" true (entry.L.cel.Cel.colour = Colitur_kernel.Colour.White);
+ Alcotest.(check bool) "subject Bvm" true (entry.L.cel.Cel.subject = Colitur_kernel.Subject.Bvm);
+ List.iter
+ (fun year ->
+ let easter = Computus.gregorian_easter year in
+ let pentecost = D.add_days easter 49 in
+ let expected = D.add_days easter 50 in
+ Alcotest.(check bool)
+ (Printf.sprintf "%d: Pentecost (Easter+49) is a Sunday" year)
+ true (D.weekday pentecost = D.Sun);
+ let resolved =
+ match DS.resolve entry.L.date ~year ~easter with
+ | Some d -> d
+ | None -> Alcotest.failf "%d: Easter_offset 50 did not resolve" year
+ in
+ Alcotest.(check bool)
+ (Printf.sprintf "%d: resolves to exactly Easter+50" year) true (resolved = expected);
+ Alcotest.(check bool)
+ (Printf.sprintf "%d: resolved date is a Monday" year) true (D.weekday resolved = D.Mon);
+ Alcotest.(check bool)
+ (Printf.sprintf "%d: resolved date is the day after Pentecost" year) true
+ (resolved = D.add_days pentecost 1))
+ [ 2026; 2027; 2035 ]
+
+let suite =
+ ( "Amendments_of (data/of/amendments/*.sexp)",
+ [ Alcotest.test_case "every overlay file parses" `Quick test_files_parse;
+ Alcotest.test_case "SHA-256 pinned per file" `Quick test_sha256_pinned;
+ Alcotest.test_case "apply in decree-chronological order: 222 entries, no diagnostics" `Quick
+ test_apply_in_order;
+ Alcotest.test_case "every new/edited slug is present after merge" `Quick
+ test_new_slugs_present;
+ Alcotest.test_case "Martha/Mary/Lazarus: renamed in place, date/rank unchanged" `Quick
+ test_martha_mary_lazarus_renamed;
+ Alcotest.test_case "Mary, Mother of the Church: Monday after Pentecost, 3 real years" `Quick
+ test_mater_ecclesiae_monday_after_pentecost ] )
diff --git a/test/test_colitur.ml b/test/test_colitur.ml
index c750e75..98e08fb 100644
--- a/test/test_colitur.ml
+++ b/test/test_colitur.ml
@@ -12,6 +12,7 @@ let () =
("sigla", Test_citation.sigla_suite);
Test_config.suite;
Test_overlay.suite; Test_overlay_ini.suite; Test_temporal_ef.suite; Test_temporal_of.suite; Test_litcal_of.suite; Test_precedence_of.suite;
+ Test_amendments_of.suite;
Test_validate.suite;
Test_precedence.suite;
Test_calendar.suite; Test_precedence_ef.suite; Test_sanctoral_ef.suite; Test_calendar_of_data.suite;