56ef1d6655
gates / gates (push) Successful in 20s
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
64 lines
2.4 KiB
Go
64 lines
2.4 KiB
Go
package backup
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
|
|
"gitea.dooplex.hu/admin/felhom-agent/internal/reconcile"
|
|
)
|
|
|
|
// R-874 (v0.145.0). THE MEASURED SHAPE (Part F spike, Tester 2): power-on sessions of ~1.5 h and ~5 min against a
|
|
// 6 h evaluation ticker that restarts at every start — no restore-test ever evaluated. Now the first evaluation runs
|
|
// FirstEval after start.
|
|
// COMPANION RED-PROOF: drop the first-evaluation timer in Run (back to the bare ticker) → "no evaluation within".
|
|
func TestR874_FirstEvaluationAfterStart(t *testing.T) {
|
|
var picks int32
|
|
s := NewScheduler(SchedulerOptions{
|
|
Runner: &fakeRTRunner{res: reconcile.RestoreTestResult{Pass: true, Verified: "boot+running"}},
|
|
Pick: func(context.Context) (string, error) {
|
|
atomic.AddInt32(&picks, 1)
|
|
return fmt.Sprintf("local:backup/vzdump-lxc-9201-%d.tar.zst", atomic.LoadInt32(&picks)), nil
|
|
},
|
|
Store: NewStore(), Spec: (&specSpy{}).build,
|
|
Cadence: 6 * time.Hour, FirstEval: 30 * time.Millisecond, Logger: quiet(),
|
|
})
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
done := make(chan struct{})
|
|
go func() { _ = s.Run(ctx); close(done) }()
|
|
deadline := time.Now().Add(3 * time.Second)
|
|
for atomic.LoadInt32(&picks) == 0 && time.Now().Before(deadline) {
|
|
time.Sleep(10 * time.Millisecond)
|
|
}
|
|
cancel()
|
|
<-done
|
|
if atomic.LoadInt32(&picks) == 0 {
|
|
t.Fatal("no evaluation within 3 s of start (FirstEval 30 ms) — a box with short sessions never gets a restore-test")
|
|
}
|
|
}
|
|
|
|
// The earned restraint stays: an agent that restarts before FirstEval never evaluates (a crash loop does not
|
|
// hammer a failing tier).
|
|
func TestR874_CrashLoopNeverEvaluates(t *testing.T) {
|
|
var picks int32
|
|
for i := 0; i < 5; i++ { // five quick "restarts"
|
|
s := NewScheduler(SchedulerOptions{
|
|
Runner: &fakeRTRunner{res: reconcile.RestoreTestResult{Pass: true}},
|
|
Pick: func(context.Context) (string, error) { atomic.AddInt32(&picks, 1); return "x", nil },
|
|
Store: NewStore(), Spec: (&specSpy{}).build,
|
|
Cadence: 6 * time.Hour, FirstEval: 200 * time.Millisecond, Logger: quiet(),
|
|
})
|
|
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Millisecond)
|
|
_ = s.Run(ctx)
|
|
cancel()
|
|
}
|
|
if n := atomic.LoadInt32(&picks); n != 0 {
|
|
t.Fatalf("a restart before FirstEval evaluated %d time(s)", n)
|
|
}
|
|
if DefaultFirstEval != 30*time.Minute {
|
|
t.Fatalf("DefaultFirstEval = %s, the documented 30 min", DefaultFirstEval)
|
|
}
|
|
}
|