diff options
Diffstat (limited to 'test/test_lang_coverage.ml')
| -rw-r--r-- | test/test_lang_coverage.ml | 118 |
1 files changed, 118 insertions, 0 deletions
diff --git a/test/test_lang_coverage.ml b/test/test_lang_coverage.ml index 0271a59..d41f7e7 100644 --- a/test/test_lang_coverage.ml +++ b/test/test_lang_coverage.ml @@ -11,6 +11,14 @@ let la () = | Ok t -> t | Error e -> Alcotest.failf "lang/la.ini: %s" e +(* en.ini resolved THROUGH la.ini, the way a real run resolves it: en + declares `fallback = la`, so a key it omits must reach the Latin table + rather than degrade to the raw key. *) +let en () = + match L.of_string (read "../lang/en.ini") with + | Ok t -> L.with_fallback t (la ()) + | Error e -> Alcotest.failf "lang/en.ini: %s" e + (* Every slug the engine can emit -- temporal ("^ef-") AND sanctoral (a fixed saint's day) alike -- must have a Latin name. THIS IS THE TEST THAT WOULD HAVE CAUGHT THE ORIGINAL DEFECT -- a booklet printed "ef-septuagesima- @@ -145,6 +153,112 @@ let test_no_book_name_is_an_internal_id () = Alcotest.(check (list string)) "no book name is its own internal id" [] leaked +(* Two DIFFERENT books must not share a name. Found by audit: `1 Cor` and + `2 Cor` both rendered as "Epistola ad Corinthios" under --sigla-book full, + and so did Thessalonians, Timothy and Peter -- 108 citations in 2027 alone + that a reader cannot resolve to a book. The same defect had already been + fixed for the two Books of Kings and simply not generalised. + + Sharing a name is CORRECT, though, when the two ids are the same physical + book under different numbering -- `osee`/`hosea`, `jonas`/`jonah`, + `apocalypse`/`revelation`. A tradition maps one onto the other, so the + rule is exact: a shared name is a defect UNLESS some tradition relates the + two ids. *) +let test_no_two_books_share_a_name () = + let la = la () in + let traditions = + 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 + match Colitur_kernel.Overlay_ini.parse_sections text with + | Ok ss -> + List.concat_map (fun (sc : Colitur_kernel.Overlay_ini.section) -> + sc.Colitur_kernel.Overlay_ini.fields) ss + | Error e -> Alcotest.failf "traditions.ini: %s" e + in + let related a b = + List.exists (fun (x, y) -> (x = a && y = b) || (x = b && y = a)) traditions + in + let offenders = ref [] in + List.iter + (fun form -> + let seen = Hashtbl.create 64 in + List.iter + (fun id -> + let n = Colitur_citation.Book.to_string id in + let key = n ^ "." ^ form in + let v = L.bible la key in + if v <> key then + match Hashtbl.find_opt seen v with + | Some other when not (related n other) -> + offenders := Printf.sprintf "%s: %s and %s" v other n :: !offenders + | _ -> Hashtbl.replace seen v n) + Colitur_citation.Book.all) + [ "full"; "abbr" ]; + Alcotest.(check (list string)) "no two different books share a name" [] + (List.sort compare !offenders) + +(* SPEC SECTION 8.5: each shipped style must parse its own rendered output. + It did not. Measured at the time: 32 of 52 Latin abbreviations and 49 of 52 + Latin full titles failed to re-parse, so a user who copied a citation out + of `colitur readings` into an overlay handed the parser a string it could + not read; [Sigla.format] passed it through untouched and, say, an English + full-name booklet printed a Latin abbreviation with no warning. Closed by + registering every shipped name as a spelling and by teaching [split_book] + multi-word titles. + + A name may resolve to a DIFFERENT id than the one it was rendered from, + but only when the two are the same physical book under different + numbering: "Sir" is registered to [ecclesiasticus] and the modern id + [sirach] maps onto it. The parser has no tradition context, so it returns + the Vulgate id, and that is right rather than tolerated. *) +let test_shipped_styles_round_trip () = + let traditions = + 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 + match Colitur_kernel.Overlay_ini.parse_sections text with + | Ok ss -> + List.concat_map + (fun (sc : Colitur_kernel.Overlay_ini.section) -> + sc.Colitur_kernel.Overlay_ini.fields) + ss + | Error e -> Alcotest.failf "traditions.ini: %s" e + in + let related a b = + a = b || List.exists (fun (x, y) -> (x = a && y = b) || (x = b && y = a)) traditions + in + let check_file label t = + List.concat_map + (fun form -> + List.filter_map + (fun id -> + let n = Colitur_citation.Book.to_string id in + let name = L.bible t (n ^ "." ^ form) in + if name = n ^ "." ^ form then None + else + match Colitur_citation.Parse.parse (name ^ " 5:12-14") with + | Ok r + when related + (Colitur_citation.Book.to_string r.Colitur_citation.Parse.book) + n -> + None + | Ok r -> + Some + (Printf.sprintf "%s %s/%s -> %s" label name form + (Colitur_citation.Book.to_string r.Colitur_citation.Parse.book)) + | Error e -> Some (Printf.sprintf "%s %s/%s: %s" label name form e)) + Colitur_citation.Book.all) + [ "full"; "abbr" ] + in + let bad = check_file "la" (la ()) @ check_file "en" (en ()) in + Alcotest.(check (list string)) "every shipped book name re-parses" [] + (List.sort compare bad) + (* lang/en.ini is DELIBERATELY partial (see its own header note): it declares [meta] fallback = la, so a slug it does not carry itself should still resolve through the chain to la.ini's name rather than degrade to the bare @@ -189,4 +303,8 @@ let suite = Alcotest.test_case "every book named, both forms" `Quick test_every_book_named; Alcotest.test_case "no book name is an internal id" `Quick test_no_book_name_is_an_internal_id; + Alcotest.test_case "no two books share a name" `Quick + test_no_two_books_share_a_name; + Alcotest.test_case "shipped styles round-trip" `Quick + test_shipped_styles_round_trip; Alcotest.test_case "en.ini falls back to Latin" `Quick test_en_falls_back_to_latin ] ) |
