diff --git a/internal/restorer/restorer_test.go b/internal/restorer/restorer_test.go index 00c56bccd..d37cffb16 100644 --- a/internal/restorer/restorer_test.go +++ b/internal/restorer/restorer_test.go @@ -6,6 +6,7 @@ import ( "io/ioutil" "os" "path/filepath" + "runtime" "strings" "testing" "time" @@ -23,14 +24,17 @@ type Snapshot struct { } type File struct { - Data string - Links uint64 - Inode uint64 + Data string + Links uint64 + Inode uint64 + Mode os.FileMode + ModTime time.Time } type Dir struct { - Nodes map[string]Node - Mode os.FileMode + Nodes map[string]Node + Mode os.FileMode + ModTime time.Time } func saveFile(t testing.TB, repo restic.Repository, node File) restic.ID { @@ -66,9 +70,14 @@ func saveDir(t testing.TB, repo restic.Repository, nodes map[string]Node, inode if len(n.(File).Data) > 0 { fc = append(fc, saveFile(t, repo, node)) } + mode := node.Mode + if mode == 0 { + mode = 0644 + } tree.Insert(&restic.Node{ Type: "file", - Mode: 0644, + Mode: mode, + ModTime: node.ModTime, Name: name, UID: uint32(os.Getuid()), GID: uint32(os.Getgid()), @@ -88,6 +97,7 @@ func saveDir(t testing.TB, repo restic.Repository, nodes map[string]Node, inode tree.Insert(&restic.Node{ Type: "dir", Mode: mode, + ModTime: node.ModTime, Name: name, UID: uint32(os.Getuid()), GID: uint32(os.Getgid()), @@ -655,6 +665,7 @@ func TestRestorerTraverseTree(t *testing.T) { }, Visitor: checkVisitOrder([]TreeVisit{ {"visitNode", "/dir/otherfile"}, + {"leaveDir", "/dir"}, }), }, } @@ -688,3 +699,108 @@ func TestRestorerTraverseTree(t *testing.T) { }) } } + +func normalizeFileMode(mode os.FileMode) os.FileMode { + if runtime.GOOS == "windows" { + if mode.IsDir() { + return 0555 | os.ModeDir + } + return os.FileMode(0444) + } + return mode +} + +func checkConsistentInfo(t testing.TB, file string, fi os.FileInfo, modtime time.Time, mode os.FileMode) { + if fi.Mode() != mode { + t.Errorf("checking %q, Mode() returned wrong value, want 0%o, got 0%o", file, mode, fi.Mode()) + } + + if !fi.ModTime().Equal(modtime) { + t.Errorf("checking %s, ModTime() returned wrong value, want %v, got %v", file, modtime, fi.ModTime()) + } +} + +// test inspired from test case https://github.com/restic/restic/issues/1212 +func TestRestorerConsistentTimestampsAndPermissions(t *testing.T) { + timeForTest := time.Date(2019, time.January, 9, 1, 46, 40, 0, time.UTC) + + repo, cleanup := repository.TestRepository(t) + defer cleanup() + + _, id := saveSnapshot(t, repo, Snapshot{ + Nodes: map[string]Node{ + "dir": Dir{ + Mode: normalizeFileMode(0750 | os.ModeDir), + ModTime: timeForTest, + Nodes: map[string]Node{ + "file1": File{ + Mode: normalizeFileMode(os.FileMode(0700)), + ModTime: timeForTest, + Data: "content: file\n", + }, + "anotherfile": File{ + Data: "content: file\n", + }, + "subdir": Dir{ + Mode: normalizeFileMode(0700 | os.ModeDir), + ModTime: timeForTest, + Nodes: map[string]Node{ + "file2": File{ + Mode: normalizeFileMode(os.FileMode(0666)), + ModTime: timeForTest, + Links: 2, + Inode: 1, + }, + }, + }, + }, + }, + }, + }) + + res, err := NewRestorer(repo, id) + rtest.OK(t, err) + + res.SelectFilter = func(item string, dstpath string, node *restic.Node) (selectedForRestore bool, childMayBeSelected bool) { + switch filepath.ToSlash(item) { + case "/dir": + childMayBeSelected = true + case "/dir/file1": + selectedForRestore = true + childMayBeSelected = false + case "/dir/subdir": + selectedForRestore = true + childMayBeSelected = true + case "/dir/subdir/file2": + selectedForRestore = true + childMayBeSelected = false + } + return selectedForRestore, childMayBeSelected + } + + tempdir, cleanup := rtest.TempDir(t) + defer cleanup() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + err = res.RestoreTo(ctx, tempdir) + rtest.OK(t, err) + + var testPatterns = []struct { + path string + modtime time.Time + mode os.FileMode + }{ + {"dir", timeForTest, normalizeFileMode(0750 | os.ModeDir)}, + {filepath.Join("dir", "file1"), timeForTest, normalizeFileMode(os.FileMode(0700))}, + {filepath.Join("dir", "subdir"), timeForTest, normalizeFileMode(0700 | os.ModeDir)}, + {filepath.Join("dir", "subdir", "file2"), timeForTest, normalizeFileMode(os.FileMode(0666))}, + } + + for _, test := range testPatterns { + f, err := os.Stat(filepath.Join(tempdir, test.path)) + rtest.OK(t, err) + checkConsistentInfo(t, test.path, f, test.modtime, test.mode) + } +}