aboutsummaryrefslogtreecommitdiff
path: root/test/test_sanctoral_ef.ml
blob: 8fb178ff2b895f6022b889df10759994d2db35dc (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
105
106
107
108
109
110
111
112
113
114
115
(* Loads the bootstrapped data/ef/sanctoral.sexp (Task 10, tools/
   bootstrap_sanctoral.ml) through Layer.load and checks it against counts
   independently derived from the source INI itself (grep -c '^\[',
   grep '^rank' | sort | uniq -c -- see the task report), plus two named
   spot-checks against the INI's own text so a passing count can't hide the
   wrong 322 entries having been converted (task brief's "live hazard"). *)

module L = Colitur_kernel.Layer
module Cel = Colitur_kernel.Celebration
module S = Colitur_kernel.Slug
module DS = Colitur_kernel.Date_spec
module Col = Colitur_kernel.Colour
module Sub = Colitur_kernel.Subject
module Lang = Colitur_kernel.Lang
module Names = Colitur_kernel.Names
module V = Rite_ef.Vocab_ef
module PE = Rite_ef.Precedence_ef

(* Relative to this test's own build directory (_build/default/test/); made
   available there because test/dune declares it as a dep of the test
   stanza. *)
let path = "../data/ef/sanctoral.sexp"

let load () =
  match L.load V.rank_of_sexp path with
  | Ok l -> l
  | Error e -> Alcotest.failf "%s: failed to load: %s" path e

let mkdate m d = match DS.fixed ~month:m ~day:d with Ok t -> t | Error e -> failwith e

let test_load_and_counts () =
  let l = load () in
  Alcotest.(check int) "322 entries" 322 (List.length l.L.entries);
  let commemoration_only =
    List.filter (fun e -> e.L.cel.Cel.status = Cel.Commemoration_only) l.L.entries
  in
  Alcotest.(check int) "114 Commemoration_only" 114 (List.length commemoration_only);
  let class1 = List.filter (fun e -> e.L.cel.Cel.rank = V.Class1) l.L.entries in
  Alcotest.(check int) "12 Class1" 12 (List.length class1);
  let temporal_subjects = List.filter (fun e -> e.L.cel.Cel.subject = Sub.Temporal) l.L.entries in
  Alcotest.(check int) "no Subject.Temporal" 0 (List.length temporal_subjects);
  (* Every slug already had to pass Slug.of_string during load (Slug.t_of_sexp
     is the validating parser -- an invalid slug would have failed the whole
     Layer.load with Error, never landing here silently). Re-checking is
     still worth doing explicitly, rather than resting entirely on "load
     didn't error", so a future loader change that weakens that guarantee
     has a test that would catch it directly. *)
  List.iter
    (fun e ->
      match S.of_string (S.to_string e.L.cel.Cel.slug) with
      | Ok _ -> ()
      | Error msg -> Alcotest.failf "slug %s failed re-validation: %s" (S.to_string e.L.cel.Cel.slug) msg)
    l.L.entries;
  (* All 322 dates are Date_spec.Fixed month/day pairs constructed via
     Date_spec.fixed, which already rejects an out-of-range day for that
     month against the LEAP-year maximum -- so every one must resolve in a
     leap year (2028: divisible by 4, not by 100). *)
  List.iter
    (fun e ->
      match DS.resolve e.L.date ~year:2028 with
      | Some _ -> ()
      | None -> Alcotest.failf "slug %s: date does not resolve in leap year 2028" (S.to_string e.L.cel.Cel.slug))
    l.L.entries

let find l slug =
  match L.find l (S.of_string_exn slug) with
  | Some e -> e
  | None -> Alcotest.failf "slug %s not found in %s" slug path

(* Named spot-check 1: one of the 6 entries carrying an explicit `class`
   field in the source (INI lines: date=02-02, rank=class-2, colour=white,
   class=lord, name.en/name.pl as below). Exercises decision 1's non-default
   branch -- an entry where subject must NOT fall back to Saint. *)
let test_spot_check_explicit_class () =
  let l = load () in
  let e = find l "purification-of-the-blessed-virgin-mary" in
  let cel = e.L.cel in
  Alcotest.(check bool) "date 02-02" true (e.L.date = mkdate 2 2);
  Alcotest.(check bool) "rank Class2" true (cel.Cel.rank = V.Class2);
  Alcotest.(check bool) "status Feast" true (cel.Cel.status = Cel.Feast);
  Alcotest.(check bool) "colour White" true (cel.Cel.colour = Col.White);
  Alcotest.(check bool) "subject Lord (from class = lord)" true (cel.Cel.subject = Sub.Lord);
  Alcotest.(check (option string)) "name.en" (Some "Purification of the Blessed Virgin Mary")
    (Names.find cel.Cel.names (Lang.of_string_exn "en"));
  Alcotest.(check (option string)) "name.pl" (Some "Oczyszczenie N. M. P.")
    (Names.find cel.Cel.names (Lang.of_string_exn "pl"));
  Alcotest.(check string) "layer tagged universal (Precedence_ef.universal_layer)" PE.universal_layer
    cel.Cel.layer

(* Named spot-check 2: a `rank = commemoration` entry (INI lines: date=01-14,
   rank=commemoration, colour=white, no class, name.en/name.pl as below).
   Exercises decision 2 (Commemoration_only + inferred Class3) AND decision
   1's default branch (no class field -> Saint) on the SAME entry. *)
let test_spot_check_commemoration () =
  let l = load () in
  let e = find l "maur-abbot" in
  let cel = e.L.cel in
  Alcotest.(check bool) "date 01-15" true (e.L.date = mkdate 1 15);
  Alcotest.(check bool) "rank inferred Class3" true (cel.Cel.rank = V.Class3);
  Alcotest.(check bool) "status Commemoration_only" true (cel.Cel.status = Cel.Commemoration_only);
  Alcotest.(check bool) "colour White" true (cel.Cel.colour = Col.White);
  Alcotest.(check bool) "subject defaults to Saint (no class field)" true (cel.Cel.subject = Sub.Saint);
  Alcotest.(check (option string)) "name.en" (Some "St. Maur, Abbot")
    (Names.find cel.Cel.names (Lang.of_string_exn "en"));
  Alcotest.(check (option string)) "name.pl" (Some "św. Maura, Opata")
    (Names.find cel.Cel.names (Lang.of_string_exn "pl"))

let suite =
  ( "Sanctoral_ef (data/ef/sanctoral.sexp)",
    [ Alcotest.test_case "load succeeds and counts match the source INI" `Quick
        test_load_and_counts;
      Alcotest.test_case "spot-check: explicit class = lord (Purification of the BVM)" `Quick
        test_spot_check_explicit_class;
      Alcotest.test_case "spot-check: rank = commemoration (St. Maur, Abbot)" `Quick
        test_spot_check_commemoration ] )