diff options
| author | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-09-17 14:03:44 +0200 |
|---|---|---|
| committer | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-09-17 14:03:44 +0200 |
| commit | 33d771438f1da363af7c9ff2d5f4c368c326e84d (patch) | |
| tree | 32ecf16f57dcbd0edf762561dda602f37c38b657 /internal/engine/session_test.go | |
| parent | a65e8a0587d3e5c971bef3062dd0d8a3b5cb53b7 (diff) | |
| download | krino-33d771438f1da363af7c9ff2d5f4c368c326e84d.tar.gz krino-33d771438f1da363af7c9ff2d5f4c368c326e84d.zip | |
{now} is the start of the run, as the spec always said
The clock was read once per directory, so a run of several directories
stamped several different times - and a review that took a few seconds
could put one run's files into two folders, or at midnight two dates.
The spec (§7.3) says "the start of the run"; krino.conf(5) documented
the behaviour rather than the intent, so the two contradicted each
other.
The session now carries the run's clock and every directory plans with
it. Engine.Plan keeps its meaning for a caller with no session of its
own; Session.Plan passes the run's own start.
Diffstat (limited to 'internal/engine/session_test.go')
| -rw-r--r-- | internal/engine/session_test.go | 53 |
1 files changed, 53 insertions, 0 deletions
diff --git a/internal/engine/session_test.go b/internal/engine/session_test.go index 5cab274..750fa04 100644 --- a/internal/engine/session_test.go +++ b/internal/engine/session_test.go @@ -270,3 +270,56 @@ func TestSessionDropsUnappliedClaims(t *testing.T) { t.Errorf("beta planned %q; the declined destination should be free", got) } } + +// TestNowIsTheStartOfTheRun: {now:FMT} is documented as "the start of the +// run" (spec §7.3), and the plan a user approves must not change under +// them. Reading the clock once per directory put one run's files into two +// folders when the review took a moment - across midnight, two dates. +func TestNowIsTheStartOfTheRun(t *testing.T) { + h := sandbox(t) + dl := filepath.Join(h, "dl") + old := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC) + for _, n := range []string{"a.pdf", "b.pdf"} { + p := filepath.Join(dl, n) + if err := os.MkdirAll(filepath.Dir(p), 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(p, []byte("x"), 0o644); err != nil { + t.Fatal(err) + } + if err := os.Chtimes(p, old, old); err != nil { + t.Fatal(err) + } + } + conf := "(path \"~/dl\")\n(min-age 0s)\n(rule \"stamp\" (move \"Out/{now:%H%M%S}\"))\n" + main := writeConfig(t, h, `(include "dl")`, map[string]string{"dl": conf}) + e, errs := Load(main) + if len(errs) > 0 { + t.Fatal(errs) + } + // A clock that moves on every reading, as a review pause does. + tick := time.Date(2026, 9, 17, 23, 59, 59, 0, time.UTC) + e.Now = func() time.Time { + tick = tick.Add(3 * time.Second) + return tick + } + + sess, err := e.NewSession(true) + if err != nil { + t.Fatal(err) + } + defer sess.Close() + first, err := sess.Plan(context.Background(), e.Dirs[0]) + if err != nil { + t.Fatal(err) + } + second, err := sess.Plan(context.Background(), e.Dirs[0]) + if err != nil { + t.Fatal(err) + } + a := filepath.Dir(first.Chains[0].Steps[0].Dst) + b := filepath.Dir(second.Chains[0].Steps[0].Dst) + if a != b { + t.Errorf("one run stamped two different folders: %s and %s", a, b) + } +} |
