From 9e65644f473d75ceb7e3ef67302189eeaba0f922 Mon Sep 17 00:00:00 2001 From: Lukasz Kasprzak Date: Mon, 14 Sep 2026 22:39:27 +0200 Subject: plan 10: missing and weak tests (per-step logging, trash path, fuzz oracle, w after error) --- internal/engine/property_test.go | 4 +-- internal/engine/undo_identity_test.go | 60 +++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 2 deletions(-) (limited to 'internal/engine') diff --git a/internal/engine/property_test.go b/internal/engine/property_test.go index 135c20a..8a43d7b 100644 --- a/internal/engine/property_test.go +++ b/internal/engine/property_test.go @@ -135,7 +135,7 @@ func contentCounts(snap map[string]string) map[string]int { // checkApplyUndo builds c in a sandbox, applies every chain, checks nothing // was lost, undoes the run and checks the home directory - files and -// directories - is as it was. It reports whether any file was applied. +// directories - is as it was. It reports whether any step actually ran. func checkApplyUndo(t *testing.T, c propertyCase) bool { h := sandbox(t) old := time.Date(2026, 3, 1, 12, 0, 0, 0, time.UTC) @@ -253,5 +253,5 @@ func checkApplyUndo(t *testing.T, c propertyCase) bool { } } } - return true + return res.Applied > 0 } diff --git a/internal/engine/undo_identity_test.go b/internal/engine/undo_identity_test.go index 7de4eb9..f9d6307 100644 --- a/internal/engine/undo_identity_test.go +++ b/internal/engine/undo_identity_test.go @@ -432,3 +432,63 @@ func TestUndoDoesNotOfferOnlyADirectoryRemoval(t *testing.T) { t.Errorf("after a complete undo, still offered: %+v", again.Files) } } + +// TestApplyLogsEachStepAsItCompletes: the first step's log entry is written +// before the second step runs - observed from the clock the log asks for +// each entry's time - so a run killed mid-chain leaves what it did undoable +// (review M9). +func TestApplyLogsEachStepAsItCompletes(t *testing.T) { + h := sandbox(t) + p := filepath.Join(h, "dl", "a.pdf") + os.MkdirAll(filepath.Dir(p), 0o755) + os.WriteFile(p, []byte("one"), 0o644) + old := time.Now().Add(-2 * time.Hour) + os.Chtimes(p, old, old) + main := writeConfig(t, h, `(include "dl")`, map[string]string{"dl": "(path \"~/dl\")\n(rule \"r\" (rename \"r-{name}\") (move \"Out\"))\n"}) + e, errs := Load(main) + if len(errs) > 0 { + t.Fatal(errs) + } + dp, err := e.Plan(context.Background(), e.Dirs[0], plan.NewClaims()) + if err != nil { + t.Fatal(err) + } + j, err := journal.Open(filepath.Join(h, "state", "krino.log")) + if err != nil { + t.Fatal(err) + } + defer j.Close() + calls := 0 + e.Now = func() time.Time { + calls++ + if calls == 2 { // the rename's own entry is about to be written + if _, err := os.Lstat(filepath.Join(h, "dl", "Out", "r-a.pdf")); err == nil { + t.Error("the move had already run when the rename was logged: steps are logged after the whole chain") + } + } + return time.Now() + } + if _, err := e.Apply(context.Background(), dp, map[string]bool{"a.pdf": true}, j, "R"); err != nil { + t.Fatal(err) + } +} + +// TestUndoRefusesATrashEntryRecordedForAnotherPath: a trash entry with the +// size and mtime the run logged, whose trashinfo now names another original +// path, belongs to another file and is refused (review M2). +func TestUndoRefusesATrashEntryRecordedForAnotherPath(t *testing.T) { + e, run, h, _ := appliedRun(t, map[string]map[string]string{"dl": {"a.pdf": "one"}}, + map[string]string{"dl": "(path \"~/dl\")\n(rule \"r\" (delete))\n"}) + info := filepath.Join(trash.Dir(), "info", "a.pdf.trashinfo") + body := "[Trash Info]\nPath=" + filepath.Join(h, "elsewhere", "a.pdf") + "\nDeletionDate=2026-09-14T10:00:00\n" + if err := os.WriteFile(info, []byte(body), 0o600); err != nil { + t.Fatal(err) + } + up, err := e.PlanUndo(run) + if err != nil { + t.Fatal(err) + } + if f := undoFileNamed(t, up, "dl", "a.pdf"); !strings.Contains(f.Refused, "belongs to another file") { + t.Errorf("Refused = %q; want the trash entry named as another file's", f.Refused) + } +} -- cgit v1.3