aboutsummaryrefslogtreecommitdiff
path: root/internal/apply/swap_test.go
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-09-14 21:26:12 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-09-14 21:26:12 +0200
commit3bfafbc8664a2a1ba8efc3f64376ff63c3dc11b9 (patch)
tree1b2de6bd79c7c0225a2f3e4a0fccc51c3cc627ba /internal/apply/swap_test.go
parent8c6afca96a9f0af41c5a9d05beb7f1dedb994740 (diff)
downloadkrino-3bfafbc8664a2a1ba8efc3f64376ff63c3dc11b9.tar.gz
krino-3bfafbc8664a2a1ba8efc3f64376ff63c3dc11b9.zip
plan 9: apply logs each step as it completes and stops a chain that landed elsewhere
Diffstat (limited to 'internal/apply/swap_test.go')
-rw-r--r--internal/apply/swap_test.go86
1 files changed, 86 insertions, 0 deletions
diff --git a/internal/apply/swap_test.go b/internal/apply/swap_test.go
index fe7542e..c185686 100644
--- a/internal/apply/swap_test.go
+++ b/internal/apply/swap_test.go
@@ -99,3 +99,89 @@ func TestChainFollowsItsOwnFile(t *testing.T) {
}
}
}
+
+// TestChainStopsWhenAStepLandsElsewhere: a move that found its planned name
+// taken at apply time lands at a free name, and every later step - planned
+// against the name it did not get - is skipped rather than acting on
+// whatever is at the planned path (review M3).
+func TestChainStopsWhenAStepLandsElsewhere(t *testing.T) {
+ root := t.TempDir()
+ p := filepath.Join(root, "a.pdf")
+ plannedPath := filepath.Join(root, "W", "a.pdf")
+ c := planned(t, root, "a.pdf", "planned-file",
+ plan.Step{Kind: plan.Move, Src: p, Dst: plannedPath},
+ plan.Step{Kind: plan.DeletePermanent, Src: plannedPath},
+ )
+ if err := os.MkdirAll(filepath.Dir(plannedPath), 0o755); err != nil {
+ t.Fatal(err)
+ }
+ if err := os.WriteFile(plannedPath, []byte("OTHER--FILE!"), 0o644); err != nil {
+ t.Fatal(err)
+ }
+ if err := os.Chtimes(plannedPath, c.File.ModTime, c.File.ModTime); err != nil {
+ t.Fatal(err)
+ }
+ res := Chain(c)
+ if res[0].Status != "ok" || res[1].Status != "skipped" || !strings.Contains(res[1].Detail, "not the planned") {
+ t.Fatalf("steps = %s %q, %s %q; want ok, then skipped naming the planned path", res[0].Status, res[0].Dst, res[1].Status, res[1].Detail)
+ }
+ if b, err := os.ReadFile(plannedPath); err != nil || string(b) != "OTHER--FILE!" {
+ t.Errorf("the file at the planned path was acted on: %q %v", b, err)
+ }
+}
+
+// TestChainLoggedReportsEachStepBeforeTheNext: done is called for each step
+// as soon as it has run - the file is at the first step's destination and
+// the second step has not happened yet - so a caller logging from done
+// never has a completed step missing from the log (review M9).
+func TestChainLoggedReportsEachStepBeforeTheNext(t *testing.T) {
+ root := t.TempDir()
+ p := filepath.Join(root, "a.pdf")
+ moved := filepath.Join(root, "W", "a.pdf")
+ renamed := filepath.Join(root, "W", "b.pdf")
+ c := planned(t, root, "a.pdf", "body",
+ plan.Step{Kind: plan.Move, Src: p, Dst: moved},
+ plan.Step{Kind: plan.Rename, Src: moved, Dst: renamed},
+ )
+ var calls []int
+ res, err := ChainLogged(c, func(i int, sr StepResult) error {
+ calls = append(calls, i)
+ if i == 0 {
+ if _, err := os.Lstat(moved); err != nil {
+ t.Errorf("at done(0) the move has not happened: %v", err)
+ }
+ if _, err := os.Lstat(renamed); !os.IsNotExist(err) {
+ t.Errorf("at done(0) the rename already happened")
+ }
+ }
+ return nil
+ })
+ if err != nil || len(calls) != 2 || res[1].Status != "ok" {
+ t.Fatalf("calls %v, err %v, results %+v", calls, err, res)
+ }
+}
+
+// TestChainRefusesToDisplaceANonRegularTarget: the file overwrite was
+// planned to replace is re-checked before it is trashed; a directory now in
+// its place is left alone (review M4).
+func TestChainRefusesToDisplaceANonRegularTarget(t *testing.T) {
+ root := t.TempDir()
+ t.Setenv("HOME", root)
+ t.Setenv("XDG_DATA_HOME", filepath.Join(root, "share"))
+ t.Setenv("XDG_STATE_HOME", "")
+ t.Setenv("XDG_CONFIG_HOME", "")
+ t.Setenv("XDG_CACHE_HOME", "")
+ p := filepath.Join(root, "a.pdf")
+ target := filepath.Join(root, "W", "a.pdf")
+ c := planned(t, root, "a.pdf", "body", plan.Step{Kind: plan.Move, Src: p, Dst: target, Displaces: target})
+ if err := os.MkdirAll(filepath.Join(target, "inside"), 0o755); err != nil {
+ t.Fatal(err)
+ }
+ res := Chain(c)
+ if res[0].Status != "failed" {
+ t.Fatalf("step = %s %q; want failed", res[0].Status, res[0].Detail)
+ }
+ if _, err := os.Stat(filepath.Join(target, "inside")); err != nil {
+ t.Errorf("the directory was displaced: %v", err)
+ }
+}