forked from golang/dep
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request sdboyer/gps#199 from spenczar/fix_symlink_vendor_dir
Follow symlinks when stripping vendor directories
- Loading branch information
Showing
7 changed files
with
584 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
package gps | ||
|
||
import ( | ||
"os" | ||
"path/filepath" | ||
"testing" | ||
) | ||
|
||
// This file contains utilities for running tests around file system state. | ||
|
||
// fspath represents a file system path in an OS-agnostic way. | ||
type fsPath []string | ||
|
||
func (f fsPath) String() string { return filepath.Join(f...) } | ||
|
||
func (f fsPath) prepend(prefix string) fsPath { | ||
p := fsPath{filepath.FromSlash(prefix)} | ||
return append(p, f...) | ||
} | ||
|
||
type fsTestCase struct { | ||
before, after filesystemState | ||
} | ||
|
||
// filesystemState represents the state of a file system. It has a setup method | ||
// which inflates its state to the actual host file system, and an assert | ||
// method which checks that the actual file system matches the described state. | ||
type filesystemState struct { | ||
root string | ||
dirs []fsPath | ||
files []fsPath | ||
links []fsLink | ||
} | ||
|
||
// assert makes sure that the fs state matches the state of the actual host | ||
// file system | ||
func (fs filesystemState) assert(t *testing.T) { | ||
dirMap := make(map[string]bool) | ||
fileMap := make(map[string]bool) | ||
linkMap := make(map[string]bool) | ||
|
||
for _, d := range fs.dirs { | ||
dirMap[d.prepend(fs.root).String()] = true | ||
} | ||
for _, f := range fs.files { | ||
fileMap[f.prepend(fs.root).String()] = true | ||
} | ||
for _, l := range fs.links { | ||
linkMap[l.path.prepend(fs.root).String()] = true | ||
} | ||
|
||
err := filepath.Walk(fs.root, func(path string, info os.FileInfo, err error) error { | ||
if err != nil { | ||
t.Errorf("filepath.Walk path=%q err=%q", path, err) | ||
return err | ||
} | ||
|
||
if path == fs.root { | ||
return nil | ||
} | ||
|
||
// Careful! Have to check whether the path is a symlink first because, on | ||
// windows, a symlink to a directory will return 'true' for info.IsDir(). | ||
if (info.Mode() & os.ModeSymlink) != 0 { | ||
if linkMap[path] { | ||
delete(linkMap, path) | ||
} else { | ||
t.Errorf("unexpected symlink exists %q", path) | ||
} | ||
return nil | ||
} | ||
|
||
if info.IsDir() { | ||
if dirMap[path] { | ||
delete(dirMap, path) | ||
} else { | ||
t.Errorf("unexpected directory exists %q", path) | ||
} | ||
return nil | ||
} | ||
|
||
if fileMap[path] { | ||
delete(fileMap, path) | ||
} else { | ||
t.Errorf("unexpected file exists %q", path) | ||
} | ||
return nil | ||
}) | ||
|
||
if err != nil { | ||
t.Errorf("filesystem.Walk err=%q", err) | ||
} | ||
|
||
for d := range dirMap { | ||
t.Errorf("could not find expected directory %q", d) | ||
} | ||
for f := range fileMap { | ||
t.Errorf("could not find expected file %q", f) | ||
} | ||
for l := range linkMap { | ||
t.Errorf("could not find expected symlink %q", l) | ||
} | ||
} | ||
|
||
// fsLink represents a symbolic link. | ||
type fsLink struct { | ||
path fsPath | ||
to string | ||
} | ||
|
||
// setup inflates fs onto the actual host file system | ||
func (fs filesystemState) setup(t *testing.T) { | ||
fs.setupDirs(t) | ||
fs.setupFiles(t) | ||
fs.setupLinks(t) | ||
} | ||
|
||
func (fs filesystemState) setupDirs(t *testing.T) { | ||
for _, dir := range fs.dirs { | ||
p := dir.prepend(fs.root) | ||
if err := os.MkdirAll(p.String(), 0777); err != nil { | ||
t.Fatalf("os.MkdirAll(%q, 0777) err=%q", p, err) | ||
} | ||
} | ||
} | ||
|
||
func (fs filesystemState) setupFiles(t *testing.T) { | ||
for _, file := range fs.files { | ||
p := file.prepend(fs.root) | ||
f, err := os.Create(p.String()) | ||
if err != nil { | ||
t.Fatalf("os.Create(%q) err=%q", p, err) | ||
} | ||
if err := f.Close(); err != nil { | ||
t.Fatalf("file %q Close() err=%q", p, err) | ||
} | ||
} | ||
} | ||
|
||
func (fs filesystemState) setupLinks(t *testing.T) { | ||
for _, link := range fs.links { | ||
p := link.path.prepend(fs.root) | ||
|
||
// On Windows, relative symlinks confuse filepath.Walk. This is golang/go | ||
// issue 17540. So, we'll just sigh and do absolute links, assuming they are | ||
// relative to the directory of link.path. | ||
dir := filepath.Dir(p.String()) | ||
to := filepath.Join(dir, link.to) | ||
|
||
if err := os.Symlink(to, p.String()); err != nil { | ||
t.Fatalf("os.Symlink(%q, %q) err=%q", to, p, err) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
//+build !windows | ||
|
||
package gps | ||
|
||
import "os" | ||
|
||
func stripVendor(path string, info os.FileInfo, err error) error { | ||
if info.Name() == "vendor" { | ||
if _, err := os.Lstat(path); err == nil { | ||
if (info.Mode() & os.ModeSymlink) != 0 { | ||
realInfo, err := os.Stat(path) | ||
if err != nil { | ||
return err | ||
} | ||
if realInfo.IsDir() { | ||
return os.Remove(path) | ||
} | ||
} | ||
if info.IsDir() { | ||
return removeAll(path) | ||
} | ||
} | ||
} | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
// +build !windows | ||
|
||
package gps | ||
|
||
import "testing" | ||
|
||
func TestStripVendorSymlinks(t *testing.T) { | ||
t.Run("vendor symlink", stripVendorTestCase(fsTestCase{ | ||
before: filesystemState{ | ||
dirs: []fsPath{ | ||
fsPath{"package"}, | ||
fsPath{"package", "_vendor"}, | ||
}, | ||
links: []fsLink{ | ||
fsLink{ | ||
path: fsPath{"package", "vendor"}, | ||
to: "_vendor", | ||
}, | ||
}, | ||
}, | ||
after: filesystemState{ | ||
dirs: []fsPath{ | ||
fsPath{"package"}, | ||
fsPath{"package", "_vendor"}, | ||
}, | ||
}, | ||
})) | ||
|
||
t.Run("nonvendor symlink", stripVendorTestCase(fsTestCase{ | ||
before: filesystemState{ | ||
dirs: []fsPath{ | ||
fsPath{"package"}, | ||
fsPath{"package", "_vendor"}, | ||
}, | ||
links: []fsLink{ | ||
fsLink{ | ||
path: fsPath{"package", "link"}, | ||
to: "_vendor", | ||
}, | ||
}, | ||
}, | ||
after: filesystemState{ | ||
dirs: []fsPath{ | ||
fsPath{"package"}, | ||
fsPath{"package", "_vendor"}, | ||
}, | ||
links: []fsLink{ | ||
fsLink{ | ||
path: fsPath{"package", "link"}, | ||
to: "_vendor", | ||
}, | ||
}, | ||
}, | ||
})) | ||
|
||
t.Run("vendor symlink to file", stripVendorTestCase(fsTestCase{ | ||
before: filesystemState{ | ||
files: []fsPath{ | ||
fsPath{"file"}, | ||
}, | ||
links: []fsLink{ | ||
fsLink{ | ||
path: fsPath{"vendor"}, | ||
to: "file", | ||
}, | ||
}, | ||
}, | ||
after: filesystemState{ | ||
files: []fsPath{ | ||
fsPath{"file"}, | ||
}, | ||
links: []fsLink{ | ||
fsLink{ | ||
path: fsPath{"vendor"}, | ||
to: "file", | ||
}, | ||
}, | ||
}, | ||
})) | ||
|
||
t.Run("chained symlinks", stripVendorTestCase(fsTestCase{ | ||
before: filesystemState{ | ||
dirs: []fsPath{ | ||
fsPath{"_vendor"}, | ||
}, | ||
links: []fsLink{ | ||
fsLink{ | ||
path: fsPath{"vendor"}, | ||
to: "vendor2", | ||
}, | ||
fsLink{ | ||
path: fsPath{"vendor2"}, | ||
to: "_vendor", | ||
}, | ||
}, | ||
}, | ||
after: filesystemState{ | ||
dirs: []fsPath{ | ||
fsPath{"_vendor"}, | ||
}, | ||
links: []fsLink{ | ||
fsLink{ | ||
path: fsPath{"vendor2"}, | ||
to: "_vendor", | ||
}, | ||
}, | ||
}, | ||
})) | ||
|
||
t.Run("circular symlinks", stripVendorTestCase(fsTestCase{ | ||
before: filesystemState{ | ||
dirs: []fsPath{ | ||
fsPath{"package"}, | ||
}, | ||
links: []fsLink{ | ||
fsLink{ | ||
path: fsPath{"package", "link1"}, | ||
to: "link2", | ||
}, | ||
fsLink{ | ||
path: fsPath{"package", "link2"}, | ||
to: "link1", | ||
}, | ||
}, | ||
}, | ||
after: filesystemState{ | ||
dirs: []fsPath{ | ||
fsPath{"package"}, | ||
}, | ||
links: []fsLink{ | ||
fsLink{ | ||
path: fsPath{"package", "link1"}, | ||
to: "link2", | ||
}, | ||
fsLink{ | ||
path: fsPath{"package", "link2"}, | ||
to: "link1", | ||
}, | ||
}, | ||
}, | ||
})) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
package gps | ||
|
||
import ( | ||
"io/ioutil" | ||
"os" | ||
"path/filepath" | ||
"testing" | ||
) | ||
|
||
func stripVendorTestCase(tc fsTestCase) func(*testing.T) { | ||
return func(t *testing.T) { | ||
tempDir, err := ioutil.TempDir("", "TestStripVendor") | ||
if err != nil { | ||
t.Fatalf("ioutil.TempDir err=%q", err) | ||
} | ||
defer func() { | ||
if err := os.RemoveAll(tempDir); err != nil { | ||
t.Errorf("os.RemoveAll(%q) err=%q", tempDir, err) | ||
} | ||
}() | ||
tc.before.root = tempDir | ||
tc.after.root = tempDir | ||
|
||
tc.before.setup(t) | ||
|
||
if err := filepath.Walk(tempDir, stripVendor); err != nil { | ||
t.Errorf("filepath.Walk err=%q", err) | ||
} | ||
|
||
tc.after.assert(t) | ||
} | ||
} | ||
|
||
func TestStripVendorDirectory(t *testing.T) { | ||
t.Run("vendor directory", stripVendorTestCase(fsTestCase{ | ||
before: filesystemState{ | ||
dirs: []fsPath{ | ||
fsPath{"package"}, | ||
fsPath{"package", "vendor"}, | ||
}, | ||
}, | ||
after: filesystemState{ | ||
dirs: []fsPath{ | ||
fsPath{"package"}, | ||
}, | ||
}, | ||
})) | ||
|
||
t.Run("vendor file", stripVendorTestCase(fsTestCase{ | ||
before: filesystemState{ | ||
dirs: []fsPath{ | ||
fsPath{"package"}, | ||
}, | ||
files: []fsPath{ | ||
fsPath{"package", "vendor"}, | ||
}, | ||
}, | ||
after: filesystemState{ | ||
dirs: []fsPath{ | ||
fsPath{"package"}, | ||
}, | ||
files: []fsPath{ | ||
fsPath{"package", "vendor"}, | ||
}, | ||
}, | ||
})) | ||
} |
Oops, something went wrong.