restic/internal/archiver/testing_test.go
Michael Eischer ff7ef5007e Replace most usages of ioutil with the underlying function
The ioutil functions are deprecated since Go 1.17 and only wrap another
library function. Thus directly call the underlying function.

This commit only mechanically replaces the function calls.
2022-12-02 19:36:43 +01:00

501 lines
11 KiB
Go

package archiver
import (
"context"
"fmt"
"os"
"path/filepath"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/restic/restic/internal/fs"
"github.com/restic/restic/internal/repository"
restictest "github.com/restic/restic/internal/test"
)
// MockT passes through all logging functions from T, but catches Fail(),
// Error/f() and Fatal/f(). It is used to test helper functions.
type MockT struct {
*testing.T
HasFailed bool
}
// Fail marks the function as having failed but continues execution.
func (t *MockT) Fail() {
t.T.Log("MockT Fail() called")
t.HasFailed = true
}
// Fatal is equivalent to Log followed by FailNow.
func (t *MockT) Fatal(args ...interface{}) {
t.T.Logf("MockT Fatal called with %v", args)
t.HasFailed = true
}
// Fatalf is equivalent to Logf followed by FailNow.
func (t *MockT) Fatalf(msg string, args ...interface{}) {
t.T.Logf("MockT Fatal called: "+msg, args...)
t.HasFailed = true
}
// Error is equivalent to Log followed by Fail.
func (t *MockT) Error(args ...interface{}) {
t.T.Logf("MockT Error called with %v", args)
t.HasFailed = true
}
// Errorf is equivalent to Logf followed by Fail.
func (t *MockT) Errorf(msg string, args ...interface{}) {
t.T.Logf("MockT Error called: "+msg, args...)
t.HasFailed = true
}
func createFilesAt(t testing.TB, targetdir string, files map[string]interface{}) {
for name, item := range files {
target := filepath.Join(targetdir, filepath.FromSlash(name))
err := fs.MkdirAll(filepath.Dir(target), 0700)
if err != nil {
t.Fatal(err)
}
switch it := item.(type) {
case TestFile:
err := os.WriteFile(target, []byte(it.Content), 0600)
if err != nil {
t.Fatal(err)
}
case TestSymlink:
err := fs.Symlink(filepath.FromSlash(it.Target), target)
if err != nil {
t.Fatal(err)
}
}
}
}
func TestTestCreateFiles(t *testing.T) {
var tests = []struct {
dir TestDir
files map[string]interface{}
}{
{
dir: TestDir{
"foo": TestFile{Content: "foo"},
"subdir": TestDir{
"subfile": TestFile{Content: "bar"},
},
"sub": TestDir{
"subsub": TestDir{
"link": TestSymlink{Target: filepath.Clean("x/y/z")},
},
},
},
files: map[string]interface{}{
"foo": TestFile{Content: "foo"},
"subdir": TestDir{},
"subdir/subfile": TestFile{Content: "bar"},
"sub/subsub/link": TestSymlink{Target: filepath.Clean("x/y/z")},
},
},
}
for i, test := range tests {
tempdir, cleanup := restictest.TempDir(t)
defer cleanup()
t.Run("", func(t *testing.T) {
tempdir := filepath.Join(tempdir, fmt.Sprintf("test-%d", i))
err := fs.MkdirAll(tempdir, 0700)
if err != nil {
t.Fatal(err)
}
TestCreateFiles(t, tempdir, test.dir)
for name, item := range test.files {
targetPath := filepath.Join(tempdir, filepath.FromSlash(name))
fi, err := fs.Lstat(targetPath)
if err != nil {
t.Error(err)
continue
}
switch node := item.(type) {
case TestFile:
if !fs.IsRegularFile(fi) {
t.Errorf("is not regular file: %v", name)
continue
}
content, err := os.ReadFile(targetPath)
if err != nil {
t.Error(err)
continue
}
if string(content) != node.Content {
t.Errorf("wrong content for %v: want %q, got %q", name, node.Content, content)
}
case TestSymlink:
if fi.Mode()&os.ModeType != os.ModeSymlink {
t.Errorf("is not symlink: %v, %o != %o", name, fi.Mode(), os.ModeSymlink)
continue
}
target, err := fs.Readlink(targetPath)
if err != nil {
t.Error(err)
continue
}
if target != node.Target {
t.Errorf("wrong target for %v: want %q, got %q", name, node.Target, target)
}
case TestDir:
if !fi.IsDir() {
t.Errorf("is not directory: %v", name)
}
}
}
})
}
}
func TestTestWalkFiles(t *testing.T) {
var tests = []struct {
dir TestDir
want map[string]string
}{
{
dir: TestDir{
"foo": TestFile{Content: "foo"},
"subdir": TestDir{
"subfile": TestFile{Content: "bar"},
},
"x": TestDir{
"y": TestDir{
"link": TestSymlink{Target: filepath.FromSlash("../../foo")},
},
},
},
want: map[string]string{
"foo": "<File>",
"subdir": "<Dir>",
filepath.FromSlash("subdir/subfile"): "<File>",
"x": "<Dir>",
filepath.FromSlash("x/y"): "<Dir>",
filepath.FromSlash("x/y/link"): "<Symlink>",
},
},
}
for _, test := range tests {
t.Run("", func(t *testing.T) {
tempdir, cleanup := restictest.TempDir(t)
defer cleanup()
got := make(map[string]string)
TestCreateFiles(t, tempdir, test.dir)
TestWalkFiles(t, tempdir, test.dir, func(path string, item interface{}) error {
p, err := filepath.Rel(tempdir, path)
if err != nil {
return err
}
got[p] = fmt.Sprint(item)
return nil
})
if !cmp.Equal(test.want, got) {
t.Error(cmp.Diff(test.want, got))
}
})
}
}
func TestTestEnsureFiles(t *testing.T) {
var tests = []struct {
expectFailure bool
files map[string]interface{}
want TestDir
}{
{
files: map[string]interface{}{
"foo": TestFile{Content: "foo"},
"subdir/subfile": TestFile{Content: "bar"},
"x/y/link": TestSymlink{Target: filepath.Clean("../../foo")},
},
want: TestDir{
"foo": TestFile{Content: "foo"},
"subdir": TestDir{
"subfile": TestFile{Content: "bar"},
},
"x": TestDir{
"y": TestDir{
"link": TestSymlink{Target: filepath.Clean("../../foo")},
},
},
},
},
{
expectFailure: true,
files: map[string]interface{}{
"foo": TestFile{Content: "foo"},
},
want: TestDir{
"foo": TestFile{Content: "foo"},
"subdir": TestDir{
"subfile": TestFile{Content: "bar"},
},
},
},
{
expectFailure: true,
files: map[string]interface{}{
"foo": TestFile{Content: "foo"},
"subdir/subfile": TestFile{Content: "bar"},
},
want: TestDir{
"foo": TestFile{Content: "foo"},
},
},
{
expectFailure: true,
files: map[string]interface{}{
"foo": TestFile{Content: "xxx"},
},
want: TestDir{
"foo": TestFile{Content: "foo"},
},
},
{
expectFailure: true,
files: map[string]interface{}{
"foo": TestSymlink{Target: "/xxx"},
},
want: TestDir{
"foo": TestFile{Content: "foo"},
},
},
{
expectFailure: true,
files: map[string]interface{}{
"foo": TestFile{Content: "foo"},
},
want: TestDir{
"foo": TestSymlink{Target: "/xxx"},
},
},
{
expectFailure: true,
files: map[string]interface{}{
"foo": TestSymlink{Target: "xxx"},
},
want: TestDir{
"foo": TestSymlink{Target: "/yyy"},
},
},
{
expectFailure: true,
files: map[string]interface{}{
"foo": TestDir{
"foo": TestFile{Content: "foo"},
},
},
want: TestDir{
"foo": TestFile{Content: "foo"},
},
},
{
expectFailure: true,
files: map[string]interface{}{
"foo": TestFile{Content: "foo"},
},
want: TestDir{
"foo": TestDir{
"foo": TestFile{Content: "foo"},
},
},
},
}
for _, test := range tests {
t.Run("", func(t *testing.T) {
tempdir, cleanup := restictest.TempDir(t)
defer cleanup()
createFilesAt(t, tempdir, test.files)
subtestT := testing.TB(t)
if test.expectFailure {
subtestT = &MockT{T: t}
}
TestEnsureFiles(subtestT, tempdir, test.want)
if test.expectFailure && !subtestT.(*MockT).HasFailed {
t.Fatal("expected failure of TestEnsureFiles not found")
}
})
}
}
func TestTestEnsureSnapshot(t *testing.T) {
var tests = []struct {
expectFailure bool
files map[string]interface{}
want TestDir
}{
{
files: map[string]interface{}{
"foo": TestFile{Content: "foo"},
filepath.FromSlash("subdir/subfile"): TestFile{Content: "bar"},
filepath.FromSlash("x/y/link"): TestSymlink{Target: filepath.FromSlash("../../foo")},
},
want: TestDir{
"target": TestDir{
"foo": TestFile{Content: "foo"},
"subdir": TestDir{
"subfile": TestFile{Content: "bar"},
},
"x": TestDir{
"y": TestDir{
"link": TestSymlink{Target: filepath.FromSlash("../../foo")},
},
},
},
},
},
{
expectFailure: true,
files: map[string]interface{}{
"foo": TestFile{Content: "foo"},
},
want: TestDir{
"target": TestDir{
"bar": TestFile{Content: "foo"},
},
},
},
{
expectFailure: true,
files: map[string]interface{}{
"foo": TestFile{Content: "foo"},
"bar": TestFile{Content: "bar"},
},
want: TestDir{
"target": TestDir{
"foo": TestFile{Content: "foo"},
},
},
},
{
expectFailure: true,
files: map[string]interface{}{
"foo": TestFile{Content: "foo"},
},
want: TestDir{
"target": TestDir{
"foo": TestFile{Content: "foo"},
"bar": TestFile{Content: "bar"},
},
},
},
{
expectFailure: true,
files: map[string]interface{}{
"foo": TestFile{Content: "foo"},
},
want: TestDir{
"target": TestDir{
"foo": TestDir{
"foo": TestFile{Content: "foo"},
},
},
},
},
{
expectFailure: true,
files: map[string]interface{}{
"foo": TestSymlink{Target: filepath.FromSlash("x/y/z")},
},
want: TestDir{
"target": TestDir{
"foo": TestFile{Content: "foo"},
},
},
},
{
expectFailure: true,
files: map[string]interface{}{
"foo": TestSymlink{Target: filepath.FromSlash("x/y/z")},
},
want: TestDir{
"target": TestDir{
"foo": TestSymlink{Target: filepath.FromSlash("x/y/z2")},
},
},
},
{
expectFailure: true,
files: map[string]interface{}{
"foo": TestFile{Content: "foo"},
},
want: TestDir{
"target": TestDir{
"foo": TestFile{Content: "xxx"},
},
},
},
}
for _, test := range tests {
t.Run("", func(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
tempdir, cleanup := restictest.TempDir(t)
defer cleanup()
targetDir := filepath.Join(tempdir, "target")
err := fs.Mkdir(targetDir, 0700)
if err != nil {
t.Fatal(err)
}
createFilesAt(t, targetDir, test.files)
back := restictest.Chdir(t, tempdir)
defer back()
repo, cleanup := repository.TestRepository(t)
defer cleanup()
arch := New(repo, fs.Local{}, Options{})
opts := SnapshotOptions{
Time: time.Now(),
Hostname: "localhost",
Tags: []string{"test"},
}
_, id, err := arch.Snapshot(ctx, []string{"."}, opts)
if err != nil {
t.Fatal(err)
}
t.Logf("snapshot saved as %v", id.Str())
subtestT := testing.TB(t)
if test.expectFailure {
subtestT = &MockT{T: t}
}
TestEnsureSnapshot(subtestT, repo, id, test.want)
if test.expectFailure && !subtestT.(*MockT).HasFailed {
t.Fatal("expected failure of TestEnsureSnapshot not found")
}
})
}
}