2024-04-14 10:32:48 +00:00
|
|
|
package repository_test
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"math"
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
"github.com/restic/restic/internal/checker"
|
|
|
|
"github.com/restic/restic/internal/repository"
|
|
|
|
"github.com/restic/restic/internal/restic"
|
|
|
|
rtest "github.com/restic/restic/internal/test"
|
|
|
|
"github.com/restic/restic/internal/ui/progress"
|
2024-04-14 11:57:19 +00:00
|
|
|
"golang.org/x/sync/errgroup"
|
2024-04-14 10:32:48 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
func testPrune(t *testing.T, opts repository.PruneOptions, errOnUnused bool) {
|
2024-05-10 14:59:09 +00:00
|
|
|
repo, be := repository.TestRepositoryWithVersion(t, 0)
|
2024-04-14 10:32:48 +00:00
|
|
|
createRandomBlobs(t, repo, 4, 0.5, true)
|
|
|
|
createRandomBlobs(t, repo, 5, 0.5, true)
|
|
|
|
keep, _ := selectBlobs(t, repo, 0.5)
|
|
|
|
|
2024-04-14 11:57:19 +00:00
|
|
|
var wg errgroup.Group
|
|
|
|
repo.StartPackUploader(context.TODO(), &wg)
|
|
|
|
// duplicate a few blobs to exercise those code paths
|
|
|
|
for blob := range keep {
|
|
|
|
buf, err := repo.LoadBlob(context.TODO(), blob.Type, blob.ID, nil)
|
|
|
|
rtest.OK(t, err)
|
|
|
|
_, _, _, err = repo.SaveBlob(context.TODO(), blob.Type, buf, blob.ID, true)
|
|
|
|
rtest.OK(t, err)
|
|
|
|
}
|
|
|
|
rtest.OK(t, repo.Flush(context.TODO()))
|
|
|
|
|
2024-04-14 10:32:48 +00:00
|
|
|
plan, err := repository.PlanPrune(context.TODO(), opts, repo, func(ctx context.Context, repo restic.Repository) (usedBlobs restic.CountedBlobSet, err error) {
|
|
|
|
return restic.NewCountedBlobSet(keep.List()...), nil
|
|
|
|
}, &progress.NoopPrinter{})
|
|
|
|
rtest.OK(t, err)
|
|
|
|
|
|
|
|
rtest.OK(t, plan.Execute(context.TODO(), &progress.NoopPrinter{}))
|
|
|
|
|
2024-05-10 14:59:09 +00:00
|
|
|
repo = repository.TestOpenBackend(t, be)
|
2024-04-14 10:32:48 +00:00
|
|
|
checker.TestCheckRepo(t, repo, true)
|
|
|
|
|
|
|
|
if errOnUnused {
|
|
|
|
existing := listBlobs(repo)
|
|
|
|
rtest.Assert(t, existing.Equals(keep), "unexpected blobs, wanted %v got %v", keep, existing)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestPrune(t *testing.T) {
|
|
|
|
for _, test := range []struct {
|
|
|
|
name string
|
|
|
|
opts repository.PruneOptions
|
|
|
|
errOnUnused bool
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "0",
|
|
|
|
opts: repository.PruneOptions{
|
|
|
|
MaxRepackBytes: math.MaxUint64,
|
|
|
|
MaxUnusedBytes: func(used uint64) (unused uint64) { return 0 },
|
|
|
|
},
|
|
|
|
errOnUnused: true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "50",
|
|
|
|
opts: repository.PruneOptions{
|
|
|
|
MaxRepackBytes: math.MaxUint64,
|
|
|
|
MaxUnusedBytes: func(used uint64) (unused uint64) { return used / 2 },
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "unlimited",
|
|
|
|
opts: repository.PruneOptions{
|
|
|
|
MaxRepackBytes: math.MaxUint64,
|
|
|
|
MaxUnusedBytes: func(used uint64) (unused uint64) { return math.MaxUint64 },
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "cachableonly",
|
|
|
|
opts: repository.PruneOptions{
|
|
|
|
MaxRepackBytes: math.MaxUint64,
|
|
|
|
MaxUnusedBytes: func(used uint64) (unused uint64) { return used / 20 },
|
|
|
|
RepackCachableOnly: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "small",
|
|
|
|
opts: repository.PruneOptions{
|
|
|
|
MaxRepackBytes: math.MaxUint64,
|
|
|
|
MaxUnusedBytes: func(used uint64) (unused uint64) { return math.MaxUint64 },
|
|
|
|
RepackSmall: true,
|
|
|
|
},
|
|
|
|
errOnUnused: true,
|
|
|
|
},
|
|
|
|
} {
|
|
|
|
t.Run(test.name, func(t *testing.T) {
|
|
|
|
testPrune(t, test.opts, test.errOnUnused)
|
|
|
|
})
|
|
|
|
t.Run(test.name+"-recovery", func(t *testing.T) {
|
|
|
|
opts := test.opts
|
|
|
|
opts.UnsafeRecovery = true
|
|
|
|
// unsafeNoSpaceRecovery does not repack partially used pack files
|
|
|
|
testPrune(t, opts, false)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|