1453cfc69b
gates / gates (push) Failing after 50s
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
56 lines
2.5 KiB
Go
56 lines
2.5 KiB
Go
package backup
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// R-104: a restic lock that survives BOTH self-heal layers was reported as „ismeretlen okból" —
|
|
// the class had no lock case, and the lock text lives in restic's OUTPUT, never in the exit error,
|
|
// so the text classifier could not have seen it anyway. The consequence asserted, through the real
|
|
// resticStep with a scripted runner: the failure the run would report is classed Locked, and the
|
|
// message names the lock in both languages instead of admitting an unknown cause.
|
|
func TestR104_SurvivingLockIsNamed(t *testing.T) {
|
|
m, _ := newOffboxManager(t)
|
|
locked := []byte("Fatal: unable to create lock in backend: repository is already locked exclusively by PID 77 on felhom-controller\n")
|
|
calls := 0
|
|
m.SetOffboxRunner(func(_ context.Context, _ []string, args ...string) ([]byte, error) {
|
|
calls++
|
|
if contains(args, "unlock") {
|
|
return nil, nil // the remove-all "succeeds", and the lock is still there on the retry
|
|
}
|
|
return locked, fmt.Errorf("exit status 1")
|
|
})
|
|
_, err := m.resticStep(context.Background(), nil, []string{"-r", "repo"}, "backup:app", "backup", "/x")
|
|
if err == nil {
|
|
t.Fatal("setup: the scripted lock did not fail the step")
|
|
}
|
|
if calls != 3 {
|
|
t.Errorf("setup: want step + unlock --remove-all + one retry (3 calls), got %d", calls)
|
|
}
|
|
runErr := fmt.Errorf("offbox backup app: %w", err) // exactly how the run wraps a step failure
|
|
if got := ClassifyOffsiteFailure(runErr); got != OffsiteFailLocked {
|
|
t.Errorf("R-104: a lock that survived the self-heal is classed %q, want %q", got, OffsiteFailLocked)
|
|
}
|
|
hu := offsiteFailureMessage(diagTarget(), runErr, time.Minute, "hu")
|
|
if strings.Contains(hu, "ismeretlen okb") || !strings.Contains(hu, "zárva hagyta") {
|
|
t.Errorf("R-104: the Hungarian message does not name the lock: %q", hu)
|
|
}
|
|
en := offsiteFailureMessage(diagTarget(), runErr, time.Minute, "en")
|
|
if strings.Contains(en, "unknown reason") || !strings.Contains(en, "still locked") {
|
|
t.Errorf("R-104: the English message does not name the lock: %q", en)
|
|
}
|
|
|
|
// The text path: an error that carries restic's own output is named too.
|
|
if got := ClassifyOffsiteFailure(fmt.Errorf("restic: %s", locked)); got != OffsiteFailLocked {
|
|
t.Errorf("R-104: restic's lock text is classed %q, want %q", got, OffsiteFailLocked)
|
|
}
|
|
// Negative control: an unrelated failure is still honest about being unknown.
|
|
if got := ClassifyOffsiteFailure(fmt.Errorf("something entirely new")); got != OffsiteFailUnknown {
|
|
t.Errorf("an unrelated failure became %q", got)
|
|
}
|
|
}
|