diff --git a/internal/restic/find_test.go b/internal/restic/find_test.go index 278a7471a..c599e5fdb 100644 --- a/internal/restic/find_test.go +++ b/internal/restic/find_test.go @@ -15,6 +15,8 @@ import ( "github.com/restic/restic/internal/errors" "github.com/restic/restic/internal/repository" "github.com/restic/restic/internal/restic" + "github.com/restic/restic/internal/test" + "github.com/restic/restic/internal/ui/progress" ) func loadIDSet(t testing.TB, filename string) restic.BlobSet { @@ -92,9 +94,12 @@ func TestFindUsedBlobs(t *testing.T) { snapshots = append(snapshots, sn) } + p := progress.New(time.Second, findTestSnapshots, func(value uint64, total uint64, runtime time.Duration, final bool) {}) + defer p.Done() + for i, sn := range snapshots { usedBlobs := restic.NewBlobSet() - err := restic.FindUsedBlobs(context.TODO(), repo, restic.IDs{*sn.Tree}, usedBlobs, nil) + err := restic.FindUsedBlobs(context.TODO(), repo, restic.IDs{*sn.Tree}, usedBlobs, p) if err != nil { t.Errorf("FindUsedBlobs returned error: %v", err) continue @@ -105,6 +110,8 @@ func TestFindUsedBlobs(t *testing.T) { continue } + test.Equals(t, p.Get(), uint64(i+1)) + goldenFilename := filepath.Join("testdata", fmt.Sprintf("used_blobs_snapshot%d", i)) want := loadIDSet(t, goldenFilename) @@ -119,6 +126,40 @@ func TestFindUsedBlobs(t *testing.T) { } } +func TestMultiFindUsedBlobs(t *testing.T) { + repo, cleanup := repository.TestRepository(t) + defer cleanup() + + var snapshotTrees restic.IDs + for i := 0; i < findTestSnapshots; i++ { + sn := restic.TestCreateSnapshot(t, repo, findTestTime.Add(time.Duration(i)*time.Second), findTestDepth, 0) + t.Logf("snapshot %v saved, tree %v", sn.ID().Str(), sn.Tree.Str()) + snapshotTrees = append(snapshotTrees, *sn.Tree) + } + + want := restic.NewBlobSet() + for i := range snapshotTrees { + goldenFilename := filepath.Join("testdata", fmt.Sprintf("used_blobs_snapshot%d", i)) + want.Merge(loadIDSet(t, goldenFilename)) + } + + p := progress.New(time.Second, findTestSnapshots, func(value uint64, total uint64, runtime time.Duration, final bool) {}) + defer p.Done() + + // run twice to check progress bar handling of duplicate tree roots + usedBlobs := restic.NewBlobSet() + for i := 1; i < 3; i++ { + err := restic.FindUsedBlobs(context.TODO(), repo, snapshotTrees, usedBlobs, p) + test.OK(t, err) + test.Equals(t, p.Get(), uint64(i*len(snapshotTrees))) + + if !want.Equals(usedBlobs) { + t.Errorf("wrong list of blobs returned:\n missing blobs: %v\n extra blobs: %v", + want.Sub(usedBlobs), usedBlobs.Sub(want)) + } + } +} + type ForbiddenRepo struct{} func (r ForbiddenRepo) LoadTree(ctx context.Context, id restic.ID) (*restic.Tree, error) { diff --git a/internal/ui/progress/counter.go b/internal/ui/progress/counter.go index 82d5e39ba..d2f75c9bf 100644 --- a/internal/ui/progress/counter.go +++ b/internal/ui/progress/counter.go @@ -82,7 +82,8 @@ func (c *Counter) Done() { *c = Counter{} // Prevent reuse. } -func (c *Counter) get() uint64 { +// Get the current Counter value. This method is concurrency-safe. +func (c *Counter) Get() uint64 { c.valueMutex.Lock() v := c.value c.valueMutex.Unlock() @@ -102,7 +103,7 @@ func (c *Counter) run() { defer close(c.stopped) defer func() { // Must be a func so that time.Since isn't called at defer time. - c.report(c.get(), c.getMax(), time.Since(c.start), true) + c.report(c.Get(), c.getMax(), time.Since(c.start), true) }() var tick <-chan time.Time @@ -122,6 +123,6 @@ func (c *Counter) run() { return } - c.report(c.get(), c.getMax(), now.Sub(c.start), false) + c.report(c.Get(), c.getMax(), now.Sub(c.start), false) } }