2
2
mirror of https://github.com/octoleo/restic.git synced 2024-09-29 06:59:01 +00:00
restic/cmd/restic/cmd_restore_integration_test.go
Michael Eischer 675a49a95b Restructure integration tests
The tests are now split into individual files for each command. The
separation isn't perfect as many tests make use of multiple commands. In
particular `init`, `backup`, `check` and `list` are used by a larger
number of test cases.

Most tests now reside in files name cmd_<name>_integration_test.go. This
provides a certain indication which commands have significant test
coverage.
2023-05-26 19:38:43 +02:00

306 lines
9.3 KiB
Go

package main
import (
"context"
"fmt"
"io"
mrand "math/rand"
"os"
"path/filepath"
"syscall"
"testing"
"time"
"github.com/restic/restic/internal/filter"
"github.com/restic/restic/internal/restic"
rtest "github.com/restic/restic/internal/test"
)
func testRunRestore(t testing.TB, opts GlobalOptions, dir string, snapshotID restic.ID) {
testRunRestoreExcludes(t, opts, dir, snapshotID, nil)
}
func testRunRestoreExcludes(t testing.TB, gopts GlobalOptions, dir string, snapshotID restic.ID, excludes []string) {
opts := RestoreOptions{
Target: dir,
Exclude: excludes,
}
rtest.OK(t, runRestore(context.TODO(), opts, gopts, nil, []string{snapshotID.String()}))
}
func testRunRestoreAssumeFailure(snapshotID string, opts RestoreOptions, gopts GlobalOptions) error {
return runRestore(context.TODO(), opts, gopts, nil, []string{snapshotID})
}
func testRunRestoreLatest(t testing.TB, gopts GlobalOptions, dir string, paths []string, hosts []string) {
opts := RestoreOptions{
Target: dir,
SnapshotFilter: restic.SnapshotFilter{
Hosts: hosts,
Paths: paths,
},
}
rtest.OK(t, runRestore(context.TODO(), opts, gopts, nil, []string{"latest"}))
}
func testRunRestoreIncludes(t testing.TB, gopts GlobalOptions, dir string, snapshotID restic.ID, includes []string) {
opts := RestoreOptions{
Target: dir,
Include: includes,
}
rtest.OK(t, runRestore(context.TODO(), opts, gopts, nil, []string{snapshotID.String()}))
}
func TestRestoreFilter(t *testing.T) {
testfiles := []struct {
name string
size uint
}{
{"testfile1.c", 100},
{"testfile2.exe", 101},
{"subdir1/subdir2/testfile3.docx", 102},
{"subdir1/subdir2/testfile4.c", 102},
}
env, cleanup := withTestEnvironment(t)
defer cleanup()
testRunInit(t, env.gopts)
for _, testFile := range testfiles {
p := filepath.Join(env.testdata, testFile.name)
rtest.OK(t, os.MkdirAll(filepath.Dir(p), 0755))
rtest.OK(t, appendRandomData(p, testFile.size))
}
opts := BackupOptions{}
testRunBackup(t, filepath.Dir(env.testdata), []string{filepath.Base(env.testdata)}, opts, env.gopts)
testRunCheck(t, env.gopts)
snapshotID := testListSnapshots(t, env.gopts, 1)[0]
// no restore filter should restore all files
testRunRestore(t, env.gopts, filepath.Join(env.base, "restore0"), snapshotID)
for _, testFile := range testfiles {
rtest.OK(t, testFileSize(filepath.Join(env.base, "restore0", "testdata", testFile.name), int64(testFile.size)))
}
for i, pat := range []string{"*.c", "*.exe", "*", "*file3*"} {
base := filepath.Join(env.base, fmt.Sprintf("restore%d", i+1))
testRunRestoreExcludes(t, env.gopts, base, snapshotID, []string{pat})
for _, testFile := range testfiles {
err := testFileSize(filepath.Join(base, "testdata", testFile.name), int64(testFile.size))
if ok, _ := filter.Match(pat, filepath.Base(testFile.name)); !ok {
rtest.OK(t, err)
} else {
rtest.Assert(t, os.IsNotExist(err),
"expected %v to not exist in restore step %v, but it exists, err %v", testFile.name, i+1, err)
}
}
}
}
func TestRestore(t *testing.T) {
env, cleanup := withTestEnvironment(t)
defer cleanup()
testRunInit(t, env.gopts)
for i := 0; i < 10; i++ {
p := filepath.Join(env.testdata, fmt.Sprintf("foo/bar/testfile%v", i))
rtest.OK(t, os.MkdirAll(filepath.Dir(p), 0755))
rtest.OK(t, appendRandomData(p, uint(mrand.Intn(2<<21))))
}
opts := BackupOptions{}
testRunBackup(t, filepath.Dir(env.testdata), []string{filepath.Base(env.testdata)}, opts, env.gopts)
testRunCheck(t, env.gopts)
// Restore latest without any filters
restoredir := filepath.Join(env.base, "restore")
testRunRestoreLatest(t, env.gopts, restoredir, nil, nil)
diff := directoriesContentsDiff(env.testdata, filepath.Join(restoredir, filepath.Base(env.testdata)))
rtest.Assert(t, diff == "", "directories are not equal %v", diff)
}
func TestRestoreLatest(t *testing.T) {
env, cleanup := withTestEnvironment(t)
defer cleanup()
testRunInit(t, env.gopts)
p := filepath.Join(env.testdata, "testfile.c")
rtest.OK(t, os.MkdirAll(filepath.Dir(p), 0755))
rtest.OK(t, appendRandomData(p, 100))
opts := BackupOptions{}
// chdir manually here so we can get the current directory. This is not the
// same as the temp dir returned by os.MkdirTemp() on darwin.
back := rtest.Chdir(t, filepath.Dir(env.testdata))
defer back()
curdir, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
testRunBackup(t, "", []string{filepath.Base(env.testdata)}, opts, env.gopts)
testRunCheck(t, env.gopts)
rtest.OK(t, os.Remove(p))
rtest.OK(t, appendRandomData(p, 101))
testRunBackup(t, "", []string{filepath.Base(env.testdata)}, opts, env.gopts)
testRunCheck(t, env.gopts)
// Restore latest without any filters
testRunRestoreLatest(t, env.gopts, filepath.Join(env.base, "restore0"), nil, nil)
rtest.OK(t, testFileSize(filepath.Join(env.base, "restore0", "testdata", "testfile.c"), int64(101)))
// Setup test files in different directories backed up in different snapshots
p1 := filepath.Join(curdir, filepath.FromSlash("p1/testfile.c"))
rtest.OK(t, os.MkdirAll(filepath.Dir(p1), 0755))
rtest.OK(t, appendRandomData(p1, 102))
testRunBackup(t, "", []string{"p1"}, opts, env.gopts)
testRunCheck(t, env.gopts)
p2 := filepath.Join(curdir, filepath.FromSlash("p2/testfile.c"))
rtest.OK(t, os.MkdirAll(filepath.Dir(p2), 0755))
rtest.OK(t, appendRandomData(p2, 103))
testRunBackup(t, "", []string{"p2"}, opts, env.gopts)
testRunCheck(t, env.gopts)
p1rAbs := filepath.Join(env.base, "restore1", "p1/testfile.c")
p2rAbs := filepath.Join(env.base, "restore2", "p2/testfile.c")
testRunRestoreLatest(t, env.gopts, filepath.Join(env.base, "restore1"), []string{filepath.Dir(p1)}, nil)
rtest.OK(t, testFileSize(p1rAbs, int64(102)))
if _, err := os.Stat(p2rAbs); os.IsNotExist(err) {
rtest.Assert(t, os.IsNotExist(err),
"expected %v to not exist in restore, but it exists, err %v", p2rAbs, err)
}
testRunRestoreLatest(t, env.gopts, filepath.Join(env.base, "restore2"), []string{filepath.Dir(p2)}, nil)
rtest.OK(t, testFileSize(p2rAbs, int64(103)))
if _, err := os.Stat(p1rAbs); os.IsNotExist(err) {
rtest.Assert(t, os.IsNotExist(err),
"expected %v to not exist in restore, but it exists, err %v", p1rAbs, err)
}
}
func TestRestoreWithPermissionFailure(t *testing.T) {
env, cleanup := withTestEnvironment(t)
defer cleanup()
datafile := filepath.Join("testdata", "repo-restore-permissions-test.tar.gz")
rtest.SetupTarTestFixture(t, env.base, datafile)
snapshots := testListSnapshots(t, env.gopts, 1)
globalOptions.stderr = io.Discard
defer func() {
globalOptions.stderr = os.Stderr
}()
testRunRestore(t, env.gopts, filepath.Join(env.base, "restore"), snapshots[0])
// make sure that all files have been restored, regardless of any
// permission errors
files := testRunLs(t, env.gopts, snapshots[0].String())
for _, filename := range files {
fi, err := os.Lstat(filepath.Join(env.base, "restore", filename))
rtest.OK(t, err)
rtest.Assert(t, !isFile(fi) || fi.Size() > 0,
"file %v restored, but filesize is 0", filename)
}
}
func setZeroModTime(filename string) error {
var utimes = []syscall.Timespec{
syscall.NsecToTimespec(0),
syscall.NsecToTimespec(0),
}
return syscall.UtimesNano(filename, utimes)
}
func TestRestoreNoMetadataOnIgnoredIntermediateDirs(t *testing.T) {
env, cleanup := withTestEnvironment(t)
defer cleanup()
testRunInit(t, env.gopts)
p := filepath.Join(env.testdata, "subdir1", "subdir2", "subdir3", "file.ext")
rtest.OK(t, os.MkdirAll(filepath.Dir(p), 0755))
rtest.OK(t, appendRandomData(p, 200))
rtest.OK(t, setZeroModTime(filepath.Join(env.testdata, "subdir1", "subdir2")))
opts := BackupOptions{}
testRunBackup(t, filepath.Dir(env.testdata), []string{filepath.Base(env.testdata)}, opts, env.gopts)
testRunCheck(t, env.gopts)
snapshotID := testListSnapshots(t, env.gopts, 1)[0]
// restore with filter "*.ext", this should restore "file.ext", but
// since the directories are ignored and only created because of
// "file.ext", no meta data should be restored for them.
testRunRestoreIncludes(t, env.gopts, filepath.Join(env.base, "restore0"), snapshotID, []string{"*.ext"})
f1 := filepath.Join(env.base, "restore0", "testdata", "subdir1", "subdir2")
_, err := os.Stat(f1)
rtest.OK(t, err)
// restore with filter "*", this should restore meta data on everything.
testRunRestoreIncludes(t, env.gopts, filepath.Join(env.base, "restore1"), snapshotID, []string{"*"})
f2 := filepath.Join(env.base, "restore1", "testdata", "subdir1", "subdir2")
fi, err := os.Stat(f2)
rtest.OK(t, err)
rtest.Assert(t, fi.ModTime() == time.Unix(0, 0),
"meta data of intermediate directory hasn't been restore")
}
func TestRestoreLocalLayout(t *testing.T) {
env, cleanup := withTestEnvironment(t)
defer cleanup()
var tests = []struct {
filename string
layout string
}{
{"repo-layout-default.tar.gz", ""},
{"repo-layout-s3legacy.tar.gz", ""},
{"repo-layout-default.tar.gz", "default"},
{"repo-layout-s3legacy.tar.gz", "s3legacy"},
}
for _, test := range tests {
datafile := filepath.Join("..", "..", "internal", "backend", "testdata", test.filename)
rtest.SetupTarTestFixture(t, env.base, datafile)
env.gopts.extended["local.layout"] = test.layout
// check the repo
testRunCheck(t, env.gopts)
// restore latest snapshot
target := filepath.Join(env.base, "restore")
testRunRestoreLatest(t, env.gopts, target, nil, nil)
rtest.RemoveAll(t, filepath.Join(env.base, "repo"))
rtest.RemoveAll(t, target)
}
}