Leave removed copies out of report and trees (closes #173) (#184)
check / check (push) Canceled after 0s
check / check (push) Canceled after 0s
This commit was merged in pull request #184.
This commit is contained in:
1 parent
a27d651532
commit
f0b679e9fa
8 files changed
+466
-96
No files matched your search
@@ -72,15 +72,37 @@ type dupeGroup struct {
|
||||
paths []string
|
||||
}
|
||||
|
||||
// everyPathExists stands in for pathExists where a test's records name
|
||||
// made-up paths that are not on disk.
|
||||
func everyPathExists(string) bool { return true }
|
||||
|
||||
// recsOnDisk returns a new directory and recs with each path moved
|
||||
// under it, after creating an empty file at each moved path: report and
|
||||
// trees then find every path still on disk.
|
||||
func recsOnDisk(t *testing.T, recs []scanRec) (string, []scanRec) {
|
||||
t.Helper()
|
||||
|
||||
dir := realTempDir(t)
|
||||
moved := make([]scanRec, len(recs))
|
||||
|
||||
for i, r := range recs {
|
||||
r.path = writeFile(t, dir, r.path, nil)
|
||||
moved[i] = r
|
||||
}
|
||||
|
||||
return dir, moved
|
||||
}
|
||||
|
||||
// dupeGroups returns the duplicate groups report reads from db, in
|
||||
// report order, from the rows it writes: a group's rows come together,
|
||||
// each naming the group's first path.
|
||||
// each naming the group's first path. Every path counts as still on
|
||||
// disk.
|
||||
func dupeGroups(t *testing.T, db *sql.DB) []dupeGroup {
|
||||
t.Helper()
|
||||
|
||||
var out bytes.Buffer
|
||||
|
||||
_, err := readReport(t.Context(), db, "", &out, nil)
|
||||
_, err := readReport(t.Context(), db, "", &out, everyPathExists, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -127,7 +149,8 @@ func dupeGroupsOf(t *testing.T, recs []scanRec) []dupeGroup {
|
||||
}
|
||||
|
||||
func TestRunReportEscapesPaths(t *testing.T) {
|
||||
t.Setenv(databaseEnv, seedDatabase(t, awkwardPairRecs()))
|
||||
dir, recs := recsOnDisk(t, awkwardPairRecs())
|
||||
t.Setenv(databaseEnv, seedDatabase(t, recs))
|
||||
|
||||
var stdout, stderr bytes.Buffer
|
||||
|
||||
@@ -138,7 +161,7 @@ func TestRunReportEscapesPaths(t *testing.T) {
|
||||
}
|
||||
|
||||
want := "first\tdupe\tsize\n" +
|
||||
`/d/\tone\ntwo\rthree\\four/f` + "\t/d/A/f\t5\n"
|
||||
dir + `/d/\tone\ntwo\rthree\\four/f` + "\t" + dir + "/d/A/f\t5\n"
|
||||
if got := stdout.String(); got != want {
|
||||
t.Errorf("stdout = %q, want %q", got, want)
|
||||
}
|
||||
@@ -172,7 +195,8 @@ func TestReportStdoutFailsWhileReading(t *testing.T) {
|
||||
// Counts the records read, and draws nothing.
|
||||
prog := &progress{now: time.Now}
|
||||
|
||||
_, err = readReport(t.Context(), db, "", failingWriter{}, prog)
|
||||
_, err = readReport(t.Context(), db, "", failingWriter{},
|
||||
everyPathExists, prog)
|
||||
if !errors.Is(err, errWriteFailed) ||
|
||||
!strings.HasPrefix(err.Error(), "write stdout: ") {
|
||||
t.Errorf("error = %v, want write stdout: %v", err, errWriteFailed)
|
||||
@@ -203,7 +227,8 @@ const smokeReport = "first\tdupe\tsize\n" +
|
||||
// it ends; read counts the 6 records before it reads them. The summary
|
||||
// comes last, and stdout holds the report alone.
|
||||
func TestRunReportProgressNamesEachPhase(t *testing.T) {
|
||||
path := seedDatabase(t, smokeTreeRecs())
|
||||
dir, recs := recsOnDisk(t, smokeTreeRecs())
|
||||
path := seedDatabase(t, recs)
|
||||
t.Setenv(databaseEnv, path)
|
||||
|
||||
stderr := captureStderr(t)
|
||||
@@ -216,8 +241,9 @@ func TestRunReportProgressNamesEachPhase(t *testing.T) {
|
||||
code, exitOK, stderr())
|
||||
}
|
||||
|
||||
if got := stdout.String(); got != smokeReport {
|
||||
t.Errorf("stdout = %q, want %q", got, smokeReport)
|
||||
report := strings.ReplaceAll(smokeReport, "/d/", dir+"/d/")
|
||||
if got := stdout.String(); got != report {
|
||||
t.Errorf("stdout = %q, want %q", got, report)
|
||||
}
|
||||
|
||||
// How each line starts; the rate at the end of a phase varies.
|
||||
@@ -266,7 +292,7 @@ func TestReadReportOnTerminal(t *testing.T) {
|
||||
waitingFor: countingRecords,
|
||||
}, terminalWidth, time.Hour, time.Now)
|
||||
|
||||
_, err = readReport(t.Context(), db, "", os.Stderr, p)
|
||||
_, err = readReport(t.Context(), db, "", os.Stderr, everyPathExists, p)
|
||||
|
||||
p.finish()
|
||||
|
||||
@@ -301,6 +327,8 @@ func TestRunReportsIgnoreInsertionOrder(t *testing.T) {
|
||||
scanRec{size: 50, head: a, tail: a, content: a, path: "/x/1"},
|
||||
)
|
||||
|
||||
_, recs = recsOnDisk(t, recs)
|
||||
|
||||
reversed := slices.Clone(recs)
|
||||
slices.Reverse(reversed)
|
||||
|
||||
@@ -455,6 +483,119 @@ func TestDupeGroupsLeavesOutEmptyFiles(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestReadReportChecksOnlyPossibleDuplicates checks that report asks
|
||||
// whether a path is on disk only when its signature has two or more
|
||||
// records, and never for an empty file.
|
||||
func TestReadReportChecksOnlyPossibleDuplicates(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
a, u, e := testHash("a"), testHash("u"), sha256.Sum256(nil)
|
||||
recs := []scanRec{
|
||||
{size: 9, head: a, tail: a, content: a, path: "/copy/1"},
|
||||
{size: 9, head: a, tail: a, content: a, path: "/copy/2"},
|
||||
{size: 9, head: a, tail: a, content: a, path: "/copy/3"},
|
||||
{size: 9, head: u, tail: u, content: u, path: "/lonely"},
|
||||
{size: 0, head: e, tail: e, content: e, path: "/empty1"},
|
||||
{size: 0, head: e, tail: e, content: e, path: "/empty2"},
|
||||
}
|
||||
|
||||
db := openTestDB(t)
|
||||
|
||||
err := applyChanges(t.Context(), db, recs, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var checked []string
|
||||
|
||||
exists := func(path string) bool {
|
||||
checked = append(checked, path)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
_, err = readReport(t.Context(), db, "", io.Discard, exists, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
slices.Sort(checked)
|
||||
|
||||
want := []string{"/copy/1", "/copy/2", "/copy/3"}
|
||||
if !slices.Equal(checked, want) {
|
||||
t.Errorf("paths checked on disk = %q, want %q", checked, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestReadReportDropsGroupLeftWithOnePath checks that a group of two
|
||||
// paths, one of them gone, is no duplicate group. If kept, it would
|
||||
// write no row, but the summary line would count it.
|
||||
func TestReadReportDropsGroupLeftWithOnePath(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
a := testHash("a")
|
||||
recs := []scanRec{
|
||||
{size: 9, head: a, tail: a, content: a, path: "/a/kept"},
|
||||
{size: 9, head: a, tail: a, content: a, path: "/a/gone"},
|
||||
}
|
||||
|
||||
db := openTestDB(t)
|
||||
|
||||
err := applyChanges(t.Context(), db, recs, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
exists := func(path string) bool { return path != "/a/gone" }
|
||||
|
||||
report, err := readReport(t.Context(), db, "", io.Discard, exists, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if report.dupeGroups != 0 {
|
||||
t.Errorf("duplicate groups = %d, want 0", report.dupeGroups)
|
||||
}
|
||||
}
|
||||
|
||||
// TestReadReportKeepsGroupLeftWithTwoPaths checks that a group of three
|
||||
// paths, one of them gone, is a duplicate group of the other two.
|
||||
func TestReadReportKeepsGroupLeftWithTwoPaths(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
a := testHash("a")
|
||||
recs := []scanRec{
|
||||
{size: 9, head: a, tail: a, content: a, path: "/b/kept1"},
|
||||
{size: 9, head: a, tail: a, content: a, path: "/b/gone"},
|
||||
{size: 9, head: a, tail: a, content: a, path: "/b/kept2"},
|
||||
}
|
||||
|
||||
db := openTestDB(t)
|
||||
|
||||
err := applyChanges(t.Context(), db, recs, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
exists := func(path string) bool { return path != "/b/gone" }
|
||||
|
||||
var out bytes.Buffer
|
||||
|
||||
report, err := readReport(t.Context(), db, "", &out, exists, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if report.dupeGroups != 1 {
|
||||
t.Errorf("duplicate groups = %d, want 1", report.dupeGroups)
|
||||
}
|
||||
|
||||
want := "first\tdupe\tsize\n/b/kept1\t/b/kept2\t9\n"
|
||||
if got := out.String(); got != want {
|
||||
t.Errorf("stdout = %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDupeGroupsContentSeparates(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
Reference in new issue
Block a user