// 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) } } }