mirror of
https://github.com/golang/go
synced 2024-11-05 15:56:12 -07:00
f60b69ed8c
Change the shouldTraverse function to no longer keep a global map of which inodes it's seen. Instead, whenever a symlink is seen for a path name, check every directory entry in that path name and see if any are the same inode as the current one, detecting any loop just from the name itself. More details of why the test was flaky are in the bug. Fixes golang/go#18142 Change-Id: I869f7a13d130c63d78b7af81802a16c4b4b2f3bd Reviewed-on: https://go-review.googlesource.com/37947 Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Ian Lance Taylor <iant@golang.org>
1755 lines
34 KiB
Go
1755 lines
34 KiB
Go
// 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 imports
|
|
|
|
import (
|
|
"bytes"
|
|
"flag"
|
|
"go/build"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
)
|
|
|
|
var only = flag.String("only", "", "If non-empty, the fix test to run")
|
|
|
|
var tests = []struct {
|
|
name string
|
|
formatOnly bool
|
|
in, out string
|
|
}{
|
|
// Adding an import to an existing parenthesized import
|
|
{
|
|
name: "factored_imports_add",
|
|
in: `package foo
|
|
import (
|
|
"fmt"
|
|
)
|
|
func bar() {
|
|
var b bytes.Buffer
|
|
fmt.Println(b.String())
|
|
}
|
|
`,
|
|
out: `package foo
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
)
|
|
|
|
func bar() {
|
|
var b bytes.Buffer
|
|
fmt.Println(b.String())
|
|
}
|
|
`,
|
|
},
|
|
|
|
// Adding an import to an existing parenthesized import,
|
|
// verifying it goes into the first section.
|
|
{
|
|
name: "factored_imports_add_first_sec",
|
|
in: `package foo
|
|
import (
|
|
"fmt"
|
|
|
|
"appengine"
|
|
)
|
|
func bar() {
|
|
var b bytes.Buffer
|
|
_ = appengine.IsDevServer
|
|
fmt.Println(b.String())
|
|
}
|
|
`,
|
|
out: `package foo
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
|
|
"appengine"
|
|
)
|
|
|
|
func bar() {
|
|
var b bytes.Buffer
|
|
_ = appengine.IsDevServer
|
|
fmt.Println(b.String())
|
|
}
|
|
`,
|
|
},
|
|
|
|
// Adding an import to an existing parenthesized import,
|
|
// verifying it goes into the first section. (test 2)
|
|
{
|
|
name: "factored_imports_add_first_sec_2",
|
|
in: `package foo
|
|
import (
|
|
"fmt"
|
|
|
|
"appengine"
|
|
)
|
|
func bar() {
|
|
_ = math.NaN
|
|
_ = fmt.Sprintf
|
|
_ = appengine.IsDevServer
|
|
}
|
|
`,
|
|
out: `package foo
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
|
|
"appengine"
|
|
)
|
|
|
|
func bar() {
|
|
_ = math.NaN
|
|
_ = fmt.Sprintf
|
|
_ = appengine.IsDevServer
|
|
}
|
|
`,
|
|
},
|
|
|
|
// Adding a new import line, without parens
|
|
{
|
|
name: "add_import_section",
|
|
in: `package foo
|
|
func bar() {
|
|
var b bytes.Buffer
|
|
}
|
|
`,
|
|
out: `package foo
|
|
|
|
import "bytes"
|
|
|
|
func bar() {
|
|
var b bytes.Buffer
|
|
}
|
|
`,
|
|
},
|
|
|
|
// Adding two new imports, which should make a parenthesized import decl.
|
|
{
|
|
name: "add_import_paren_section",
|
|
in: `package foo
|
|
func bar() {
|
|
_, _ := bytes.Buffer, zip.NewReader
|
|
}
|
|
`,
|
|
out: `package foo
|
|
|
|
import (
|
|
"archive/zip"
|
|
"bytes"
|
|
)
|
|
|
|
func bar() {
|
|
_, _ := bytes.Buffer, zip.NewReader
|
|
}
|
|
`,
|
|
},
|
|
|
|
// Make sure we don't add things twice
|
|
{
|
|
name: "no_double_add",
|
|
in: `package foo
|
|
func bar() {
|
|
_, _ := bytes.Buffer, bytes.NewReader
|
|
}
|
|
`,
|
|
out: `package foo
|
|
|
|
import "bytes"
|
|
|
|
func bar() {
|
|
_, _ := bytes.Buffer, bytes.NewReader
|
|
}
|
|
`,
|
|
},
|
|
|
|
// Remove unused imports, 1 of a factored block
|
|
{
|
|
name: "remove_unused_1_of_2",
|
|
in: `package foo
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
)
|
|
|
|
func bar() {
|
|
_, _ := bytes.Buffer, bytes.NewReader
|
|
}
|
|
`,
|
|
out: `package foo
|
|
|
|
import (
|
|
"bytes"
|
|
)
|
|
|
|
func bar() {
|
|
_, _ := bytes.Buffer, bytes.NewReader
|
|
}
|
|
`,
|
|
},
|
|
|
|
// Remove unused imports, 2 of 2
|
|
{
|
|
name: "remove_unused_2_of_2",
|
|
in: `package foo
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
)
|
|
|
|
func bar() {
|
|
}
|
|
`,
|
|
out: `package foo
|
|
|
|
func bar() {
|
|
}
|
|
`,
|
|
},
|
|
|
|
// Remove unused imports, 1 of 1
|
|
{
|
|
name: "remove_unused_1_of_1",
|
|
in: `package foo
|
|
|
|
import "fmt"
|
|
|
|
func bar() {
|
|
}
|
|
`,
|
|
out: `package foo
|
|
|
|
func bar() {
|
|
}
|
|
`,
|
|
},
|
|
|
|
// Don't remove empty imports.
|
|
{
|
|
name: "dont_remove_empty_imports",
|
|
in: `package foo
|
|
import (
|
|
_ "image/png"
|
|
_ "image/jpeg"
|
|
)
|
|
`,
|
|
out: `package foo
|
|
|
|
import (
|
|
_ "image/jpeg"
|
|
_ "image/png"
|
|
)
|
|
`,
|
|
},
|
|
|
|
// Don't remove dot imports.
|
|
{
|
|
name: "dont_remove_dot_imports",
|
|
in: `package foo
|
|
import (
|
|
. "foo"
|
|
. "bar"
|
|
)
|
|
`,
|
|
out: `package foo
|
|
|
|
import (
|
|
. "bar"
|
|
. "foo"
|
|
)
|
|
`,
|
|
},
|
|
|
|
// Skip refs the parser can resolve.
|
|
{
|
|
name: "skip_resolved_refs",
|
|
in: `package foo
|
|
|
|
func f() {
|
|
type t struct{ Println func(string) }
|
|
fmt := t{Println: func(string) {}}
|
|
fmt.Println("foo")
|
|
}
|
|
`,
|
|
out: `package foo
|
|
|
|
func f() {
|
|
type t struct{ Println func(string) }
|
|
fmt := t{Println: func(string) {}}
|
|
fmt.Println("foo")
|
|
}
|
|
`,
|
|
},
|
|
|
|
// Do not add a package we already have a resolution for.
|
|
{
|
|
name: "skip_template",
|
|
in: `package foo
|
|
|
|
import "html/template"
|
|
|
|
func f() { t = template.New("sometemplate") }
|
|
`,
|
|
out: `package foo
|
|
|
|
import "html/template"
|
|
|
|
func f() { t = template.New("sometemplate") }
|
|
`,
|
|
},
|
|
|
|
// Don't touch cgo
|
|
{
|
|
name: "cgo",
|
|
in: `package foo
|
|
|
|
/*
|
|
#include <foo.h>
|
|
*/
|
|
import "C"
|
|
`,
|
|
out: `package foo
|
|
|
|
/*
|
|
#include <foo.h>
|
|
*/
|
|
import "C"
|
|
`,
|
|
},
|
|
|
|
// Put some things in their own section
|
|
{
|
|
name: "make_sections",
|
|
in: `package foo
|
|
|
|
import (
|
|
"os"
|
|
)
|
|
|
|
func foo () {
|
|
_, _ = os.Args, fmt.Println
|
|
_, _ = appengine.FooSomething, user.Current
|
|
}
|
|
`,
|
|
out: `package foo
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
|
|
"appengine"
|
|
"appengine/user"
|
|
)
|
|
|
|
func foo() {
|
|
_, _ = os.Args, fmt.Println
|
|
_, _ = appengine.FooSomething, user.Current
|
|
}
|
|
`,
|
|
},
|
|
|
|
// Delete existing empty import block
|
|
{
|
|
name: "delete_empty_import_block",
|
|
in: `package foo
|
|
|
|
import ()
|
|
`,
|
|
out: `package foo
|
|
`,
|
|
},
|
|
|
|
// Use existing empty import block
|
|
{
|
|
name: "use_empty_import_block",
|
|
in: `package foo
|
|
|
|
import ()
|
|
|
|
func f() {
|
|
_ = fmt.Println
|
|
}
|
|
`,
|
|
out: `package foo
|
|
|
|
import "fmt"
|
|
|
|
func f() {
|
|
_ = fmt.Println
|
|
}
|
|
`,
|
|
},
|
|
|
|
// Blank line before adding new section.
|
|
{
|
|
name: "blank_line_before_new_group",
|
|
in: `package foo
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
)
|
|
|
|
func f() {
|
|
_ = net.Dial
|
|
_ = fmt.Printf
|
|
_ = snappy.Foo
|
|
}
|
|
`,
|
|
out: `package foo
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
|
|
"code.google.com/p/snappy-go/snappy"
|
|
)
|
|
|
|
func f() {
|
|
_ = net.Dial
|
|
_ = fmt.Printf
|
|
_ = snappy.Foo
|
|
}
|
|
`,
|
|
},
|
|
|
|
// Blank line between standard library and third-party stuff.
|
|
{
|
|
name: "blank_line_separating_std_and_third_party",
|
|
in: `package foo
|
|
|
|
import (
|
|
"code.google.com/p/snappy-go/snappy"
|
|
"fmt"
|
|
"net"
|
|
)
|
|
|
|
func f() {
|
|
_ = net.Dial
|
|
_ = fmt.Printf
|
|
_ = snappy.Foo
|
|
}
|
|
`,
|
|
out: `package foo
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
|
|
"code.google.com/p/snappy-go/snappy"
|
|
)
|
|
|
|
func f() {
|
|
_ = net.Dial
|
|
_ = fmt.Printf
|
|
_ = snappy.Foo
|
|
}
|
|
`,
|
|
},
|
|
|
|
// golang.org/issue/6884
|
|
{
|
|
name: "issue 6884",
|
|
in: `package main
|
|
|
|
// A comment
|
|
func main() {
|
|
fmt.Println("Hello, world")
|
|
}
|
|
`,
|
|
out: `package main
|
|
|
|
import "fmt"
|
|
|
|
// A comment
|
|
func main() {
|
|
fmt.Println("Hello, world")
|
|
}
|
|
`,
|
|
},
|
|
|
|
// golang.org/issue/7132
|
|
{
|
|
name: "issue 7132",
|
|
in: `package main
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"gu"
|
|
"github.com/foo/bar"
|
|
)
|
|
|
|
var (
|
|
a = bar.a
|
|
b = gu.a
|
|
c = fmt.Printf
|
|
)
|
|
`,
|
|
out: `package main
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"gu"
|
|
|
|
"github.com/foo/bar"
|
|
)
|
|
|
|
var (
|
|
a = bar.a
|
|
b = gu.a
|
|
c = fmt.Printf
|
|
)
|
|
`,
|
|
},
|
|
|
|
{
|
|
name: "renamed package",
|
|
in: `package main
|
|
|
|
var _ = str.HasPrefix
|
|
`,
|
|
out: `package main
|
|
|
|
import str "strings"
|
|
|
|
var _ = str.HasPrefix
|
|
`,
|
|
},
|
|
|
|
{
|
|
name: "fragment with main",
|
|
in: `func main(){fmt.Println("Hello, world")}`,
|
|
out: `package main
|
|
|
|
import "fmt"
|
|
|
|
func main() { fmt.Println("Hello, world") }
|
|
`,
|
|
},
|
|
|
|
{
|
|
name: "fragment without main",
|
|
in: `func notmain(){fmt.Println("Hello, world")}`,
|
|
out: `import "fmt"
|
|
|
|
func notmain() { fmt.Println("Hello, world") }`,
|
|
},
|
|
|
|
// Remove first import within in a 2nd/3rd/4th/etc. section.
|
|
// golang.org/issue/7679
|
|
{
|
|
name: "issue 7679",
|
|
in: `package main
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/foo/bar"
|
|
"github.com/foo/qux"
|
|
)
|
|
|
|
func main() {
|
|
var _ = fmt.Println
|
|
//var _ = bar.A
|
|
var _ = qux.B
|
|
}
|
|
`,
|
|
out: `package main
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/foo/qux"
|
|
)
|
|
|
|
func main() {
|
|
var _ = fmt.Println
|
|
//var _ = bar.A
|
|
var _ = qux.B
|
|
}
|
|
`,
|
|
},
|
|
|
|
// Blank line can be added before all types of import declarations.
|
|
// golang.org/issue/7866
|
|
{
|
|
name: "issue 7866",
|
|
in: `package main
|
|
|
|
import (
|
|
"fmt"
|
|
renamed_bar "github.com/foo/bar"
|
|
|
|
. "github.com/foo/baz"
|
|
"io"
|
|
|
|
_ "github.com/foo/qux"
|
|
"strings"
|
|
)
|
|
|
|
func main() {
|
|
_, _, _, _, _ = fmt.Errorf, io.Copy, strings.Contains, renamed_bar.A, B
|
|
}
|
|
`,
|
|
out: `package main
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
renamed_bar "github.com/foo/bar"
|
|
|
|
"io"
|
|
|
|
. "github.com/foo/baz"
|
|
|
|
"strings"
|
|
|
|
_ "github.com/foo/qux"
|
|
)
|
|
|
|
func main() {
|
|
_, _, _, _, _ = fmt.Errorf, io.Copy, strings.Contains, renamed_bar.A, B
|
|
}
|
|
`,
|
|
},
|
|
|
|
// Non-idempotent comment formatting
|
|
// golang.org/issue/8035
|
|
{
|
|
name: "issue 8035",
|
|
in: `package main
|
|
|
|
import (
|
|
"fmt" // A
|
|
"go/ast" // B
|
|
_ "launchpad.net/gocheck" // C
|
|
)
|
|
|
|
func main() { _, _ = fmt.Print, ast.Walk }
|
|
`,
|
|
out: `package main
|
|
|
|
import (
|
|
"fmt" // A
|
|
"go/ast" // B
|
|
|
|
_ "launchpad.net/gocheck" // C
|
|
)
|
|
|
|
func main() { _, _ = fmt.Print, ast.Walk }
|
|
`,
|
|
},
|
|
|
|
// Failure to delete all duplicate imports
|
|
// golang.org/issue/8459
|
|
{
|
|
name: "issue 8459",
|
|
in: `package main
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"log"
|
|
"math"
|
|
)
|
|
|
|
func main() { fmt.Println("pi:", math.Pi) }
|
|
`,
|
|
out: `package main
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
)
|
|
|
|
func main() { fmt.Println("pi:", math.Pi) }
|
|
`,
|
|
},
|
|
|
|
// Too aggressive prefix matching
|
|
// golang.org/issue/9961
|
|
{
|
|
name: "issue 9961",
|
|
in: `package p
|
|
|
|
import (
|
|
"zip"
|
|
|
|
"rsc.io/p"
|
|
)
|
|
|
|
var (
|
|
_ = fmt.Print
|
|
_ = zip.Store
|
|
_ p.P
|
|
_ = regexp.Compile
|
|
)
|
|
`,
|
|
out: `package p
|
|
|
|
import (
|
|
"fmt"
|
|
"regexp"
|
|
"zip"
|
|
|
|
"rsc.io/p"
|
|
)
|
|
|
|
var (
|
|
_ = fmt.Print
|
|
_ = zip.Store
|
|
_ p.P
|
|
_ = regexp.Compile
|
|
)
|
|
`,
|
|
},
|
|
|
|
// Unused named import is mistaken for unnamed import
|
|
// golang.org/issue/8149
|
|
{
|
|
name: "issue 8149",
|
|
in: `package main
|
|
|
|
import foo "fmt"
|
|
|
|
func main() { fmt.Println() }
|
|
`,
|
|
out: `package main
|
|
|
|
import "fmt"
|
|
|
|
func main() { fmt.Println() }
|
|
`,
|
|
},
|
|
|
|
// Unused named import is mistaken for unnamed import
|
|
// golang.org/issue/8149
|
|
{
|
|
name: "issue 8149",
|
|
in: `package main
|
|
|
|
import (
|
|
"fmt"
|
|
x "fmt"
|
|
)
|
|
|
|
func main() { fmt.Println() }
|
|
`,
|
|
out: `package main
|
|
|
|
import (
|
|
"fmt"
|
|
)
|
|
|
|
func main() { fmt.Println() }
|
|
`,
|
|
},
|
|
|
|
// FormatOnly
|
|
{
|
|
name: "format only",
|
|
formatOnly: true,
|
|
in: `package main
|
|
|
|
import (
|
|
"fmt"
|
|
"golang.org/x/foo"
|
|
)
|
|
|
|
func main() {}
|
|
`,
|
|
out: `package main
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"golang.org/x/foo"
|
|
)
|
|
|
|
func main() {}
|
|
`,
|
|
},
|
|
|
|
{
|
|
name: "do not make grouped imports non-grouped",
|
|
in: `package p
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
)
|
|
|
|
var _ = fmt.Sprintf
|
|
`,
|
|
out: `package p
|
|
|
|
import (
|
|
"fmt"
|
|
)
|
|
|
|
var _ = fmt.Sprintf
|
|
`,
|
|
},
|
|
}
|
|
|
|
func TestFixImports(t *testing.T) {
|
|
simplePkgs := map[string]string{
|
|
"appengine": "appengine",
|
|
"bytes": "bytes",
|
|
"fmt": "fmt",
|
|
"math": "math",
|
|
"os": "os",
|
|
"p": "rsc.io/p",
|
|
"regexp": "regexp",
|
|
"snappy": "code.google.com/p/snappy-go/snappy",
|
|
"str": "strings",
|
|
"user": "appengine/user",
|
|
"zip": "archive/zip",
|
|
}
|
|
old := findImport
|
|
defer func() {
|
|
findImport = old
|
|
}()
|
|
findImport = func(pkgName string, symbols map[string]bool, filename string) (string, bool, error) {
|
|
return simplePkgs[pkgName], pkgName == "str", nil
|
|
}
|
|
|
|
options := &Options{
|
|
TabWidth: 8,
|
|
TabIndent: true,
|
|
Comments: true,
|
|
Fragment: true,
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
options.FormatOnly = tt.formatOnly
|
|
if *only != "" && tt.name != *only {
|
|
continue
|
|
}
|
|
buf, err := Process(tt.name+".go", []byte(tt.in), options)
|
|
if err != nil {
|
|
t.Errorf("error on %q: %v", tt.name, err)
|
|
continue
|
|
}
|
|
if got := string(buf); got != tt.out {
|
|
t.Errorf("results diff on %q\nGOT:\n%s\nWANT:\n%s\n", tt.name, got, tt.out)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Test support for packages in GOPATH that are actually symlinks.
|
|
// Also test that a symlink loop does not block the process.
|
|
func TestImportSymlinks(t *testing.T) {
|
|
switch runtime.GOOS {
|
|
case "windows", "plan9":
|
|
t.Skipf("skipping test on %q as there are no symlinks", runtime.GOOS)
|
|
}
|
|
|
|
newGoPath, err := ioutil.TempDir("", "symlinktest")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer os.RemoveAll(newGoPath)
|
|
|
|
// Create:
|
|
// $GOPATH/target/
|
|
// $GOPATH/target/f.go // package mypkg\nvar Foo = 123\n
|
|
// $GOPATH/src/x/
|
|
// $GOPATH/src/x/mypkg => $GOPATH/target // symlink
|
|
// $GOPATH/src/x/apkg => $GOPATH/src/x // symlink loop
|
|
// Test:
|
|
// $GOPATH/src/myotherpkg/toformat.go referencing mypkg.Foo
|
|
|
|
targetPath := newGoPath + "/target"
|
|
if err := os.MkdirAll(targetPath, 0755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := ioutil.WriteFile(targetPath+"/f.go", []byte("package mypkg\nvar Foo = 123\n"), 0666); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
symlinkPath := newGoPath + "/src/x/mypkg"
|
|
if err := os.MkdirAll(filepath.Dir(symlinkPath), 0755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.Symlink(targetPath, symlinkPath); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Add a symlink loop.
|
|
if err := os.Symlink(newGoPath+"/src/x", newGoPath+"/src/x/apkg"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
withEmptyGoPath(func() {
|
|
build.Default.GOPATH = newGoPath
|
|
|
|
input := `package p
|
|
|
|
var (
|
|
_ = fmt.Print
|
|
_ = mypkg.Foo
|
|
)
|
|
`
|
|
output := `package p
|
|
|
|
import (
|
|
"fmt"
|
|
"x/mypkg"
|
|
)
|
|
|
|
var (
|
|
_ = fmt.Print
|
|
_ = mypkg.Foo
|
|
)
|
|
`
|
|
buf, err := Process(newGoPath+"/src/myotherpkg/toformat.go", []byte(input), &Options{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got := string(buf); got != output {
|
|
t.Fatalf("results differ\nGOT:\n%s\nWANT:\n%s\n", got, output)
|
|
}
|
|
})
|
|
|
|
// Add a .goimportsignore and ensure it is respected.
|
|
if err := ioutil.WriteFile(newGoPath+"/src/.goimportsignore", []byte("x/mypkg\n"), 0666); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
withEmptyGoPath(func() {
|
|
build.Default.GOPATH = newGoPath
|
|
|
|
input := `package p
|
|
|
|
var (
|
|
_ = fmt.Print
|
|
_ = mypkg.Foo
|
|
)
|
|
`
|
|
output := `package p
|
|
|
|
import "fmt"
|
|
|
|
var (
|
|
_ = fmt.Print
|
|
_ = mypkg.Foo
|
|
)
|
|
`
|
|
buf, err := Process(newGoPath+"/src/myotherpkg/toformat.go", []byte(input), &Options{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got := string(buf); got != output {
|
|
t.Fatalf("ignored results differ\nGOT:\n%s\nWANT:\n%s\n", got, output)
|
|
}
|
|
})
|
|
|
|
}
|
|
|
|
// Test for correctly identifying the name of a vendored package when it
|
|
// differs from its directory name. In this test, the import line
|
|
// "mypkg.com/mypkg.v1" would be removed if goimports wasn't able to detect
|
|
// that the package name is "mypkg".
|
|
func TestFixImportsVendorPackage(t *testing.T) {
|
|
// Skip this test on go versions with no vendor support.
|
|
if _, err := os.Stat(filepath.Join(runtime.GOROOT(), "src/vendor")); err != nil {
|
|
t.Skip(err)
|
|
}
|
|
testConfig{
|
|
gopathFiles: map[string]string{
|
|
"mypkg.com/outpkg/vendor/mypkg.com/mypkg.v1/f.go": "package mypkg\nvar Foo = 123\n",
|
|
},
|
|
}.test(t, func(t *goimportTest) {
|
|
input := `package p
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"mypkg.com/mypkg.v1"
|
|
)
|
|
|
|
var (
|
|
_ = fmt.Print
|
|
_ = mypkg.Foo
|
|
)
|
|
`
|
|
buf, err := Process(filepath.Join(t.gopath, "src/mypkg.com/outpkg/toformat.go"), []byte(input), &Options{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got := string(buf); got != input {
|
|
t.Fatalf("results differ\nGOT:\n%s\nWANT:\n%s\n", got, input)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestFindImportGoPath(t *testing.T) {
|
|
goroot, err := ioutil.TempDir("", "goimports-")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer os.RemoveAll(goroot)
|
|
|
|
origStdlib := stdlib
|
|
defer func() {
|
|
stdlib = origStdlib
|
|
}()
|
|
stdlib = nil
|
|
|
|
withEmptyGoPath(func() {
|
|
// Test against imaginary bits/bytes package in std lib
|
|
bytesDir := filepath.Join(goroot, "src", "pkg", "bits", "bytes")
|
|
for _, tag := range build.Default.ReleaseTags {
|
|
// Go 1.4 rearranged the GOROOT tree to remove the "pkg" path component.
|
|
if tag == "go1.4" {
|
|
bytesDir = filepath.Join(goroot, "src", "bits", "bytes")
|
|
}
|
|
}
|
|
if err := os.MkdirAll(bytesDir, 0755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
bytesSrcPath := filepath.Join(bytesDir, "bytes.go")
|
|
bytesPkgPath := "bits/bytes"
|
|
bytesSrc := []byte(`package bytes
|
|
|
|
type Buffer2 struct {}
|
|
`)
|
|
if err := ioutil.WriteFile(bytesSrcPath, bytesSrc, 0775); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
build.Default.GOROOT = goroot
|
|
|
|
got, rename, err := findImportGoPath("bytes", map[string]bool{"Buffer2": true}, "x.go")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got != bytesPkgPath || rename {
|
|
t.Errorf(`findImportGoPath("bytes", Buffer2 ...)=%q, %t, want "%s", false`, got, rename, bytesPkgPath)
|
|
}
|
|
|
|
got, rename, err = findImportGoPath("bytes", map[string]bool{"Missing": true}, "x.go")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got != "" || rename {
|
|
t.Errorf(`findImportGoPath("bytes", Missing ...)=%q, %t, want "", false`, got, rename)
|
|
}
|
|
})
|
|
}
|
|
|
|
func init() {
|
|
inTests = true
|
|
}
|
|
|
|
func withEmptyGoPath(fn func()) {
|
|
testMu.Lock()
|
|
|
|
dirScanMu.Lock()
|
|
populateIgnoreOnce = sync.Once{}
|
|
scanGoRootOnce = sync.Once{}
|
|
scanGoPathOnce = sync.Once{}
|
|
dirScan = nil
|
|
ignoredDirs = nil
|
|
scanGoRootDone = make(chan struct{})
|
|
dirScanMu.Unlock()
|
|
|
|
oldGOPATH := build.Default.GOPATH
|
|
oldGOROOT := build.Default.GOROOT
|
|
build.Default.GOPATH = ""
|
|
testHookScanDir = func(string) {}
|
|
testMu.Unlock()
|
|
|
|
defer func() {
|
|
testMu.Lock()
|
|
testHookScanDir = func(string) {}
|
|
build.Default.GOPATH = oldGOPATH
|
|
build.Default.GOROOT = oldGOROOT
|
|
testMu.Unlock()
|
|
}()
|
|
|
|
fn()
|
|
}
|
|
|
|
func TestFindImportInternal(t *testing.T) {
|
|
withEmptyGoPath(func() {
|
|
// Check for src/internal/race, not just src/internal,
|
|
// so that we can run this test also against go1.5
|
|
// (which doesn't contain that file).
|
|
_, err := os.Stat(filepath.Join(runtime.GOROOT(), "src/internal/race"))
|
|
if err != nil {
|
|
t.Skip(err)
|
|
}
|
|
|
|
got, rename, err := findImportGoPath("race", map[string]bool{"Acquire": true}, filepath.Join(runtime.GOROOT(), "src/math/x.go"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got != "internal/race" || rename {
|
|
t.Errorf(`findImportGoPath("race", Acquire ...) = %q, %t; want "internal/race", false`, got, rename)
|
|
}
|
|
|
|
// should not be able to use internal from outside that tree
|
|
got, rename, err = findImportGoPath("race", map[string]bool{"Acquire": true}, filepath.Join(runtime.GOROOT(), "x.go"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got != "" || rename {
|
|
t.Errorf(`findImportGoPath("race", Acquire ...)=%q, %t, want "", false`, got, rename)
|
|
}
|
|
})
|
|
}
|
|
|
|
// rand.Read should prefer crypto/rand.Read, not math/rand.Read.
|
|
func TestFindImportRandRead(t *testing.T) {
|
|
withEmptyGoPath(func() {
|
|
file := filepath.Join(runtime.GOROOT(), "src/foo/x.go") // dummy
|
|
tests := []struct {
|
|
syms []string
|
|
want string
|
|
}{
|
|
{
|
|
syms: []string{"Read"},
|
|
want: "crypto/rand",
|
|
},
|
|
{
|
|
syms: []string{"Read", "NewZipf"},
|
|
want: "math/rand",
|
|
},
|
|
{
|
|
syms: []string{"NewZipf"},
|
|
want: "math/rand",
|
|
},
|
|
{
|
|
syms: []string{"Read", "Prime"},
|
|
want: "crypto/rand",
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
m := map[string]bool{}
|
|
for _, sym := range tt.syms {
|
|
m[sym] = true
|
|
}
|
|
got, _, err := findImportGoPath("rand", m, file)
|
|
if err != nil {
|
|
t.Errorf("for %q: %v", tt.syms, err)
|
|
continue
|
|
}
|
|
if got != tt.want {
|
|
t.Errorf("for %q, findImportGoPath = %q; want %q", tt.syms, got, tt.want)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestFindImportVendor(t *testing.T) {
|
|
testConfig{
|
|
gorootFiles: map[string]string{
|
|
"vendor/golang.org/x/net/http2/hpack/huffman.go": "package hpack\nfunc HuffmanDecode() { }\n",
|
|
},
|
|
}.test(t, func(t *goimportTest) {
|
|
got, rename, err := findImportGoPath("hpack", map[string]bool{"HuffmanDecode": true}, filepath.Join(t.goroot, "src/math/x.go"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
want := "golang.org/x/net/http2/hpack"
|
|
if got != want || rename {
|
|
t.Errorf(`findImportGoPath("hpack", HuffmanDecode ...) = %q, %t; want %q, false`, got, rename, want)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestProcessVendor(t *testing.T) {
|
|
withEmptyGoPath(func() {
|
|
_, err := os.Stat(filepath.Join(runtime.GOROOT(), "src/vendor"))
|
|
if err != nil {
|
|
t.Skip(err)
|
|
}
|
|
|
|
target := filepath.Join(runtime.GOROOT(), "src/math/x.go")
|
|
out, err := Process(target, []byte("package http\nimport \"bytes\"\nfunc f() { strings.NewReader(); hpack.HuffmanDecode() }\n"), nil)
|
|
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
want := "golang_org/x/net/http2/hpack"
|
|
if _, err := os.Stat(filepath.Join(runtime.GOROOT(), "src/vendor", want)); os.IsNotExist(err) {
|
|
want = "golang.org/x/net/http2/hpack"
|
|
}
|
|
|
|
if !bytes.Contains(out, []byte(want)) {
|
|
t.Fatalf("Process(%q) did not add expected hpack import %q; got:\n%s", target, want, out)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestFindImportStdlib(t *testing.T) {
|
|
tests := []struct {
|
|
pkg string
|
|
symbols []string
|
|
want string
|
|
}{
|
|
{"http", []string{"Get"}, "net/http"},
|
|
{"http", []string{"Get", "Post"}, "net/http"},
|
|
{"http", []string{"Get", "Foo"}, ""},
|
|
{"bytes", []string{"Buffer"}, "bytes"},
|
|
{"ioutil", []string{"Discard"}, "io/ioutil"},
|
|
}
|
|
for _, tt := range tests {
|
|
got, rename, ok := findImportStdlib(tt.pkg, strSet(tt.symbols))
|
|
if (got != "") != ok {
|
|
t.Error("findImportStdlib return value inconsistent")
|
|
}
|
|
if got != tt.want || rename {
|
|
t.Errorf("findImportStdlib(%q, %q) = %q, %t; want %q, false", tt.pkg, tt.symbols, got, rename, tt.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
type testConfig struct {
|
|
// goroot and gopath optionally specifies the path on disk
|
|
// to use for the GOROOT and GOPATH. If empty, a temp directory
|
|
// is made if needed.
|
|
goroot, gopath string
|
|
|
|
// gorootFiles optionally specifies the complete contents of GOROOT to use,
|
|
// If nil, the normal current $GOROOT is used.
|
|
gorootFiles map[string]string // paths relative to $GOROOT/src to contents
|
|
|
|
// gopathFiles is like gorootFiles, but for $GOPATH.
|
|
// If nil, there is no GOPATH, though.
|
|
gopathFiles map[string]string // paths relative to $GOPATH/src to contents
|
|
}
|
|
|
|
func mustTempDir(t *testing.T, prefix string) string {
|
|
dir, err := ioutil.TempDir("", prefix)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return dir
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
func (c testConfig) test(t *testing.T, fn func(*goimportTest)) {
|
|
goroot := c.goroot
|
|
gopath := c.gopath
|
|
|
|
if c.gorootFiles != nil && goroot == "" {
|
|
goroot = mustTempDir(t, "goroot-")
|
|
defer os.RemoveAll(goroot)
|
|
}
|
|
if err := mapToDir(goroot, c.gorootFiles); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if c.gopathFiles != nil && gopath == "" {
|
|
gopath = mustTempDir(t, "gopath-")
|
|
defer os.RemoveAll(gopath)
|
|
}
|
|
if err := mapToDir(gopath, c.gopathFiles); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
withEmptyGoPath(func() {
|
|
if goroot != "" {
|
|
build.Default.GOROOT = goroot
|
|
}
|
|
build.Default.GOPATH = gopath
|
|
|
|
it := &goimportTest{
|
|
T: t,
|
|
goroot: build.Default.GOROOT,
|
|
gopath: gopath,
|
|
ctx: &build.Default,
|
|
}
|
|
fn(it)
|
|
})
|
|
}
|
|
|
|
type goimportTest struct {
|
|
*testing.T
|
|
ctx *build.Context
|
|
goroot string
|
|
gopath string
|
|
}
|
|
|
|
// Tests that added imports are renamed when the import path's base doesn't
|
|
// match its package name. For example, we want to generate:
|
|
//
|
|
// import cloudbilling "google.golang.org/api/cloudbilling/v1"
|
|
func TestRenameWhenPackageNameMismatch(t *testing.T) {
|
|
testConfig{
|
|
gopathFiles: map[string]string{
|
|
"foo/bar/v1/x.go": "package bar \n const X = 1",
|
|
},
|
|
}.test(t, func(t *goimportTest) {
|
|
buf, err := Process(t.gopath+"/src/test/t.go", []byte("package main \n const Y = bar.X"), &Options{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
const want = `package main
|
|
|
|
import bar "foo/bar/v1"
|
|
|
|
const Y = bar.X
|
|
`
|
|
if string(buf) != want {
|
|
t.Errorf("Got:\n%s\nWant:\n%s", buf, want)
|
|
}
|
|
})
|
|
}
|
|
|
|
// Tests that the LocalPrefix option causes imports
|
|
// to be added into a later group (num=3).
|
|
func TestLocalPrefix(t *testing.T) {
|
|
defer func(s string) { LocalPrefix = s }(LocalPrefix)
|
|
LocalPrefix = "foo/"
|
|
|
|
testConfig{
|
|
gopathFiles: map[string]string{
|
|
"foo/bar/bar.go": "package bar \n const X = 1",
|
|
},
|
|
}.test(t, func(t *goimportTest) {
|
|
buf, err := Process(t.gopath+"/src/test/t.go", []byte("package main \n const Y = bar.X \n const _ = runtime.GOOS"), &Options{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
const want = `package main
|
|
|
|
import (
|
|
"runtime"
|
|
|
|
"foo/bar"
|
|
)
|
|
|
|
const Y = bar.X
|
|
const _ = runtime.GOOS
|
|
`
|
|
if string(buf) != want {
|
|
t.Errorf("Got:\n%s\nWant:\n%s", buf, want)
|
|
}
|
|
})
|
|
}
|
|
|
|
// Tests that running goimport on files in GOROOT (for people hacking
|
|
// on Go itself) don't cause the GOPATH to be scanned (which might be
|
|
// much bigger).
|
|
func TestOptimizationWhenInGoroot(t *testing.T) {
|
|
testConfig{
|
|
gopathFiles: map[string]string{
|
|
"foo/foo.go": "package foo\nconst X = 1\n",
|
|
},
|
|
}.test(t, func(t *goimportTest) {
|
|
testHookScanDir = func(dir string) {
|
|
if dir != filepath.Join(build.Default.GOROOT, "src") {
|
|
t.Errorf("unexpected dir scan of %s", dir)
|
|
}
|
|
}
|
|
const in = "package foo\n\nconst Y = bar.X\n"
|
|
buf, err := Process(t.goroot+"/src/foo/foo.go", []byte(in), nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if string(buf) != in {
|
|
t.Errorf("got:\n%q\nwant unchanged:\n%q\n", in, buf)
|
|
}
|
|
})
|
|
}
|
|
|
|
// Tests that "package documentation" files are ignored.
|
|
func TestIgnoreDocumentationPackage(t *testing.T) {
|
|
testConfig{
|
|
gopathFiles: map[string]string{
|
|
"foo/foo.go": "package foo\nconst X = 1\n",
|
|
"foo/doc.go": "package documentation \n // just to confuse things\n",
|
|
},
|
|
}.test(t, func(t *goimportTest) {
|
|
const in = "package x\n\nconst Y = foo.X\n"
|
|
const want = "package x\n\nimport \"foo\"\n\nconst Y = foo.X\n"
|
|
buf, err := Process(t.gopath+"/src/x/x.go", []byte(in), nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if string(buf) != want {
|
|
t.Errorf("wrong output.\ngot:\n%q\nwant:\n%q\n", in, want)
|
|
}
|
|
})
|
|
}
|
|
|
|
// Tests importPathToNameGoPathParse and in particular that it stops
|
|
// after finding the first non-documentation package name, not
|
|
// reporting an error on inconsistent package names (since it should
|
|
// never make it that far).
|
|
func TestImportPathToNameGoPathParse(t *testing.T) {
|
|
testConfig{
|
|
gopathFiles: map[string]string{
|
|
"example.net/pkg/doc.go": "package documentation\n", // ignored
|
|
"example.net/pkg/gen.go": "package main\n", // also ignored
|
|
"example.net/pkg/pkg.go": "package the_pkg_name_to_find\n and this syntax error is ignored because of parser.PackageClauseOnly",
|
|
"example.net/pkg/z.go": "package inconsistent\n", // inconsistent but ignored
|
|
},
|
|
}.test(t, func(t *goimportTest) {
|
|
got, err := importPathToNameGoPathParse("example.net/pkg", filepath.Join(t.gopath, "src", "other.net"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
const want = "the_pkg_name_to_find"
|
|
if got != want {
|
|
t.Errorf("importPathToNameGoPathParse(..) = %q; want %q", got, want)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestIgnoreConfiguration(t *testing.T) {
|
|
testConfig{
|
|
gopathFiles: map[string]string{
|
|
".goimportsignore": "# comment line\n\n example.net", // tests comment, blank line, whitespace trimming
|
|
"example.net/pkg/pkg.go": "package pkg\nconst X = 1",
|
|
"otherwise-longer-so-worse.example.net/foo/pkg/pkg.go": "package pkg\nconst X = 1",
|
|
},
|
|
}.test(t, func(t *goimportTest) {
|
|
const in = "package x\n\nconst _ = pkg.X\n"
|
|
const want = "package x\n\nimport \"otherwise-longer-so-worse.example.net/foo/pkg\"\n\nconst _ = pkg.X\n"
|
|
buf, err := Process(t.gopath+"/src/x/x.go", []byte(in), nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if string(buf) != want {
|
|
t.Errorf("wrong output.\ngot:\n%q\nwant:\n%q\n", buf, want)
|
|
}
|
|
})
|
|
}
|
|
|
|
// Skip "node_modules" directory.
|
|
func TestSkipNodeModules(t *testing.T) {
|
|
testConfig{
|
|
gopathFiles: map[string]string{
|
|
"example.net/node_modules/pkg/a.go": "package pkg\nconst X = 1",
|
|
"otherwise-longer.net/not_modules/pkg/a.go": "package pkg\nconst X = 1",
|
|
},
|
|
}.test(t, func(t *goimportTest) {
|
|
const in = "package x\n\nconst _ = pkg.X\n"
|
|
const want = "package x\n\nimport \"otherwise-longer.net/not_modules/pkg\"\n\nconst _ = pkg.X\n"
|
|
buf, err := Process(t.gopath+"/src/x/x.go", []byte(in), nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if string(buf) != want {
|
|
t.Errorf("wrong output.\ngot:\n%q\nwant:\n%q\n", buf, want)
|
|
}
|
|
})
|
|
}
|
|
|
|
// golang.org/issue/16458 -- if GOROOT is a prefix of GOPATH, GOPATH is ignored.
|
|
func TestGoRootPrefixOfGoPath(t *testing.T) {
|
|
dir := mustTempDir(t, "importstest")
|
|
defer os.RemoveAll(dir)
|
|
testConfig{
|
|
goroot: filepath.Join(dir, "go"),
|
|
gopath: filepath.Join(dir, "gopath"),
|
|
gopathFiles: map[string]string{
|
|
"example.com/foo/pkg.go": "package foo\nconst X = 1",
|
|
},
|
|
}.test(t, func(t *goimportTest) {
|
|
const in = "package x\n\nconst _ = foo.X\n"
|
|
const want = "package x\n\nimport \"example.com/foo\"\n\nconst _ = foo.X\n"
|
|
buf, err := Process(t.gopath+"/src/x/x.go", []byte(in), nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if string(buf) != want {
|
|
t.Errorf("wrong output.\ngot:\n%q\nwant:\n%q\n", buf, want)
|
|
}
|
|
})
|
|
|
|
}
|
|
|
|
const testGlobalImportsUsesGlobal = `package globalimporttest
|
|
|
|
func doSomething() {
|
|
t := time.Now()
|
|
}
|
|
`
|
|
|
|
const testGlobalImportsGlobalDecl = `package globalimporttest
|
|
|
|
type Time struct{}
|
|
|
|
func (t Time) Now() Time {
|
|
return Time{}
|
|
}
|
|
|
|
var time Time
|
|
`
|
|
|
|
// Tests that package global variables with the same name and function name as
|
|
// a function in a separate package do not result in an import which masks
|
|
// the global variable
|
|
func TestGlobalImports(t *testing.T) {
|
|
const pkg = "globalimporttest"
|
|
const usesGlobalFile = pkg + "/uses_global.go"
|
|
testConfig{
|
|
gopathFiles: map[string]string{
|
|
usesGlobalFile: testGlobalImportsUsesGlobal,
|
|
pkg + "/global.go": testGlobalImportsGlobalDecl,
|
|
},
|
|
}.test(t, func(t *goimportTest) {
|
|
buf, err := Process(
|
|
t.gopath+"/src/"+usesGlobalFile, []byte(testGlobalImportsUsesGlobal), nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if string(buf) != testGlobalImportsUsesGlobal {
|
|
t.Errorf("wrong output.\ngot:\n%q\nwant:\n%q\n", buf, testGlobalImportsUsesGlobal)
|
|
}
|
|
})
|
|
}
|
|
|
|
func strSet(ss []string) map[string]bool {
|
|
m := make(map[string]bool)
|
|
for _, s := range ss {
|
|
m[s] = true
|
|
}
|
|
return m
|
|
}
|
|
|
|
func TestPkgIsCandidate(t *testing.T) {
|
|
tests := [...]struct {
|
|
filename string
|
|
pkgIdent string
|
|
pkg *pkg
|
|
want bool
|
|
}{
|
|
// normal match
|
|
0: {
|
|
filename: "/gopath/src/my/pkg/pkg.go",
|
|
pkgIdent: "client",
|
|
pkg: &pkg{
|
|
dir: "/gopath/src/client",
|
|
importPath: "client",
|
|
importPathShort: "client",
|
|
},
|
|
want: true,
|
|
},
|
|
// not a match
|
|
1: {
|
|
filename: "/gopath/src/my/pkg/pkg.go",
|
|
pkgIdent: "zzz",
|
|
pkg: &pkg{
|
|
dir: "/gopath/src/client",
|
|
importPath: "client",
|
|
importPathShort: "client",
|
|
},
|
|
want: false,
|
|
},
|
|
// would be a match, but "client" appears too deep.
|
|
2: {
|
|
filename: "/gopath/src/my/pkg/pkg.go",
|
|
pkgIdent: "client",
|
|
pkg: &pkg{
|
|
dir: "/gopath/src/client/foo/foo/foo",
|
|
importPath: "client/foo/foo",
|
|
importPathShort: "client/foo/foo",
|
|
},
|
|
want: false,
|
|
},
|
|
// not an exact match, but substring is good enough.
|
|
3: {
|
|
filename: "/gopath/src/my/pkg/pkg.go",
|
|
pkgIdent: "client",
|
|
pkg: &pkg{
|
|
dir: "/gopath/src/foo/go-client",
|
|
importPath: "foo/go-client",
|
|
importPathShort: "foo/go-client",
|
|
},
|
|
want: true,
|
|
},
|
|
// "internal" package, and not visible
|
|
4: {
|
|
filename: "/gopath/src/my/pkg/pkg.go",
|
|
pkgIdent: "client",
|
|
pkg: &pkg{
|
|
dir: "/gopath/src/foo/internal/client",
|
|
importPath: "foo/internal/client",
|
|
importPathShort: "foo/internal/client",
|
|
},
|
|
want: false,
|
|
},
|
|
// "internal" package but visible
|
|
5: {
|
|
filename: "/gopath/src/foo/bar.go",
|
|
pkgIdent: "client",
|
|
pkg: &pkg{
|
|
dir: "/gopath/src/foo/internal/client",
|
|
importPath: "foo/internal/client",
|
|
importPathShort: "foo/internal/client",
|
|
},
|
|
want: true,
|
|
},
|
|
// "vendor" package not visible
|
|
6: {
|
|
filename: "/gopath/src/foo/bar.go",
|
|
pkgIdent: "client",
|
|
pkg: &pkg{
|
|
dir: "/gopath/src/other/vendor/client",
|
|
importPath: "other/vendor/client",
|
|
importPathShort: "client",
|
|
},
|
|
want: false,
|
|
},
|
|
// "vendor" package, visible
|
|
7: {
|
|
filename: "/gopath/src/foo/bar.go",
|
|
pkgIdent: "client",
|
|
pkg: &pkg{
|
|
dir: "/gopath/src/foo/vendor/client",
|
|
importPath: "other/foo/client",
|
|
importPathShort: "client",
|
|
},
|
|
want: true,
|
|
},
|
|
// Ignore hyphens.
|
|
8: {
|
|
filename: "/gopath/src/foo/bar.go",
|
|
pkgIdent: "socketio",
|
|
pkg: &pkg{
|
|
dir: "/gopath/src/foo/socket-io",
|
|
importPath: "foo/socket-io",
|
|
importPathShort: "foo/socket-io",
|
|
},
|
|
want: true,
|
|
},
|
|
// Ignore case.
|
|
9: {
|
|
filename: "/gopath/src/foo/bar.go",
|
|
pkgIdent: "fooprod",
|
|
pkg: &pkg{
|
|
dir: "/gopath/src/foo/FooPROD",
|
|
importPath: "foo/FooPROD",
|
|
importPathShort: "foo/FooPROD",
|
|
},
|
|
want: true,
|
|
},
|
|
// Ignoring both hyphens and case together.
|
|
10: {
|
|
filename: "/gopath/src/foo/bar.go",
|
|
pkgIdent: "fooprod",
|
|
pkg: &pkg{
|
|
dir: "/gopath/src/foo/Foo-PROD",
|
|
importPath: "foo/Foo-PROD",
|
|
importPathShort: "foo/Foo-PROD",
|
|
},
|
|
want: true,
|
|
},
|
|
}
|
|
for i, tt := range tests {
|
|
got := pkgIsCandidate(tt.filename, tt.pkgIdent, tt.pkg)
|
|
if got != tt.want {
|
|
t.Errorf("test %d. pkgIsCandidate(%q, %q, %+v) = %v; want %v",
|
|
i, tt.filename, tt.pkgIdent, *tt.pkg, got, tt.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
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)
|
|
if got != tt.want {
|
|
t.Errorf("%d. shouldTraverse(%q, %q) = %v; want %v", i, tt.dir, tt.file, got, tt.want)
|
|
}
|
|
}
|
|
}
|