1
0
mirror of https://github.com/golang/go synced 2024-11-19 03:44:40 -07:00
go/refactor/rename/mvpkg_test.go

431 lines
9.2 KiB
Go
Raw Normal View History

// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// licence that can be found in the LICENSE file.
package rename
import (
"fmt"
"go/build"
"go/token"
"io/ioutil"
"path/filepath"
"reflect"
"regexp"
cmd/goimports, imports: make goimports great again I felt the burn of my laptop on my legs, spinning away while processing goimports, and felt that it was time to make goimports great again. Over the past few years goimports fell into a slow state of disrepair with too many feature additions and no attention to the performance death by a thousand cuts. This was particularly terrible on OS X with its lackluster filesystem buffering. This CL makes goimports stronger, together with various optimizations and more visibility into what goimports is doing. * adds more internal documentation * avoids scanning $GOPATH for answers when running goimports on a file under $GOROOT (for Go core hackers) * don't read all $GOROOT & $GOPATH directories' Go code looking for their package names until much later. Require the package name of missing imports to be present in the last two directory path components. Then only try importing them in order from best to worst (shortest to longest, as before), so we can stop early. * when adding imports, add names to imports when the imported package name doesn't match the baes of its import path. For example: import foo "example.net/foo/v1" * don't read all *.go files in a package directory once the first file in a directory has revealed itself to be a package we're not looking for. For example, if we're looking for the right "client" for "client.Foo", we used to consider a directory "bar/client" as a candidate and read all 50 of its *.go files instead of stopping after its first *.go file had a "package main" line. * add some fast paths to remove allocations * add some fast paths to remove disk I/O when looking up the base package name of a standard library import (of existing imports in a file, which are very common) * adds a special case for import "C", to avoid some disk I/O. * add a -verbose flag to goimports for debugging On my Mac laptop with a huge $GOPATH, with a test file like: package foo import ( "fmt" "net/http" ) /* */ import "C" var _ = cloudbilling.New var _ = http.NewRequest var _ = client.New ... this took like 10 seconds before, and now 1.3 seconds. (Still slow; disk-based caching can come later) Updates golang/go#16367 (goimports is slow) Updates golang/go#16384 (refactor TestRename is broken on Windows) Change-Id: I97e85d3016afc9f2ad5501f97babad30c7989183 Reviewed-on: https://go-review.googlesource.com/24941 Reviewed-by: Andrew Gerrand <adg@golang.org> Reviewed-by: Rob Pike <r@golang.org> Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org>
2016-07-14 18:08:27 -06:00
"runtime"
"strings"
"testing"
"golang.org/x/tools/go/buildutil"
)
func TestErrors(t *testing.T) {
tests := []struct {
ctxt *build.Context
from, to string
want string // regexp to match error, or "OK"
}{
// Simple example.
{
ctxt: fakeContext(map[string][]string{
"foo": {`package foo; type T int`},
"bar": {`package bar`},
"main": {`package main
import "foo"
var _ foo.T
`},
}),
from: "foo", to: "bar",
want: `invalid move destination: bar conflicts with directory .go.src.bar`,
},
// Subpackage already exists.
{
ctxt: fakeContext(map[string][]string{
"foo": {`package foo; type T int`},
"foo/sub": {`package sub`},
"bar/sub": {`package sub`},
"main": {`package main
import "foo"
var _ foo.T
`},
}),
from: "foo", to: "bar",
want: "invalid move destination: bar; package or subpackage bar/sub already exists",
},
// Invalid base name.
{
ctxt: fakeContext(map[string][]string{
"foo": {`package foo; type T int`},
"main": {`package main
import "foo"
var _ foo.T
`},
}),
from: "foo", to: "bar-v2.0",
want: "invalid move destination: bar-v2.0; gomvpkg does not " +
"support move destinations whose base names are not valid " +
"go identifiers",
},
}
for _, test := range tests {
ctxt := test.ctxt
got := make(map[string]string)
writeFile = func(filename string, content []byte) error {
got[filename] = string(content)
return nil
}
moveDirectory = func(from, to string) error {
for path, contents := range got {
if strings.HasPrefix(path, from) {
newPath := strings.Replace(path, from, to, 1)
delete(got, path)
got[newPath] = contents
}
}
return nil
}
err := Move(ctxt, test.from, test.to, "")
prefix := fmt.Sprintf("-from %q -to %q", test.from, test.to)
if err == nil {
t.Errorf("%s: nil error. Expected error: %s", prefix, test.want)
continue
}
matched, err2 := regexp.MatchString(test.want, err.Error())
if err2 != nil {
t.Errorf("regexp.MatchString failed %s", err2)
continue
}
if !matched {
t.Errorf("%s: conflict does not match expectation:\n"+
"Error: %q\n"+
"Pattern: %q",
prefix, err.Error(), test.want)
}
}
}
func TestMoves(t *testing.T) {
cmd/goimports, imports: make goimports great again I felt the burn of my laptop on my legs, spinning away while processing goimports, and felt that it was time to make goimports great again. Over the past few years goimports fell into a slow state of disrepair with too many feature additions and no attention to the performance death by a thousand cuts. This was particularly terrible on OS X with its lackluster filesystem buffering. This CL makes goimports stronger, together with various optimizations and more visibility into what goimports is doing. * adds more internal documentation * avoids scanning $GOPATH for answers when running goimports on a file under $GOROOT (for Go core hackers) * don't read all $GOROOT & $GOPATH directories' Go code looking for their package names until much later. Require the package name of missing imports to be present in the last two directory path components. Then only try importing them in order from best to worst (shortest to longest, as before), so we can stop early. * when adding imports, add names to imports when the imported package name doesn't match the baes of its import path. For example: import foo "example.net/foo/v1" * don't read all *.go files in a package directory once the first file in a directory has revealed itself to be a package we're not looking for. For example, if we're looking for the right "client" for "client.Foo", we used to consider a directory "bar/client" as a candidate and read all 50 of its *.go files instead of stopping after its first *.go file had a "package main" line. * add some fast paths to remove allocations * add some fast paths to remove disk I/O when looking up the base package name of a standard library import (of existing imports in a file, which are very common) * adds a special case for import "C", to avoid some disk I/O. * add a -verbose flag to goimports for debugging On my Mac laptop with a huge $GOPATH, with a test file like: package foo import ( "fmt" "net/http" ) /* */ import "C" var _ = cloudbilling.New var _ = http.NewRequest var _ = client.New ... this took like 10 seconds before, and now 1.3 seconds. (Still slow; disk-based caching can come later) Updates golang/go#16367 (goimports is slow) Updates golang/go#16384 (refactor TestRename is broken on Windows) Change-Id: I97e85d3016afc9f2ad5501f97babad30c7989183 Reviewed-on: https://go-review.googlesource.com/24941 Reviewed-by: Andrew Gerrand <adg@golang.org> Reviewed-by: Rob Pike <r@golang.org> Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org>
2016-07-14 18:08:27 -06:00
if runtime.GOOS == "windows" {
t.Skip("broken on Windows; see golang.org/issue/16384")
}
tests := []struct {
ctxt *build.Context
from, to string
want map[string]string
wantWarnings []string
}{
// Simple example.
{
ctxt: fakeContext(map[string][]string{
"foo": {`package foo; type T int`},
"main": {`package main
import "foo"
var _ foo.T
`},
}),
from: "foo", to: "bar",
want: map[string]string{
"/go/src/main/0.go": `package main
import "bar"
var _ bar.T
`,
"/go/src/bar/0.go": `package bar
type T int
`,
},
},
// Example with subpackage.
{
ctxt: fakeContext(map[string][]string{
"foo": {`package foo; type T int`},
"foo/sub": {`package sub; type T int`},
"main": {`package main
import "foo"
import "foo/sub"
var _ foo.T
var _ sub.T
`},
}),
from: "foo", to: "bar",
want: map[string]string{
"/go/src/main/0.go": `package main
import "bar"
import "bar/sub"
var _ bar.T
var _ sub.T
`,
"/go/src/bar/0.go": `package bar
type T int
`,
"/go/src/bar/sub/0.go": `package sub; type T int`,
},
},
// References into subpackages
{
ctxt: fakeContext(map[string][]string{
"foo": {`package foo; import "foo/a"; var _ a.T`},
"foo/a": {`package a; type T int`},
"foo/b": {`package b; import "foo/a"; var _ a.T`},
}),
from: "foo", to: "bar",
want: map[string]string{
"/go/src/bar/0.go": `package bar
import "bar/a"
var _ a.T
`,
"/go/src/bar/a/0.go": `package a; type T int`,
"/go/src/bar/b/0.go": `package b
import "bar/a"
var _ a.T
`,
},
},
// External test packages
{
ctxt: buildutil.FakeContext(map[string]map[string]string{
"foo": {
"0.go": `package foo; type T int`,
"0_test.go": `package foo_test; import "foo"; var _ foo.T`,
},
"baz": {
"0_test.go": `package baz_test; import "foo"; var _ foo.T`,
},
}),
from: "foo", to: "bar",
want: map[string]string{
"/go/src/bar/0.go": `package bar
type T int
`,
"/go/src/bar/0_test.go": `package bar_test
import "bar"
var _ bar.T
`,
"/go/src/baz/0_test.go": `package baz_test
import "bar"
var _ bar.T
`,
},
},
// package import comments
{
ctxt: fakeContext(map[string][]string{"foo": {`package foo // import "baz"`}}),
from: "foo", to: "bar",
want: map[string]string{"/go/src/bar/0.go": `package bar // import "bar"
`},
},
{
ctxt: fakeContext(map[string][]string{"foo": {`package foo /* import "baz" */`}}),
from: "foo", to: "bar",
want: map[string]string{"/go/src/bar/0.go": `package bar /* import "bar" */
`},
},
{
ctxt: fakeContext(map[string][]string{"foo": {`package foo // import "baz"`}}),
from: "foo", to: "bar",
want: map[string]string{"/go/src/bar/0.go": `package bar // import "bar"
`},
},
{
ctxt: fakeContext(map[string][]string{"foo": {`package foo
// import " this is not an import comment`}}),
from: "foo", to: "bar",
want: map[string]string{"/go/src/bar/0.go": `package bar
// import " this is not an import comment
`},
},
{
ctxt: fakeContext(map[string][]string{"foo": {`package foo
/* import " this is not an import comment */`}}),
from: "foo", to: "bar",
want: map[string]string{"/go/src/bar/0.go": `package bar
/* import " this is not an import comment */
`},
},
// Import name conflict generates a warning, not an error.
{
ctxt: fakeContext(map[string][]string{
"x": {},
"a": {`package a; type A int`},
"b": {`package b; type B int`},
"conflict": {`package conflict
import "a"
import "b"
var _ a.A
var _ b.B
`},
"ok": {`package ok
import "b"
var _ b.B
`},
}),
from: "b", to: "x/a",
want: map[string]string{
"/go/src/a/0.go": `package a; type A int`,
"/go/src/ok/0.go": `package ok
import "x/a"
var _ a.B
`,
"/go/src/conflict/0.go": `package conflict
import "a"
import "x/a"
var _ a.A
var _ b.B
`,
"/go/src/x/a/0.go": `package a
type B int
`,
},
wantWarnings: []string{
`/go/src/conflict/0.go:4:8: renaming this imported package name "b" to "a"`,
`/go/src/conflict/0.go:3:8: conflicts with imported package name in same block`,
`/go/src/conflict/0.go:3:8: skipping update of this file`,
},
},
// Rename with same base name.
{
ctxt: fakeContext(map[string][]string{
"x": {},
"y": {},
"x/foo": {`package foo
type T int
`},
"main": {`package main; import "x/foo"; var _ foo.T`},
}),
from: "x/foo", to: "y/foo",
want: map[string]string{
"/go/src/y/foo/0.go": `package foo
type T int
`,
"/go/src/main/0.go": `package main
import "y/foo"
var _ foo.T
`,
},
},
}
for _, test := range tests {
ctxt := test.ctxt
got := make(map[string]string)
// Populate got with starting file set. rewriteFile and moveDirectory
// will mutate got to produce resulting file set.
buildutil.ForEachPackage(ctxt, func(importPath string, err error) {
if err != nil {
return
}
path := filepath.Join("/go/src", importPath, "0.go")
if !buildutil.FileExists(ctxt, path) {
return
}
f, err := ctxt.OpenFile(path)
if err != nil {
t.Errorf("unexpected error opening file: %s", err)
return
}
bytes, err := ioutil.ReadAll(f)
f.Close()
if err != nil {
t.Errorf("unexpected error reading file: %s", err)
return
}
got[path] = string(bytes)
})
var warnings []string
reportError = func(posn token.Position, message string) {
warnings = append(warnings, posn.String()+": "+message)
}
writeFile = func(filename string, content []byte) error {
got[filename] = string(content)
return nil
}
moveDirectory = func(from, to string) error {
for path, contents := range got {
if strings.HasPrefix(path, from) {
newPath := strings.Replace(path, from, to, 1)
delete(got, path)
got[newPath] = contents
}
}
return nil
}
err := Move(ctxt, test.from, test.to, "")
prefix := fmt.Sprintf("-from %q -to %q", test.from, test.to)
if err != nil {
t.Errorf("%s: unexpected error: %s", prefix, err)
continue
}
if !reflect.DeepEqual(warnings, test.wantWarnings) {
t.Errorf("%s: unexpected warnings:\n%s\nwant:\n%s",
prefix,
strings.Join(warnings, "\n"),
strings.Join(test.wantWarnings, "\n"))
}
for file, wantContent := range test.want {
k := filepath.FromSlash(file)
gotContent, ok := got[k]
delete(got, k)
if !ok {
// TODO(matloob): some testcases might have files that won't be
// rewritten
t.Errorf("%s: file %s not rewritten", prefix, file)
continue
}
if gotContent != wantContent {
t.Errorf("%s: rewritten file %s does not match expectation; got <<<%s>>>\n"+
"want <<<%s>>>", prefix, file, gotContent, wantContent)
}
}
// got should now be empty
for file := range got {
t.Errorf("%s: unexpected rewrite of file %s", prefix, file)
}
}
}