From 366e27042d04e36bdf2d0033252e4e29f958f175 Mon Sep 17 00:00:00 2001 From: Rebecca Stambler Date: Fri, 2 Dec 2016 10:55:52 -0500 Subject: [PATCH] cmd/guru: handle source file aliasing gracefully Resolve symlinks in GOPATH or source file path in order to find correct package. Fixes golang/go#17515 Change-Id: Iaf7e85578fce040b329427ce6f51948a69e57a39 Reviewed-on: https://go-review.googlesource.com/33858 Reviewed-by: Alan Donovan --- cmd/guru/unit_test.go | 85 +++++++++++++++++++++++++++++++++++++++++++ cmd/guru/what.go | 20 +++++++--- 2 files changed, 100 insertions(+), 5 deletions(-) create mode 100644 cmd/guru/unit_test.go diff --git a/cmd/guru/unit_test.go b/cmd/guru/unit_test.go new file mode 100644 index 00000000..45678ff9 --- /dev/null +++ b/cmd/guru/unit_test.go @@ -0,0 +1,85 @@ +// Copyright 2013 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 main + +import ( + "fmt" + "go/build" + "io/ioutil" + "os" + "strings" + "testing" +) + +// Unit tests for internal guru functions + +func TestIssue17515(t *testing.T) { + // Tests handling of symlinks in function guessImportPath + // If we have Go code inside $HOME/go/src and create a symlink $HOME/src to it + // there are 4 possible cases that need to be tested: + // (1) absolute & absolute: GOPATH=$HOME/go/src file=$HOME/go/src/test/test.go + // (2) absolute & symlink: GOPATH=$HOME/go/src file=$HOME/src/test/test.go + // (3) symlink & symlink: GOPATH=$HOME/src file=$HOME/src/test/test.go + // (4) symlink & absolute: GOPATH=$HOME/src file= $HOME/go/src/test/test.go + + // Create a temporary home directory under /tmp + home, err := ioutil.TempDir(os.TempDir(), "home") + if err != nil { + t.Errorf("Unable to create a temporary directory in %s", os.TempDir()) + } + + // create filepath /tmp/home/go/src/test/test.go + if err = os.MkdirAll(home+"/go/src/test", 0755); err != nil { + t.Fatal(err) + } + + // symlink between /tmp/home/go/src and /tmp/home/src + if err = os.Symlink(home+"/go/src", home+"/src"); err != nil { + t.Fatal(err) + } + + // Defer tear down (removing files, symlinks) + defer os.RemoveAll(home) + + var buildContext = build.Default + + // Success test cases + for _, test := range []struct { + gopath, filename, wantSrcdir string + }{ + {home + "/go", home + "/go/src/test/test.go", home + "/go/src"}, + {home + "/go", home + "/src/test/test.go", home + "/go/src"}, + {home, home + "/src/test/test.go", home + "/src"}, + {home, home + "/go/src/test/test.go", home + "/src"}, + } { + buildContext.GOPATH = test.gopath + srcdir, importPath, err := guessImportPath(test.filename, &buildContext) + if srcdir != test.wantSrcdir || importPath != "test" || err != nil { + t.Errorf("guessImportPath(%v, %v) = %v, %v, %v; want %v, %v, %v", + test.filename, test.gopath, srcdir, importPath, err, test.wantSrcdir, "test", "nil") + } + } + // Function to format expected error message + errFormat := func(fpath string) string { + return fmt.Sprintf("can't evaluate symlinks of %s", fpath) + } + + // Failure test cases + for _, test := range []struct { + gopath, filename, wantErr string + }{ + {home + "/go", home + "/go/src/fake/test.go", errFormat(home + "/go/src/fake")}, + {home + "/go", home + "/src/fake/test.go", errFormat(home + "/src/fake")}, + {home, home + "/src/fake/test.go", errFormat(home + "/src/fake")}, + {home, home + "/go/src/fake/test.go", errFormat(home + "/go/src/fake")}, + } { + buildContext.GOPATH = test.gopath + srcdir, importPath, err := guessImportPath(test.filename, &buildContext) + if !strings.HasPrefix(fmt.Sprint(err), test.wantErr) { + t.Errorf("guessImportPath(%v, %v) = %v, %v, %v; want %v, %v, %v", + test.filename, test.gopath, srcdir, importPath, err, "", "", test.wantErr) + } + } +} diff --git a/cmd/guru/what.go b/cmd/guru/what.go index 4f888b02..a970cf85 100644 --- a/cmd/guru/what.go +++ b/cmd/guru/what.go @@ -169,11 +169,16 @@ func what(q *Query) error { func guessImportPath(filename string, buildContext *build.Context) (srcdir, importPath string, err error) { absFile, err := filepath.Abs(filename) if err != nil { - err = fmt.Errorf("can't form absolute path of %s", filename) - return + return "", "", fmt.Errorf("can't form absolute path of %s: %v", filename, err) } - absFileDir := segments(filepath.Dir(absFile)) + absFileDir := filepath.Dir(absFile) + resolvedAbsFileDir, err := filepath.EvalSymlinks(absFileDir) + if err != nil { + return "", "", fmt.Errorf("can't evaluate symlinks of %s: %v", absFileDir, err) + } + + segmentedAbsFileDir := segments(resolvedAbsFileDir) // Find the innermost directory in $GOPATH that encloses filename. minD := 1024 for _, gopathDir := range buildContext.SrcDirs() { @@ -181,14 +186,19 @@ func guessImportPath(filename string, buildContext *build.Context) (srcdir, impo if err != nil { continue // e.g. non-existent dir on $GOPATH } - d := prefixLen(segments(absDir), absFileDir) + resolvedAbsDir, err := filepath.EvalSymlinks(absDir) + if err != nil { + continue // e.g. non-existent dir on $GOPATH + } + + d := prefixLen(segments(resolvedAbsDir), segmentedAbsFileDir) // If there are multiple matches, // prefer the innermost enclosing directory // (smallest d). if d >= 0 && d < minD { minD = d srcdir = gopathDir - importPath = strings.Join(absFileDir[len(absFileDir)-minD:], string(os.PathSeparator)) + importPath = strings.Join(segmentedAbsFileDir[len(segmentedAbsFileDir)-minD:], string(os.PathSeparator)) } } if srcdir == "" {