forked from TrueCloudLab/restic
1d6d3656b0
LoadRaw also includes improved context cancellation handling similar to the implementation in repository.LoadUnpacked. The removed cache backend test will be added again later on.
132 lines
4.3 KiB
Go
132 lines
4.3 KiB
Go
package repository_test
|
|
|
|
import (
|
|
"context"
|
|
"math/rand"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/restic/restic/internal/backend"
|
|
backendtest "github.com/restic/restic/internal/backend/test"
|
|
"github.com/restic/restic/internal/index"
|
|
"github.com/restic/restic/internal/repository"
|
|
"github.com/restic/restic/internal/restic"
|
|
"github.com/restic/restic/internal/test"
|
|
rtest "github.com/restic/restic/internal/test"
|
|
"github.com/restic/restic/internal/ui/progress"
|
|
)
|
|
|
|
func listBlobs(repo restic.Repository) restic.BlobSet {
|
|
blobs := restic.NewBlobSet()
|
|
_ = repo.Index().Each(context.TODO(), func(pb restic.PackedBlob) {
|
|
blobs.Insert(pb.BlobHandle)
|
|
})
|
|
return blobs
|
|
}
|
|
|
|
func replaceFile(t *testing.T, repo restic.Repository, h backend.Handle, damage func([]byte) []byte) {
|
|
buf, err := backendtest.LoadAll(context.TODO(), repo.Backend(), h)
|
|
test.OK(t, err)
|
|
buf = damage(buf)
|
|
test.OK(t, repo.Backend().Remove(context.TODO(), h))
|
|
test.OK(t, repo.Backend().Save(context.TODO(), h, backend.NewByteReader(buf, repo.Backend().Hasher())))
|
|
}
|
|
|
|
func TestRepairBrokenPack(t *testing.T) {
|
|
repository.TestAllVersions(t, testRepairBrokenPack)
|
|
}
|
|
|
|
func testRepairBrokenPack(t *testing.T, version uint) {
|
|
tests := []struct {
|
|
name string
|
|
damage func(t *testing.T, repo restic.Repository, packsBefore restic.IDSet) (restic.IDSet, restic.BlobSet)
|
|
}{
|
|
{
|
|
"valid pack",
|
|
func(t *testing.T, repo restic.Repository, packsBefore restic.IDSet) (restic.IDSet, restic.BlobSet) {
|
|
return packsBefore, restic.NewBlobSet()
|
|
},
|
|
},
|
|
{
|
|
"broken pack",
|
|
func(t *testing.T, repo restic.Repository, packsBefore restic.IDSet) (restic.IDSet, restic.BlobSet) {
|
|
wrongBlob := createRandomWrongBlob(t, repo)
|
|
damagedPacks := findPacksForBlobs(t, repo, restic.NewBlobSet(wrongBlob))
|
|
return damagedPacks, restic.NewBlobSet(wrongBlob)
|
|
},
|
|
},
|
|
{
|
|
"partially broken pack",
|
|
func(t *testing.T, repo restic.Repository, packsBefore restic.IDSet) (restic.IDSet, restic.BlobSet) {
|
|
// damage one of the pack files
|
|
damagedID := packsBefore.List()[0]
|
|
replaceFile(t, repo, backend.Handle{Type: backend.PackFile, Name: damagedID.String()},
|
|
func(buf []byte) []byte {
|
|
buf[0] ^= 0xff
|
|
return buf
|
|
})
|
|
|
|
// find blob that starts at offset 0
|
|
var damagedBlob restic.BlobHandle
|
|
for blobs := range repo.Index().ListPacks(context.TODO(), restic.NewIDSet(damagedID)) {
|
|
for _, blob := range blobs.Blobs {
|
|
if blob.Offset == 0 {
|
|
damagedBlob = blob.BlobHandle
|
|
}
|
|
}
|
|
}
|
|
|
|
return restic.NewIDSet(damagedID), restic.NewBlobSet(damagedBlob)
|
|
},
|
|
}, {
|
|
"truncated pack",
|
|
func(t *testing.T, repo restic.Repository, packsBefore restic.IDSet) (restic.IDSet, restic.BlobSet) {
|
|
// damage one of the pack files
|
|
damagedID := packsBefore.List()[0]
|
|
replaceFile(t, repo, backend.Handle{Type: backend.PackFile, Name: damagedID.String()},
|
|
func(buf []byte) []byte {
|
|
buf = buf[0:10]
|
|
return buf
|
|
})
|
|
|
|
// all blobs in the file are broken
|
|
damagedBlobs := restic.NewBlobSet()
|
|
for blobs := range repo.Index().ListPacks(context.TODO(), restic.NewIDSet(damagedID)) {
|
|
for _, blob := range blobs.Blobs {
|
|
damagedBlobs.Insert(blob.BlobHandle)
|
|
}
|
|
}
|
|
return restic.NewIDSet(damagedID), damagedBlobs
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
// disable verification to allow adding corrupted blobs to the repository
|
|
repo := repository.TestRepositoryWithBackend(t, nil, version, repository.Options{NoExtraVerify: true})
|
|
|
|
seed := time.Now().UnixNano()
|
|
rand.Seed(seed)
|
|
t.Logf("rand seed is %v", seed)
|
|
|
|
createRandomBlobs(t, repo, 5, 0.7, true)
|
|
packsBefore := listPacks(t, repo)
|
|
blobsBefore := listBlobs(repo)
|
|
|
|
toRepair, damagedBlobs := test.damage(t, repo, packsBefore)
|
|
|
|
rtest.OK(t, repository.RepairPacks(context.TODO(), repo, toRepair, &progress.NoopPrinter{}))
|
|
// reload index
|
|
rtest.OK(t, repo.SetIndex(index.NewMasterIndex()))
|
|
rtest.OK(t, repo.LoadIndex(context.TODO(), nil))
|
|
|
|
packsAfter := listPacks(t, repo)
|
|
blobsAfter := listBlobs(repo)
|
|
|
|
rtest.Assert(t, len(packsAfter.Intersect(toRepair)) == 0, "some damaged packs were not removed")
|
|
rtest.Assert(t, len(packsBefore.Sub(toRepair).Sub(packsAfter)) == 0, "not-damaged packs were removed")
|
|
rtest.Assert(t, blobsBefore.Sub(damagedBlobs).Equals(blobsAfter), "diverging blob lists")
|
|
})
|
|
}
|
|
}
|