mirror of
https://github.com/restic/restic.git
synced 2024-08-18 18:33:22 +02:00
This improves restore performance by several orders of magniture by not going through the whole tree recursively when we can anticipate that no match will ever occur.
360 lines
9.3 KiB
Go
360 lines
9.3 KiB
Go
package filter_test
|
|
|
|
import (
|
|
"bufio"
|
|
"compress/bzip2"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/restic/restic/internal/filter"
|
|
)
|
|
|
|
var matchTests = []struct {
|
|
pattern string
|
|
path string
|
|
match bool
|
|
}{
|
|
{"", "", true},
|
|
{"", "foo", true},
|
|
{"", "/x/y/z/foo", true},
|
|
{"*.go", "/foo/bar/test.go", true},
|
|
{"*.c", "/foo/bar/test.go", false},
|
|
{"*", "/foo/bar/test.go", true},
|
|
{"foo*", "/foo/bar/test.go", true},
|
|
{"bar*", "/foo/bar/test.go", true},
|
|
{"/bar*", "/foo/bar/test.go", false},
|
|
{"bar/*", "/foo/bar/test.go", true},
|
|
{"baz/*", "/foo/bar/test.go", false},
|
|
{"bar/test.go", "/foo/bar/test.go", true},
|
|
{"bar/*.go", "/foo/bar/test.go", true},
|
|
{"ba*/*.go", "/foo/bar/test.go", true},
|
|
{"bb*/*.go", "/foo/bar/test.go", false},
|
|
{"test.*", "/foo/bar/test.go", true},
|
|
{"tesT.*", "/foo/bar/test.go", false},
|
|
{"bar/*", "/foo/bar/baz", true},
|
|
{"bar", "/foo/bar", true},
|
|
{"/foo/bar", "/foo/bar", true},
|
|
{"/foo/bar/", "/foo/bar", true},
|
|
{"/foo/bar", "/foo/baz", false},
|
|
{"/foo/bar", "/foo/baz/", false},
|
|
{"/foo///bar", "/foo/bar", true},
|
|
{"/foo/../bar", "/foo/bar", false},
|
|
{"/foo/../bar", "/bar", true},
|
|
{"/foo", "/foo/baz", true},
|
|
{"/foo/", "/foo/baz", true},
|
|
{"/foo/*", "/foo", false},
|
|
{"/foo/*", "/foo/baz", true},
|
|
{"bar", "/foo/bar/baz", true},
|
|
{"bar", "/foo/bar/test.go", true},
|
|
{"/foo/*test.*", "/foo/bar/test.go", false},
|
|
{"/foo/*/test.*", "/foo/bar/test.go", true},
|
|
{"/foo/*/bar/test.*", "/foo/bar/test.go", false},
|
|
{"/*/*/bar/test.*", "/foo/bar/test.go", false},
|
|
{"/*/*/bar/test.*", "/foo/bar/baz/test.go", false},
|
|
{"/*/*/baz/test.*", "/foo/bar/baz/test.go", true},
|
|
{"/*/foo/bar/test.*", "/foo/bar/baz/test.go", false},
|
|
{"/*/foo/bar/test.*", "/foo/bar/baz/test.go", false},
|
|
{"/foo/bar/test.*", "bar/baz/test.go", false},
|
|
{"/x/y/bar/baz/test.*", "bar/baz/test.go", false},
|
|
{"/x/y/bar/baz/test.c", "bar/baz/test.go", false},
|
|
{"baz/test.*", "bar/baz/test.go", true},
|
|
{"baz/tesT.*", "bar/baz/test.go", false},
|
|
{"test.go", "bar/baz/test.go", true},
|
|
{"*.go", "bar/baz/test.go", true},
|
|
{"*.c", "bar/baz/test.go", false},
|
|
{"sdk", "/foo/bar/sdk", true},
|
|
{"sdk", "/foo/bar/sdk/test/sdk_foo.go", true},
|
|
{
|
|
"sdk/*/cpp/*/*vars*.html",
|
|
"/usr/share/doc/libreoffice/sdk/docs/cpp/ref/a00517.html",
|
|
false,
|
|
},
|
|
{"foo/**/bar/*.go", "/home/user/foo/work/special/project/bar/test.go", true},
|
|
{"foo/**/bar/*.go", "/home/user/foo/bar/test.go", true},
|
|
{"foo/**/bar/*.go", "x/foo/bar/test.go", true},
|
|
{"foo/**/bar/*.go", "foo/bar/test.go", true},
|
|
{"foo/**/bar/*.go", "/home/user/foo/test.c", false},
|
|
{"foo/**/bar/*.go", "bar/foo/main.go", false},
|
|
{"foo/**/bar/*.go", "/foo/bar/main.go", true},
|
|
{"foo/**/bar/*.go", "bar/main.go", false},
|
|
{"foo/**/bar", "/home/user/foo/x/y/bar", true},
|
|
{"foo/**/bar", "/home/user/foo/x/y/bar/main.go", true},
|
|
{"user/**/important*", "/home/user/work/x/y/hidden/x", false},
|
|
{"user/**/hidden*/**/c", "/home/user/work/x/y/hidden/z/a/b/c", true},
|
|
{"c:/foo/*test.*", "c:/foo/bar/test.go", false},
|
|
{"c:/foo", "c:/foo/bar", true},
|
|
{"c:/foo/", "c:/foo/bar", true},
|
|
{"c:/foo/*/test.*", "c:/foo/bar/test.go", true},
|
|
{"c:/foo/*/bar/test.*", "c:/foo/bar/test.go", false},
|
|
}
|
|
|
|
func testpattern(t *testing.T, pattern, path string, shouldMatch bool) {
|
|
match, err := filter.Match(pattern, path)
|
|
if err != nil {
|
|
t.Errorf("test pattern %q failed: expected no error for path %q, but error returned: %v",
|
|
pattern, path, err)
|
|
}
|
|
|
|
if match != shouldMatch {
|
|
t.Errorf("test: filter.Match(%q, %q): expected %v, got %v",
|
|
pattern, path, shouldMatch, match)
|
|
}
|
|
}
|
|
|
|
func TestMatch(t *testing.T) {
|
|
for _, test := range matchTests {
|
|
testpattern(t, test.pattern, test.path, test.match)
|
|
|
|
// Test with native path separator
|
|
if filepath.Separator != '/' {
|
|
// Test with pattern as native
|
|
pattern := strings.Replace(test.pattern, "/", string(filepath.Separator), -1)
|
|
testpattern(t, pattern, test.path, test.match)
|
|
|
|
// Test with path as native
|
|
path := strings.Replace(test.path, "/", string(filepath.Separator), -1)
|
|
testpattern(t, test.pattern, path, test.match)
|
|
|
|
// Test with both pattern and path as native
|
|
testpattern(t, pattern, path, test.match)
|
|
}
|
|
}
|
|
}
|
|
|
|
var childMatchTests = []struct {
|
|
pattern string
|
|
path string
|
|
match bool
|
|
}{
|
|
{"", "", true},
|
|
{"", "/foo", true},
|
|
{"", "/x/y/z/foo", true},
|
|
{"foo/bar", "/foo", true},
|
|
{"baz/bar", "/foo", true},
|
|
{"foo", "/foo/bar", true},
|
|
{"bar", "/foo", true},
|
|
{"baz", "/foo/bar", true},
|
|
{"*", "/foo", true},
|
|
{"*", "/foo/bar", true},
|
|
{"/foo/bar", "/foo", true},
|
|
{"/foo/bar/baz", "/foo", true},
|
|
{"/foo/bar/baz", "/foo/bar", true},
|
|
{"/foo/bar/baz", "/foo/baz", false},
|
|
{"/foo/**/baz", "/foo/bar/baz", true},
|
|
{"/foo/**/qux", "/foo/bar/baz/qux", true},
|
|
{"/baz/bar", "/foo", false},
|
|
{"/foo", "/foo/bar", true},
|
|
{"/*", "/foo", true},
|
|
{"/*", "/foo/bar", true},
|
|
{"/foo", "/foo/bar", true},
|
|
{"/**", "/foo", true},
|
|
{"/*/**", "/foo", true},
|
|
{"/*/**", "/foo/bar", true},
|
|
{"/*/bar", "/foo", true},
|
|
{"/bar/*", "/foo", false},
|
|
{"/foo/*/baz", "/foo/bar", true},
|
|
{"/foo/*/baz", "/foo/baz", true},
|
|
{"/foo/*/baz", "/bar/baz", false},
|
|
{"/**/*", "/foo", true},
|
|
{"/**/bar", "/foo/bar", true},
|
|
}
|
|
|
|
func testchildpattern(t *testing.T, pattern, path string, shouldMatch bool) {
|
|
match, err := filter.ChildMatch(pattern, path)
|
|
if err != nil {
|
|
t.Errorf("test child pattern %q failed: expected no error for path %q, but error returned: %v",
|
|
pattern, path, err)
|
|
}
|
|
|
|
if match != shouldMatch {
|
|
t.Errorf("test: filter.ChildMatch(%q, %q): expected %v, got %v",
|
|
pattern, path, shouldMatch, match)
|
|
}
|
|
}
|
|
|
|
func TestChildMatch(t *testing.T) {
|
|
for _, test := range childMatchTests {
|
|
testchildpattern(t, test.pattern, test.path, test.match)
|
|
|
|
// Test with native path separator
|
|
if filepath.Separator != '/' {
|
|
// Test with pattern as native
|
|
pattern := strings.Replace(test.pattern, "/", string(filepath.Separator), -1)
|
|
testchildpattern(t, pattern, test.path, test.match)
|
|
|
|
// Test with path as native
|
|
path := strings.Replace(test.path, "/", string(filepath.Separator), -1)
|
|
testchildpattern(t, test.pattern, path, test.match)
|
|
|
|
// Test with both pattern and path as native
|
|
testchildpattern(t, pattern, path, test.match)
|
|
}
|
|
}
|
|
}
|
|
|
|
func ExampleMatch() {
|
|
match, _ := filter.Match("*.go", "/home/user/file.go")
|
|
fmt.Printf("match: %v\n", match)
|
|
// Output:
|
|
// match: true
|
|
}
|
|
|
|
func ExampleMatch_wildcards() {
|
|
match, _ := filter.Match("/home/[uU]ser/?.go", "/home/user/F.go")
|
|
fmt.Printf("match: %v\n", match)
|
|
// Output:
|
|
// match: true
|
|
}
|
|
|
|
var filterListTests = []struct {
|
|
patterns []string
|
|
path string
|
|
match bool
|
|
}{
|
|
{[]string{"*.go"}, "/foo/bar/test.go", true},
|
|
{[]string{"*.c"}, "/foo/bar/test.go", false},
|
|
{[]string{"*.go", "*.c"}, "/foo/bar/test.go", true},
|
|
{[]string{"*"}, "/foo/bar/test.go", true},
|
|
{[]string{"x"}, "/foo/bar/test.go", false},
|
|
{[]string{"?"}, "/foo/bar/test.go", false},
|
|
{[]string{"?", "x"}, "/foo/bar/x", true},
|
|
{[]string{"/*/*/bar/test.*"}, "/foo/bar/test.go", false},
|
|
{[]string{"/*/*/bar/test.*", "*.go"}, "/foo/bar/test.go", true},
|
|
{[]string{"", "*.c"}, "/foo/bar/test.go", false},
|
|
}
|
|
|
|
func TestList(t *testing.T) {
|
|
for i, test := range filterListTests {
|
|
match, _, err := filter.List(test.patterns, test.path)
|
|
if err != nil {
|
|
t.Errorf("test %d failed: expected no error for patterns %q, but error returned: %v",
|
|
i, test.patterns, err)
|
|
continue
|
|
}
|
|
|
|
if match != test.match {
|
|
t.Errorf("test %d: filter.MatchList(%q, %q): expected %v, got %v",
|
|
i, test.patterns, test.path, test.match, match)
|
|
}
|
|
}
|
|
}
|
|
|
|
func ExampleList() {
|
|
match, _, _ := filter.List([]string{"*.c", "*.go"}, "/home/user/file.go")
|
|
fmt.Printf("match: %v\n", match)
|
|
// Output:
|
|
// match: true
|
|
}
|
|
|
|
func extractTestLines(t testing.TB) (lines []string) {
|
|
f, err := os.Open("testdata/libreoffice.txt.bz2")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
defer func() {
|
|
if err := f.Close(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}()
|
|
|
|
sc := bufio.NewScanner(bzip2.NewReader(f))
|
|
for sc.Scan() {
|
|
lines = append(lines, sc.Text())
|
|
}
|
|
|
|
return lines
|
|
}
|
|
|
|
func TestFilterPatternsFile(t *testing.T) {
|
|
lines := extractTestLines(t)
|
|
|
|
var testPatterns = []struct {
|
|
pattern string
|
|
hits uint
|
|
}{
|
|
{"*.html", 18249},
|
|
{"sdk", 22186},
|
|
{"sdk/*/cpp/*/*vars.html", 3},
|
|
}
|
|
|
|
for _, test := range testPatterns {
|
|
var c uint
|
|
for _, line := range lines {
|
|
match, err := filter.Match(test.pattern, line)
|
|
if err != nil {
|
|
t.Error(err)
|
|
continue
|
|
}
|
|
|
|
if match {
|
|
c++
|
|
// fmt.Printf("pattern %q, line %q\n", test.pattern, line)
|
|
}
|
|
}
|
|
|
|
if c != test.hits {
|
|
t.Errorf("wrong number of hits for pattern %q: want %d, got %d",
|
|
test.pattern, test.hits, c)
|
|
}
|
|
}
|
|
}
|
|
|
|
func BenchmarkFilterLines(b *testing.B) {
|
|
pattern := "sdk/*/cpp/*/*vars.html"
|
|
lines := extractTestLines(b)
|
|
var c uint
|
|
|
|
b.ResetTimer()
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
c = 0
|
|
for _, line := range lines {
|
|
match, err := filter.Match(pattern, line)
|
|
if err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
|
|
if match {
|
|
c++
|
|
}
|
|
}
|
|
|
|
if c != 3 {
|
|
b.Fatalf("wrong number of matches: expected 3, got %d", c)
|
|
}
|
|
}
|
|
}
|
|
|
|
func BenchmarkFilterPatterns(b *testing.B) {
|
|
patterns := []string{
|
|
"sdk/*",
|
|
"*.html",
|
|
}
|
|
lines := extractTestLines(b)
|
|
var c uint
|
|
|
|
b.ResetTimer()
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
c = 0
|
|
for _, line := range lines {
|
|
match, _, err := filter.List(patterns, line)
|
|
if err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
|
|
if match {
|
|
c++
|
|
}
|
|
}
|
|
|
|
if c != 22185 {
|
|
b.Fatalf("wrong number of matches: expected 22185, got %d", c)
|
|
}
|
|
}
|
|
}
|