v0.303.0 (code): after a restart, a backup capture waits for the drive's bind (R-897)
gates / gates (push) Successful in 1m18s
gates / gates (push) Successful in 1m18s
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
package backup
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// drivePathProvider puts every app on /mnt/felhom-drives/hdd_1 (the measured R-897 case).
|
||||
type drivePathProvider struct{ unitFailProvider }
|
||||
|
||||
func (p *drivePathProvider) GetStackHDDPath(string) string { return "/mnt/felhom-drives/hdd_1" }
|
||||
|
||||
// R-897: right after a boot, a capture for an app on a drive waits until the drive's bind is LIVE. The drive reads
|
||||
// not-bound, then bound → the capture (here a capture that fails, so it is visible as a unit event) starts only after.
|
||||
// COMPANION RED-PROOF (observed): remove the driveReady call in captureAllRecoveryUnits → "captured while the drive
|
||||
// was not bound".
|
||||
func TestDriveReady_RefreshWaitsForTheBindThenCaptures(t *testing.T) {
|
||||
m, got := newUnitNotifyManager(t, []string{"calibre-web"}, map[string]bool{"calibre-web": true})
|
||||
base := m.stackProvider.(*unitFailProvider)
|
||||
m.stackProvider = &drivePathProvider{*base}
|
||||
now := time.Date(2026, 10, 7, 15, 16, 28, 0, time.UTC)
|
||||
m.startedAt, m.nowFn = now.Add(-20*time.Second), func() time.Time { return now }
|
||||
bound := false
|
||||
var asked []string
|
||||
m.driveLive = func(root string) bool { asked = append(asked, root); return bound }
|
||||
|
||||
m.captureAllRecoveryUnits(false)
|
||||
if len(*got) != 0 {
|
||||
t.Fatalf("captured while the drive was not bound: %+v", *got)
|
||||
}
|
||||
if len(asked) == 0 || asked[0] != "/mnt/felhom-drives/hdd_1" {
|
||||
t.Fatalf("the drive root asked = %v", asked)
|
||||
}
|
||||
bound = true
|
||||
m.captureAllRecoveryUnits(false)
|
||||
if len(*got) != 1 {
|
||||
t.Fatalf("after the bind the capture must run: %+v", *got)
|
||||
}
|
||||
}
|
||||
|
||||
// After the window the capture runs whatever the bind says (and says so).
|
||||
func TestDriveReady_AfterTheWindowItRunsAnyway(t *testing.T) {
|
||||
m, got := newUnitNotifyManager(t, []string{"calibre-web"}, map[string]bool{"calibre-web": true})
|
||||
base := m.stackProvider.(*unitFailProvider)
|
||||
m.stackProvider = &drivePathProvider{*base}
|
||||
now := time.Date(2026, 10, 7, 15, 30, 0, 0, time.UTC)
|
||||
m.startedAt, m.nowFn = now.Add(-DriveWaitWindow), func() time.Time { return now }
|
||||
m.driveLive = func(string) bool { return false }
|
||||
m.captureAllRecoveryUnits(false)
|
||||
if len(*got) != 1 {
|
||||
t.Fatalf("after %s the capture must run anyway: %+v", DriveWaitWindow, *got)
|
||||
}
|
||||
}
|
||||
|
||||
// A data run WAITS (polls) instead of skipping; a non-drive path and a Manager without a start time never wait.
|
||||
func TestDriveReady_DataRunPollsAndOthersNeverWait(t *testing.T) {
|
||||
m, _ := newUnitNotifyManager(t, nil, nil)
|
||||
now := time.Date(2026, 10, 7, 15, 16, 28, 0, time.UTC)
|
||||
m.startedAt, m.nowFn = now, func() time.Time { return now }
|
||||
n := 0
|
||||
m.driveLive = func(string) bool { n++; return n >= 3 }
|
||||
polls := 0
|
||||
m.driveWaitPoll = func(d time.Duration) { polls++; now = now.Add(d) }
|
||||
if !m.driveReady("/mnt/felhom-drives/hdd_1/x", true) || polls != 2 {
|
||||
t.Fatalf("data run: polls=%d", polls)
|
||||
}
|
||||
if !m.driveReady("/var/lib/felhom/system-data", false) {
|
||||
t.Fatal("the system SSD never waits")
|
||||
}
|
||||
m2, _ := newUnitNotifyManager(t, nil, nil)
|
||||
m2.driveLive = func(string) bool { return false }
|
||||
if !m2.driveReady("/mnt/felhom-drives/hdd_1", false) {
|
||||
t.Fatal("no start time → no wait")
|
||||
}
|
||||
if driveRootOf("/mnt/felhom-drives/") != "" || driveRootOf("/mnt/felhom-drives/hdd_2/a/b") != "/mnt/felhom-drives/hdd_2" {
|
||||
t.Fatal("driveRootOf")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user