summaryrefslogtreecommitdiff
path: root/test/test_citation_coverage_of.ml
diff options
context:
space:
mode:
Diffstat (limited to 'test/test_citation_coverage_of.ml')
-rw-r--r--test/test_citation_coverage_of.ml140
1 files changed, 140 insertions, 0 deletions
diff --git a/test/test_citation_coverage_of.ml b/test/test_citation_coverage_of.ml
new file mode 100644
index 0000000..3444706
--- /dev/null
+++ b/test/test_citation_coverage_of.ml
@@ -0,0 +1,140 @@
+(* SPDX-License-Identifier: AGPL-3.0-or-later *)
+
+(* Fix wave I2 (final-review.md, 2026-08-25-colitur-of-phases-3-5): the OF
+ counterpart of test_citation_coverage.ml. That file's own header states
+ the rule this one follows: "every citation the engine can emit must
+ parse". EF reaches zero unconverted citations; OF does not, and this
+ file is the honest record of exactly where it falls short, mirroring the
+ review's own explicit instruction -- "enumerate them rather than
+ lowering the bar to fit".
+
+ MEASURED, not estimated: before this fix wave, 259 of 730 citation
+ fields in `colitur readings --rite of 2026` (35%) printed unconverted --
+ 345 distinct book spellings unregistered ({!Colitur_citation.Book}),
+ plus verse sub-letter markers ("11a") and a handful of single-chapter-
+ book misreads ({!Colitur_citation.Book.is_single_chapter}) the parser
+ could not read at all. All of that is now fixed at the source (book.ml's
+ own [of_lectionary_table] and parse.ml's own [verse_num]/[single_chapter]
+ additions), closing every genuinely fixable shape. What remains, walking
+ the FULL data/of/lectionary.sexp (not merely what one civil year happens
+ to observe -- a stricter check than EF's own year-walk, and the same one
+ tools/bootstrap_lectionary_of.ml's own generated header now runs and
+ discloses): 41 distinct references, out of 1540 total, EVERY ONE a
+ hyphenated range crossing a chapter boundary ("2:29-3:6", "1 John") --
+ a {!Colitur_citation.Parse.t} shape [part]/[verse_range] do not
+ represent (a [part] is one chapter and a list of within-chapter ranges;
+ representing a cross-chapter range would need restructuring that type
+ and {!Colitur_citation.Render}'s own consumption of it), deliberately
+ not built this task.
+
+ This file pins that residual EXACTLY -- not merely "at least these fail"
+ -- so it fails loudly in BOTH directions: a NEW unconverted reference
+ appearing (a regression) and one of these 41 starting to convert (this
+ list going stale, e.g. because a future task builds cross-chapter
+ support) are both caught, the same discipline every allow-list in this
+ project's suite already follows (`expected_rows`, `expected_bands`, the
+ litcal/missalemeum comparators). *)
+
+let lectionary_path = "../data/of/lectionary.sexp"
+
+let lectionary () =
+ match Colitur_kernel.Lectionary.load lectionary_path with
+ | Ok l -> l
+ | Error e -> Alcotest.failf "%s: %s" lectionary_path e
+
+(* The exact, pinned residual -- copied from data/of/lectionary.sexp's own
+ generated "Citations that do not parse" listing, itself produced by the
+ SAME {!Colitur_citation.Parse.parse} call this test makes, so the two
+ can never silently drift apart without one of them visibly failing to
+ regenerate/re-test clean. *)
+let known_cross_chapter_residual =
+ [ "1 Corinthians 12:31-13:13"; "1 John 1:5-2:2"; "1 John 2:29-3:6"; "1 John 3:22-4:6";
+ "1 John 4:19-5:4"; "2 Corinthians 3:15-4:1,3-6"; "2 Samuel 18:9-10,14b,24-25a,30-19:3";
+ "Colossians 1:24-2:3"; "Ecclesiastes 11:9-12:8"; "Ephesians 4:32-5:8"; "Exodus 11:10-12:14";
+ "Exodus 14:21-15:1"; "Ezekiel 2:8-3:4"; "Galatians 4:22-24,26-27,31-5:1"; "Genesis 1:1-2:2";
+ "Genesis 1:20-2:4a"; "Habakkuk 1:12-2:4"; "Hebrews 7:25-8:6"; "Isaiah 52:13-53:12";
+ "Isaiah 8:23b-9:3"; "John 15:26-16:4a"; "John 18:1-19:42"; "Jonah 1:1-2:2,11"; "Luke 7:36-8:3";
+ "Malachi 1:14b-2:2b,8-10"; "Mark 2:23-3:6"; "Mark 8:34-9:1"; "Matthew 10:34-11:1";
+ "Matthew 18:21-19:1"; "Matthew 9:35-10:1,5a,6-8"; "Matthew 9:36-10:8";
+ "Numbers 13:1-2,25-14:1,26a-29a,34-35"; "Philippians 3:17-4:1"; "Revelation 20:1-4,11-21:2";
+ "Sirach 27:30-28:7"; "The Acts 12:24-13:5a"; "The Acts 17:15,22-18:1"; "The Acts 7:51-8:1a";
+ "Wisdom 11:22-12:2"; "Wisdom 2:23-3:9"; "Wisdom 7:22b-8:1" ]
+
+(* Every failure this walk finds really is a chapter-crossing hyphen range
+ (a "-" whose right side itself contains a ":") -- checked mechanically,
+ not merely asserted, so a future genuinely-different failure shape
+ cannot hide inside this test by coincidentally also being in the pinned
+ list above. No [Str]/regex (frozen deps): plain character scanning. *)
+let looks_chapter_crossing reference =
+ let n = String.length reference in
+ let rec scan i saw_dash =
+ if i >= n then false
+ else if reference.[i] = '-' then scan (i + 1) true
+ else if reference.[i] = ':' && saw_dash then true
+ else scan (i + 1) saw_dash
+ in
+ scan 0 false
+
+let test_of_citations_convert_or_are_the_known_cross_chapter_residual () =
+ let total = ref 0 in
+ let bad = ref [] in
+ List.iter
+ (fun (_slug, cits) ->
+ List.iter
+ (fun (c : Colitur_kernel.Citation.t) ->
+ incr total;
+ match Colitur_citation.Parse.parse c.Colitur_kernel.Citation.reference with
+ | Ok _ -> ()
+ | Error _ ->
+ if not (List.mem c.Colitur_kernel.Citation.reference !bad) then
+ bad := c.Colitur_kernel.Citation.reference :: !bad)
+ cits)
+ (Colitur_kernel.Lectionary.entries (lectionary ()));
+ let bad = List.sort compare !bad in
+ Alcotest.(check int) "1540 citation fields in data/of/lectionary.sexp today -- if this changes, the \
+ pinned residual below may need updating too, not just this count"
+ 1540 !total;
+ Alcotest.(check (list string)) "the unconverted set is EXACTLY the known cross-chapter residual, no \
+ more and no fewer"
+ (List.sort compare known_cross_chapter_residual) bad;
+ List.iter
+ (fun r ->
+ Alcotest.(check bool) (Printf.sprintf "%s: really is a chapter-crossing range, not some other \
+ unrelated failure shape" r)
+ true (looks_chapter_crossing r))
+ bad
+
+(* Everything OUTSIDE the pinned residual round-trips through Sigla, the
+ same structural (not textual) comparison test_citation_coverage.ml's own
+ EF version makes, and for the identical reason its own header gives. *)
+let test_round_trip_outside_the_residual () =
+ let sg =
+ Colitur_citation.Sigla.make ~style:Colitur_citation.Render.default_style
+ ~tradition:Colitur_citation.Book.vulgate
+ ~names:(fun id _form -> Colitur_citation.Book.default_spelling id)
+ in
+ let bad = ref [] in
+ List.iter
+ (fun (_slug, cits) ->
+ List.iter
+ (fun (c : Colitur_kernel.Citation.t) ->
+ let r = c.Colitur_kernel.Citation.reference in
+ if not (List.mem r known_cross_chapter_residual) then
+ match Colitur_citation.Parse.parse r with
+ | Error e -> bad := Printf.sprintf "%s: unexpectedly does not parse (%s)" r e :: !bad
+ | Ok first -> (
+ let rendered = Colitur_citation.Sigla.format sg r in
+ match Colitur_citation.Parse.parse rendered with
+ | Error e -> bad := Printf.sprintf "%s: re-parse failed: %s" r e :: !bad
+ | Ok again -> if again <> first then bad := Printf.sprintf "%s: structure changed: %s" r rendered :: !bad))
+ cits)
+ (Colitur_kernel.Lectionary.entries (lectionary ()));
+ Alcotest.(check (list string)) "every OF citation outside the pinned residual round-trips through Sigla"
+ [] (List.sort compare !bad)
+
+let suite =
+ ( "citation-coverage-of",
+ [ Alcotest.test_case "every OF citation converts, or is the pinned cross-chapter residual" `Slow
+ test_of_citations_convert_or_are_the_known_cross_chapter_residual;
+ Alcotest.test_case "every OF citation outside the residual round-trips" `Slow
+ test_round_trip_outside_the_residual ] )