mirror of
https://github.com/Twigpine/zero.git
synced 2026-11-08 01:09:46 -10:00
fix(installtxn): do not read a target we could not probe as absent
recoverWorkspace read any os.Lstat failure on the target as nothing being there, which enters the restore branch: the one branch that moves a tree, taken on the strength of a question that never got an answer. Only a not-exist error means the swap did not finish; every other probe failure leaves the phase unknown, so it is reported and the workspace is left as it was. The same distinction the reconcilers already draw between a missing file and a file that could not be read, applied to the probe that decides which branch recovery takes at all.
This commit is contained in:
2 files changed
+33
No files matched your search
@@ -211,6 +211,12 @@ func Recover(dir string, reconcile Reconciler) error {
|
||||
// recoverWorkspace resolves one attributable workspace whose backup is present.
|
||||
func recoverWorkspace(workspace string, target string, backup string, name string, reconcile Reconciler) error {
|
||||
if _, err := os.Lstat(target); err != nil {
|
||||
if !errors.Is(err, os.ErrNotExist) {
|
||||
// A target we could not probe is not a target that is absent. Reading
|
||||
// it as absent enters the restore branch, the one branch that moves a
|
||||
// tree, on the strength of a question that never got an answer.
|
||||
return fmt.Errorf("inspect install %s: %w", name, err)
|
||||
}
|
||||
// Nothing at the target, so the swap never finished and the backup is the
|
||||
// only copy there is. There is no second tree to weigh it against, and
|
||||
// asking could only produce an answer that throws it away.
|
||||
|
||||
@@ -953,3 +953,30 @@ func TestRecoverKeepsAttributionWhenRetirementFailsPartway(t *testing.T) {
|
||||
t.Fatal("the second pass went silent on a workspace still holding a backup")
|
||||
}
|
||||
}
|
||||
|
||||
// A target we could not probe is not a target that is absent. Reading a failed
|
||||
// Lstat as "nothing there" sends recovery into the restore branch, which is the
|
||||
// one branch that moves a tree, on the strength of a question it never got an
|
||||
// answer to. The recorded name here is too long for the filesystem to resolve,
|
||||
// which is the one probe failure a test can produce without a fault seam.
|
||||
func TestRecoverReportsATargetItCannotProbe(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
name := strings.Repeat("a", 300)
|
||||
workspace := plantInterruptedCommit(t, dir, "demo", name, "old")
|
||||
reconciler := &recordingReconciler{phase: PhaseCommitted}
|
||||
|
||||
err := Recover(dir, reconciler.reconcile)
|
||||
|
||||
if err == nil {
|
||||
t.Fatal("a target that could not be probed must be reported")
|
||||
}
|
||||
if strings.Contains(err.Error(), "restore interrupted install") {
|
||||
t.Fatalf("recovery tried to restore over a target it never resolved: %v", err)
|
||||
}
|
||||
if !strings.Contains(err.Error(), "inspect install") {
|
||||
t.Fatalf("error does not report the probe failure: %v", err)
|
||||
}
|
||||
if _, statErr := os.Stat(filepath.Join(workspace, "previous")); statErr != nil {
|
||||
t.Fatalf("the retained backup must stay put: %v", statErr)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user