ebab35581c
As an exception prune is still allowed to load the index before snapshots, as it uses exclusive locks. In case of problems with locking it is also better to load snapshots created after loading the index, as this will lead to a prune sanity check failure instead of a broken snapshot.
218 lines
5.8 KiB
Go
218 lines
5.8 KiB
Go
//go:build darwin || freebsd || linux
|
|
// +build darwin freebsd linux
|
|
|
|
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/restic/restic/internal/repository"
|
|
"github.com/restic/restic/internal/restic"
|
|
rtest "github.com/restic/restic/internal/test"
|
|
)
|
|
|
|
const (
|
|
mountWait = 20
|
|
mountSleep = 100 * time.Millisecond
|
|
mountTestSubdir = "snapshots"
|
|
)
|
|
|
|
func snapshotsDirExists(t testing.TB, dir string) bool {
|
|
f, err := os.Open(filepath.Join(dir, mountTestSubdir))
|
|
if err != nil && os.IsNotExist(err) {
|
|
return false
|
|
}
|
|
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
|
|
if err := f.Close(); err != nil {
|
|
t.Error(err)
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// waitForMount blocks (max mountWait * mountSleep) until the subdir
|
|
// "snapshots" appears in the dir.
|
|
func waitForMount(t testing.TB, dir string) {
|
|
for i := 0; i < mountWait; i++ {
|
|
if snapshotsDirExists(t, dir) {
|
|
t.Log("mounted directory is ready")
|
|
return
|
|
}
|
|
|
|
time.Sleep(mountSleep)
|
|
}
|
|
|
|
t.Errorf("subdir %q of dir %s never appeared", mountTestSubdir, dir)
|
|
}
|
|
|
|
func testRunMount(t testing.TB, gopts GlobalOptions, dir string) {
|
|
opts := MountOptions{
|
|
SnapshotTemplate: time.RFC3339,
|
|
}
|
|
rtest.OK(t, runMount(opts, gopts, []string{dir}))
|
|
}
|
|
|
|
func testRunUmount(t testing.TB, gopts GlobalOptions, dir string) {
|
|
var err error
|
|
for i := 0; i < mountWait; i++ {
|
|
if err = umount(dir); err == nil {
|
|
t.Logf("directory %v umounted", dir)
|
|
return
|
|
}
|
|
|
|
time.Sleep(mountSleep)
|
|
}
|
|
|
|
t.Errorf("unable to umount dir %v, last error was: %v", dir, err)
|
|
}
|
|
|
|
func listSnapshots(t testing.TB, dir string) []string {
|
|
snapshotsDir, err := os.Open(filepath.Join(dir, "snapshots"))
|
|
rtest.OK(t, err)
|
|
names, err := snapshotsDir.Readdirnames(-1)
|
|
rtest.OK(t, err)
|
|
rtest.OK(t, snapshotsDir.Close())
|
|
return names
|
|
}
|
|
|
|
func checkSnapshots(t testing.TB, global GlobalOptions, repo *repository.Repository, mountpoint, repodir string, snapshotIDs restic.IDs, expectedSnapshotsInFuseDir int) {
|
|
t.Logf("checking for %d snapshots: %v", len(snapshotIDs), snapshotIDs)
|
|
|
|
go testRunMount(t, global, mountpoint)
|
|
waitForMount(t, mountpoint)
|
|
defer testRunUmount(t, global, mountpoint)
|
|
|
|
if !snapshotsDirExists(t, mountpoint) {
|
|
t.Fatal(`virtual directory "snapshots" doesn't exist`)
|
|
}
|
|
|
|
ids := listSnapshots(t, repodir)
|
|
t.Logf("found %v snapshots in repo: %v", len(ids), ids)
|
|
|
|
namesInSnapshots := listSnapshots(t, mountpoint)
|
|
t.Logf("found %v snapshots in fuse mount: %v", len(namesInSnapshots), namesInSnapshots)
|
|
rtest.Assert(t,
|
|
expectedSnapshotsInFuseDir == len(namesInSnapshots),
|
|
"Invalid number of snapshots: expected %d, got %d", expectedSnapshotsInFuseDir, len(namesInSnapshots))
|
|
|
|
namesMap := make(map[string]bool)
|
|
for _, name := range namesInSnapshots {
|
|
namesMap[name] = false
|
|
}
|
|
|
|
// Is "latest" present?
|
|
if len(namesMap) != 0 {
|
|
_, ok := namesMap["latest"]
|
|
if !ok {
|
|
t.Errorf("Symlink latest isn't present in fuse dir")
|
|
} else {
|
|
namesMap["latest"] = true
|
|
}
|
|
}
|
|
|
|
for _, id := range snapshotIDs {
|
|
snapshot, err := restic.LoadSnapshot(global.ctx, repo, id)
|
|
rtest.OK(t, err)
|
|
|
|
ts := snapshot.Time.Format(time.RFC3339)
|
|
present, ok := namesMap[ts]
|
|
if !ok {
|
|
t.Errorf("Snapshot %v (%q) isn't present in fuse dir", id.Str(), ts)
|
|
}
|
|
|
|
for i := 1; present; i++ {
|
|
ts = fmt.Sprintf("%s-%d", snapshot.Time.Format(time.RFC3339), i)
|
|
present, ok = namesMap[ts]
|
|
if !ok {
|
|
t.Errorf("Snapshot %v (%q) isn't present in fuse dir", id.Str(), ts)
|
|
}
|
|
|
|
if !present {
|
|
break
|
|
}
|
|
}
|
|
|
|
namesMap[ts] = true
|
|
}
|
|
|
|
for name, present := range namesMap {
|
|
rtest.Assert(t, present, "Directory %s is present in fuse dir but is not a snapshot", name)
|
|
}
|
|
}
|
|
|
|
func TestMount(t *testing.T) {
|
|
if !rtest.RunFuseTest {
|
|
t.Skip("Skipping fuse tests")
|
|
}
|
|
|
|
env, cleanup := withTestEnvironment(t)
|
|
// must list snapshots more than once
|
|
env.gopts.backendTestHook = nil
|
|
defer cleanup()
|
|
|
|
testRunInit(t, env.gopts)
|
|
|
|
repo, err := OpenRepository(env.gopts)
|
|
rtest.OK(t, err)
|
|
|
|
checkSnapshots(t, env.gopts, repo, env.mountpoint, env.repo, []restic.ID{}, 0)
|
|
|
|
rtest.SetupTarTestFixture(t, env.testdata, filepath.Join("testdata", "backup-data.tar.gz"))
|
|
|
|
// first backup
|
|
testRunBackup(t, "", []string{env.testdata}, BackupOptions{}, env.gopts)
|
|
snapshotIDs := testRunList(t, "snapshots", env.gopts)
|
|
rtest.Assert(t, len(snapshotIDs) == 1,
|
|
"expected one snapshot, got %v", snapshotIDs)
|
|
|
|
checkSnapshots(t, env.gopts, repo, env.mountpoint, env.repo, snapshotIDs, 2)
|
|
|
|
// second backup, implicit incremental
|
|
testRunBackup(t, "", []string{env.testdata}, BackupOptions{}, env.gopts)
|
|
snapshotIDs = testRunList(t, "snapshots", env.gopts)
|
|
rtest.Assert(t, len(snapshotIDs) == 2,
|
|
"expected two snapshots, got %v", snapshotIDs)
|
|
|
|
checkSnapshots(t, env.gopts, repo, env.mountpoint, env.repo, snapshotIDs, 3)
|
|
|
|
// third backup, explicit incremental
|
|
bopts := BackupOptions{Parent: snapshotIDs[0].String()}
|
|
testRunBackup(t, "", []string{env.testdata}, bopts, env.gopts)
|
|
snapshotIDs = testRunList(t, "snapshots", env.gopts)
|
|
rtest.Assert(t, len(snapshotIDs) == 3,
|
|
"expected three snapshots, got %v", snapshotIDs)
|
|
|
|
checkSnapshots(t, env.gopts, repo, env.mountpoint, env.repo, snapshotIDs, 4)
|
|
}
|
|
|
|
func TestMountSameTimestamps(t *testing.T) {
|
|
if !rtest.RunFuseTest {
|
|
t.Skip("Skipping fuse tests")
|
|
}
|
|
|
|
env, cleanup := withTestEnvironment(t)
|
|
// must list snapshots more than once
|
|
env.gopts.backendTestHook = nil
|
|
defer cleanup()
|
|
|
|
rtest.SetupTarTestFixture(t, env.base, filepath.Join("testdata", "repo-same-timestamps.tar.gz"))
|
|
|
|
repo, err := OpenRepository(env.gopts)
|
|
rtest.OK(t, err)
|
|
|
|
ids := []restic.ID{
|
|
restic.TestParseID("280303689e5027328889a06d718b729e96a1ce6ae9ef8290bff550459ae611ee"),
|
|
restic.TestParseID("75ad6cdc0868e082f2596d5ab8705e9f7d87316f5bf5690385eeff8dbe49d9f5"),
|
|
restic.TestParseID("5fd0d8b2ef0fa5d23e58f1e460188abb0f525c0f0c4af8365a1280c807a80a1b"),
|
|
}
|
|
|
|
checkSnapshots(t, env.gopts, repo, env.mountpoint, env.repo, ids, 4)
|
|
}
|