aboutsummaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/fixtures/lectio-ef-2005-2050.provenance8
-rw-r--r--test/test_differential.ml109
2 files changed, 107 insertions, 10 deletions
diff --git a/test/fixtures/lectio-ef-2005-2050.provenance b/test/fixtures/lectio-ef-2005-2050.provenance
index e738484..872517b 100644
--- a/test/fixtures/lectio-ef-2005-2050.provenance
+++ b/test/fixtures/lectio-ef-2005-2050.provenance
@@ -5,6 +5,14 @@ Produced by the sibling project ~/git/projects/lectio at commit:
2386a45 cmd(lectio-ef-dump): new EF calendar dumper for colitur's differential oracle
(branch polish-ui-and-calendar at the time; see that repo's own log)
+SHA-256: 2ca3eeeda4e7a0406c4d004c1b2003fc0df671aca9af18a1b506543a721c8bac
+(same digest as [fixture_sha256] in test/test_differential.ml, ASSERTED by
+that suite's own "fixture SHA-256 matches its provenance note" test -- this
+line documents the pin for a reader who never runs the suite; that test is
+what actually enforces it. Regenerate both together, deliberately, after
+re-running the exact command below -- never by copying a mismatch's actual
+value back in, which would defeat the pin's purpose.)
+
Exact command:
cd ~/git/projects/lectio && go run ./cmd/lectio-ef-dump 2005 2050
diff --git a/test/test_differential.ml b/test/test_differential.ml
index aea1dac..dd601a1 100644
--- a/test/test_differential.ml
+++ b/test/test_differential.ml
@@ -2,11 +2,13 @@
2005-2050 -- validation layer 3 of the design spec's five (colitur CLAUDE.md
"Validation" section; layers 1-2, Types and Property, are already built and
green). lectio's own EF stream is committed as a fixture
- (test/fixtures/lectio-ef-2005-2050.txt, provenance in the sibling
- .provenance file next to it); colitur's side is recomputed fresh from this
- library on every run, through the SAME pipeline `colitur day` uses
- (Colitur_kernel.Calendar over the real data/ef/sanctoral.sexp +
- adjustments.sexp), not the compiled binary.
+ (test/fixtures/lectio-ef-2005-2050.txt, provenance -- including the
+ SHA-256 this file's own [test_fixture_checksum] asserts, so a hand-edit or
+ partial re-copy of the fixture fails loudly rather than silently becoming
+ an unlabelled snapshot -- in the sibling .provenance file next to it);
+ colitur's side is recomputed fresh from this library on every run, through
+ the SAME pipeline `colitur day` uses (Colitur_kernel.Calendar over the
+ real data/ef/sanctoral.sexp + adjustments.sexp), not the compiled binary.
*** THREE STATED LIMITS THIS COMPARATOR DOES NOT PRETEND TO EXCEED ***
@@ -108,6 +110,49 @@ let adjustments_path = "../data/ef/adjustments.sexp"
let fixture_path = "fixtures/lectio-ef-2005-2050.txt"
let allow_list_path = "../data/ef/expected-divergences.sexp"
+(* The fixture's own provenance note (test/fixtures/lectio-ef-2005-2050.provenance)
+ records this same digest, so it is discoverable by a reader who never runs
+ the suite. Recorded here too, and ASSERTED (fix round 1, finding 4): a
+ provenance note is enough to REGENERATE the fixture but says nothing about
+ whether the committed bytes still match the commit they claim to come
+ from -- an unnoticed hand-edit or partial re-copy would silently turn the
+ whole oracle into an unlabelled snapshot of whatever someone last ran.
+ Regenerate this constant (and the provenance note's copy) together,
+ deliberately, after re-running the exact command the provenance note
+ names -- never by copying the actual value back in to make a mismatch
+ pass, which would defeat the point of pinning it at all. *)
+let fixture_sha256 = "2ca3eeeda4e7a0406c4d004c1b2003fc0df671aca9af18a1b506543a721c8bac"
+
+(* Same technique tools/bootstrap_sanctoral.ml already uses for this exact
+ purpose (that file's own comment: shelling out to the system's
+ [sha256sum], not an OCaml crypto library -- Task 15's deps are frozen).
+ Unlike that tool, this avoids even the (already-permitted, per that
+ file's own comment, "already in the switch") [unix] library: [Sys.command]
+ plus a redirected-to-file capture needs nothing beyond the Stdlib every
+ dune executable already links. Depends on [sha256sum] being on PATH,
+ which every environment this suite has actually run in (Debian, per
+ CLAUDE.md) provides via coreutils; a machine without it fails this check
+ with a command-not-found exit code rather than silently skipping it. *)
+let sha256_of_file path =
+ let tmp = Filename.temp_file "colitur_differential_sha256" ".txt" in
+ Fun.protect
+ ~finally:(fun () -> try Sys.remove tmp with Sys_error _ -> ())
+ (fun () ->
+ let cmd = Printf.sprintf "sha256sum %s > %s" (Filename.quote path) (Filename.quote tmp) in
+ let rc = Sys.command cmd in
+ if rc <> 0 then Alcotest.failf "sha256sum exited %d for %s (is it on PATH?)" rc path;
+ let ic = open_in tmp in
+ let line =
+ try input_line ic
+ with End_of_file ->
+ close_in ic;
+ Alcotest.failf "sha256sum produced no output for %s" path
+ in
+ close_in ic;
+ match String.index_opt line ' ' with
+ | Some i -> String.sub line 0 i
+ | None -> Alcotest.failf "unexpected sha256sum output for %s: %S" path line)
+
let real_layer () =
let layer =
match Layer.load V.rank_of_sexp sanctoral_path with
@@ -293,7 +338,7 @@ let strip_epiphany_index slug =
(* ---------------------------------------------------------------------- *)
(* Field-diff computation: applies Layers A and B, then reports exactly *)
-(* which of the SIX substantive columns still differ (week is never *)
+(* which of the FIVE substantive columns still differ (week is never *)
(* inspected at all -- limit 3). weekday is included defensively: dates *)
(* are checked 1:1 aligned before this runs, so it should never fire, and *)
(* if it ever does that is real signal, not noise to normalise away. *)
@@ -346,18 +391,48 @@ let sunday_iclass_slugs =
let rose_sunday_slugs = [ "ef-advent-sunday-3"; "ef-lent-sunday-4" ]
+(* Fix round 1, finding 1: C1 and C6 (below) originally gated on calendar
+ date alone, with no slug/slug-family check -- unlike every other entry
+ here. The reviewer constructed the failure this leaves open: if a future
+ sanctoral regeneration made some OTHER 29-31 December celebration win the
+ day (colliding coincidentally with C6's own [Slug_f; Rank] diff shape),
+ it would be silently absorbed under "RG 91 entry 17, Nativity Octave" --
+ a citation that has nothing to do with the real cause. [subset diffs
+ [...]] alone was never enough; the SLUG that actually won must also be
+ the one each citation is about. Both lists below are exact literals (the
+ Nativity Octave's three colitur-only day slugs; the closed set of
+ offices that can legitimately observe C1's Jan 6-13 window), not
+ patterns -- a slug outside them fails through to [None] instead of being
+ absorbed. *)
+let jan_6_13_slug slug =
+ String.equal slug "ef-epiphany"
+ || String.equal slug "commemoration-of-the-baptism-of-the-lord"
+ || List.exists (fun wd -> String.equal slug ("ef-christmas-2-" ^ wd)) weekdays
+
+let nativity_octave_day_slugs =
+ [ "ef-nativity-octave-day-5"; "ef-nativity-octave-day-6"; "ef-nativity-octave-day-7" ]
+
(* [layer_c_reason l c diffs] returns the [data/ef/expected-divergences.sexp]
[id] this row-pair's remaining (post Layer A/B) diff set belongs to, or
[None] if nothing here explains it (a genuine, uncovered failure). *)
let layer_c_reason (l : row) (c : row) diffs =
let m = month_of_date l.date and d = day_of_date l.date in
if diffs = [] then None
- else if m = 1 && d >= 6 && d <= 13 && subset diffs [ Season; Colour_f; Slug_f ] then Some "C1"
+ else if
+ m = 1 && d >= 6 && d <= 13
+ && subset diffs [ Season; Colour_f; Slug_f ]
+ && (not (List.mem Slug_f diffs) || jan_6_13_slug c.slug)
+ then Some "C1"
else if List.mem c.slug sunday_iclass_slugs && diffs = [ Rank ] then Some "C2"
else if List.mem c.slug rose_sunday_slugs && subset diffs [ Rank; Colour_f ] then Some "C3"
else if advent_feria_slug c.slug && diffs = [ Rank ] then Some "C4"
else if starts_with ~prefix:"ef-lent-ember-" c.slug && subset diffs [ Slug_f; Rank ] then Some "C5"
- else if m = 12 && (d = 29 || d = 30 || d = 31) && subset diffs [ Slug_f; Rank ] then Some "C6"
+ else if
+ m = 12
+ && (d = 29 || d = 30 || d = 31)
+ && subset diffs [ Slug_f; Rank ]
+ && (not (List.mem Slug_f diffs) || List.mem c.slug nativity_octave_day_slugs)
+ then Some "C6"
else if (String.equal c.slug "matthew" || String.equal c.slug "thomas") && subset diffs [ Slug_f; Colour_f ]
then Some "C7"
else if
@@ -369,7 +444,10 @@ let layer_c_reason (l : row) (c : row) diffs =
|| String.equal c.slug "joseph-spouse-of-the-bl-virgin-mary")
&& subset diffs [ Season; Slug_f; Rank; Colour_f ]
then Some "C9"
- else if (String.equal l.date "2011-07-02" || String.equal l.date "2011-07-04") then Some "C10"
+ else if
+ (String.equal l.date "2011-07-02" || String.equal l.date "2011-07-04")
+ && subset diffs [ Slug_f; Rank; Colour_f ]
+ then Some "C10"
else None
(* ---------------------------------------------------------------------- *)
@@ -423,6 +501,16 @@ let describe_unexplained (l : row) (c : row) diffs =
(String.concat "," (List.map field_name diffs))
l.weekday l.season l.slug l.rank l.colour c.weekday c.season c.slug c.rank c.colour
+(* Fix round 1, finding 4: the fixture's own byte content must still match
+ the SHA-256 its provenance note claims (lectio commit 2386a45, recorded
+ both here and in test/fixtures/lectio-ef-2005-2050.provenance). Checked
+ before anything else reads the fixture -- a drifted fixture makes every
+ other assertion in this suite a statement about an unlabelled snapshot,
+ not about the pinned lectio commit it claims to be. *)
+let test_fixture_checksum () =
+ Alcotest.(check string) "fixture SHA-256 matches its provenance note" fixture_sha256
+ (sha256_of_file fixture_path)
+
(* Dates align 1:1 in the same order on both streams (both are one line per
civil day, 2005-01-01..2050-12-31 -- see the fixture's own provenance
note and [colitur_rows_2005_2050]'s construction). A silent misalignment
@@ -486,7 +574,8 @@ let test_layer_c_counts_match_citations () =
let suite =
( "differential (lectio, EF, 2005-2050)",
- [ Alcotest.test_case "streams are 16801 rows each, dates aligned 1:1" `Quick test_dates_align;
+ [ Alcotest.test_case "fixture SHA-256 matches its provenance note" `Quick test_fixture_checksum;
+ Alcotest.test_case "streams are 16801 rows each, dates aligned 1:1" `Quick test_dates_align;
Alcotest.test_case "every difference is normalised (A/B) or cited (C) -- none unexplained" `Quick
test_no_unexplained_differences;
Alcotest.test_case "Layer C counts match data/ef/expected-divergences.sexp exactly" `Quick