diff options
Diffstat (limited to 'test/test_validate.ml')
| -rw-r--r-- | test/test_validate.ml | 109 |
1 files changed, 98 insertions, 11 deletions
diff --git a/test/test_validate.ml b/test/test_validate.ml index 8b3014a..7387971 100644 --- a/test/test_validate.ml +++ b/test/test_validate.ml @@ -300,15 +300,16 @@ module Synthetic = struct defaults against the default empty [layer], since nothing ever contests the temporal office there) so the resolution fixtures further down can override them without duplicating every other field. *) - (* Most fixtures here exercise no citations -- [readings] is a harmless - constant [], the same role the other placeholder defaults above play, - and {!Validate}'s own citation checks are gated on a rite producing SOME - citation somewhere, so a constant [] leaves them entirely dormant. Task + (* Most fixtures here exercise no citations and no formulary -- [readings] + is a harmless constant [(None, [])], the same role the other + placeholder defaults above play, and {!Validate}'s own citation and + formulary checks are gated on a rite producing SOME citation/formulary + somewhere, so a constant [(None, [])] leaves them entirely dormant. Task 10 makes it overridable ([?readings] below) so the citation fixtures at the end of this file can drive those checks directly, exactly as every - other check here is driven -- rather than leaving two kernel checks with - no committed proof that they can fire at all. *) - let readings ~observed:_ ~temporal:_ ~date:_ ~temporal_at:_ = [] + other check here is driven -- rather than leaving kernel checks with no + committed proof that they can fire at all. *) + let readings ~observed:_ ~temporal:_ ~date:_ ~temporal_at:_ = (None, []) (* The shape {!Validate} accepts: exactly one First and one Gospel. The references are deliberately nonsense -- these fixtures assert SHAPE, @@ -317,6 +318,12 @@ module Synthetic = struct [ { Citation.part = Citation.First; reference = "Synth 1:1" }; { Citation.part = Citation.Gospel; reference = "Synth 2:2" } ] + (* The formulary equivalent of [well_formed_citations] above -- shape only, + never rubrically meaningful content. *) + let well_formed_formulary = + { Colitur_kernel.Mass_formulary.said = Slug.of_string_exn "syn-formulary"; + via = Colitur_kernel.Mass_formulary.Own_slug } + let rite ?(vocab = vocab) ?(anchors = fun _ -> []) ?(season_runs = [ A; B ]) ?(rules = rules) ?(transfer_target = fun _ origin _ -> origin) ?(readings = readings) temporal : (season, rank) Rite.t = @@ -759,7 +766,7 @@ let test_citations_silent_without_a_lectionary () = (* The positive: well-formed citations on every day report nothing. *) let test_citations_clean_when_well_formed () = - let readings ~observed:_ ~temporal:_ ~date:_ ~temporal_at:_ = well_formed_citations in + let readings ~observed:_ ~temporal:_ ~date:_ ~temporal_at:_ = (None, well_formed_citations) in let fs = run ~readings good in Alcotest.(check bool) "neither citation check fires when every day carries First + Gospel" false (has_check "citations" fs || has_check "citations-unresolved" fs) @@ -769,7 +776,7 @@ let test_citations_clean_when_well_formed () = plausibly produce -- half a lookup succeeding. *) let test_citations_fires_on_a_lone_epistle () = let readings ~observed:_ ~temporal:_ ~date:_ ~temporal_at:_ = - [ { Citation.part = Citation.First; reference = "Synth 1:1" } ] + (None, [ { Citation.part = Citation.First; reference = "Synth 1:1" } ]) in Alcotest.(check bool) "citations check fires when a day carries an Epistle but no Gospel" true (has_check "citations" (run ~readings good)) @@ -780,7 +787,7 @@ let test_citations_fires_on_a_lone_epistle () = even though the day is otherwise a well-formed pair. *) let test_citations_fires_on_an_out_of_scope_part () = let readings ~observed:_ ~temporal:_ ~date:_ ~temporal_at:_ = - { Citation.part = Citation.Tract; reference = "Synth 3:3" } :: well_formed_citations + (None, { Citation.part = Citation.Tract; reference = "Synth 3:3" } :: well_formed_citations) in Alcotest.(check bool) "citations check fires when a part outside First/Gospel appears" true (has_check "citations" (run ~readings good)) @@ -792,7 +799,7 @@ let test_citations_fires_on_an_out_of_scope_part () = this file singles out. *) let test_citations_unresolved_fires_on_a_gap () = let readings ~observed:_ ~temporal:_ ~date ~temporal_at:_ = - if D.compare date target = 0 then [] else well_formed_citations + if D.compare date target = 0 then (None, []) else (None, well_formed_citations) in Alcotest.(check bool) "citations-unresolved fires when one day of the year resolves nothing" true (has_check "citations-unresolved" (run ~readings good)); @@ -801,6 +808,79 @@ let test_citations_unresolved_fires_on_a_gap () = Alcotest.(check bool) "the well-formedness check stays silent on a pure coverage gap" false (has_check "citations" (run ~readings good)) +(* ---------------------------------------------------------------------- *) +(* The formulary invariant (Task 3, celebrant-rubrics-phase1): the same *) +(* negative-path discipline the citation checks above already hold *) +(* themselves to, driven through the same synthetic fixture. Model on the *) +(* citations trio above -- "same shape, its own name" is what {!Validate} *) +(* itself now does, so the tests proving it can fire follow the same *) +(* pattern. *) +(* ---------------------------------------------------------------------- *) + +(* The gate: a rite that resolves no formulary at all (the default constant + [(None, [])]) must report nothing -- not "usually", not "on this year". *) +let test_formulary_silent_without_a_lectionary () = + let fs = run good in + Alcotest.(check bool) "no formulary check fires for a rite with no readings at all" false + (has_check "formulary" fs) + +(* The positive: a formulary on every day reports nothing. *) +let test_formulary_clean_when_well_formed () = + let readings ~observed:_ ~temporal:_ ~date:_ ~temporal_at:_ = + (Some well_formed_formulary, well_formed_citations) + in + Alcotest.(check bool) "formulary check stays silent when every day resolves one" false + (has_check "formulary" (run ~readings good)) + +(* The coverage gap: a rite that resolves a formulary on most days but falls + through on one. On real EF data this has no witness at all (Task 3's own + [test_every_day_has_a_formulary] below confirms it directly), so this + fixture is the only thing that holds it honest. *) +let test_formulary_fires_on_a_gap () = + let readings ~observed:_ ~temporal:_ ~date ~temporal_at:_ = + if D.compare date target = 0 then (None, []) + else (Some well_formed_formulary, well_formed_citations) + in + Alcotest.(check bool) "formulary check fires when one day of the year resolves none" true + (has_check "formulary" (run ~readings good)) + +(* ---------------------------------------------------------------------- *) +(* Direct real-EF-data coverage (Task 3 brief): every day of every year in *) +(* the sample resolves a formulary, the same discipline the "citations" *) +(* checks already hold EF to -- asserted directly against *) +(* [Liturgical_day.t] rather than through [Validate.run]'s failure list, *) +(* so a bug in [Validate]'s own gating could not hide this gap. *) +(* ---------------------------------------------------------------------- *) + +(* 2005-2050: the same 46-year sample test_rite_ef.ml's own [sample_years] + uses, for the same reason -- non-trivial, deterministic, and already the + project's differential-testing window (CLAUDE.md). Defined locally + rather than shared: test executables in this project cross-reference + only [.suite] values (test_colitur.ml), never each other's internal + helpers. *) +let sample_years = + let rec range a b = if a > b then [] else a :: range (a + 1) b in + range 2005 2050 + +let year_of y = Colitur_kernel.Calendar.year real_ef_rite real_ef_layer y + +(* Every day of every year resolves a formulary, for the same reason + [Validate] already asserts exactly one First and one Gospel: a day that + says no Mass at all is a defect, not a gap. Mirrors the "citations" + check. *) +let test_every_day_has_a_formulary () = + let missing = ref [] in + List.iter + (fun y -> + Array.iter + (fun (d : (_, _) Colitur_kernel.Liturgical_day.t) -> + if d.Colitur_kernel.Liturgical_day.formulary = None then + missing := + Colitur_kernel.Date.to_iso8601 d.Colitur_kernel.Liturgical_day.date :: !missing) + (year_of y)) + sample_years; + Alcotest.(check (list string)) "every day resolves a formulary" [] !missing + let suite = ( "Validate", [ Alcotest.test_case "landmark years" `Quick test_landmark_years; @@ -830,6 +910,13 @@ let suite = test_citations_fires_on_an_out_of_scope_part; Alcotest.test_case "citations-unresolved fires on a gap" `Quick test_citations_unresolved_fires_on_a_gap; + Alcotest.test_case "formulary silent without a lectionary" `Quick + test_formulary_silent_without_a_lectionary; + Alcotest.test_case "formulary clean when well formed" `Quick + test_formulary_clean_when_well_formed; + Alcotest.test_case "formulary fires on a gap" `Quick test_formulary_fires_on_a_gap; + Alcotest.test_case "every day (2005-2050) resolves a formulary" `Quick + test_every_day_has_a_formulary; Alcotest.test_case "lost fires on resolution exception" `Quick test_lost_fires_on_resolution_exception; Alcotest.test_case "duplicated fires" `Quick test_duplicated_fires; Alcotest.test_case "unconverged fires" `Quick test_unconverged_fires; |
