aboutsummaryrefslogtreecommitdiff
path: root/test/test_lang_coverage.ml
blob: 96a3383d0e5c7384e42507d2cac752a839caeb7b (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
module L = Colitur_naming.Lang

let read path =
  let ic = open_in_bin path in
  let s = really_input_string ic (in_channel_length ic) in
  close_in ic;
  s

let la () =
  match L.of_string (read "../lang/la.ini") with
  | Ok t -> t
  | Error e -> Alcotest.failf "lang/la.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-
   sunday-2" because nothing asserted coverage. It must fail loudly the
   moment a new slug appears without a name.

   Task 3 restricted this to "^ef-" slugs only (la.ini's [celebration] table
   carried the temporal half alone at the time); Task 4 added the sanctoral
   half and REMOVED that filter -- every slug is now in scope, with no
   exceptions. *)
let test_every_slug_has_a_latin_name () =
  let t = la () in
  let layer = match Test_support.load_ef_layer () with Ok l -> l | Error e -> Alcotest.failf "%s" e in
  let ctx = Test_support.ef_context () in
  let missing = ref [] in
  for y = 2020 to 2045 do
    Array.iter
      (fun (d : (Rite_ef.Vocab_ef.season, Rite_ef.Vocab_ef.rank) Colitur_kernel.Liturgical_day.t) ->
        let slug =
          Colitur_kernel.Slug.to_string
            d.Colitur_kernel.Liturgical_day.observed.Colitur_kernel.Celebration.slug
        in
        (* A miss returns the key itself, so name = slug means "no entry". *)
        if L.celebration t slug = slug && not (List.mem slug !missing) then
          missing := slug :: !missing)
      (Colitur_kernel.Calendar.year ctx layer y)
  done;
  if !missing <> [] then
    Alcotest.failf "%d slugs have no Latin name, e.g. %s" (List.length !missing)
      (String.concat ", " (List.filteri (fun i _ -> i < 5) !missing))

let test_vocabularies_are_complete () =
  let t = la () in
  List.iter
    (fun s -> if L.season t s = s then Alcotest.failf "no Latin season name for %S" s)
    [ "advent"; "christmastide"; "time-after-epiphany"; "septuagesima"; "lent";
      "passiontide"; "paschaltide"; "time-after-pentecost" ];
  List.iter
    (fun r -> if L.rank t r = r then Alcotest.failf "no Latin rank name for %S" r)
    [ "class-1"; "class-2"; "class-3"; "class-4" ];
  List.iter
    (fun c -> if L.colour t c = c then Alcotest.failf "no Latin colour name for %S" c)
    [ "white"; "red"; "green"; "violet"; "rose"; "black" ];
  for n = 0 to 6 do
    if L.weekday t n = string_of_int n then Alcotest.failf "no Latin weekday for %d" n
  done;
  for n = 1 to 12 do
    if L.month t n = string_of_int n then Alcotest.failf "no Latin month for %d" n
  done

(* 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
   slug -- that is what makes an incomplete translation shippable from its
   first line. This test proves the CHAIN MECHANISM itself, independent of
   how complete lang/en.ini happens to be today: a from-scratch table with NO
   [celebration] entries at all, chained to the real la.ini, must still
   resolve a real la.ini key -- so the test cannot be defeated simply by
   en.ini becoming more complete over time. It also sanity-checks the real
   shipped file: that it parses, declares the right fallback code, and that
   at least one of its own entries resolves directly (not merely through the
   chain). *)
let test_en_falls_back_to_latin () =
  let en =
    match L.of_string (read "../lang/en.ini") with
    | Ok t -> t
    | Error e -> Alcotest.failf "lang/en.ini: %s" e
  in
  Alcotest.(check string) "declares la fallback" "la"
    (Option.value (L.fallback_code en) ~default:"NONE");
  (* The real shipped file: at least one of its own entries resolves without
     needing the chain at all. *)
  Alcotest.(check bool) "en.ini names ef-epiphany directly" true
    (L.celebration en "ef-epiphany" <> "ef-epiphany");
  (* The mechanism, isolated from today's en.ini coverage: an EMPTY table
     (no [celebration] section) chained to la.ini must still resolve a real
     la.ini-only key through the fallback. *)
  let empty =
    match L.of_string "[meta]\nlang = en\nfallback = la\n" with
    | Ok t -> t
    | Error e -> Alcotest.failf "synthetic empty en table: %s" e
  in
  let chained = L.with_fallback empty (la ()) in
  Alcotest.(check bool) "empty table falls back to la.ini for a real slug" true
    (L.celebration chained "hilary" <> "hilary")

let suite =
  ( "Lang/coverage",
    [ Alcotest.test_case "every slug has a Latin name" `Slow test_every_slug_has_a_latin_name;
      Alcotest.test_case "vocabularies complete" `Quick test_vocabularies_are_complete;
      Alcotest.test_case "en.ini falls back to Latin" `Quick test_en_falls_back_to_latin ] )