archiver: test backup summary calculation

This commit is contained in:
Michael Eischer 2024-02-23 21:46:39 +01:00
parent b6520038fd
commit 681395955e
2 changed files with 108 additions and 43 deletions

View file

@ -41,12 +41,14 @@ type ItemStats struct {
TreeSizeInRepo uint64 // sum of the bytes added to the repo (including compression and crypto overhead) TreeSizeInRepo uint64 // sum of the bytes added to the repo (including compression and crypto overhead)
} }
type Summary struct { type ChangeStats struct {
Files, Dirs struct {
New uint New uint
Changed uint Changed uint
Unchanged uint Unchanged uint
} }
type Summary struct {
Files, Dirs ChangeStats
ProcessedBytes uint64 ProcessedBytes uint64
ItemStats ItemStats
} }

View file

@ -986,9 +986,9 @@ func TestArchiverSaveDirIncremental(t *testing.T) {
// bothZeroOrNeither fails the test if only one of exp, act is zero. // bothZeroOrNeither fails the test if only one of exp, act is zero.
func bothZeroOrNeither(tb testing.TB, exp, act uint64) { func bothZeroOrNeither(tb testing.TB, exp, act uint64) {
tb.Helper()
if (exp == 0 && act != 0) || (exp != 0 && act == 0) { if (exp == 0 && act != 0) || (exp != 0 && act == 0) {
_, file, line, _ := runtime.Caller(1) restictest.Equals(tb, exp, act)
tb.Fatalf("\033[31m%s:%d:\n\n\texp: %#v\n\n\tgot: %#v\033[39m\n\n", filepath.Base(file), line, exp, act)
} }
} }
@ -1008,7 +1008,7 @@ func TestArchiverSaveTree(t *testing.T) {
prepare func(t testing.TB) prepare func(t testing.TB)
targets []string targets []string
want TestDir want TestDir
stat ItemStats stat Summary
}{ }{
{ {
src: TestDir{ src: TestDir{
@ -1018,7 +1018,12 @@ func TestArchiverSaveTree(t *testing.T) {
want: TestDir{ want: TestDir{
"targetfile": TestFile{Content: string("foobar")}, "targetfile": TestFile{Content: string("foobar")},
}, },
stat: ItemStats{1, 6, 32 + 6, 0, 0, 0}, stat: Summary{
ItemStats: ItemStats{1, 6, 32 + 6, 0, 0, 0},
ProcessedBytes: 6,
Files: ChangeStats{1, 0, 0},
Dirs: ChangeStats{0, 0, 0},
},
}, },
{ {
src: TestDir{ src: TestDir{
@ -1030,7 +1035,12 @@ func TestArchiverSaveTree(t *testing.T) {
"targetfile": TestFile{Content: string("foobar")}, "targetfile": TestFile{Content: string("foobar")},
"filesymlink": TestSymlink{Target: "targetfile"}, "filesymlink": TestSymlink{Target: "targetfile"},
}, },
stat: ItemStats{1, 6, 32 + 6, 0, 0, 0}, stat: Summary{
ItemStats: ItemStats{1, 6, 32 + 6, 0, 0, 0},
ProcessedBytes: 6,
Files: ChangeStats{1, 0, 0},
Dirs: ChangeStats{0, 0, 0},
},
}, },
{ {
src: TestDir{ src: TestDir{
@ -1050,7 +1060,12 @@ func TestArchiverSaveTree(t *testing.T) {
"symlink": TestSymlink{Target: "subdir"}, "symlink": TestSymlink{Target: "subdir"},
}, },
}, },
stat: ItemStats{0, 0, 0, 1, 0x154, 0x16a}, stat: Summary{
ItemStats: ItemStats{0, 0, 0, 1, 0x154, 0x16a},
ProcessedBytes: 0,
Files: ChangeStats{0, 0, 0},
Dirs: ChangeStats{1, 0, 0},
},
}, },
{ {
src: TestDir{ src: TestDir{
@ -1074,7 +1089,12 @@ func TestArchiverSaveTree(t *testing.T) {
}, },
}, },
}, },
stat: ItemStats{1, 6, 32 + 6, 3, 0x47f, 0x4c1}, stat: Summary{
ItemStats: ItemStats{1, 6, 32 + 6, 3, 0x47f, 0x4c1},
ProcessedBytes: 6,
Files: ChangeStats{1, 0, 0},
Dirs: ChangeStats{3, 0, 0},
},
}, },
} }
@ -1086,14 +1106,6 @@ func TestArchiverSaveTree(t *testing.T) {
arch := New(repo, testFS, Options{}) arch := New(repo, testFS, Options{})
var stat ItemStats
lock := &sync.Mutex{}
arch.CompleteItem = func(item string, previous, current *restic.Node, s ItemStats, d time.Duration) {
lock.Lock()
defer lock.Unlock()
stat.Add(s)
}
wg, ctx := errgroup.WithContext(context.TODO()) wg, ctx := errgroup.WithContext(context.TODO())
repo.StartPackUploader(ctx, wg) repo.StartPackUploader(ctx, wg)
@ -1139,11 +1151,15 @@ func TestArchiverSaveTree(t *testing.T) {
want = test.src want = test.src
} }
TestEnsureTree(context.TODO(), t, "/", repo, treeID, want) TestEnsureTree(context.TODO(), t, "/", repo, treeID, want)
stat := arch.summary
bothZeroOrNeither(t, uint64(test.stat.DataBlobs), uint64(stat.DataBlobs)) bothZeroOrNeither(t, uint64(test.stat.DataBlobs), uint64(stat.DataBlobs))
bothZeroOrNeither(t, uint64(test.stat.TreeBlobs), uint64(stat.TreeBlobs)) bothZeroOrNeither(t, uint64(test.stat.TreeBlobs), uint64(stat.TreeBlobs))
bothZeroOrNeither(t, test.stat.DataSize, stat.DataSize) bothZeroOrNeither(t, test.stat.DataSize, stat.DataSize)
bothZeroOrNeither(t, test.stat.DataSizeInRepo, stat.DataSizeInRepo) bothZeroOrNeither(t, test.stat.DataSizeInRepo, stat.DataSizeInRepo)
bothZeroOrNeither(t, test.stat.TreeSizeInRepo, stat.TreeSizeInRepo) bothZeroOrNeither(t, test.stat.TreeSizeInRepo, stat.TreeSizeInRepo)
restictest.Equals(t, test.stat.ProcessedBytes, stat.ProcessedBytes)
restictest.Equals(t, test.stat.Files, stat.Files)
restictest.Equals(t, test.stat.Dirs, stat.Dirs)
}) })
} }
} }
@ -1624,14 +1640,63 @@ func (f MockFile) Read(p []byte) (int, error) {
func TestArchiverParent(t *testing.T) { func TestArchiverParent(t *testing.T) {
var tests = []struct { var tests = []struct {
src TestDir src TestDir
read map[string]int // tracks number of times a file must have been read modify func(path string)
statInitial Summary
statSecond Summary
}{ }{
{ {
src: TestDir{ src: TestDir{
"targetfile": TestFile{Content: string(restictest.Random(888, 2*1024*1024+5000))}, "targetfile": TestFile{Content: string(restictest.Random(888, 2*1024*1024+5000))},
}, },
read: map[string]int{ statInitial: Summary{
"targetfile": 1, Files: ChangeStats{1, 0, 0},
Dirs: ChangeStats{0, 0, 0},
ProcessedBytes: 2102152,
},
statSecond: Summary{
Files: ChangeStats{0, 0, 1},
Dirs: ChangeStats{0, 0, 0},
ProcessedBytes: 2102152,
},
},
{
src: TestDir{
"targetDir": TestDir{
"targetfile": TestFile{Content: string(restictest.Random(888, 1234))},
"targetfile2": TestFile{Content: string(restictest.Random(888, 1235))},
},
},
statInitial: Summary{
Files: ChangeStats{2, 0, 0},
Dirs: ChangeStats{1, 0, 0},
ProcessedBytes: 2469,
},
statSecond: Summary{
Files: ChangeStats{0, 0, 2},
Dirs: ChangeStats{0, 0, 1},
ProcessedBytes: 2469,
},
},
{
src: TestDir{
"targetDir": TestDir{
"targetfile": TestFile{Content: string(restictest.Random(888, 1234))},
},
"targetfile2": TestFile{Content: string(restictest.Random(888, 1235))},
},
modify: func(path string) {
remove(t, filepath.Join(path, "targetDir", "targetfile"))
save(t, filepath.Join(path, "targetfile2"), []byte("foobar"))
},
statInitial: Summary{
Files: ChangeStats{2, 0, 0},
Dirs: ChangeStats{1, 0, 0},
ProcessedBytes: 2469,
},
statSecond: Summary{
Files: ChangeStats{0, 1, 0},
Dirs: ChangeStats{0, 1, 0},
ProcessedBytes: 6,
}, },
}, },
} }
@ -1653,7 +1718,7 @@ func TestArchiverParent(t *testing.T) {
back := restictest.Chdir(t, tempdir) back := restictest.Chdir(t, tempdir)
defer back() defer back()
firstSnapshot, firstSnapshotID, _, err := arch.Snapshot(ctx, []string{"."}, SnapshotOptions{Time: time.Now()}) firstSnapshot, firstSnapshotID, summary, err := arch.Snapshot(ctx, []string{"."}, SnapshotOptions{Time: time.Now()})
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -1678,33 +1743,31 @@ func TestArchiverParent(t *testing.T) {
} }
return nil return nil
}) })
restictest.Equals(t, test.statInitial.Files, summary.Files)
restictest.Equals(t, test.statInitial.Dirs, summary.Dirs)
restictest.Equals(t, test.statInitial.ProcessedBytes, summary.ProcessedBytes)
if test.modify != nil {
test.modify(tempdir)
}
opts := SnapshotOptions{ opts := SnapshotOptions{
Time: time.Now(), Time: time.Now(),
ParentSnapshot: firstSnapshot, ParentSnapshot: firstSnapshot,
} }
_, secondSnapshotID, _, err := arch.Snapshot(ctx, []string{"."}, opts) testFS.bytesRead = map[string]int{}
_, secondSnapshotID, summary, err := arch.Snapshot(ctx, []string{"."}, opts)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
// check that all files still been read exactly once if test.modify == nil {
TestWalkFiles(t, ".", test.src, func(filename string, item interface{}) error { // check that no files were read this time
file, ok := item.(TestFile) restictest.Equals(t, map[string]int{}, testFS.bytesRead)
if !ok {
return nil
} }
restictest.Equals(t, test.statSecond.Files, summary.Files)
n, ok := testFS.bytesRead[filename] restictest.Equals(t, test.statSecond.Dirs, summary.Dirs)
if !ok { restictest.Equals(t, test.statSecond.ProcessedBytes, summary.ProcessedBytes)
t.Fatalf("file %v was not read at all", filename)
}
if n != len(file.Content) {
t.Fatalf("file %v: read %v bytes, wanted %v bytes", filename, n, len(file.Content))
}
return nil
})
t.Logf("second backup saved as %v", secondSnapshotID.Str()) t.Logf("second backup saved as %v", secondSnapshotID.Str())
t.Logf("testfs: %v", testFS) t.Logf("testfs: %v", testFS)