Skip to content

Commit

Permalink
cmd/go: use pattern to prune file tree walk
Browse files Browse the repository at this point in the history
For example, if the pattern is m... there is
no need to look in directories not beginning with m.

Fixes #5214.

R=golang-dev, adg
CC=golang-dev
https://golang.org/cl/13253049
  • Loading branch information
rsc committed Sep 11, 2013
1 parent 08b26e4 commit e6a4955
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 9 deletions.
39 changes: 39 additions & 0 deletions src/cmd/go/main.go
Expand Up @@ -434,6 +434,37 @@ func matchPattern(pattern string) func(name string) bool {
}
}

// hasPathPrefix reports whether the path s begins with the
// elements in prefix.
func hasPathPrefix(s, prefix string) bool {
switch {
default:
return false
case len(s) == len(prefix):
return s == prefix
case len(s) > len(prefix):
if prefix != "" && prefix[len(prefix)-1] == '/' {
return strings.HasPrefix(s, prefix)
}
return s[len(prefix)] == '/' && s[:len(prefix)] == prefix
}
}

// treeCanMatchPattern(pattern)(name) reports whether
// name or children of name can possibly match pattern.
// Pattern is the same limited glob accepted by matchPattern.
func treeCanMatchPattern(pattern string) func(name string) bool {
wildCard := false
if i := strings.Index(pattern, "..."); i >= 0 {
wildCard = true
pattern = pattern[:i]
}
return func(name string) bool {
return len(name) <= len(pattern) && hasPathPrefix(pattern, name) ||
wildCard && strings.HasPrefix(name, pattern)
}
}

// allPackages returns all the packages that can be found
// under the $GOPATH directories and $GOROOT matching pattern.
// The pattern is either "all" (all packages), "std" (standard packages)
Expand All @@ -448,8 +479,10 @@ func allPackages(pattern string) []string {

func matchPackages(pattern string) []string {
match := func(string) bool { return true }
treeCanMatch := func(string) bool { return true }
if pattern != "all" && pattern != "std" {
match = matchPattern(pattern)
treeCanMatch = treeCanMatchPattern(pattern)
}

have := map[string]bool{
Expand All @@ -467,6 +500,9 @@ func matchPackages(pattern string) []string {
return nil
}
name := path[len(cmd):]
if !treeCanMatch(name) {
return filepath.SkipDir
}
// Commands are all in cmd/, not in subdirectories.
if strings.Contains(name, string(filepath.Separator)) {
return filepath.SkipDir
Expand Down Expand Up @@ -512,6 +548,9 @@ func matchPackages(pattern string) []string {
if pattern == "std" && strings.Contains(name, ".") {
return filepath.SkipDir
}
if !treeCanMatch(name) {
return filepath.SkipDir
}
if have[name] {
return nil
}
Expand Down
70 changes: 61 additions & 9 deletions src/cmd/go/match_test.go
Expand Up @@ -6,11 +6,7 @@ package main

import "testing"

var matchTests = []struct {
pattern string
path string
match bool
}{
var matchPatternTests = []stringPairTest{
{"...", "foo", true},
{"net", "net", true},
{"net", "net/http", false},
Expand All @@ -27,10 +23,66 @@ var matchTests = []struct {
}

func TestMatchPattern(t *testing.T) {
for _, tt := range matchTests {
match := matchPattern(tt.pattern)(tt.path)
if match != tt.match {
t.Errorf("matchPattern(%q)(%q) = %v, want %v", tt.pattern, tt.path, match, tt.match)
testStringPairs(t, "matchPattern", matchPatternTests, func(pattern, name string) bool {
return matchPattern(pattern)(name)
})
}

var treeCanMatchPatternTests = []stringPairTest{
{"...", "foo", true},
{"net", "net", true},
{"net", "net/http", false},
{"net/http", "net", true},
{"net/http", "net/http", true},
{"net...", "netchan", true},
{"net...", "net", true},
{"net...", "net/http", true},
{"net...", "not/http", false},
{"net/...", "netchan", false},
{"net/...", "net", true},
{"net/...", "net/http", true},
{"net/...", "not/http", false},
{"abc.../def", "abcxyz", true},
{"abc.../def", "xyxabc", false},
{"x/y/z/...", "x", true},
{"x/y/z/...", "x/y", true},
{"x/y/z/...", "x/y/z", true},
{"x/y/z/...", "x/y/z/w", true},
{"x/y/z", "x", true},
{"x/y/z", "x/y", true},
{"x/y/z", "x/y/z", true},
{"x/y/z", "x/y/z/w", false},
{"x/.../y/z", "x/a/b/c", true},
{"x/.../y/z", "y/x/a/b/c", false},
}

func TestChildrenCanMatchPattern(t *testing.T) {
testStringPairs(t, "treeCanMatchPattern", treeCanMatchPatternTests, func(pattern, name string) bool {
return treeCanMatchPattern(pattern)(name)
})
}

var hasPathPrefixTests = []stringPairTest{
{"abc", "a", false},
{"a/bc", "a", true},
{"a", "a", true},
{"a/bc", "a/", true},
}

func TestHasPathPrefix(t *testing.T) {
testStringPairs(t, "hasPathPrefix", hasPathPrefixTests, hasPathPrefix)
}

type stringPairTest struct {
in1 string
in2 string
out bool
}

func testStringPairs(t *testing.T, name string, tests []stringPairTest, f func(string, string) bool) {
for _, tt := range tests {
if out := f(tt.in1, tt.in2); out != tt.out {
t.Errorf("%s(%q, %q) = %v, want %v", name, tt.in1, tt.in2, out, tt.out)
}
}
}
16 changes: 16 additions & 0 deletions src/cmd/go/test.bash
Expand Up @@ -117,6 +117,22 @@ fi
rm -f ./testdata/err
unset GOPATH

TEST wildcards do not look in useless directories
export GOPATH=$(pwd)/testdata
if ./testgo list ... >testdata/err 2>&1; then
echo "go list ... succeeded"
ok=false
elif ! grep badpkg testdata/err >/dev/null; then
echo "go list ... failure does not mention badpkg"
cat testdata/err
ok=false
elif ! ./testgo list m... >testdata/err 2>&1; then
echo "go list m... failed"
ok=false
fi
rm -rf ./testdata/err
unset GOPATH

# Test tests with relative imports.
TEST relative imports '(go test)'
if ! ./testgo test ./testdata/testimport; then
Expand Down
1 change: 1 addition & 0 deletions src/cmd/go/testdata/src/badpkg/x.go
@@ -0,0 +1 @@
pkg badpkg

0 comments on commit e6a4955

Please sign in to comment.