diff options
Diffstat (limited to 'internal/engine')
| -rw-r--r-- | internal/engine/apply.go | 8 | ||||
| -rw-r--r-- | internal/engine/undo_identity_test.go | 42 |
2 files changed, 50 insertions, 0 deletions
diff --git a/internal/engine/apply.go b/internal/engine/apply.go index cc87f30..ed2fbe5 100644 --- a/internal/engine/apply.go +++ b/internal/engine/apply.go @@ -496,6 +496,14 @@ func isFileAffecting(action string) bool { // overrides a real occupant that this same chain is itself about to clear. func planUndoFile(dir, file string, ents []journal.Entry, reversed map[journal.ReversedKey]int) UndoFile { uf := UndoFile{File: file, Dir: dir} + for _, en := range ents { + if en.Action == "damaged" { + // A log line of this file is cut or damaged (journal.Entries): a + // step may be missing from its chain, so none of it is reversed. + uf.Refused = fmt.Sprintf("its log is damaged (%s); a step may not be recorded", en.Detail) + return uf + } + } proj := newUndoProjection() for i := len(ents) - 1; i >= 0; i-- { en := ents[i] diff --git a/internal/engine/undo_identity_test.go b/internal/engine/undo_identity_test.go index 23e0bf9..5040ece 100644 --- a/internal/engine/undo_identity_test.go +++ b/internal/engine/undo_identity_test.go @@ -317,3 +317,45 @@ func TestApplyReportsAStepThatCouldNotBeLogged(t *testing.T) { } } } + +// TestUndoRefusesOnlyTheFileWithADamagedLine: a crash that cuts one file's +// log line refuses that file; the other files of the run are still undone +// (re-review N1). +func TestUndoRefusesOnlyTheFileWithADamagedLine(t *testing.T) { + e, run, h, logPath := appliedRun(t, map[string]map[string]string{"dl": {"a.pdf": "one", "b.pdf": "two"}}, + map[string]string{"dl": "(path \"~/dl\")\n(rule \"r\" (move \"Out\"))\n"}) + raw, err := os.ReadFile(logPath) + if err != nil { + t.Fatal(err) + } + lines := strings.Split(strings.TrimRight(string(raw), "\n"), "\n") + for i, l := range lines { + if strings.Contains(l, "\tb.pdf\t") && strings.Contains(l, "\tmove\t") { + lines[i] = l[:len(l)/2] // cut mid-write + } + } + if err := os.WriteFile(logPath, []byte(strings.Join(lines, "\n")+"\n"), 0o644); err != nil { + t.Fatal(err) + } + up, err := e.PlanUndo(run) + if err != nil { + t.Fatalf("PlanUndo refused the whole run: %v", err) + } + if f := undoFileNamed(t, up, "dl", "b.pdf"); !strings.Contains(f.Refused, "damaged") { + t.Errorf("b.pdf: Refused %q; want its damaged log named", f.Refused) + } + if f := undoFileNamed(t, up, "dl", "a.pdf"); f.Refused != "" { + t.Errorf("a.pdf refused: %q", f.Refused) + } + j, err := journal.Open(logPath) + if err != nil { + t.Fatal(err) + } + defer j.Close() + if _, err := e.ApplyUndo(context.Background(), up, j, journal.NewRunID(time.Now())); err != nil { + t.Fatal(err) + } + if b, err := os.ReadFile(filepath.Join(h, "dl", "a.pdf")); err != nil || string(b) != "one" { + t.Errorf("a.pdf not restored: %q %v", b, err) + } +} |
