aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-08-20 16:05:34 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-08-20 16:05:34 +0200
commit263d0d889073d214229aef9655d3db29540c0d12 (patch)
tree895753cf5ec4e2aaacff5da50a8abfdf32eacb74
parent9dfa264f2941f1ca38eff08b6157a94d332cffbd (diff)
downloadcolitur-263d0d889073d214229aef9655d3db29540c0d12.tar.gz
colitur-263d0d889073d214229aef9655d3db29540c0d12.zip
fix(citation): complete the modern tradition, and test the shipped file
Apocalypse -> Revelation was missing from [modern] while `revelation` was already a declared tradition target, so the target existed with nothing mapping onto it. Same Vulgate-to-modern shape as Ecclesiasticus -> Sirach. The injectivity test used to hand-copy the mapping table, which asserted things about the copy and nothing about the artifact: a mapping added to traditions.ini and forgotten in the test would have passed. It now reads lang/traditions.ini and additionally asserts that [vulgate] is empty -- an entry there would silently renumber the DEFAULT, the one thing this design promises never happens -- and that no declared target is left unreachable. Mutation-proved three ways: declaring an unused target, deleting a mapping, and adding an entry to [vulgate] each redden it, the first two naming the offending book.
-rw-r--r--lang/traditions.ini1
-rw-r--r--test/test_citation.ml44
2 files changed, 41 insertions, 4 deletions
diff --git a/lang/traditions.ini b/lang/traditions.ini
index e2d53ab..46f6ee7 100644
--- a/lang/traditions.ini
+++ b/lang/traditions.ini
@@ -19,3 +19,4 @@ esdras_2 = nehemiah
ecclesiasticus = sirach
osee = hosea
jonas = jonah
+apocalypse = revelation
diff --git a/test/test_citation.ml b/test/test_citation.ml
index d9be86e..1efaa10 100644
--- a/test/test_citation.ml
+++ b/test/test_citation.ml
@@ -180,13 +180,49 @@ let test_every_data_file_token_resolves () =
always a typo -- it would silently merge two books -- so this asserts the
mapping is injective, not merely that every id is known. *)
let test_modern_tradition_is_injective () =
- let fields =
- [ ("kings_3", "kings_1"); ("kings_4", "kings_2");
- ("esdras_2", "nehemiah"); ("ecclesiasticus", "sirach");
- ("osee", "hosea"); ("jonas", "jonah") ]
+ (* READ the shipped file. An earlier version of this test hand-copied the
+ table, which asserted things about the copy and nothing at all about the
+ artifact -- a mapping added to traditions.ini and forgotten here would
+ have passed. *)
+ let text =
+ let ic = open_in_bin "../lang/traditions.ini" in
+ let s = really_input_string ic (in_channel_length ic) in
+ close_in ic; s
in
+ let sections =
+ match Colitur_kernel.Overlay_ini.parse_sections text with
+ | Ok ss -> ss
+ | Error e -> Alcotest.failf "lang/traditions.ini: %s" e
+ in
+ let section name =
+ List.filter_map
+ (fun (sc : Colitur_kernel.Overlay_ini.section) ->
+ if sc.Colitur_kernel.Overlay_ini.name = name then
+ Some sc.Colitur_kernel.Overlay_ini.fields
+ else None)
+ sections
+ |> List.concat
+ in
+ (* [vulgate] must be the identity: an entry there would silently renumber
+ the DEFAULT, which is the one thing this design promises never happens. *)
+ Alcotest.(check (list (pair string string))) "vulgate is identity" []
+ (section "vulgate");
+ let fields = section "modern" in
+ Alcotest.(check bool) "modern is not empty" true (fields <> []);
Alcotest.(check (list string)) "all ids known" [] (B.unknown_fields fields);
+ (* every declared tradition target is actually reachable -- an unused target
+ is either a forgotten mapping or dead weight *)
let targets = List.map snd fields in
+ (* [B.tokens] is (spelling, id) pairs, so membership of an ID is a lookup
+ over the VALUES, not [mem_assoc] over the keys. Getting that backwards
+ flags every ordinary book as an unused target. *)
+ let cited = List.map (fun (_, i) -> B.to_string i) B.tokens in
+ List.iter
+ (fun id ->
+ let n = B.to_string id in
+ if (not (List.mem n cited)) && not (List.mem n targets) then
+ Alcotest.failf "%s is a declared target no tradition maps onto" n)
+ B.all;
let uniq = List.sort_uniq compare targets in
Alcotest.(check int) "no two ids share a target"
(List.length targets) (List.length uniq)