diff options
Diffstat (limited to 'internal/plan/conflict_test.go')
| -rw-r--r-- | internal/plan/conflict_test.go | 54 |
1 files changed, 53 insertions, 1 deletions
diff --git a/internal/plan/conflict_test.go b/internal/plan/conflict_test.go index ba415d5..ba111f8 100644 --- a/internal/plan/conflict_test.go +++ b/internal/plan/conflict_test.go @@ -3,6 +3,7 @@ package plan import ( + "fmt" "os" "path/filepath" "testing" @@ -232,7 +233,7 @@ func TestSuffixedCapsAttempts(t *testing.T) { func TestOverwriteNeverDisplacesADirectory(t *testing.T) { dir := "/home/x/Documents/Invoices" d := fakeDisk{exists: map[string]bool{dir: true}, dirs: map[string]bool{dir: true}} - _, skip, displaces := resolveConflict(Move, config.ConflictOverwrite, "/r/Invoices", dir, d, claimed{}) + _, skip, displaces := resolveConflict(Move, config.ConflictOverwrite, "/r/Invoices", dir, d, newClaimed()) if displaces != "" || skip != "target is not a regular file" { t.Errorf("skip %q displaces %q; want skipped, nothing displaced", skip, displaces) } @@ -252,3 +253,54 @@ func TestOverwriteNeverDisplacesAnotherScannedFile(t *testing.T) { t.Errorf("Displaces %q Dst %q Skip %q; want no displacement and /r/b_1.pdf", s.Displaces, s.Dst, s.Skip) } } + +// countingDisk counts Exists calls, so a test can pin how much probing a +// plan does rather than only what it produces. +type countingDisk struct { + fakeDisk + calls int +} + +func (d *countingDisk) Exists(p string) bool { + d.calls++ + return d.fakeDisk.Exists(p) +} + +// TestSuffixedDoesNotRescanFromOne: every file renamed to one name probes +// stem_1, stem_2, ... for a free suffix. Starting each file's search at 1 +// makes N files into N^2/2 Exists calls - 4000 files were eight million of +// them - so the search continues from the highest suffix already tried for +// that name. Within one plan that is the same answer: the taken set only +// grows while a plan is built, so a suffix taken once stays taken. +func TestSuffixedDoesNotRescanFromOne(t *testing.T) { + const n = 60 + in := make([]Input, 0, n) + for i := 0; i < n; i++ { + in = append(in, Input{File: file("/r", fmt.Sprintf("f%02d.pdf", i)), Rules: []RuleMatch{ + {Name: "one", Actions: []config.Action{act(config.Move, "Work"), act(config.Rename, "same.pdf")}}}}) + } + d := &countingDisk{fakeDisk: fakeDisk{exists: map[string]bool{}}} + chains := Build("/r", in, time.Now(), d, NewClaims()) + + // Every file must still land on its own name, the lowest free one. + seen := map[string]bool{} + for _, c := range chains { + last := c.Steps[len(c.Steps)-1] + if last.Dst == "" { + t.Fatalf("%s was skipped: %q", c.File.Rel, last.Skip) + } + if seen[last.Dst] { + t.Errorf("two files planned onto %s", last.Dst) + } + seen[last.Dst] = true + } + if len(seen) != n { + t.Errorf("%d distinct destinations for %d files", len(seen), n) + } + // Quadratic probing would be about n*n/2 = 1800 here; linear is a few + // per file. The bound is loose on purpose - it must fail on n^2 and + // pass on anything sane. + if d.calls > 6*n { + t.Errorf("%d Exists calls for %d files: the suffix search is rescanning from _1", d.calls, n) + } +} |
