diff --git a/cmd/dep/integration_test.go b/cmd/dep/integration_test.go index 672f1aa071..8f1acb9f96 100644 --- a/cmd/dep/integration_test.go +++ b/cmd/dep/integration_test.go @@ -65,31 +65,57 @@ func TestDepCachedir(t *testing.T) { initPath := filepath.Join("testdata", "cachedir") - testProj := integration.NewTestProject(t, initPath, wd, runMain) - defer testProj.Cleanup() + t.Run("env-cachedir", func(t *testing.T) { + t.Parallel() + testProj := integration.NewTestProject(t, initPath, wd, runMain) + defer testProj.Cleanup() - testProj.TempDir("cachedir") - cachedir := testProj.Path("cachedir") - testProj.Setenv("DEPCACHEDIR", cachedir) + testProj.TempDir("cachedir") + cachedir := testProj.Path("cachedir") + testProj.Setenv("DEPCACHEDIR", cachedir) - // Running `dep ensure` will pull in the dependency into cachedir. - err = testProj.DoRun([]string{"ensure"}) - if err != nil { - // Log the error output from running `dep ensure`, could be useful. - t.Log(testProj.GetStderr()) - t.Fatalf("got an unexpected error: %s", err.Error()) - } + // Running `dep ensure` will pull in the dependency into cachedir. + err = testProj.DoRun([]string{"ensure"}) + if err != nil { + // Log the error output from running `dep ensure`, could be useful. + t.Logf("`dep ensure` error output: \n%s", testProj.GetStderr()) + t.Errorf("got an unexpected error: %s", err) + } - // Check that the cache was created in the cachedir. Our fixture has the dependency - // `github.com/sdboyer/deptest` - _, err = os.Stat(testProj.Path("cachedir", "sources", "https---github.com-sdboyer-deptest")) - if err != nil { - if os.IsNotExist(err) { - t.Fatal("Expected cachedir to have been populated but none was found") - } else { - t.Fatalf("Got unexpected error: %s", err) + // Check that the cache was created in the cachedir. Our fixture has the dependency + // `github.com/sdboyer/deptest` + _, err = os.Stat(testProj.Path("cachedir", "sources", "https---github.com-sdboyer-deptest")) + if err != nil { + if os.IsNotExist(err) { + t.Error("expected cachedir to have been populated but none was found") + } else { + t.Errorf("got an unexpected error: %s", err) + } } - } + }) + t.Run("env-invalid-cachedir", func(t *testing.T) { + t.Parallel() + testProj := integration.NewTestProject(t, initPath, wd, runMain) + defer testProj.Cleanup() + + cachedir := "/invalid/path" + testProj.Setenv("DEPCACHEDIR", cachedir) + wantErr := fmt.Sprintf( + "dep: $DEPCACHEDIR set to an invalid or inaccessible path: %q", cachedir, + ) + + // Running `dep ensure` will pull in the dependency into cachedir. + err = testProj.DoRun([]string{"ensure"}) + + if err == nil { + // Log the output from running `dep ensure`, could be useful. + t.Logf("test run output: \n%s\n%s", testProj.GetStdout(), testProj.GetStderr()) + t.Error("unexpected result: \n\t(GOT) nil\n\t(WNT) exit status 1") + } else if gotErr := strings.TrimSpace(testProj.GetStderr()); gotErr != wantErr { + t.Errorf("unexpected error output: \n\t(GOT) %s\n\t(WNT) %s", gotErr, wantErr) + } + }) + } // execCmd is a test.RunFunc which runs the program in another process. diff --git a/cmd/dep/main.go b/cmd/dep/main.go index 34fcd7a1cb..bf15060e6d 100644 --- a/cmd/dep/main.go +++ b/cmd/dep/main.go @@ -17,6 +17,7 @@ import ( "text/tabwriter" "github.com/golang/dep" + "github.com/golang/dep/internal/fs" ) type command interface { @@ -121,18 +122,18 @@ func (c *Config) Run() (exitCode int) { for _, cmd := range commands { if cmd.Name() == cmdName { // Build flag set with global flags in there. - fs := flag.NewFlagSet(cmdName, flag.ContinueOnError) - fs.SetOutput(c.Stderr) - verbose := fs.Bool("v", false, "enable verbose logging") + flags := flag.NewFlagSet(cmdName, flag.ContinueOnError) + flags.SetOutput(c.Stderr) + verbose := flags.Bool("v", false, "enable verbose logging") // Register the subcommand flags in there, too. - cmd.Register(fs) + cmd.Register(flags) // Override the usage text to something nicer. - resetUsage(errLogger, fs, cmdName, cmd.Args(), cmd.LongHelp()) + resetUsage(errLogger, flags, cmdName, cmd.Args(), cmd.LongHelp()) if printCommandHelp { - fs.Usage() + flags.Usage() exitCode = 1 return } @@ -140,7 +141,7 @@ func (c *Config) Run() (exitCode int) { // Parse the flags the user gave us. // flag package automatically prints usage and error message in err != nil // or if '-h' flag provided - if err := fs.Parse(c.Args[2:]); err != nil { + if err := flags.Parse(c.Args[2:]); err != nil { exitCode = 1 return } @@ -148,6 +149,13 @@ func (c *Config) Run() (exitCode int) { // Cachedir is loaded from env if present. `$GOPATH/pkg/dep` is used as the // fallback cache location. cachedir := getEnv(c.Env, "DEPCACHEDIR") + if cachedir != "" && !fs.IsValidPath(cachedir) { + errLogger.Printf( + "dep: $DEPCACHEDIR set to an invalid or inaccessible path: %q\n", cachedir, + ) + exitCode = 1 + return + } // Set up dep context. ctx := &dep.Ctx{ @@ -162,7 +170,7 @@ func (c *Config) Run() (exitCode int) { ctx.SetPaths(c.WorkingDir, GOPATHS...) // Run the command with the post-flag-processing args. - if err := cmd.Run(ctx, fs.Args()); err != nil { + if err := cmd.Run(ctx, flags.Args()); err != nil { errLogger.Printf("%v\n", err) exitCode = 1 return diff --git a/internal/fs/fs.go b/internal/fs/fs.go index bfacfd95af..874d075989 100644 --- a/internal/fs/fs.go +++ b/internal/fs/fs.go @@ -480,6 +480,24 @@ func cloneSymlink(sl, dst string) error { return os.Symlink(resolved, dst) } +// IsValidPath checks if the given string is a valid path. +func IsValidPath(fp string) bool { + // See https://stackoverflow.com/questions/35231846/golang-check-if-string-is-valid-path + // Check if file/dir already exists + if _, err := os.Stat(fp); err == nil { + return true + } + + // Attempt to create it + var d []byte + if err := ioutil.WriteFile(fp, d, 0644); err == nil { + os.Remove(fp) // And delete it + return true + } + + return false +} + // IsDir determines is the path given is a directory or not. func IsDir(name string) (bool, error) { fi, err := os.Stat(name) diff --git a/internal/fs/fs_test.go b/internal/fs/fs_test.go index 4b9422dd88..63a86cb7cd 100644 --- a/internal/fs/fs_test.go +++ b/internal/fs/fs_test.go @@ -837,6 +837,47 @@ func setupInaccessibleDir(t *testing.T, op func(dir string) error) func() { return cleanup } +func TestIsValidPath(t *testing.T) { + wd, err := os.Getwd() + if err != nil { + t.Fatal(err) + } + + var dn string + + cleanup := setupInaccessibleDir(t, func(dir string) error { + dn = filepath.Join(dir, "dir") + return os.Mkdir(dn, 0777) + }) + defer cleanup() + + tests := map[string]bool{ + wd: true, + filepath.Join(wd, "testdata"): true, + filepath.Join(wd, "main.go"): true, + filepath.Join(wd, "this_file_does_not_exist.thing"): true, + dn: false, + "": false, + "/invalid/path": false, + } + + if runtime.GOOS == "windows" { + // This test doesn't work on Microsoft Windows because + // of the differences in how file permissions are + // implemented. For this to work, the directory where + // the directory exists should be inaccessible. + delete(tests, dn) + } + + for fp, want := range tests { + got := IsValidPath(fp) + + if got != want { + t.Fatalf("expected %t for %s, got %t", want, fp, got) + } + } +} + func TestIsRegular(t *testing.T) { wd, err := os.Getwd() if err != nil {