mirror of
https://github.com/octoleo/restic.git
synced 2024-11-22 21:05:10 +00:00
repair snapshots: add basic tests
This commit is contained in:
parent
e71367e6b9
commit
78e5aa6d30
135
cmd/restic/integration_repair_snapshots_test.go
Normal file
135
cmd/restic/integration_repair_snapshots_test.go
Normal file
@ -0,0 +1,135 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"hash/fnv"
|
||||
"io"
|
||||
"math/rand"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/restic/restic/internal/restic"
|
||||
rtest "github.com/restic/restic/internal/test"
|
||||
)
|
||||
|
||||
func testRunRepairSnapshot(t testing.TB, gopts GlobalOptions, forget bool) {
|
||||
opts := RepairOptions{
|
||||
Forget: forget,
|
||||
}
|
||||
|
||||
rtest.OK(t, runRepairSnapshots(context.TODO(), gopts, opts, nil))
|
||||
}
|
||||
|
||||
func createRandomFile(t testing.TB, env *testEnvironment, path string, size int) {
|
||||
fn := filepath.Join(env.testdata, path)
|
||||
rtest.OK(t, os.MkdirAll(filepath.Dir(fn), 0o755))
|
||||
|
||||
h := fnv.New64()
|
||||
_, err := h.Write([]byte(path))
|
||||
rtest.OK(t, err)
|
||||
r := rand.New(rand.NewSource(int64(h.Sum64())))
|
||||
|
||||
f, err := os.OpenFile(fn, os.O_CREATE|os.O_RDWR, 0o644)
|
||||
rtest.OK(t, err)
|
||||
_, err = io.Copy(f, io.LimitReader(r, int64(size)))
|
||||
rtest.OK(t, err)
|
||||
rtest.OK(t, f.Close())
|
||||
}
|
||||
|
||||
func TestRepairSnapshotsWithLostData(t *testing.T) {
|
||||
env, cleanup := withTestEnvironment(t)
|
||||
defer cleanup()
|
||||
|
||||
testRunInit(t, env.gopts)
|
||||
|
||||
createRandomFile(t, env, "foo/bar/file", 512*1024)
|
||||
testRunBackup(t, "", []string{env.testdata}, BackupOptions{}, env.gopts)
|
||||
testListSnapshots(t, env.gopts, 1)
|
||||
// damage repository
|
||||
removePacksExcept(env.gopts, t, restic.NewIDSet(), false)
|
||||
|
||||
createRandomFile(t, env, "foo/bar/file2", 256*1024)
|
||||
testRunBackup(t, "", []string{env.testdata}, BackupOptions{}, env.gopts)
|
||||
snapshotIDs := testListSnapshots(t, env.gopts, 2)
|
||||
testRunCheckMustFail(t, env.gopts)
|
||||
|
||||
// repair but keep broken snapshots
|
||||
testRunRebuildIndex(t, env.gopts)
|
||||
testRunRepairSnapshot(t, env.gopts, false)
|
||||
testListSnapshots(t, env.gopts, 4)
|
||||
testRunCheckMustFail(t, env.gopts)
|
||||
|
||||
// repository must be ok after removing the broken snapshots
|
||||
testRunForget(t, env.gopts, snapshotIDs[0].String(), snapshotIDs[1].String())
|
||||
testListSnapshots(t, env.gopts, 2)
|
||||
_, err := testRunCheckOutput(env.gopts)
|
||||
rtest.OK(t, err)
|
||||
}
|
||||
|
||||
func TestRepairSnapshotsWithLostTree(t *testing.T) {
|
||||
env, cleanup := withTestEnvironment(t)
|
||||
defer cleanup()
|
||||
|
||||
testRunInit(t, env.gopts)
|
||||
|
||||
createRandomFile(t, env, "foo/bar/file", 12345)
|
||||
testRunBackup(t, "", []string{env.testdata}, BackupOptions{}, env.gopts)
|
||||
oldSnapshot := testListSnapshots(t, env.gopts, 1)
|
||||
oldPacks := testRunList(t, "packs", env.gopts)
|
||||
|
||||
// keep foo/bar unchanged
|
||||
createRandomFile(t, env, "foo/bar2", 1024)
|
||||
testRunBackup(t, "", []string{env.testdata}, BackupOptions{}, env.gopts)
|
||||
testListSnapshots(t, env.gopts, 2)
|
||||
|
||||
// remove tree for foo/bar and the now completely broken first snapshot
|
||||
removePacks(env.gopts, t, restic.NewIDSet(oldPacks...))
|
||||
testRunForget(t, env.gopts, oldSnapshot[0].String())
|
||||
testRunCheckMustFail(t, env.gopts)
|
||||
|
||||
// repair
|
||||
testRunRebuildIndex(t, env.gopts)
|
||||
testRunRepairSnapshot(t, env.gopts, true)
|
||||
testListSnapshots(t, env.gopts, 1)
|
||||
_, err := testRunCheckOutput(env.gopts)
|
||||
rtest.OK(t, err)
|
||||
}
|
||||
|
||||
func TestRepairSnapshotsWithLostRootTree(t *testing.T) {
|
||||
env, cleanup := withTestEnvironment(t)
|
||||
defer cleanup()
|
||||
|
||||
testRunInit(t, env.gopts)
|
||||
|
||||
createRandomFile(t, env, "foo/bar/file", 12345)
|
||||
testRunBackup(t, "", []string{env.testdata}, BackupOptions{}, env.gopts)
|
||||
testListSnapshots(t, env.gopts, 1)
|
||||
oldPacks := testRunList(t, "packs", env.gopts)
|
||||
|
||||
// remove all trees
|
||||
removePacks(env.gopts, t, restic.NewIDSet(oldPacks...))
|
||||
testRunCheckMustFail(t, env.gopts)
|
||||
|
||||
// repair
|
||||
testRunRebuildIndex(t, env.gopts)
|
||||
testRunRepairSnapshot(t, env.gopts, true)
|
||||
testListSnapshots(t, env.gopts, 0)
|
||||
_, err := testRunCheckOutput(env.gopts)
|
||||
rtest.OK(t, err)
|
||||
}
|
||||
|
||||
func TestRepairSnapshotsIntact(t *testing.T) {
|
||||
env, cleanup := withTestEnvironment(t)
|
||||
defer cleanup()
|
||||
testSetupBackupData(t, env)
|
||||
testRunBackup(t, filepath.Dir(env.testdata), []string{"testdata"}, BackupOptions{}, env.gopts)
|
||||
oldSnapshotIDs := testListSnapshots(t, env.gopts, 1)
|
||||
|
||||
// use an exclude that will not exclude anything
|
||||
testRunRepairSnapshot(t, env.gopts, false)
|
||||
snapshotIDs := testListSnapshots(t, env.gopts, 1)
|
||||
rtest.Assert(t, reflect.DeepEqual(oldSnapshotIDs, snapshotIDs), "unexpected snapshot id mismatch %v vs. %v", oldSnapshotIDs, snapshotIDs)
|
||||
testRunCheck(t, env.gopts)
|
||||
}
|
@ -100,6 +100,13 @@ func testRunList(t testing.TB, tpe string, opts GlobalOptions) restic.IDs {
|
||||
return parseIDsFromReader(t, buf)
|
||||
}
|
||||
|
||||
func testListSnapshots(t testing.TB, opts GlobalOptions, expected int) restic.IDs {
|
||||
t.Helper()
|
||||
snapshotIDs := testRunList(t, "snapshots", opts)
|
||||
rtest.Assert(t, len(snapshotIDs) == expected, "expected %v snapshot, got %v", expected, snapshotIDs)
|
||||
return snapshotIDs
|
||||
}
|
||||
|
||||
func testRunRestore(t testing.TB, opts GlobalOptions, dir string, snapshotID restic.ID) {
|
||||
testRunRestoreExcludes(t, opts, dir, snapshotID, nil)
|
||||
}
|
||||
@ -164,6 +171,11 @@ func testRunCheckOutput(gopts GlobalOptions) (string, error) {
|
||||
return buf.String(), err
|
||||
}
|
||||
|
||||
func testRunCheckMustFail(t testing.TB, gopts GlobalOptions) {
|
||||
_, err := testRunCheckOutput(gopts)
|
||||
rtest.Assert(t, err != nil, "expected non nil error after check of damaged repository")
|
||||
}
|
||||
|
||||
func testRunDiffOutput(gopts GlobalOptions, firstSnapshotID string, secondSnapshotID string) (string, error) {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
|
||||
@ -486,7 +498,16 @@ func TestBackupNonExistingFile(t *testing.T) {
|
||||
testRunBackup(t, "", dirs, opts, env.gopts)
|
||||
}
|
||||
|
||||
func removePacksExcept(gopts GlobalOptions, t *testing.T, keep restic.IDSet, removeTreePacks bool) {
|
||||
func removePacks(gopts GlobalOptions, t testing.TB, remove restic.IDSet) {
|
||||
r, err := OpenRepository(context.TODO(), gopts)
|
||||
rtest.OK(t, err)
|
||||
|
||||
for id := range remove {
|
||||
rtest.OK(t, r.Backend().Remove(context.TODO(), restic.Handle{Type: restic.PackFile, Name: id.String()}))
|
||||
}
|
||||
}
|
||||
|
||||
func removePacksExcept(gopts GlobalOptions, t testing.TB, keep restic.IDSet, removeTreePacks bool) {
|
||||
r, err := OpenRepository(context.TODO(), gopts)
|
||||
rtest.OK(t, err)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user