forked from TrueCloudLab/restic
bf054c09d2
On FreeBSD, limited users may not be able to even list xattrs for the parent directories above the snapshot source paths. As this can cause the backup to fail, just ignore those errors.
246 lines
6.5 KiB
Go
246 lines
6.5 KiB
Go
package restic_test
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"testing"
|
|
|
|
"github.com/restic/restic/internal/archiver"
|
|
"github.com/restic/restic/internal/repository"
|
|
"github.com/restic/restic/internal/restic"
|
|
rtest "github.com/restic/restic/internal/test"
|
|
"golang.org/x/sync/errgroup"
|
|
)
|
|
|
|
var testFiles = []struct {
|
|
name string
|
|
content []byte
|
|
}{
|
|
{"foo", []byte("bar")},
|
|
{"bar/foo2", []byte("bar2")},
|
|
{"bar/bla/blubb", []byte("This is just a test!\n")},
|
|
}
|
|
|
|
func createTempDir(t *testing.T) string {
|
|
tempdir, err := os.MkdirTemp(rtest.TestTempDir, "restic-test-")
|
|
rtest.OK(t, err)
|
|
|
|
for _, test := range testFiles {
|
|
file := filepath.Join(tempdir, test.name)
|
|
dir := filepath.Dir(file)
|
|
if dir != "." {
|
|
rtest.OK(t, os.MkdirAll(dir, 0755))
|
|
}
|
|
|
|
f, err := os.Create(file)
|
|
defer func() {
|
|
rtest.OK(t, f.Close())
|
|
}()
|
|
|
|
rtest.OK(t, err)
|
|
|
|
_, err = f.Write(test.content)
|
|
rtest.OK(t, err)
|
|
}
|
|
|
|
return tempdir
|
|
}
|
|
|
|
func TestTree(t *testing.T) {
|
|
dir := createTempDir(t)
|
|
defer func() {
|
|
if rtest.TestCleanupTempDirs {
|
|
rtest.RemoveAll(t, dir)
|
|
}
|
|
}()
|
|
}
|
|
|
|
var testNodes = []restic.Node{
|
|
{Name: "normal"},
|
|
{Name: "with backslashes \\zzz"},
|
|
{Name: "test utf-8 föbärß"},
|
|
{Name: "test invalid \x00\x01\x02\x03\x04"},
|
|
{Name: "test latin1 \x75\x6d\x6c\xe4\xfc\x74\xf6\x6e\xdf\x6e\x6c\x6c"},
|
|
}
|
|
|
|
func TestNodeMarshal(t *testing.T) {
|
|
for i, n := range testNodes {
|
|
data, err := json.Marshal(&n)
|
|
rtest.OK(t, err)
|
|
|
|
var node restic.Node
|
|
err = json.Unmarshal(data, &node)
|
|
rtest.OK(t, err)
|
|
|
|
if n.Name != node.Name {
|
|
t.Fatalf("Node %d: Names are not equal, want: %q got: %q", i, n.Name, node.Name)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestNodeComparison(t *testing.T) {
|
|
fi, err := os.Lstat("tree_test.go")
|
|
rtest.OK(t, err)
|
|
|
|
node, err := restic.NodeFromFileInfo("tree_test.go", fi, false)
|
|
rtest.OK(t, err)
|
|
|
|
n2 := *node
|
|
rtest.Assert(t, node.Equals(n2), "nodes aren't equal")
|
|
|
|
n2.Size--
|
|
rtest.Assert(t, !node.Equals(n2), "nodes are equal")
|
|
}
|
|
|
|
func TestEmptyLoadTree(t *testing.T) {
|
|
repo := repository.TestRepository(t)
|
|
|
|
var wg errgroup.Group
|
|
repo.StartPackUploader(context.TODO(), &wg)
|
|
// save tree
|
|
tree := restic.NewTree(0)
|
|
id, err := restic.SaveTree(context.TODO(), repo, tree)
|
|
rtest.OK(t, err)
|
|
|
|
// save packs
|
|
rtest.OK(t, repo.Flush(context.Background()))
|
|
|
|
// load tree again
|
|
tree2, err := restic.LoadTree(context.TODO(), repo, id)
|
|
rtest.OK(t, err)
|
|
|
|
rtest.Assert(t, tree.Equals(tree2),
|
|
"trees are not equal: want %v, got %v",
|
|
tree, tree2)
|
|
}
|
|
|
|
func TestTreeEqualSerialization(t *testing.T) {
|
|
files := []string{"node.go", "tree.go", "tree_test.go"}
|
|
for i := 1; i <= len(files); i++ {
|
|
tree := restic.NewTree(i)
|
|
builder := restic.NewTreeJSONBuilder()
|
|
|
|
for _, fn := range files[:i] {
|
|
fi, err := os.Lstat(fn)
|
|
rtest.OK(t, err)
|
|
node, err := restic.NodeFromFileInfo(fn, fi, false)
|
|
rtest.OK(t, err)
|
|
|
|
rtest.OK(t, tree.Insert(node))
|
|
rtest.OK(t, builder.AddNode(node))
|
|
|
|
rtest.Assert(t, tree.Insert(node) != nil, "no error on duplicate node")
|
|
rtest.Assert(t, builder.AddNode(node) != nil, "no error on duplicate node")
|
|
rtest.Assert(t, errors.Is(builder.AddNode(node), restic.ErrTreeNotOrdered), "wrong error returned")
|
|
}
|
|
|
|
treeBytes, err := json.Marshal(tree)
|
|
treeBytes = append(treeBytes, '\n')
|
|
rtest.OK(t, err)
|
|
|
|
stiBytes, err := builder.Finalize()
|
|
rtest.OK(t, err)
|
|
|
|
// compare serialization of an individual node and the SaveTreeIterator
|
|
rtest.Equals(t, treeBytes, stiBytes)
|
|
}
|
|
}
|
|
|
|
func BenchmarkBuildTree(b *testing.B) {
|
|
const size = 100 // Directories of this size are not uncommon.
|
|
|
|
nodes := make([]restic.Node, size)
|
|
for i := range nodes {
|
|
// Archiver.SaveTree inputs in sorted order, so do that here too.
|
|
nodes[i].Name = strconv.Itoa(i)
|
|
}
|
|
|
|
b.ResetTimer()
|
|
b.ReportAllocs()
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
t := restic.NewTree(size)
|
|
|
|
for i := range nodes {
|
|
_ = t.Insert(&nodes[i])
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestLoadTree(t *testing.T) {
|
|
repository.TestAllVersions(t, testLoadTree)
|
|
}
|
|
|
|
func testLoadTree(t *testing.T, version uint) {
|
|
if rtest.BenchArchiveDirectory == "" {
|
|
t.Skip("benchdir not set, skipping")
|
|
}
|
|
|
|
// archive a few files
|
|
repo := repository.TestRepositoryWithVersion(t, version)
|
|
sn := archiver.TestSnapshot(t, repo, rtest.BenchArchiveDirectory, nil)
|
|
rtest.OK(t, repo.Flush(context.Background()))
|
|
|
|
_, err := restic.LoadTree(context.TODO(), repo, *sn.Tree)
|
|
rtest.OK(t, err)
|
|
}
|
|
|
|
func BenchmarkLoadTree(t *testing.B) {
|
|
repository.BenchmarkAllVersions(t, benchmarkLoadTree)
|
|
}
|
|
|
|
func benchmarkLoadTree(t *testing.B, version uint) {
|
|
if rtest.BenchArchiveDirectory == "" {
|
|
t.Skip("benchdir not set, skipping")
|
|
}
|
|
|
|
// archive a few files
|
|
repo := repository.TestRepositoryWithVersion(t, version)
|
|
sn := archiver.TestSnapshot(t, repo, rtest.BenchArchiveDirectory, nil)
|
|
rtest.OK(t, repo.Flush(context.Background()))
|
|
|
|
t.ResetTimer()
|
|
|
|
for i := 0; i < t.N; i++ {
|
|
_, err := restic.LoadTree(context.TODO(), repo, *sn.Tree)
|
|
rtest.OK(t, err)
|
|
}
|
|
}
|
|
|
|
func TestFindTreeDirectory(t *testing.T) {
|
|
repo := repository.TestRepository(t)
|
|
sn := restic.TestCreateSnapshot(t, repo, parseTimeUTC("2017-07-07 07:07:08"), 3)
|
|
|
|
for _, exp := range []struct {
|
|
subfolder string
|
|
id restic.ID
|
|
err error
|
|
}{
|
|
{"", restic.TestParseID("c25199703a67455b34cc0c6e49a8ac8861b268a5dd09dc5b2e31e7380973fc97"), nil},
|
|
{"/", restic.TestParseID("c25199703a67455b34cc0c6e49a8ac8861b268a5dd09dc5b2e31e7380973fc97"), nil},
|
|
{".", restic.TestParseID("c25199703a67455b34cc0c6e49a8ac8861b268a5dd09dc5b2e31e7380973fc97"), nil},
|
|
{"..", restic.ID{}, errors.New("path ..: not found")},
|
|
{"file-1", restic.ID{}, errors.New("path file-1: not a directory")},
|
|
{"dir-21", restic.TestParseID("76172f9dec15d7e4cb98d2993032e99f06b73b2f02ffea3b7cfd9e6b4d762712"), nil},
|
|
{"/dir-21", restic.TestParseID("76172f9dec15d7e4cb98d2993032e99f06b73b2f02ffea3b7cfd9e6b4d762712"), nil},
|
|
{"dir-21/", restic.TestParseID("76172f9dec15d7e4cb98d2993032e99f06b73b2f02ffea3b7cfd9e6b4d762712"), nil},
|
|
{"dir-21/dir-24", restic.TestParseID("74626b3fb2bd4b3e572b81a4059b3e912bcf2a8f69fecd9c187613b7173f13b1"), nil},
|
|
} {
|
|
t.Run("", func(t *testing.T) {
|
|
id, err := restic.FindTreeDirectory(context.TODO(), repo, sn.Tree, exp.subfolder)
|
|
if exp.err == nil {
|
|
rtest.OK(t, err)
|
|
rtest.Assert(t, exp.id == *id, "unexpected id, expected %v, got %v", exp.id, id)
|
|
} else {
|
|
rtest.Assert(t, exp.err.Error() == err.Error(), "unexpected err, expected %v, got %v", exp.err, err)
|
|
}
|
|
})
|
|
}
|
|
|
|
_, err := restic.FindTreeDirectory(context.TODO(), repo, nil, "")
|
|
rtest.Assert(t, err != nil, "missing error on null tree id")
|
|
}
|