imports: extract fastWalk into new package internal/fastwalk
It is going to be used by a new tool.
Moved to an internal package so it does not become a publicly supported
api.
Modified the tests so they don't depend on the fix_test infrastructure.
Change-Id: Ib8ebef24dc23e180960af04aa3d06b5f41a7c02b
Reviewed-on: https://go-review.googlesource.com/99678
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
diff --git a/imports/fix.go b/imports/fix.go
index 68961ba..73f939a 100644
--- a/imports/fix.go
+++ b/imports/fix.go
@@ -22,6 +22,7 @@
"sync"
"golang.org/x/tools/go/ast/astutil"
+ "golang.org/x/tools/internal/fastwalk"
)
// Debug controls verbose logging.
@@ -545,16 +546,13 @@
return false
}
-// shouldTraverse reports whether the symlink fi should, found in dir,
+// shouldTraverse reports whether the symlink fi, found in dir,
// should be followed. It makes sure symlinks were never visited
// before to avoid symlink loops.
func shouldTraverse(dir string, fi os.FileInfo) bool {
path := filepath.Join(dir, fi.Name())
target, err := filepath.EvalSymlinks(path)
if err != nil {
- if !os.IsNotExist(err) {
- fmt.Fprintln(os.Stderr, err)
- }
return false
}
ts, err := os.Stat(target)
@@ -674,12 +672,12 @@
return nil
}
if shouldTraverse(dir, fi) {
- return traverseLink
+ return fastwalk.TraverseLink
}
}
return nil
}
- if err := fastWalk(srcDir, walkFn); err != nil {
+ if err := fastwalk.Walk(srcDir, walkFn); err != nil {
log.Printf("goimports: scanning directory %v: %v", srcDir, err)
}
}
diff --git a/imports/fastwalk.go b/internal/fastwalk/fastwalk.go
similarity index 83%
rename from imports/fastwalk.go
rename to internal/fastwalk/fastwalk.go
index 31e6e27..5cc7df5 100644
--- a/imports/fastwalk.go
+++ b/internal/fastwalk/fastwalk.go
@@ -2,17 +2,9 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-// A faster implementation of filepath.Walk.
-//
-// filepath.Walk's design necessarily calls os.Lstat on each file,
-// even if the caller needs less info. And goimports only need to know
-// the type of each file. The kernel interface provides the type in
-// the Readdir call but the standard library ignored it.
-// fastwalk_unix.go contains a fork of the syscall routines.
-//
-// See golang.org/issue/16399
-
-package imports
+// Package fastwalk provides a faster version of filepath.Walk for file system
+// scanning tools.
+package fastwalk
import (
"errors"
@@ -22,10 +14,22 @@
"sync"
)
-// traverseLink is a sentinel error for fastWalk, similar to filepath.SkipDir.
-var traverseLink = errors.New("traverse symlink, assuming target is a directory")
+// TraverseLink is used as a return value from WalkFuncs to indicate that the
+// symlink named in the call may be traversed.
+var TraverseLink = errors.New("fastwalk: traverse symlink, assuming target is a directory")
-// fastWalk walks the file tree rooted at root, calling walkFn for
+// Walk is a faster implementation of filepath.Walk.
+//
+// filepath.Walk's design necessarily calls os.Lstat on each file,
+// even if the caller needs less info.
+// Many tools need only the type of each file.
+// On some platforms, this information is provided directly by the readdir
+// system call, avoiding the need to stat each file individually.
+// fastwalk_unix.go contains a fork of the syscall routines.
+//
+// See golang.org/issue/16399
+//
+// Walk walks the file tree rooted at root, calling walkFn for
// each file or directory in the tree, including root.
//
// If fastWalk returns filepath.SkipDir, the directory is skipped.
@@ -36,10 +40,10 @@
// any permission bits.
// * multiple goroutines stat the filesystem concurrently. The provided
// walkFn must be safe for concurrent use.
-// * fastWalk can follow symlinks if walkFn returns the traverseLink
+// * fastWalk can follow symlinks if walkFn returns the TraverseLink
// sentinel error. It is the walkFn's responsibility to prevent
// fastWalk from going into symlink cycles.
-func fastWalk(root string, walkFn func(path string, typ os.FileMode) error) error {
+func Walk(root string, walkFn func(path string, typ os.FileMode) error) error {
// TODO(bradfitz): make numWorkers configurable? We used a
// minimum of 4 to give the kernel more info about multiple
// things we want, in hopes its I/O scheduling can take
@@ -158,7 +162,7 @@
err := w.fn(joined, typ)
if typ == os.ModeSymlink {
- if err == traverseLink {
+ if err == TraverseLink {
// Set callbackDone so we don't call it twice for both the
// symlink-as-symlink and the symlink-as-directory later:
w.enqueue(walkItem{dir: joined, callbackDone: true})
diff --git a/imports/fastwalk_dirent_fileno.go b/internal/fastwalk/fastwalk_dirent_fileno.go
similarity index 94%
rename from imports/fastwalk_dirent_fileno.go
rename to internal/fastwalk/fastwalk_dirent_fileno.go
index f1fd649..ccffec5 100644
--- a/imports/fastwalk_dirent_fileno.go
+++ b/internal/fastwalk/fastwalk_dirent_fileno.go
@@ -4,7 +4,7 @@
// +build freebsd openbsd netbsd
-package imports
+package fastwalk
import "syscall"
diff --git a/imports/fastwalk_dirent_ino.go b/internal/fastwalk/fastwalk_dirent_ino.go
similarity index 94%
rename from imports/fastwalk_dirent_ino.go
rename to internal/fastwalk/fastwalk_dirent_ino.go
index 9fc6de0..ab7fbc0 100644
--- a/imports/fastwalk_dirent_ino.go
+++ b/internal/fastwalk/fastwalk_dirent_ino.go
@@ -5,7 +5,7 @@
// +build linux darwin
// +build !appengine
-package imports
+package fastwalk
import "syscall"
diff --git a/imports/fastwalk_portable.go b/internal/fastwalk/fastwalk_portable.go
similarity index 97%
rename from imports/fastwalk_portable.go
rename to internal/fastwalk/fastwalk_portable.go
index 6c26583..e8ea50d 100644
--- a/imports/fastwalk_portable.go
+++ b/internal/fastwalk/fastwalk_portable.go
@@ -4,7 +4,7 @@
// +build appengine !linux,!darwin,!freebsd,!openbsd,!netbsd
-package imports
+package fastwalk
import (
"io/ioutil"
diff --git a/imports/fastwalk_test.go b/internal/fastwalk/fastwalk_test.go
similarity index 70%
rename from imports/fastwalk_test.go
rename to internal/fastwalk/fastwalk_test.go
index 5b307d1..e9ab0d5 100644
--- a/imports/fastwalk_test.go
+++ b/internal/fastwalk/fastwalk_test.go
@@ -2,12 +2,13 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-package imports
+package fastwalk_test
import (
"bytes"
"flag"
"fmt"
+ "io/ioutil"
"os"
"path/filepath"
"reflect"
@@ -16,6 +17,8 @@
"strings"
"sync"
"testing"
+
+ "golang.org/x/tools/internal/fastwalk"
)
func formatFileModes(m map[string]os.FileMode) string {
@@ -32,30 +35,46 @@
}
func testFastWalk(t *testing.T, files map[string]string, callback func(path string, typ os.FileMode) error, want map[string]os.FileMode) {
- testConfig{
- gopathFiles: files,
- }.test(t, func(t *goimportTest) {
- got := map[string]os.FileMode{}
- var mu sync.Mutex
- if err := fastWalk(t.gopath, func(path string, typ os.FileMode) error {
- mu.Lock()
- defer mu.Unlock()
- if !strings.HasPrefix(path, t.gopath) {
- t.Fatalf("bogus prefix on %q, expect %q", path, t.gopath)
- }
- key := filepath.ToSlash(strings.TrimPrefix(path, t.gopath))
- if old, dup := got[key]; dup {
- t.Fatalf("callback called twice for key %q: %v -> %v", key, old, typ)
- }
- got[key] = typ
- return callback(path, typ)
- }); err != nil {
- t.Fatalf("callback returned: %v", err)
+ tempdir, err := ioutil.TempDir("", "test-fast-walk")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(tempdir)
+ for path, contents := range files {
+ file := filepath.Join(tempdir, "/src", path)
+ if err := os.MkdirAll(filepath.Dir(file), 0755); err != nil {
+ t.Fatal(err)
}
- if !reflect.DeepEqual(got, want) {
- t.Errorf("walk mismatch.\n got:\n%v\nwant:\n%v", formatFileModes(got), formatFileModes(want))
+ var err error
+ if strings.HasPrefix(contents, "LINK:") {
+ err = os.Symlink(strings.TrimPrefix(contents, "LINK:"), file)
+ } else {
+ err = ioutil.WriteFile(file, []byte(contents), 0644)
}
- })
+ if err != nil {
+ t.Fatal(err)
+ }
+ }
+ got := map[string]os.FileMode{}
+ var mu sync.Mutex
+ if err := fastwalk.Walk(tempdir, func(path string, typ os.FileMode) error {
+ mu.Lock()
+ defer mu.Unlock()
+ if !strings.HasPrefix(path, tempdir) {
+ t.Fatalf("bogus prefix on %q, expect %q", path, tempdir)
+ }
+ key := filepath.ToSlash(strings.TrimPrefix(path, tempdir))
+ if old, dup := got[key]; dup {
+ t.Fatalf("callback called twice for key %q: %v -> %v", key, old, typ)
+ }
+ got[key] = typ
+ return callback(path, typ)
+ }); 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) {
@@ -140,7 +159,7 @@
},
func(path string, typ os.FileMode) error {
if typ == os.ModeSymlink {
- return traverseLink
+ return fastwalk.TraverseLink
}
return nil
},
@@ -163,7 +182,7 @@
func BenchmarkFastWalk(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
- err := fastWalk(*benchDir, func(path string, typ os.FileMode) error { return nil })
+ err := fastwalk.Walk(*benchDir, func(path string, typ os.FileMode) error { return nil })
if err != nil {
b.Fatal(err)
}
diff --git a/imports/fastwalk_unix.go b/internal/fastwalk/fastwalk_unix.go
similarity index 99%
rename from imports/fastwalk_unix.go
rename to internal/fastwalk/fastwalk_unix.go
index e0fc8b7..67db6ca 100644
--- a/imports/fastwalk_unix.go
+++ b/internal/fastwalk/fastwalk_unix.go
@@ -5,7 +5,7 @@
// +build linux darwin freebsd openbsd netbsd
// +build !appengine
-package imports
+package fastwalk
import (
"bytes"