| 
									
										
										
										
											2016-09-04 12:52:43 +02:00
										 |  |  | package archiver | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							| 
									
										
										
										
											2017-06-04 11:16:55 +02:00
										 |  |  | 	"context" | 
					
						
							| 
									
										
										
										
											2018-03-30 22:43:18 +02:00
										 |  |  | 	"os" | 
					
						
							|  |  |  | 	"path" | 
					
						
							|  |  |  | 	"path/filepath" | 
					
						
							|  |  |  | 	"runtime" | 
					
						
							|  |  |  | 	"strings" | 
					
						
							| 
									
										
										
										
											2016-09-04 12:52:43 +02:00
										 |  |  | 	"testing" | 
					
						
							| 
									
										
										
										
											2017-08-29 18:29:46 +02:00
										 |  |  | 	"time" | 
					
						
							| 
									
										
										
										
											2017-07-23 14:21:03 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-12 14:48:30 +02:00
										 |  |  | 	"github.com/restic/restic/internal/crypto" | 
					
						
							| 
									
										
										
										
											2018-03-30 22:43:18 +02:00
										 |  |  | 	"github.com/restic/restic/internal/debug" | 
					
						
							|  |  |  | 	"github.com/restic/restic/internal/fs" | 
					
						
							| 
									
										
										
										
											2017-07-24 17:42:25 +02:00
										 |  |  | 	"github.com/restic/restic/internal/restic" | 
					
						
							| 
									
										
										
										
											2016-09-04 12:52:43 +02:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // TestSnapshot creates a new snapshot of path. | 
					
						
							|  |  |  | func TestSnapshot(t testing.TB, repo restic.Repository, path string, parent *restic.ID) *restic.Snapshot { | 
					
						
							| 
									
										
										
										
											2018-03-25 23:36:31 +02:00
										 |  |  | 	arch := New(repo, fs.Local{}, Options{}) | 
					
						
							|  |  |  | 	opts := SnapshotOptions{ | 
					
						
							|  |  |  | 		Time:     time.Now(), | 
					
						
							|  |  |  | 		Hostname: "localhost", | 
					
						
							|  |  |  | 		Tags:     []string{"test"}, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if parent != nil { | 
					
						
							| 
									
										
										
										
											2022-10-03 14:48:14 +02:00
										 |  |  | 		sn, err := restic.LoadSnapshot(context.TODO(), arch.Repo, *parent) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			t.Fatal(err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		opts.ParentSnapshot = sn | 
					
						
							| 
									
										
										
										
											2018-03-25 23:36:31 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	sn, _, err := arch.Snapshot(context.TODO(), []string{path}, opts) | 
					
						
							| 
									
										
										
										
											2016-09-04 12:52:43 +02:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return sn | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2018-03-30 22:43:18 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | // TestDir describes a directory structure to create for a test. | 
					
						
							|  |  |  | type TestDir map[string]interface{} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (d TestDir) String() string { | 
					
						
							|  |  |  | 	return "<Dir>" | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // TestFile describes a file created for a test. | 
					
						
							|  |  |  | type TestFile struct { | 
					
						
							|  |  |  | 	Content string | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (f TestFile) String() string { | 
					
						
							|  |  |  | 	return "<File>" | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // TestSymlink describes a symlink created for a test. | 
					
						
							|  |  |  | type TestSymlink struct { | 
					
						
							|  |  |  | 	Target string | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (s TestSymlink) String() string { | 
					
						
							|  |  |  | 	return "<Symlink>" | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // TestCreateFiles creates a directory structure described by dir at target, | 
					
						
							|  |  |  | // which must already exist. On Windows, symlinks aren't created. | 
					
						
							|  |  |  | func TestCreateFiles(t testing.TB, target string, dir TestDir) { | 
					
						
							| 
									
										
										
										
											2020-02-17 13:07:55 +01:00
										 |  |  | 	t.Helper() | 
					
						
							| 
									
										
										
										
											2018-03-30 22:43:18 +02:00
										 |  |  | 	for name, item := range dir { | 
					
						
							|  |  |  | 		targetPath := filepath.Join(target, name) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		switch it := item.(type) { | 
					
						
							|  |  |  | 		case TestFile: | 
					
						
							| 
									
										
										
										
											2022-12-02 19:36:43 +01:00
										 |  |  | 			err := os.WriteFile(targetPath, []byte(it.Content), 0644) | 
					
						
							| 
									
										
										
										
											2018-03-30 22:43:18 +02:00
										 |  |  | 			if err != nil { | 
					
						
							|  |  |  | 				t.Fatal(err) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		case TestSymlink: | 
					
						
							|  |  |  | 			err := fs.Symlink(filepath.FromSlash(it.Target), targetPath) | 
					
						
							|  |  |  | 			if err != nil { | 
					
						
							|  |  |  | 				t.Fatal(err) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		case TestDir: | 
					
						
							|  |  |  | 			err := fs.Mkdir(targetPath, 0755) | 
					
						
							|  |  |  | 			if err != nil { | 
					
						
							|  |  |  | 				t.Fatal(err) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			TestCreateFiles(t, targetPath, it) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // TestWalkFunc is used by TestWalkFiles to traverse the dir. When an error is | 
					
						
							|  |  |  | // returned, traversal stops and the surrounding test is marked as failed. | 
					
						
							|  |  |  | type TestWalkFunc func(path string, item interface{}) error | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // TestWalkFiles runs fn for each file/directory in dir, the filename will be | 
					
						
							|  |  |  | // constructed with target as the prefix. Symlinks on Windows are ignored. | 
					
						
							|  |  |  | func TestWalkFiles(t testing.TB, target string, dir TestDir, fn TestWalkFunc) { | 
					
						
							| 
									
										
										
										
											2020-02-17 13:07:55 +01:00
										 |  |  | 	t.Helper() | 
					
						
							| 
									
										
										
										
											2018-03-30 22:43:18 +02:00
										 |  |  | 	for name, item := range dir { | 
					
						
							|  |  |  | 		targetPath := filepath.Join(target, name) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		err := fn(targetPath, item) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			t.Fatalf("TestWalkFunc returned error for %v: %v", targetPath, err) | 
					
						
							|  |  |  | 			return | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if dir, ok := item.(TestDir); ok { | 
					
						
							|  |  |  | 			TestWalkFiles(t, targetPath, dir, fn) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // fixpath removes UNC paths (starting with `\\?`) on windows. On Linux, it's a noop. | 
					
						
							|  |  |  | func fixpath(item string) string { | 
					
						
							|  |  |  | 	if runtime.GOOS != "windows" { | 
					
						
							|  |  |  | 		return item | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if strings.HasPrefix(item, `\\?`) { | 
					
						
							|  |  |  | 		return item[4:] | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return item | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // TestEnsureFiles tests if the directory structure at target is the same as | 
					
						
							|  |  |  | // described in dir. | 
					
						
							|  |  |  | func TestEnsureFiles(t testing.TB, target string, dir TestDir) { | 
					
						
							| 
									
										
										
										
											2020-02-17 13:07:55 +01:00
										 |  |  | 	t.Helper() | 
					
						
							| 
									
										
										
										
											2018-03-30 22:43:18 +02:00
										 |  |  | 	pathsChecked := make(map[string]struct{}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// first, test that all items are there | 
					
						
							|  |  |  | 	TestWalkFiles(t, target, dir, func(path string, item interface{}) error { | 
					
						
							|  |  |  | 		fi, err := fs.Lstat(path) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		switch node := item.(type) { | 
					
						
							|  |  |  | 		case TestDir: | 
					
						
							|  |  |  | 			if !fi.IsDir() { | 
					
						
							|  |  |  | 				t.Errorf("is not a directory: %v", path) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			return nil | 
					
						
							|  |  |  | 		case TestFile: | 
					
						
							|  |  |  | 			if !fs.IsRegularFile(fi) { | 
					
						
							|  |  |  | 				t.Errorf("is not a regular file: %v", path) | 
					
						
							|  |  |  | 				return nil | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-12-02 19:36:43 +01:00
										 |  |  | 			content, err := os.ReadFile(path) | 
					
						
							| 
									
										
										
										
											2018-03-30 22:43:18 +02:00
										 |  |  | 			if err != nil { | 
					
						
							|  |  |  | 				return err | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			if string(content) != node.Content { | 
					
						
							|  |  |  | 				t.Errorf("wrong content for %v, want %q, got %q", path, node.Content, content) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		case TestSymlink: | 
					
						
							|  |  |  | 			if fi.Mode()&os.ModeType != os.ModeSymlink { | 
					
						
							|  |  |  | 				t.Errorf("is not a symlink: %v", path) | 
					
						
							|  |  |  | 				return nil | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			target, err := fs.Readlink(path) | 
					
						
							|  |  |  | 			if err != nil { | 
					
						
							|  |  |  | 				return err | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			if target != node.Target { | 
					
						
							|  |  |  | 				t.Errorf("wrong target for %v, want %v, got %v", path, node.Target, target) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		pathsChecked[path] = struct{}{} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		for parent := filepath.Dir(path); parent != target; parent = filepath.Dir(parent) { | 
					
						
							|  |  |  | 			pathsChecked[parent] = struct{}{} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// then, traverse the directory again, looking for additional files | 
					
						
							|  |  |  | 	err := fs.Walk(target, func(path string, fi os.FileInfo, err error) error { | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		path = fixpath(path) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if path == target { | 
					
						
							|  |  |  | 			return nil | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		_, ok := pathsChecked[path] | 
					
						
							|  |  |  | 		if !ok { | 
					
						
							|  |  |  | 			t.Errorf("additional item found: %v %v", path, fi.Mode()) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // TestEnsureFileContent checks if the file in the repo is the same as file. | 
					
						
							| 
									
										
										
										
											2024-01-19 22:44:50 +01:00
										 |  |  | func TestEnsureFileContent(ctx context.Context, t testing.TB, repo restic.BlobLoader, filename string, node *restic.Node, file TestFile) { | 
					
						
							| 
									
										
										
										
											2018-03-30 22:43:18 +02:00
										 |  |  | 	if int(node.Size) != len(file.Content) { | 
					
						
							|  |  |  | 		t.Fatalf("%v: wrong node size: want %d, got %d", filename, node.Size, len(file.Content)) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-12 14:48:30 +02:00
										 |  |  | 	content := make([]byte, crypto.CiphertextLength(len(file.Content))) | 
					
						
							| 
									
										
										
										
											2018-03-30 22:43:18 +02:00
										 |  |  | 	pos := 0 | 
					
						
							|  |  |  | 	for _, id := range node.Content { | 
					
						
							| 
									
										
										
										
											2020-03-10 16:41:22 +01:00
										 |  |  | 		part, err := repo.LoadBlob(ctx, restic.DataBlob, id, content[pos:]) | 
					
						
							| 
									
										
										
										
											2018-03-30 22:43:18 +02:00
										 |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			t.Fatalf("error loading blob %v: %v", id.Str(), err) | 
					
						
							|  |  |  | 			return | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-02-13 16:47:03 +01:00
										 |  |  | 		copy(content[pos:pos+len(part)], part) | 
					
						
							| 
									
										
										
										
											2020-03-10 16:41:22 +01:00
										 |  |  | 		pos += len(part) | 
					
						
							| 
									
										
										
										
											2018-03-30 22:43:18 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	content = content[:pos] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if string(content) != file.Content { | 
					
						
							|  |  |  | 		t.Fatalf("%v: wrong content returned, want %q, got %q", filename, file.Content, content) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // TestEnsureTree checks that the tree ID in the repo matches dir. On Windows, | 
					
						
							|  |  |  | // Symlinks are ignored. | 
					
						
							| 
									
										
										
										
											2024-01-19 22:44:50 +01:00
										 |  |  | func TestEnsureTree(ctx context.Context, t testing.TB, prefix string, repo restic.BlobLoader, treeID restic.ID, dir TestDir) { | 
					
						
							| 
									
										
										
										
											2020-02-17 13:07:55 +01:00
										 |  |  | 	t.Helper() | 
					
						
							| 
									
										
										
										
											2018-03-30 22:43:18 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-12 14:38:19 +02:00
										 |  |  | 	tree, err := restic.LoadTree(ctx, repo, treeID) | 
					
						
							| 
									
										
										
										
											2018-03-30 22:43:18 +02:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	var nodeNames []string | 
					
						
							|  |  |  | 	for _, node := range tree.Nodes { | 
					
						
							|  |  |  | 		nodeNames = append(nodeNames, node.Name) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	debug.Log("%v (%v) %v", prefix, treeID.Str(), nodeNames) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	checked := make(map[string]struct{}) | 
					
						
							|  |  |  | 	for _, node := range tree.Nodes { | 
					
						
							|  |  |  | 		nodePrefix := path.Join(prefix, node.Name) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		entry, ok := dir[node.Name] | 
					
						
							|  |  |  | 		if !ok { | 
					
						
							|  |  |  | 			t.Errorf("unexpected tree node %q found, want: %#v", node.Name, dir) | 
					
						
							|  |  |  | 			return | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		checked[node.Name] = struct{}{} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		switch e := entry.(type) { | 
					
						
							|  |  |  | 		case TestDir: | 
					
						
							|  |  |  | 			if node.Type != "dir" { | 
					
						
							|  |  |  | 				t.Errorf("tree node %v has wrong type %q, want %q", nodePrefix, node.Type, "dir") | 
					
						
							|  |  |  | 				return | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			if node.Subtree == nil { | 
					
						
							|  |  |  | 				t.Errorf("tree node %v has nil subtree", nodePrefix) | 
					
						
							|  |  |  | 				return | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			TestEnsureTree(ctx, t, path.Join(prefix, node.Name), repo, *node.Subtree, e) | 
					
						
							|  |  |  | 		case TestFile: | 
					
						
							|  |  |  | 			if node.Type != "file" { | 
					
						
							|  |  |  | 				t.Errorf("tree node %v has wrong type %q, want %q", nodePrefix, node.Type, "file") | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			TestEnsureFileContent(ctx, t, repo, nodePrefix, node, e) | 
					
						
							|  |  |  | 		case TestSymlink: | 
					
						
							|  |  |  | 			if node.Type != "symlink" { | 
					
						
							|  |  |  | 				t.Errorf("tree node %v has wrong type %q, want %q", nodePrefix, node.Type, "file") | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			if e.Target != node.LinkTarget { | 
					
						
							|  |  |  | 				t.Errorf("symlink %v has wrong target, want %q, got %q", nodePrefix, e.Target, node.LinkTarget) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for name := range dir { | 
					
						
							|  |  |  | 		_, ok := checked[name] | 
					
						
							|  |  |  | 		if !ok { | 
					
						
							|  |  |  | 			t.Errorf("tree %v: expected node %q not found, has: %v", prefix, name, nodeNames) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // TestEnsureSnapshot tests if the snapshot in the repo has exactly the same | 
					
						
							|  |  |  | // structure as dir. On Windows, Symlinks are ignored. | 
					
						
							|  |  |  | func TestEnsureSnapshot(t testing.TB, repo restic.Repository, snapshotID restic.ID, dir TestDir) { | 
					
						
							| 
									
										
										
										
											2020-02-17 13:07:55 +01:00
										 |  |  | 	t.Helper() | 
					
						
							| 
									
										
										
										
											2018-03-30 22:43:18 +02:00
										 |  |  | 	ctx, cancel := context.WithCancel(context.Background()) | 
					
						
							|  |  |  | 	defer cancel() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	sn, err := restic.LoadSnapshot(ctx, repo, snapshotID) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if sn.Tree == nil { | 
					
						
							|  |  |  | 		t.Fatal("snapshot has nil tree ID") | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	TestEnsureTree(ctx, t, "/", repo, *sn.Tree, dir) | 
					
						
							|  |  |  | } |