package index
import (
"os"
"path/filepath"
"strings"
"testing"
)
// Close waits for the rewarms it started.
//
// A fold spawns one recompute per branch that was warm, off the request
// path. Nothing owned those goroutines: Close returned while they were
// still querying, so the handle went away underneath them. In a test
// that surfaced as SQLite recreating a -wal file inside a directory
// t.TempDir was removing — "directory not empty", failing a test whose
// assertions had all passed. In a running forge it is a query against a
// closed database at shutdown or when a repository is unregistered.
func TestCloseWaitsForTheRewarmsItStarted(t *testing.T) {
dir := t.TempDir()
ix, err := Open(filepath.Join(dir, "i.db"))
if err != nil {
t.Fatal(err)
}
ciu(t, ix, "a1", "dan", "trunk", "2026-06-01T09:00:00")
if _, err := ix.ActivityDays(""); err != nil {
t.Fatal(err)
}
// A fold invalidates and rewarms what was warm.
ciu(t, ix, "a2", "dan", "trunk", "2026-06-02T09:00:00")
ix.invalidateCounts()
if err := ix.Close(); err != nil {
t.Fatalf("close: %v", err)
}
// Nothing is still running, so nothing can put a file back.
ix.warmMu.Lock()
inflight := len(ix.warming)
ix.warmMu.Unlock()
if inflight != 0 {
t.Errorf("FAIL: Close returned with %d rewarm(s) still in flight", inflight)
}
// The directory is quiet: removing it must not race a writer.
if err := os.RemoveAll(dir); err != nil {
t.Fatalf("FAIL: the store is still being written after Close: %v", err)
}
if _, err := os.Stat(dir); !os.IsNotExist(err) {
t.Errorf("FAIL: %s survived removal, so something recreated it: %v", dir, err)
}
}
// A closing index starts no new rewarm. The next reader pays a cold
// read — the same cost as never having warmed it, and far cheaper than
// a query against a database that is going away.
func TestAClosingIndexStartsNoRewarm(t *testing.T) {
ix, err := Open(filepath.Join(t.TempDir(), "i.db"))
if err != nil {
t.Fatal(err)
}
ciu(t, ix, "a1", "dan", "trunk", "2026-06-01T09:00:00")
if _, err := ix.ActivityDays(""); err != nil {
t.Fatal(err)
}
if err := ix.Close(); err != nil {
t.Fatal(err)
}
// After Close, a fold's rewarm request is refused rather than run.
ix.rewarmActivityDays([]string{"", "trunk"})
ix.warmMu.Lock()
started := len(ix.warming)
ix.warmMu.Unlock()
if started != 0 {
t.Errorf("FAIL: %d rewarm(s) started on a closed index", started)
}
}
// Close is idempotent enough to be called twice without hanging: the
// second call waits on a WaitGroup nobody will add to.
func TestCloseTwiceDoesNotHang(t *testing.T) {
ix, err := Open(filepath.Join(t.TempDir(), "i.db"))
if err != nil {
t.Fatal(err)
}
if err := ix.Close(); err != nil {
t.Fatal(err)
}
if err := ix.Close(); err != nil && !strings.Contains(err.Error(), "closed") {
t.Logf("second close: %v", err)
}
}