2016-07-17 18:47:35 -06:00
|
|
|
// 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.
|
|
|
|
|
2018-03-08 14:10:10 -07:00
|
|
|
package fastwalk_test
|
2016-07-17 18:47:35 -06:00
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"flag"
|
|
|
|
"fmt"
|
2018-03-08 14:10:10 -07:00
|
|
|
"io/ioutil"
|
2016-07-17 18:47:35 -06:00
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"reflect"
|
|
|
|
"runtime"
|
|
|
|
"sort"
|
|
|
|
"strings"
|
|
|
|
"sync"
|
|
|
|
"testing"
|
2018-03-08 14:10:10 -07:00
|
|
|
|
|
|
|
"golang.org/x/tools/internal/fastwalk"
|
2016-07-17 18:47:35 -06:00
|
|
|
)
|
|
|
|
|
|
|
|
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) {
|
2018-03-08 14:10:10 -07:00
|
|
|
tempdir, err := ioutil.TempDir("", "test-fast-walk")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
defer os.RemoveAll(tempdir)
|
2020-05-18 22:33:09 -06:00
|
|
|
|
|
|
|
symlinks := map[string]string{}
|
2018-03-08 14:10:10 -07:00
|
|
|
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:") {
|
2020-05-18 22:33:09 -06:00
|
|
|
symlinks[file] = filepath.FromSlash(strings.TrimPrefix(contents, "LINK:"))
|
2018-03-08 14:10:10 -07:00
|
|
|
} else {
|
|
|
|
err = ioutil.WriteFile(file, []byte(contents), 0644)
|
2016-07-17 18:47:35 -06:00
|
|
|
}
|
2018-03-08 14:10:10 -07:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
2016-07-17 18:47:35 -06:00
|
|
|
}
|
2018-03-08 14:10:10 -07:00
|
|
|
}
|
2020-05-18 22:33:09 -06:00
|
|
|
|
|
|
|
// 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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-08 14:10:10 -07:00
|
|
|
got := map[string]os.FileMode{}
|
|
|
|
var mu sync.Mutex
|
2020-05-18 22:33:09 -06:00
|
|
|
err = fastwalk.Walk(tempdir, func(path string, typ os.FileMode) error {
|
2018-03-08 14:10:10 -07:00
|
|
|
mu.Lock()
|
|
|
|
defer mu.Unlock()
|
|
|
|
if !strings.HasPrefix(path, tempdir) {
|
2020-05-18 22:33:09 -06:00
|
|
|
t.Errorf("bogus prefix on %q, expect %q", path, tempdir)
|
2018-03-08 14:10:10 -07:00
|
|
|
}
|
|
|
|
key := filepath.ToSlash(strings.TrimPrefix(path, tempdir))
|
|
|
|
if old, dup := got[key]; dup {
|
2020-05-18 22:33:09 -06:00
|
|
|
t.Errorf("callback called twice for key %q: %v -> %v", key, old, typ)
|
2018-03-08 14:10:10 -07:00
|
|
|
}
|
|
|
|
got[key] = typ
|
|
|
|
return callback(path, typ)
|
2020-05-18 22:33:09 -06:00
|
|
|
})
|
|
|
|
|
|
|
|
if err != nil {
|
2018-03-08 14:10:10 -07:00
|
|
|
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))
|
|
|
|
}
|
2016-07-17 18:47:35 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
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,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2018-11-10 10:09:09 -07:00
|
|
|
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,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2016-07-17 18:47:35 -06:00
|
|
|
func TestFastWalk_Symlink(t *testing.T) {
|
|
|
|
testFastWalk(t, map[string]string{
|
2020-05-18 22:33:09 -06:00
|
|
|
"foo/foo.go": "one",
|
|
|
|
"bar/bar.go": "LINK:../foo/foo.go",
|
|
|
|
"symdir": "LINK:foo",
|
|
|
|
"broken/broken.go": "LINK:../nonexistent",
|
2016-07-17 18:47:35 -06:00
|
|
|
},
|
|
|
|
func(path string, typ os.FileMode) error {
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
map[string]os.FileMode{
|
2020-05-18 22:33:09 -06:00
|
|
|
"": 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,
|
2016-07-17 18:47:35 -06:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
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,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2018-09-18 13:06:34 -06:00
|
|
|
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
|
2019-11-20 20:43:00 -07:00
|
|
|
return fastwalk.ErrSkipFiles
|
2018-09-18 13:06:34 -06:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
want)
|
|
|
|
if len(want) != 5 {
|
|
|
|
t.Errorf("saw too many files: wanted 5, got %v (%v)", len(want), want)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-07-17 18:47:35 -06:00
|
|
|
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 {
|
2019-11-20 20:43:00 -07:00
|
|
|
return fastwalk.ErrTraverseLink
|
2016-07-17 18:47:35 -06:00
|
|
|
}
|
|
|
|
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++ {
|
2018-03-08 14:10:10 -07:00
|
|
|
err := fastwalk.Walk(*benchDir, func(path string, typ os.FileMode) error { return nil })
|
2016-07-17 18:47:35 -06:00
|
|
|
if err != nil {
|
|
|
|
b.Fatal(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|