aboutsummaryrefslogtreecommitdiff
path: root/internal/readings/readings_test.go
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-08-25 10:45:28 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-08-25 10:45:28 +0200
commitf110c338718a6eea3693fb6afb4f7fcc5b77003b (patch)
tree52b6adb382445a0762f63fb862361d7fbd8637f2 /internal/readings/readings_test.go
parentfd3eda0da228d2417b8b7f9007533e214d7a5e80 (diff)
parentb24702cc9b8d7f2e408bb92032e8f7fa2aaa836b (diff)
downloadlectio-f110c338718a6eea3693fb6afb4f7fcc5b77003b.tar.gz
lectio-f110c338718a6eea3693fb6afb4f7fcc5b77003b.zip
merge: make the calendar engine stop repeating whole-year work per day
A seven-day view resolved the year seven times. Each readings.Load stacked the calendar data layers and re-read the Bible book table -- both date-independent -- and the EF path rebuilt the year's entire transfer plan and its occupancy scan on every single day. Three logic-neutral fixes: hoist the date-independent setup out of the Days loop; memoise the EF transfer plan on (year, Selection, content hash of the merged sanctoral); and build the occupancy index once alongside it rather than scanning per candidate. EF's seven-day view goes 74ms to 16ms, its multiple over OF from ~13x to ~3x. OF is unchanged, as it never touched the EF paths. Output is byte-identical throughout, proven by SHA-256 over a 492-case sweep spanning both forms, both languages, four corpora, the Triduum, a Requiem day and the three Joseph/Annunciation collision years. The caches are keyed on content, not identity, so a user overlay invalidates them -- asserted by a test that varies the overlay and requires the answer to change, itself mutation-proved by dropping the content hash and watching it fail.
Diffstat (limited to 'internal/readings/readings_test.go')
-rw-r--r--internal/readings/readings_test.go45
1 files changed, 45 insertions, 0 deletions
diff --git a/internal/readings/readings_test.go b/internal/readings/readings_test.go
index 1674d85..c81e324 100644
--- a/internal/readings/readings_test.go
+++ b/internal/readings/readings_test.go
@@ -1,6 +1,7 @@
package readings
import (
+ "reflect"
"strings"
"testing"
@@ -136,6 +137,50 @@ func TestSundayRankIsDisplayOnly(t *testing.T) {
}
}
+// TestLoadWithAgreesWithLoad guards the fast path multi-date callers (e.g.
+// mobile.Days) use to avoid re-stacking the calendar layers and re-parsing
+// the book table once per date: LoadWith, given a cfg's own Prepared value,
+// must return exactly what Load(cfg, opts) returns, for every date and both
+// forms. This is the correctness backstop for the perf fix -- Prepare only
+// hoists WHEN the date-independent setup happens, never WHAT it computes.
+func TestLoadWithAgreesWithLoad(t *testing.T) {
+ dates := []string{
+ "2026-07-22", // ordinary weekday
+ "2028-02-29", // leap day
+ "2026-04-09", // Holy Thursday 2026
+ "2026-04-10", // Good Friday 2026
+ "2026-04-11", // Holy Saturday 2026
+ "2026-11-02", // All Souls (a Requiem day)
+ "2026-12-25", // Christmas
+ }
+ for _, lect := range []string{"new", "traditional"} {
+ cfg := config.Config{Lectionary: lect}
+ p := Prepare(cfg)
+ for _, date := range dates {
+ opts := Options{Date: date, All: true}
+ wantSecs, wantInfo, wantErr := Load(cfg, opts)
+ gotSecs, gotInfo, gotErr := LoadWith(p, cfg, opts)
+ if (wantErr == nil) != (gotErr == nil) {
+ t.Fatalf("%s %s: Load err=%v, LoadWith err=%v", lect, date, wantErr, gotErr)
+ }
+ if wantErr != nil {
+ continue
+ }
+ if gotInfo != wantInfo {
+ t.Errorf("%s %s: LoadWith info = %+v, want %+v", lect, date, gotInfo, wantInfo)
+ }
+ if len(gotSecs) != len(wantSecs) {
+ t.Fatalf("%s %s: LoadWith %d sections, want %d", lect, date, len(gotSecs), len(wantSecs))
+ }
+ for i := range wantSecs {
+ if !reflect.DeepEqual(gotSecs[i], wantSecs[i]) {
+ t.Errorf("%s %s: section %d = %+v, want %+v", lect, date, i, gotSecs[i], wantSecs[i])
+ }
+ }
+ }
+ }
+}
+
// TestLoadTraditional computes the Extraordinary Form day offline: it never
// needs the network, and yields the EF epistle+gospel with a header name.
func TestLoadTraditional(t *testing.T) {