--- /dev/null
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package fastwalk_test
+
+import (
+ "bytes"
+ "flag"
+ "fmt"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "reflect"
+ "runtime"
+ "sort"
+ "strings"
+ "sync"
+ "testing"
+
+ "golang.org/x/tools/internal/fastwalk"
+)
+
+func formatFileModes(m map[string]os.FileMode) string {
+ var keys []string
+ for k := range m {
+ keys = append(keys, k)
+ }
+ sort.Strings(keys)
+ var buf bytes.Buffer
+ for _, k := range keys {
+ fmt.Fprintf(&buf, "%-20s: %v\n", k, m[k])
+ }
+ return buf.String()
+}
+
+func testFastWalk(t *testing.T, files map[string]string, callback func(path string, typ os.FileMode) error, want map[string]os.FileMode) {
+ tempdir, err := ioutil.TempDir("", "test-fast-walk")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(tempdir)
+
+ symlinks := map[string]string{}
+ for path, contents := range files {
+ file := filepath.Join(tempdir, "/src", path)
+ if err := os.MkdirAll(filepath.Dir(file), 0755); err != nil {
+ t.Fatal(err)
+ }
+ var err error
+ if strings.HasPrefix(contents, "LINK:") {
+ symlinks[file] = filepath.FromSlash(strings.TrimPrefix(contents, "LINK:"))
+ } else {
+ err = ioutil.WriteFile(file, []byte(contents), 0644)
+ }
+ if err != nil {
+ t.Fatal(err)
+ }
+ }
+
+ // Create symlinks after all other files. Otherwise, directory symlinks on
+ // Windows are unusable (see https://golang.org/issue/39183).
+ for file, dst := range symlinks {
+ err = os.Symlink(dst, file)
+ if err != nil {
+ if writeErr := ioutil.WriteFile(file, []byte(dst), 0644); writeErr == nil {
+ // Couldn't create symlink, but could write the file.
+ // Probably this filesystem doesn't support symlinks.
+ // (Perhaps we are on an older Windows and not running as administrator.)
+ t.Skipf("skipping because symlinks appear to be unsupported: %v", err)
+ }
+ }
+ }
+
+ got := map[string]os.FileMode{}
+ var mu sync.Mutex
+ err = fastwalk.Walk(tempdir, func(path string, typ os.FileMode) error {
+ mu.Lock()
+ defer mu.Unlock()
+ if !strings.HasPrefix(path, tempdir) {
+ t.Errorf("bogus prefix on %q, expect %q", path, tempdir)
+ }
+ key := filepath.ToSlash(strings.TrimPrefix(path, tempdir))
+ if old, dup := got[key]; dup {
+ t.Errorf("callback called twice for key %q: %v -> %v", key, old, typ)
+ }
+ got[key] = typ
+ return callback(path, typ)
+ })
+
+ if err != nil {
+ t.Fatalf("callback returned: %v", err)
+ }
+ if !reflect.DeepEqual(got, want) {
+ t.Errorf("walk mismatch.\n got:\n%v\nwant:\n%v", formatFileModes(got), formatFileModes(want))
+ }
+}
+
+func TestFastWalk_Basic(t *testing.T) {
+ testFastWalk(t, map[string]string{
+ "foo/foo.go": "one",
+ "bar/bar.go": "two",
+ "skip/skip.go": "skip",
+ },
+ func(path string, typ os.FileMode) error {
+ return nil
+ },
+ map[string]os.FileMode{
+ "": os.ModeDir,
+ "/src": os.ModeDir,
+ "/src/bar": os.ModeDir,
+ "/src/bar/bar.go": 0,
+ "/src/foo": os.ModeDir,
+ "/src/foo/foo.go": 0,
+ "/src/skip": os.ModeDir,
+ "/src/skip/skip.go": 0,
+ })
+}
+
+func TestFastWalk_LongFileName(t *testing.T) {
+ longFileName := strings.Repeat("x", 255)
+
+ testFastWalk(t, map[string]string{
+ longFileName: "one",
+ },
+ func(path string, typ os.FileMode) error {
+ return nil
+ },
+ map[string]os.FileMode{
+ "": os.ModeDir,
+ "/src": os.ModeDir,
+ "/src/" + longFileName: 0,
+ },
+ )
+}
+
+func TestFastWalk_Symlink(t *testing.T) {
+ testFastWalk(t, map[string]string{
+ "foo/foo.go": "one",
+ "bar/bar.go": "LINK:../foo/foo.go",
+ "symdir": "LINK:foo",
+ "broken/broken.go": "LINK:../nonexistent",
+ },
+ func(path string, typ os.FileMode) error {
+ return nil
+ },
+ map[string]os.FileMode{
+ "": os.ModeDir,
+ "/src": os.ModeDir,
+ "/src/bar": os.ModeDir,
+ "/src/bar/bar.go": os.ModeSymlink,
+ "/src/foo": os.ModeDir,
+ "/src/foo/foo.go": 0,
+ "/src/symdir": os.ModeSymlink,
+ "/src/broken": os.ModeDir,
+ "/src/broken/broken.go": os.ModeSymlink,
+ })
+}
+
+func TestFastWalk_SkipDir(t *testing.T) {
+ testFastWalk(t, map[string]string{
+ "foo/foo.go": "one",
+ "bar/bar.go": "two",
+ "skip/skip.go": "skip",
+ },
+ func(path string, typ os.FileMode) error {
+ if typ == os.ModeDir && strings.HasSuffix(path, "skip") {
+ return filepath.SkipDir
+ }
+ return nil
+ },
+ map[string]os.FileMode{
+ "": os.ModeDir,
+ "/src": os.ModeDir,
+ "/src/bar": os.ModeDir,
+ "/src/bar/bar.go": 0,
+ "/src/foo": os.ModeDir,
+ "/src/foo/foo.go": 0,
+ "/src/skip": os.ModeDir,
+ })
+}
+
+func TestFastWalk_SkipFiles(t *testing.T) {
+ // Directory iteration order is undefined, so there's no way to know
+ // which file to expect until the walk happens. Rather than mess
+ // with the test infrastructure, just mutate want.
+ var mu sync.Mutex
+ want := map[string]os.FileMode{
+ "": os.ModeDir,
+ "/src": os.ModeDir,
+ "/src/zzz": os.ModeDir,
+ "/src/zzz/c.go": 0,
+ }
+
+ testFastWalk(t, map[string]string{
+ "a_skipfiles.go": "a",
+ "b_skipfiles.go": "b",
+ "zzz/c.go": "c",
+ },
+ func(path string, typ os.FileMode) error {
+ if strings.HasSuffix(path, "_skipfiles.go") {
+ mu.Lock()
+ defer mu.Unlock()
+ want["/src/"+filepath.Base(path)] = 0
+ return fastwalk.ErrSkipFiles
+ }
+ return nil
+ },
+ want)
+ if len(want) != 5 {
+ t.Errorf("saw too many files: wanted 5, got %v (%v)", len(want), want)
+ }
+}
+
+func TestFastWalk_TraverseSymlink(t *testing.T) {
+ testFastWalk(t, map[string]string{
+ "foo/foo.go": "one",
+ "bar/bar.go": "two",
+ "skip/skip.go": "skip",
+ "symdir": "LINK:foo",
+ },
+ func(path string, typ os.FileMode) error {
+ if typ == os.ModeSymlink {
+ return fastwalk.ErrTraverseLink
+ }
+ return nil
+ },
+ map[string]os.FileMode{
+ "": os.ModeDir,
+ "/src": os.ModeDir,
+ "/src/bar": os.ModeDir,
+ "/src/bar/bar.go": 0,
+ "/src/foo": os.ModeDir,
+ "/src/foo/foo.go": 0,
+ "/src/skip": os.ModeDir,
+ "/src/skip/skip.go": 0,
+ "/src/symdir": os.ModeSymlink,
+ "/src/symdir/foo.go": 0,
+ })
+}
+
+var benchDir = flag.String("benchdir", runtime.GOROOT(), "The directory to scan for BenchmarkFastWalk")
+
+func BenchmarkFastWalk(b *testing.B) {
+ b.ReportAllocs()
+ for i := 0; i < b.N; i++ {
+ err := fastwalk.Walk(*benchDir, func(path string, typ os.FileMode) error { return nil })
+ if err != nil {
+ b.Fatal(err)
+ }
+ }
+}