mirror of
https://github.com/restic/restic.git
synced 2024-08-24 21:27:23 +02:00
Before, the scanner would could files twice if they were included in the list of backup targets twice, e.g. `restic backup foo foo/bar` would could the file `foo/bar` twice. This commit uses the tree structure from the archiver to run the scanner, so both parts see the same files.
328 lines
7.5 KiB
Go
328 lines
7.5 KiB
Go
package archiver
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"testing"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
"github.com/restic/restic/internal/fs"
|
|
restictest "github.com/restic/restic/internal/test"
|
|
)
|
|
|
|
func TestScanner(t *testing.T) {
|
|
var tests = []struct {
|
|
name string
|
|
src TestDir
|
|
want map[string]ScanStats
|
|
selFn SelectFunc
|
|
}{
|
|
{
|
|
name: "include-all",
|
|
src: TestDir{
|
|
"other": TestFile{Content: "another file"},
|
|
"work": TestDir{
|
|
"foo": TestFile{Content: "foo"},
|
|
"foo.txt": TestFile{Content: "foo text file"},
|
|
"subdir": TestDir{
|
|
"other": TestFile{Content: "other in subdir"},
|
|
"bar.txt": TestFile{Content: "bar.txt in subdir"},
|
|
},
|
|
},
|
|
},
|
|
want: map[string]ScanStats{
|
|
filepath.FromSlash("other"): {Files: 1, Bytes: 12},
|
|
filepath.FromSlash("work/foo"): {Files: 2, Bytes: 15},
|
|
filepath.FromSlash("work/foo.txt"): {Files: 3, Bytes: 28},
|
|
filepath.FromSlash("work/subdir/bar.txt"): {Files: 4, Bytes: 45},
|
|
filepath.FromSlash("work/subdir/other"): {Files: 5, Bytes: 60},
|
|
filepath.FromSlash("work/subdir"): {Files: 5, Dirs: 1, Bytes: 60},
|
|
filepath.FromSlash("work"): {Files: 5, Dirs: 2, Bytes: 60},
|
|
filepath.FromSlash(""): {Files: 5, Dirs: 2, Bytes: 60},
|
|
},
|
|
},
|
|
{
|
|
name: "select-txt",
|
|
src: TestDir{
|
|
"other": TestFile{Content: "another file"},
|
|
"work": TestDir{
|
|
"foo": TestFile{Content: "foo"},
|
|
"foo.txt": TestFile{Content: "foo text file"},
|
|
"subdir": TestDir{
|
|
"other": TestFile{Content: "other in subdir"},
|
|
"bar.txt": TestFile{Content: "bar.txt in subdir"},
|
|
},
|
|
},
|
|
},
|
|
selFn: func(item string, fi os.FileInfo) bool {
|
|
if fi.IsDir() {
|
|
return true
|
|
}
|
|
|
|
if filepath.Ext(item) == ".txt" {
|
|
return true
|
|
}
|
|
return false
|
|
},
|
|
want: map[string]ScanStats{
|
|
filepath.FromSlash("work/foo.txt"): {Files: 1, Bytes: 13},
|
|
filepath.FromSlash("work/subdir/bar.txt"): {Files: 2, Bytes: 30},
|
|
filepath.FromSlash("work/subdir"): {Files: 2, Dirs: 1, Bytes: 30},
|
|
filepath.FromSlash("work"): {Files: 2, Dirs: 2, Bytes: 30},
|
|
filepath.FromSlash(""): {Files: 2, Dirs: 2, Bytes: 30},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
|
|
tempdir, cleanup := restictest.TempDir(t)
|
|
defer cleanup()
|
|
|
|
TestCreateFiles(t, tempdir, test.src)
|
|
|
|
back := restictest.Chdir(t, tempdir)
|
|
defer back()
|
|
|
|
cur, err := os.Getwd()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
sc := NewScanner(fs.Track{FS: fs.Local{}})
|
|
if test.selFn != nil {
|
|
sc.Select = test.selFn
|
|
}
|
|
|
|
results := make(map[string]ScanStats)
|
|
sc.Result = func(item string, s ScanStats) {
|
|
var p string
|
|
var err error
|
|
|
|
if item != "" {
|
|
p, err = filepath.Rel(cur, item)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
results[p] = s
|
|
}
|
|
|
|
err = sc.Scan(ctx, []string{"."})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if !cmp.Equal(test.want, results) {
|
|
t.Error(cmp.Diff(test.want, results))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestScannerError(t *testing.T) {
|
|
var tests = []struct {
|
|
name string
|
|
unix bool
|
|
src TestDir
|
|
result ScanStats
|
|
selFn SelectFunc
|
|
errFn func(t testing.TB, item string, fi os.FileInfo, err error) error
|
|
resFn func(t testing.TB, item string, s ScanStats)
|
|
prepare func(t testing.TB)
|
|
}{
|
|
{
|
|
name: "no-error",
|
|
src: TestDir{
|
|
"other": TestFile{Content: "another file"},
|
|
"work": TestDir{
|
|
"foo": TestFile{Content: "foo"},
|
|
"foo.txt": TestFile{Content: "foo text file"},
|
|
"subdir": TestDir{
|
|
"other": TestFile{Content: "other in subdir"},
|
|
"bar.txt": TestFile{Content: "bar.txt in subdir"},
|
|
},
|
|
},
|
|
},
|
|
result: ScanStats{Files: 5, Dirs: 2, Bytes: 60},
|
|
},
|
|
{
|
|
name: "unreadable-dir",
|
|
unix: true,
|
|
src: TestDir{
|
|
"other": TestFile{Content: "another file"},
|
|
"work": TestDir{
|
|
"foo": TestFile{Content: "foo"},
|
|
"foo.txt": TestFile{Content: "foo text file"},
|
|
"subdir": TestDir{
|
|
"other": TestFile{Content: "other in subdir"},
|
|
"bar.txt": TestFile{Content: "bar.txt in subdir"},
|
|
},
|
|
},
|
|
},
|
|
result: ScanStats{Files: 3, Dirs: 1, Bytes: 28},
|
|
prepare: func(t testing.TB) {
|
|
err := os.Chmod(filepath.Join("work", "subdir"), 0000)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
},
|
|
errFn: func(t testing.TB, item string, fi os.FileInfo, err error) error {
|
|
if item == filepath.FromSlash("work/subdir") {
|
|
return nil
|
|
}
|
|
|
|
return err
|
|
},
|
|
},
|
|
{
|
|
name: "removed-item",
|
|
src: TestDir{
|
|
"bar": TestFile{Content: "bar"},
|
|
"baz": TestFile{Content: "baz"},
|
|
"foo": TestFile{Content: "foo"},
|
|
"other": TestFile{Content: "other"},
|
|
},
|
|
result: ScanStats{Files: 3, Dirs: 0, Bytes: 11},
|
|
resFn: func(t testing.TB, item string, s ScanStats) {
|
|
if item == "bar" {
|
|
err := os.Remove("foo")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
},
|
|
errFn: func(t testing.TB, item string, fi os.FileInfo, err error) error {
|
|
if item == "foo" {
|
|
t.Logf("ignoring error for %v: %v", item, err)
|
|
return nil
|
|
}
|
|
|
|
return err
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
if test.unix && runtime.GOOS == "windows" {
|
|
t.Skipf("skip on windows")
|
|
}
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
|
|
tempdir, cleanup := restictest.TempDir(t)
|
|
defer cleanup()
|
|
|
|
TestCreateFiles(t, tempdir, test.src)
|
|
|
|
back := restictest.Chdir(t, tempdir)
|
|
defer back()
|
|
|
|
cur, err := os.Getwd()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if test.prepare != nil {
|
|
test.prepare(t)
|
|
}
|
|
|
|
sc := NewScanner(fs.Track{FS: fs.Local{}})
|
|
if test.selFn != nil {
|
|
sc.Select = test.selFn
|
|
}
|
|
|
|
var stats ScanStats
|
|
|
|
sc.Result = func(item string, s ScanStats) {
|
|
if item == "" {
|
|
stats = s
|
|
return
|
|
}
|
|
|
|
if test.resFn != nil {
|
|
p, relErr := filepath.Rel(cur, item)
|
|
if relErr != nil {
|
|
panic(relErr)
|
|
}
|
|
test.resFn(t, p, s)
|
|
}
|
|
}
|
|
if test.errFn != nil {
|
|
sc.Error = func(item string, fi os.FileInfo, err error) error {
|
|
p, relErr := filepath.Rel(cur, item)
|
|
if relErr != nil {
|
|
panic(relErr)
|
|
}
|
|
|
|
return test.errFn(t, p, fi, err)
|
|
}
|
|
}
|
|
|
|
err = sc.Scan(ctx, []string{"."})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if stats != test.result {
|
|
t.Errorf("wrong final result, want\n %#v\ngot:\n %#v", test.result, stats)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestScannerCancel(t *testing.T) {
|
|
src := TestDir{
|
|
"bar": TestFile{Content: "bar"},
|
|
"baz": TestFile{Content: "baz"},
|
|
"foo": TestFile{Content: "foo"},
|
|
"other": TestFile{Content: "other"},
|
|
}
|
|
|
|
result := ScanStats{Files: 2, Dirs: 0, Bytes: 6}
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
|
|
tempdir, cleanup := restictest.TempDir(t)
|
|
defer cleanup()
|
|
|
|
TestCreateFiles(t, tempdir, src)
|
|
|
|
back := restictest.Chdir(t, tempdir)
|
|
defer back()
|
|
|
|
cur, err := os.Getwd()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
sc := NewScanner(fs.Track{FS: fs.Local{}})
|
|
var lastStats ScanStats
|
|
sc.Result = func(item string, s ScanStats) {
|
|
lastStats = s
|
|
|
|
if item == filepath.Join(cur, "baz") {
|
|
t.Logf("found baz")
|
|
cancel()
|
|
}
|
|
}
|
|
|
|
err = sc.Scan(ctx, []string{"."})
|
|
if err != nil {
|
|
t.Errorf("unexpected error %v found", err)
|
|
}
|
|
|
|
if lastStats != result {
|
|
t.Errorf("wrong final result, want\n %#v\ngot:\n %#v", result, lastStats)
|
|
}
|
|
}
|