diff options
Diffstat (limited to 'test/test_amendments_of.ml')
| -rw-r--r-- | test/test_amendments_of.ml | 217 |
1 files changed, 217 insertions, 0 deletions
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 ] ) |
