internal/gopathwalk: create
Extract goimports' logic for walking Go source directories into a
separate package, suitable for use in go/packages. No functional
changes. Added a convenience feature to fastwalk, allowing the user to
say that they're done with a directory and stop receiving callbacks for
it.
Testing is a little light; I expect goimports' tests to cover most
everything we care about.
Change-Id: If047ada4414f5f282637d11fd07e8342fadc9c33
Reviewed-on: https://go-review.googlesource.com/c/138877
Run-TryBot: Heschi Kreinick <heschi@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Michael Matloob <matloob@golang.org>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
diff --git a/imports/fix.go b/imports/fix.go
index d9cbe65..75d37f8 100644
--- a/imports/fix.go
+++ b/imports/fix.go
@@ -5,8 +5,6 @@
package imports
import (
- "bufio"
- "bytes"
"context"
"fmt"
"go/ast"
@@ -24,7 +22,7 @@
"sync"
"golang.org/x/tools/go/ast/astutil"
- "golang.org/x/tools/internal/fastwalk"
+ "golang.org/x/tools/internal/gopathwalk"
)
// Debug controls verbose logging.
@@ -523,195 +521,27 @@
return strings.Count(p, string(filepath.Separator)) + 1
}
-// getIgnoredDirs reads an optional config file at <path>/.goimportsignore
-// of relative directories to ignore when scanning for go files.
-// The provided path is one of the $GOPATH entries with "src" appended.
-func getIgnoredDirs(path string) []os.FileInfo {
- file := filepath.Join(path, ".goimportsignore")
- slurp, err := ioutil.ReadFile(file)
- if Debug {
- if err != nil {
- log.Print(err)
- } else {
- log.Printf("Read %s", file)
- }
- }
- if err != nil {
- return nil
- }
-
- var ignoredDirs []os.FileInfo
- bs := bufio.NewScanner(bytes.NewReader(slurp))
- for bs.Scan() {
- line := strings.TrimSpace(bs.Text())
- if line == "" || strings.HasPrefix(line, "#") {
- continue
- }
- full := filepath.Join(path, line)
- if fi, err := os.Stat(full); err == nil {
- ignoredDirs = append(ignoredDirs, fi)
- if Debug {
- log.Printf("Directory added to ignore list: %s", full)
- }
- } else if Debug {
- log.Printf("Error statting entry in .goimportsignore: %v", err)
- }
- }
- return ignoredDirs
-}
-
-func shouldSkipDir(fi os.FileInfo, ignoredDirs []os.FileInfo) bool {
- for _, ignoredDir := range ignoredDirs {
- if os.SameFile(fi, ignoredDir) {
- return true
- }
- }
- return false
-}
-
-// 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, ignoredDirs []os.FileInfo) bool {
- path := filepath.Join(dir, fi.Name())
- target, err := filepath.EvalSymlinks(path)
- if err != nil {
- return false
- }
- ts, err := os.Stat(target)
- if err != nil {
- fmt.Fprintln(os.Stderr, err)
- return false
- }
- if !ts.IsDir() {
- return false
- }
- if shouldSkipDir(ts, ignoredDirs) {
- return false
- }
- // Check for symlink loops by statting each directory component
- // and seeing if any are the same file as ts.
- for {
- parent := filepath.Dir(path)
- if parent == path {
- // Made it to the root without seeing a cycle.
- // Use this symlink.
- return true
- }
- parentInfo, err := os.Stat(parent)
- if err != nil {
- return false
- }
- if os.SameFile(ts, parentInfo) {
- // Cycle. Don't traverse.
- return false
- }
- path = parent
- }
-
-}
-
-var testHookScanDir = func(dir string) {}
-
// scanGoDirs populates the dirScan map for GOPATH and GOROOT.
func scanGoDirs() map[string]*pkg {
result := make(map[string]*pkg)
var mu sync.Mutex
- for _, srcDir := range build.Default.SrcDirs() {
- if Debug {
- log.Printf("scanning %s", srcDir)
- }
+ add := func(srcDir, dir string) {
+ mu.Lock()
+ defer mu.Unlock()
- testHookScanDir(srcDir)
- w := &walker{
- srcDir: srcDir,
- srcV: filepath.Join(srcDir, "v"),
- srcMod: filepath.Join(srcDir, "mod"),
- ignoredDirs: getIgnoredDirs(srcDir),
- dirScanMu: &mu,
- dirScan: result,
+ if _, dup := result[dir]; dup {
+ return
}
- if err := fastwalk.Walk(srcDir, w.walk); err != nil {
- log.Printf("goimports: scanning directory %v: %v", srcDir, err)
- }
-
- if Debug {
- defer log.Printf("scanned %s", srcDir)
- }
- }
- return result
-}
-
-// walker is the callback for fastwalk.Walk.
-type walker struct {
- srcDir string // The source directory to scan.
- srcV, srcMod string // vgo-style module cache dirs. Optional.
- ignoredDirs []os.FileInfo // The ignored directories, loaded from .goimportsignore files.
-
- dirScanMu *sync.Mutex // The shared mutex guarding dirScan.
- dirScan map[string]*pkg // The results of the scan, sharable across multiple Walk calls.
-}
-
-func (w *walker) walk(path string, typ os.FileMode) error {
- if path == w.srcV || path == w.srcMod {
- return filepath.SkipDir
- }
- dir := filepath.Dir(path)
- if typ.IsRegular() {
- if dir == w.srcDir {
- // Doesn't make sense to have regular files
- // directly in your $GOPATH/src or $GOROOT/src.
- return nil
- }
- if !strings.HasSuffix(path, ".go") {
- return nil
- }
-
- w.dirScanMu.Lock()
- defer w.dirScanMu.Unlock()
- if _, dup := w.dirScan[dir]; dup {
- return nil
- }
- importpath := filepath.ToSlash(dir[len(w.srcDir)+len("/"):])
- w.dirScan[dir] = &pkg{
+ importpath := filepath.ToSlash(dir[len(srcDir)+len("/"):])
+ result[dir] = &pkg{
importPath: importpath,
importPathShort: VendorlessPath(importpath),
dir: dir,
}
- return nil
}
- if typ == os.ModeDir {
- base := filepath.Base(path)
- if base == "" || base[0] == '.' || base[0] == '_' ||
- base == "testdata" || base == "node_modules" {
- return filepath.SkipDir
- }
- fi, err := os.Lstat(path)
- if err == nil && shouldSkipDir(fi, w.ignoredDirs) {
- if Debug {
- log.Printf("skipping directory %q under %s", fi.Name(), dir)
- }
- return filepath.SkipDir
- }
- return nil
- }
- if typ == os.ModeSymlink {
- base := filepath.Base(path)
- if strings.HasPrefix(base, ".#") {
- // Emacs noise.
- return nil
- }
- fi, err := os.Lstat(path)
- if err != nil {
- // Just ignore it.
- return nil
- }
- if shouldTraverse(dir, fi, w.ignoredDirs) {
- return fastwalk.TraverseLink
- }
- }
- return nil
+ gopathwalk.Walk(add, gopathwalk.Options{Debug: Debug, ModulesEnabled: false})
+ return result
}
// VendorlessPath returns the devendorized version of the import path ipath.
diff --git a/imports/fix_test.go b/imports/fix_test.go
index 4a43150..f78fb5d 100644
--- a/imports/fix_test.go
+++ b/imports/fix_test.go
@@ -1559,10 +1559,8 @@
oldCompiler := build.Default.Compiler
build.Default.GOPATH = ""
build.Default.Compiler = "gc"
- testHookScanDir = func(string) {}
defer func() {
- testHookScanDir = func(string) {}
build.Default.GOPATH = oldGOPATH
build.Default.GOROOT = oldGOROOT
build.Default.Compiler = oldCompiler
@@ -2268,77 +2266,6 @@
}
}
-func TestShouldTraverse(t *testing.T) {
- switch runtime.GOOS {
- case "windows", "plan9":
- t.Skipf("skipping symlink-requiring test on %s", runtime.GOOS)
- }
-
- dir, err := ioutil.TempDir("", "goimports-")
- if err != nil {
- t.Fatal(err)
- }
- defer os.RemoveAll(dir)
-
- // Note: mapToDir prepends "src" to each element, since
- // mapToDir was made for creating GOPATHs.
- if err := mapToDir(dir, map[string]string{
- "foo/foo2/file.txt": "",
- "foo/foo2/link-to-src": "LINK:" + dir + "/src",
- "foo/foo2/link-to-src-foo": "LINK:" + dir + "/src/foo",
- "foo/foo2/link-to-dot": "LINK:.",
- "bar/bar2/file.txt": "",
- "bar/bar2/link-to-src-foo": "LINK:" + dir + "/src/foo",
-
- "a/b/c": "LINK:" + dir + "/src/a/d",
- "a/d/e": "LINK:" + dir + "/src/a/b",
- }); err != nil {
- t.Fatal(err)
- }
- tests := []struct {
- dir string
- file string
- want bool
- }{
- {
- dir: dir + "/src/foo/foo2",
- file: "link-to-src-foo",
- want: false, // loop
- },
- {
- dir: dir + "/src/foo/foo2",
- file: "link-to-src",
- want: false, // loop
- },
- {
- dir: dir + "/src/foo/foo2",
- file: "link-to-dot",
- want: false, // loop
- },
- {
- dir: dir + "/src/bar/bar2",
- file: "link-to-src-foo",
- want: true, // not a loop
- },
- {
- dir: dir + "/src/a/b/c",
- file: "e",
- want: false, // loop: "e" is the same as "b".
- },
- }
- for i, tt := range tests {
- fi, err := os.Stat(filepath.Join(tt.dir, tt.file))
- if err != nil {
- t.Errorf("%d. Stat = %v", i, err)
- continue
- }
- got := shouldTraverse(tt.dir, fi, nil)
- if got != tt.want {
- t.Errorf("%d. shouldTraverse(%q, %q) = %v; want %v", i, tt.dir, tt.file, got, tt.want)
- }
- }
-}
-
// Issue 20941: this used to panic on Windows.
func TestProcessStdin(t *testing.T) {
got, err := Process("<standard input>", []byte("package main\nfunc main() {\n\tfmt.Println(123)\n}\n"), nil)
diff --git a/internal/fastwalk/fastwalk.go b/internal/fastwalk/fastwalk.go
index 5cc7df5..7219c8e 100644
--- a/internal/fastwalk/fastwalk.go
+++ b/internal/fastwalk/fastwalk.go
@@ -18,6 +18,11 @@
// symlink named in the call may be traversed.
var TraverseLink = errors.New("fastwalk: traverse symlink, assuming target is a directory")
+// SkipFiles is a used as a return value from WalkFuncs to indicate that the
+// callback should not be called for any other files in the current directory.
+// Child directories will still be traversed.
+var SkipFiles = errors.New("fastwalk: skip remaining files in directory")
+
// Walk is a faster implementation of filepath.Walk.
//
// filepath.Walk's design necessarily calls os.Lstat on each file,
diff --git a/internal/fastwalk/fastwalk_portable.go b/internal/fastwalk/fastwalk_portable.go
index e8ea50d..a906b87 100644
--- a/internal/fastwalk/fastwalk_portable.go
+++ b/internal/fastwalk/fastwalk_portable.go
@@ -20,8 +20,16 @@
if err != nil {
return err
}
+ skipFiles := false
for _, fi := range fis {
+ if fi.Mode().IsRegular() && skipFiles {
+ continue
+ }
if err := fn(dirName, fi.Name(), fi.Mode()&os.ModeType); err != nil {
+ if err == SkipFiles {
+ skipFiles = true
+ continue
+ }
return err
}
}
diff --git a/internal/fastwalk/fastwalk_test.go b/internal/fastwalk/fastwalk_test.go
index e9ab0d5..efb813c 100644
--- a/internal/fastwalk/fastwalk_test.go
+++ b/internal/fastwalk/fastwalk_test.go
@@ -145,6 +145,38 @@
})
}
+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.SkipFiles
+ }
+ 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) {
switch runtime.GOOS {
case "windows", "plan9":
diff --git a/internal/fastwalk/fastwalk_unix.go b/internal/fastwalk/fastwalk_unix.go
index 67db6ca..e541c57 100644
--- a/internal/fastwalk/fastwalk_unix.go
+++ b/internal/fastwalk/fastwalk_unix.go
@@ -32,6 +32,7 @@
buf := make([]byte, blockSize) // stack-allocated; doesn't escape
bufp := 0 // starting read position in buf
nbuf := 0 // end valid data in buf
+ skipFiles := false
for {
if bufp >= nbuf {
bufp = 0
@@ -62,7 +63,14 @@
}
typ = fi.Mode() & os.ModeType
}
+ if skipFiles && typ.IsRegular() {
+ continue
+ }
if err := fn(dirName, name, typ); err != nil {
+ if err == SkipFiles {
+ skipFiles = true
+ continue
+ }
return err
}
}
diff --git a/internal/gopathwalk/walk.go b/internal/gopathwalk/walk.go
new file mode 100644
index 0000000..cbca5b0
--- /dev/null
+++ b/internal/gopathwalk/walk.go
@@ -0,0 +1,209 @@
+// Copyright 2018 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 gopathwalk is like filepath.Walk but specialized for finding Go
+// packages, particularly in $GOPATH and $GOROOT.
+package gopathwalk
+
+import (
+ "bufio"
+ "bytes"
+ "fmt"
+ "go/build"
+ "golang.org/x/tools/internal/fastwalk"
+ "io/ioutil"
+ "log"
+ "os"
+ "path/filepath"
+ "strings"
+)
+
+// Options controls the behavior of a Walk call.
+type Options struct {
+ Debug bool // Enable debug logging
+ ModulesEnabled bool // Search module caches. Also disables legacy goimports ignore rules.
+}
+
+// Walk walks Go source directories ($GOROOT, $GOPATH, etc) to find packages.
+// For each package found, add will be called (concurrently) with the absolute
+// paths of the containing source directory and the package directory.
+func Walk(add func(srcDir string, dir string), opts Options) {
+ for _, srcDir := range build.Default.SrcDirs() {
+ walkDir(srcDir, add, opts)
+ }
+}
+
+func walkDir(srcDir string, add func(string, string), opts Options) {
+ if opts.Debug {
+ log.Printf("scanning %s", srcDir)
+ }
+ w := &walker{
+ srcDir: srcDir,
+ srcV: filepath.Join(srcDir, "v"),
+ srcMod: filepath.Join(srcDir, "mod"),
+ add: add,
+ opts: opts,
+ }
+ w.init()
+ if err := fastwalk.Walk(srcDir, w.walk); err != nil {
+ log.Printf("goimports: scanning directory %v: %v", srcDir, err)
+ }
+
+ if opts.Debug {
+ defer log.Printf("scanned %s", srcDir)
+ }
+}
+
+// walker is the callback for fastwalk.Walk.
+type walker struct {
+ srcDir string // The source directory to scan.
+ srcV, srcMod string // vgo-style module cache dirs. Optional.
+ add func(string, string) // The callback that will be invoked for every possible Go package dir.
+ opts Options // Options passed to Walk by the user.
+
+ ignoredDirs []os.FileInfo // The ignored directories, loaded from .goimportsignore files.
+}
+
+// init initializes the walker based on its Options.
+func (w *walker) init() {
+ if !w.opts.ModulesEnabled {
+ w.ignoredDirs = w.getIgnoredDirs(w.srcDir)
+ }
+}
+
+// getIgnoredDirs reads an optional config file at <path>/.goimportsignore
+// of relative directories to ignore when scanning for go files.
+// The provided path is one of the $GOPATH entries with "src" appended.
+func (w *walker) getIgnoredDirs(path string) []os.FileInfo {
+ file := filepath.Join(path, ".goimportsignore")
+ slurp, err := ioutil.ReadFile(file)
+ if w.opts.Debug {
+ if err != nil {
+ log.Print(err)
+ } else {
+ log.Printf("Read %s", file)
+ }
+ }
+ if err != nil {
+ return nil
+ }
+
+ var ignoredDirs []os.FileInfo
+ bs := bufio.NewScanner(bytes.NewReader(slurp))
+ for bs.Scan() {
+ line := strings.TrimSpace(bs.Text())
+ if line == "" || strings.HasPrefix(line, "#") {
+ continue
+ }
+ full := filepath.Join(path, line)
+ if fi, err := os.Stat(full); err == nil {
+ ignoredDirs = append(ignoredDirs, fi)
+ if w.opts.Debug {
+ log.Printf("Directory added to ignore list: %s", full)
+ }
+ } else if w.opts.Debug {
+ log.Printf("Error statting entry in .goimportsignore: %v", err)
+ }
+ }
+ return ignoredDirs
+}
+
+func (w *walker) shouldSkipDir(fi os.FileInfo) bool {
+ for _, ignoredDir := range w.ignoredDirs {
+ if os.SameFile(fi, ignoredDir) {
+ return true
+ }
+ }
+ return false
+}
+
+func (w *walker) walk(path string, typ os.FileMode) error {
+ if !w.opts.ModulesEnabled && (path == w.srcV || path == w.srcMod) {
+ return filepath.SkipDir
+ }
+ dir := filepath.Dir(path)
+ if typ.IsRegular() {
+ if dir == w.srcDir {
+ // Doesn't make sense to have regular files
+ // directly in your $GOPATH/src or $GOROOT/src.
+ return fastwalk.SkipFiles
+ }
+ if !strings.HasSuffix(path, ".go") {
+ return nil
+ }
+
+ w.add(w.srcDir, dir)
+ return fastwalk.SkipFiles
+ }
+ if typ == os.ModeDir {
+ base := filepath.Base(path)
+ if base == "" || base[0] == '.' || base[0] == '_' ||
+ base == "testdata" || (!w.opts.ModulesEnabled && base == "node_modules") {
+ return filepath.SkipDir
+ }
+ fi, err := os.Lstat(path)
+ if err == nil && w.shouldSkipDir(fi) {
+ return filepath.SkipDir
+ }
+ return nil
+ }
+ if typ == os.ModeSymlink {
+ base := filepath.Base(path)
+ if strings.HasPrefix(base, ".#") {
+ // Emacs noise.
+ return nil
+ }
+ fi, err := os.Lstat(path)
+ if err != nil {
+ // Just ignore it.
+ return nil
+ }
+ if w.shouldTraverse(dir, fi) {
+ return fastwalk.TraverseLink
+ }
+ }
+ return nil
+}
+
+// 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 (w *walker) shouldTraverse(dir string, fi os.FileInfo) bool {
+ path := filepath.Join(dir, fi.Name())
+ target, err := filepath.EvalSymlinks(path)
+ if err != nil {
+ return false
+ }
+ ts, err := os.Stat(target)
+ if err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ return false
+ }
+ if !ts.IsDir() {
+ return false
+ }
+ if w.shouldSkipDir(ts) {
+ return false
+ }
+ // Check for symlink loops by statting each directory component
+ // and seeing if any are the same file as ts.
+ for {
+ parent := filepath.Dir(path)
+ if parent == path {
+ // Made it to the root without seeing a cycle.
+ // Use this symlink.
+ return true
+ }
+ parentInfo, err := os.Stat(parent)
+ if err != nil {
+ return false
+ }
+ if os.SameFile(ts, parentInfo) {
+ // Cycle. Don't traverse.
+ return false
+ }
+ path = parent
+ }
+
+}
diff --git a/internal/gopathwalk/walk_test.go b/internal/gopathwalk/walk_test.go
new file mode 100644
index 0000000..8e310c0
--- /dev/null
+++ b/internal/gopathwalk/walk_test.go
@@ -0,0 +1,135 @@
+// Copyright 2018 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 gopathwalk
+
+import (
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "reflect"
+ "runtime"
+ "strings"
+ "testing"
+)
+
+func TestShouldTraverse(t *testing.T) {
+ switch runtime.GOOS {
+ case "windows", "plan9":
+ t.Skipf("skipping symlink-requiring test on %s", runtime.GOOS)
+ }
+
+ dir, err := ioutil.TempDir("", "goimports-")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(dir)
+
+ // Note: mapToDir prepends "src" to each element, since
+ // mapToDir was made for creating GOPATHs.
+ if err := mapToDir(dir, map[string]string{
+ "foo/foo2/file.txt": "",
+ "foo/foo2/link-to-src": "LINK:../../",
+ "foo/foo2/link-to-src-foo": "LINK:../../foo",
+ "foo/foo2/link-to-dot": "LINK:.",
+ "bar/bar2/file.txt": "",
+ "bar/bar2/link-to-src-foo": "LINK:../../foo",
+
+ "a/b/c": "LINK:../../a/d",
+ "a/d/e": "LINK:../../a/b",
+ }); err != nil {
+ t.Fatal(err)
+ }
+ tests := []struct {
+ dir string
+ file string
+ want bool
+ }{
+ {
+ dir: "src/foo/foo2",
+ file: "link-to-src-foo",
+ want: false, // loop
+ },
+ {
+ dir: "src/foo/foo2",
+ file: "link-to-src",
+ want: false, // loop
+ },
+ {
+ dir: "src/foo/foo2",
+ file: "link-to-dot",
+ want: false, // loop
+ },
+ {
+ dir: "src/bar/bar2",
+ file: "link-to-src-foo",
+ want: true, // not a loop
+ },
+ {
+ dir: "src/a/b/c",
+ file: "e",
+ want: false, // loop: "e" is the same as "b".
+ },
+ }
+ for i, tt := range tests {
+ fi, err := os.Stat(filepath.Join(dir, tt.dir, tt.file))
+ if err != nil {
+ t.Errorf("%d. Stat = %v", i, err)
+ continue
+ }
+ var w walker
+ got := w.shouldTraverse(filepath.Join(dir, tt.dir), fi)
+ if got != tt.want {
+ t.Errorf("%d. shouldTraverse(%q, %q) = %v; want %v", i, tt.dir, tt.file, got, tt.want)
+ }
+ }
+}
+
+// TestSkip tests that various goimports rules are followed in non-modules mode.
+func TestSkip(t *testing.T) {
+ dir, err := ioutil.TempDir("", "goimports-")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(dir)
+
+ if err := mapToDir(dir, map[string]string{
+ "ignoreme/f.go": "package ignoreme", // ignored by .goimportsignore
+ "node_modules/f.go": "package nodemodules;", // ignored by hardcoded node_modules filter
+ "v/f.go": "package v;", // ignored by hardcoded vgo cache rule
+ "mod/f.go": "package mod;", // ignored by hardcoded vgo cache rule
+ "shouldfind/f.go": "package shouldfind;", // not ignored
+
+ ".goimportsignore": "ignoreme\n",
+ }); err != nil {
+ t.Fatal(err)
+ }
+
+ var found []string
+ walkDir(filepath.Join(dir, "src"), func(srcDir string, dir string) {
+ found = append(found, dir[len(srcDir)+1:])
+ }, Options{ModulesEnabled: false})
+ if want := []string{"shouldfind"}; !reflect.DeepEqual(found, want) {
+ t.Errorf("expected to find only %v, got %v", want, found)
+ }
+}
+
+func mapToDir(destDir string, files map[string]string) error {
+ for path, contents := range files {
+ file := filepath.Join(destDir, "src", path)
+ if err := os.MkdirAll(filepath.Dir(file), 0755); err != nil {
+ return err
+ }
+ 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 {
+ return err
+ }
+ }
+ return nil
+}