1
0
mirror of https://github.com/golang/go synced 2024-10-01 16:08:33 -06:00
go/refactor/rename/rename_test.go
Bryan C. Mills df13fa7beb all: do not write to testdata directories
I got tired of spurious 'git' diffs while a 'go test' was running, so
I fixed the test that produced the diffs. (We need to do that anyway
in order to run them in the module cache, plus it's just good hygiene
not to have tests interfering with each other's sources.)

Tested using:

	$ chmod -R ugo-w . && go test ./...; chmod -R u+w .

Updates golang/go#28387

Change-Id: Ie17e31aecf0e3cb022df5503d7c443000366a5c6
Reviewed-on: https://go-review.googlesource.com/c/tools/+/192577
Run-TryBot: Bryan C. Mills <bcmills@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
2019-08-30 17:05:20 +00:00

1375 lines
26 KiB
Go

// Copyright 2014 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 rename
import (
"bytes"
"fmt"
"go/build"
"go/token"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"regexp"
"runtime"
"strings"
"testing"
"golang.org/x/tools/go/buildutil"
"golang.org/x/tools/internal/testenv"
)
// TODO(adonovan): test reported source positions, somehow.
func TestConflicts(t *testing.T) {
defer func(savedWriteFile func(string, []byte) error, savedReportError func(token.Position, string)) {
writeFile = savedWriteFile
reportError = savedReportError
}(writeFile, reportError)
writeFile = func(string, []byte) error { return nil }
var ctxt *build.Context
for _, test := range []struct {
ctxt *build.Context // nil => use previous
offset, from, to string // values of the -offset/-from and -to flags
want string // regexp to match conflict errors, or "OK"
}{
// init() checks
{
ctxt: fakeContext(map[string][]string{
"fmt": {`package fmt; type Stringer interface { String() }`},
"main": {`
package main
import foo "fmt"
var v foo.Stringer
func f() { v.String(); f() }
`,
`package main; var w int`},
}),
from: "main.v", to: "init",
want: `you cannot have a var at package level named "init"`,
},
{
from: "main.f", to: "init",
want: `renaming this func "f" to "init" would make it a package initializer.*` +
`but references to it exist`,
},
{
from: "/go/src/main/0.go::foo", to: "init",
want: `"init" is not a valid imported package name`,
},
// Export checks
{
from: "fmt.Stringer", to: "stringer",
want: `renaming this type "Stringer" to "stringer" would make it unexported.*` +
`breaking references from packages such as "main"`,
},
{
from: "(fmt.Stringer).String", to: "string",
want: `renaming this method "String" to "string" would make it unexported.*` +
`breaking references from packages such as "main"`,
},
// Lexical scope checks
{
// file/package conflict, same file
from: "main.v", to: "foo",
want: `renaming this var "v" to "foo" would conflict.*` +
`with this imported package name`,
},
{
// file/package conflict, same file
from: "main::foo", to: "v",
want: `renaming this imported package name "foo" to "v" would conflict.*` +
`with this package member var`,
},
{
// file/package conflict, different files
from: "main.w", to: "foo",
want: `renaming this var "w" to "foo" would conflict.*` +
`with this imported package name`,
},
{
// file/package conflict, different files
from: "main::foo", to: "w",
want: `renaming this imported package name "foo" to "w" would conflict.*` +
`with this package member var`,
},
{
ctxt: main(`
package main
var x, z int
func f(y int) {
print(x)
print(y)
}
func g(w int) {
print(x)
x := 1
print(x)
}`),
from: "main.x", to: "y",
want: `renaming this var "x" to "y".*` +
`would cause this reference to become shadowed.*` +
`by this intervening var definition`,
},
{
from: "main.g::x", to: "w",
want: `renaming this var "x" to "w".*` +
`conflicts with var in same block`,
},
{
from: "main.f::y", to: "x",
want: `renaming this var "y" to "x".*` +
`would shadow this reference.*` +
`to the var declared here`,
},
{
from: "main.g::w", to: "x",
want: `renaming this var "w" to "x".*` +
`conflicts with var in same block`,
},
{
from: "main.z", to: "y", want: "OK",
},
// Label checks
{
ctxt: main(`
package main
func f() {
foo:
goto foo
bar:
goto bar
func(x int) {
wiz:
goto wiz
}(0)
}
`),
from: "main.f::foo", to: "bar",
want: `renaming this label "foo" to "bar".*` +
`would conflict with this one`,
},
{
from: "main.f::foo", to: "wiz", want: "OK",
},
{
from: "main.f::wiz", to: "x", want: "OK",
},
{
from: "main.f::x", to: "wiz", want: "OK",
},
{
from: "main.f::wiz", to: "foo", want: "OK",
},
// Struct fields
{
ctxt: main(`
package main
type U struct { u int }
type V struct { v int }
func (V) x() {}
type W (struct {
U
V
w int
})
func f() {
var w W
print(w.u) // NB: there is no selection of w.v
var _ struct { yy, zz int }
}
`),
// field/field conflict in named struct declaration
from: "(main.W).U", to: "w",
want: `renaming this field "U" to "w".*` +
`would conflict with this field`,
},
{
// rename type used as embedded field
// => rename field
// => field/field conflict
// This is an entailed renaming;
// it would be nice if we checked source positions.
from: "main.U", to: "w",
want: `renaming this field "U" to "w".*` +
`would conflict with this field`,
},
{
// field/field conflict in unnamed struct declaration
from: "main.f::zz", to: "yy",
want: `renaming this field "zz" to "yy".*` +
`would conflict with this field`,
},
// Now we test both directions of (u,v) (u,w) (v,w) (u,x) (v,x).
// Too bad we don't test position info...
{
// field/field ambiguity at same promotion level ('from' selection)
from: "(main.U).u", to: "v",
want: `renaming this field "u" to "v".*` +
`would make this reference ambiguous.*` +
`with this field`,
},
{
// field/field ambiguity at same promotion level ('to' selection)
from: "(main.V).v", to: "u",
want: `renaming this field "v" to "u".*` +
`would make this reference ambiguous.*` +
`with this field`,
},
{
// field/method conflict at different promotion level ('from' selection)
from: "(main.U).u", to: "w",
want: `renaming this field "u" to "w".*` +
`would change the referent of this selection.*` +
`of this field`,
},
{
// field/field shadowing at different promotion levels ('to' selection)
from: "(main.W).w", to: "u",
want: `renaming this field "w" to "u".*` +
`would shadow this selection.*` +
`of the field declared here`,
},
{
from: "(main.V).v", to: "w",
want: "OK", // since no selections are made ambiguous
},
{
from: "(main.W).w", to: "v",
want: "OK", // since no selections are made ambiguous
},
{
// field/method ambiguity at same promotion level ('from' selection)
from: "(main.U).u", to: "x",
want: `renaming this field "u" to "x".*` +
`would make this reference ambiguous.*` +
`with this method`,
},
{
// field/field ambiguity at same promotion level ('to' selection)
from: "(main.V).x", to: "u",
want: `renaming this method "x" to "u".*` +
`would make this reference ambiguous.*` +
`with this field`,
},
{
// field/method conflict at named struct declaration
from: "(main.V).v", to: "x",
want: `renaming this field "v" to "x".*` +
`would conflict with this method`,
},
{
// field/method conflict at named struct declaration
from: "(main.V).x", to: "v",
want: `renaming this method "x" to "v".*` +
`would conflict with this field`,
},
// Methods
{
ctxt: main(`
package main
type C int
func (C) f()
func (C) g()
type D int
func (*D) f()
func (*D) g()
type I interface { f(); g() }
type J interface { I; h() }
var _ I = new(D)
var _ interface {f()} = C(0)
`),
from: "(main.I).f", to: "g",
want: `renaming this interface method "f" to "g".*` +
`would conflict with this method`,
},
{
from: `("main".I).f`, to: "h", // NB: exercises quoted import paths too
want: `renaming this interface method "f" to "h".*` +
`would conflict with this method.*` +
`in named interface type "J"`,
},
{
// type J interface { h; h() } is not a conflict, amusingly.
from: "main.I", to: "h",
want: `OK`,
},
{
from: "(main.J).h", to: "f",
want: `renaming this interface method "h" to "f".*` +
`would conflict with this method`,
},
{
from: "(main.C).f", to: "e",
want: `renaming this method "f" to "e".*` +
`would make main.C no longer assignable to interface{f..}.*` +
`(rename interface{f..}.f if you intend to change both types)`,
},
{
from: "(main.D).g", to: "e",
want: `renaming this method "g" to "e".*` +
`would make \*main.D no longer assignable to interface I.*` +
`(rename main.I.g if you intend to change both types)`,
},
{
from: "(main.I).f", to: "e",
want: `OK`,
},
// Indirect C/I method coupling via another concrete type D.
{
ctxt: main(`
package main
type I interface { f() }
type C int
func (C) f()
type D struct{C}
var _ I = D{}
`),
from: "(main.C).f", to: "F",
want: `renaming this method "f" to "F".*` +
`would make main.D no longer assignable to interface I.*` +
`(rename main.I.f if you intend to change both types)`,
},
// Renaming causes promoted method to become shadowed; C no longer satisfies I.
{
ctxt: main(`
package main
type I interface { f() }
type C struct { I }
func (C) g() int
var _ I = C{}
`),
from: "main.I.f", to: "g",
want: `renaming this method "f" to "g".*` +
`would change the g method of main.C invoked via interface main.I.*` +
`from \(main.I\).g.*` +
`to \(main.C\).g`,
},
// Renaming causes promoted method to become ambiguous; C no longer satisfies I.
{
ctxt: main(`
package main
type I interface{f()}
type C int
func (C) f()
type D int
func (D) g()
type E struct{C;D}
var _ I = E{}
`),
from: "main.I.f", to: "g",
want: `renaming this method "f" to "g".*` +
`would make the g method of main.E invoked via interface main.I ambiguous.*` +
`with \(main.D\).g`,
},
} {
var conflicts []string
reportError = func(posn token.Position, message string) {
conflicts = append(conflicts, message)
}
if test.ctxt != nil {
ctxt = test.ctxt
}
err := Main(ctxt, test.offset, test.from, test.to)
var prefix string
if test.offset == "" {
prefix = fmt.Sprintf("-from %q -to %q", test.from, test.to)
} else {
prefix = fmt.Sprintf("-offset %q -to %q", test.offset, test.to)
}
if err == ConflictError {
got := strings.Join(conflicts, "\n")
if false {
t.Logf("%s: %s", prefix, got)
}
pattern := "(?s:" + test.want + ")" // enable multi-line matching
if !regexp.MustCompile(pattern).MatchString(got) {
t.Errorf("%s: conflict does not match pattern:\n"+
"Conflict:\t%s\n"+
"Pattern: %s",
prefix, got, test.want)
}
} else if err != nil {
t.Errorf("%s: unexpected error: %s", prefix, err)
} else if test.want != "OK" {
t.Errorf("%s: unexpected success, want conflicts matching:\n%s",
prefix, test.want)
}
}
}
func TestInvalidIdentifiers(t *testing.T) {
ctxt := fakeContext(map[string][]string{
"main": {`
package main
func f() { }
`}})
for _, test := range []struct {
from, to string // values of the -offset/-from and -to flags
want string // expected error message
}{
{
from: "main.f", to: "_",
want: `-to "_": not a valid identifier`,
},
{
from: "main.f", to: "123",
want: `-to "123": not a valid identifier`,
},
{
from: "main.f", to: "for",
want: `-to "for": not a valid identifier`,
},
{
from: "switch", to: "v",
want: `-from "switch": invalid expression`,
},
} {
err := Main(ctxt, "", test.from, test.to)
prefix := fmt.Sprintf("-from %q -to %q", test.from, test.to)
if err == nil {
t.Errorf("%s: expected error %q", prefix, test.want)
} else if err.Error() != test.want {
t.Errorf("%s: unexpected error\nwant: %s\n got: %s", prefix, test.want, err.Error())
}
}
}
func TestRewrites(t *testing.T) {
defer func(savedWriteFile func(string, []byte) error) {
writeFile = savedWriteFile
}(writeFile)
var ctxt *build.Context
for _, test := range []struct {
ctxt *build.Context // nil => use previous
offset, from, to string // values of the -from/-offset and -to flags
want map[string]string // contents of updated files
}{
// Elimination of renaming import.
{
ctxt: fakeContext(map[string][]string{
"foo": {`package foo; type T int`},
"main": {`package main
import foo2 "foo"
var _ foo2.T
`},
}),
from: "main::foo2", to: "foo",
want: map[string]string{
"/go/src/main/0.go": `package main
import "foo"
var _ foo.T
`,
},
},
// Introduction of renaming import.
{
ctxt: fakeContext(map[string][]string{
"foo": {`package foo; type T int`},
"main": {`package main
import "foo"
var _ foo.T
`},
}),
offset: "/go/src/main/0.go:#36", to: "foo2", // the "foo" in foo.T
want: map[string]string{
"/go/src/main/0.go": `package main
import foo2 "foo"
var _ foo2.T
`,
},
},
// Renaming of package-level member.
{
from: "foo.T", to: "U",
want: map[string]string{
"/go/src/main/0.go": `package main
import "foo"
var _ foo.U
`,
"/go/src/foo/0.go": `package foo
type U int
`,
},
},
// Rename package-level func plus doc
{
ctxt: main(`package main
// Foo is a no-op.
// Calling Foo does nothing.
func Foo() {
}
`),
from: "main.Foo", to: "FooBar",
want: map[string]string{
"/go/src/main/0.go": `package main
// FooBar is a no-op.
// Calling FooBar does nothing.
func FooBar() {
}
`,
},
},
// Rename method plus doc
{
ctxt: main(`package main
type Foo struct{}
// Bar does nothing.
func (Foo) Bar() {
}
`),
from: "main.Foo.Bar", to: "Baz",
want: map[string]string{
"/go/src/main/0.go": `package main
type Foo struct{}
// Baz does nothing.
func (Foo) Baz() {
}
`,
},
},
// Rename type spec plus doc
{
ctxt: main(`package main
type (
// Test but not Testing.
Test struct{}
)
`),
from: "main.Test", to: "Type",
want: map[string]string{
"/go/src/main/0.go": `package main
type (
// Type but not Testing.
Type struct{}
)
`,
},
},
// Rename type in gen decl plus doc
{
ctxt: main(`package main
// T is a test type.
type T struct{}
`),
from: "main.T", to: "Type",
want: map[string]string{
"/go/src/main/0.go": `package main
// Type is a test type.
type Type struct{}
`,
},
},
// Rename value spec with doc
{
ctxt: main(`package main
const (
// C is the speed of light.
C = 2.998e8
)
`),
from: "main.C", to: "Lightspeed",
want: map[string]string{
"/go/src/main/0.go": `package main
const (
// Lightspeed is the speed of light.
Lightspeed = 2.998e8
)
`,
},
},
// Rename value inside gen decl with doc
{
ctxt: main(`package main
var out *string
`),
from: "main.out", to: "discard",
want: map[string]string{
"/go/src/main/0.go": `package main
var discard *string
`,
},
},
// Rename field plus doc
{
ctxt: main(`package main
type Struct struct {
// Field is a struct field.
Field string
}
`),
from: "main.Struct.Field", to: "Foo",
want: map[string]string{
"/go/src/main/0.go": `package main
type Struct struct {
// Foo is a struct field.
Foo string
}
`,
},
},
// Label renamings.
{
ctxt: main(`package main
func f() {
loop:
loop := 0
go func() {
loop:
goto loop
}()
loop++
goto loop
}
`),
offset: "/go/src/main/0.go:#25", to: "loop2", // def of outer label "loop"
want: map[string]string{
"/go/src/main/0.go": `package main
func f() {
loop2:
loop := 0
go func() {
loop:
goto loop
}()
loop++
goto loop2
}
`,
},
},
{
offset: "/go/src/main/0.go:#70", to: "loop2", // ref to inner label "loop"
want: map[string]string{
"/go/src/main/0.go": `package main
func f() {
loop:
loop := 0
go func() {
loop2:
goto loop2
}()
loop++
goto loop
}
`,
},
},
// Renaming of type used as embedded field.
{
ctxt: main(`package main
type T int
type U struct { T }
var _ = U{}.T
`),
from: "main.T", to: "T2",
want: map[string]string{
"/go/src/main/0.go": `package main
type T2 int
type U struct{ T2 }
var _ = U{}.T2
`,
},
},
// Renaming of embedded field.
{
ctxt: main(`package main
type T int
type U struct { T }
var _ = U{}.T
`),
offset: "/go/src/main/0.go:#58", to: "T2", // T in "U{}.T"
want: map[string]string{
"/go/src/main/0.go": `package main
type T2 int
type U struct{ T2 }
var _ = U{}.T2
`,
},
},
// Renaming of pointer embedded field.
{
ctxt: main(`package main
type T int
type U struct { *T }
var _ = U{}.T
`),
offset: "/go/src/main/0.go:#59", to: "T2", // T in "U{}.T"
want: map[string]string{
"/go/src/main/0.go": `package main
type T2 int
type U struct{ *T2 }
var _ = U{}.T2
`,
},
},
// Lexical scope tests.
{
ctxt: main(`package main
var y int
func f() {
print(y)
y := ""
print(y)
}
`),
from: "main.y", to: "x",
want: map[string]string{
"/go/src/main/0.go": `package main
var x int
func f() {
print(x)
y := ""
print(y)
}
`,
},
},
{
from: "main.f::y", to: "x",
want: map[string]string{
"/go/src/main/0.go": `package main
var y int
func f() {
print(y)
x := ""
print(x)
}
`,
},
},
// Renaming of typeswitch vars (a corner case).
{
ctxt: main(`package main
func f(z interface{}) {
switch y := z.(type) {
case int:
print(y)
default:
print(y)
}
}
`),
offset: "/go/src/main/0.go:#46", to: "x", // def of y
want: map[string]string{
"/go/src/main/0.go": `package main
func f(z interface{}) {
switch x := z.(type) {
case int:
print(x)
default:
print(x)
}
}
`},
},
{
offset: "/go/src/main/0.go:#81", to: "x", // ref of y in case int
want: map[string]string{
"/go/src/main/0.go": `package main
func f(z interface{}) {
switch x := z.(type) {
case int:
print(x)
default:
print(x)
}
}
`},
},
{
offset: "/go/src/main/0.go:#102", to: "x", // ref of y in default case
want: map[string]string{
"/go/src/main/0.go": `package main
func f(z interface{}) {
switch x := z.(type) {
case int:
print(x)
default:
print(x)
}
}
`},
},
// Renaming of embedded field that is a qualified reference.
// (Regression test for bug 8924.)
{
ctxt: fakeContext(map[string][]string{
"foo": {`package foo; type T int`},
"main": {`package main
import "foo"
type _ struct{ *foo.T }
`},
}),
offset: "/go/src/main/0.go:#48", to: "U", // the "T" in *foo.T
want: map[string]string{
"/go/src/foo/0.go": `package foo
type U int
`,
"/go/src/main/0.go": `package main
import "foo"
type _ struct{ *foo.U }
`,
},
},
// Renaming of embedded field that is a qualified reference with the '-from' flag.
// (Regression test for bug 12038.)
{
ctxt: fakeContext(map[string][]string{
"foo": {`package foo; type T int`},
"main": {`package main
import "foo"
type V struct{ *foo.T }
`},
}),
from: "(main.V).T", to: "U", // the "T" in *foo.T
want: map[string]string{
"/go/src/foo/0.go": `package foo
type U int
`,
"/go/src/main/0.go": `package main
import "foo"
type V struct{ *foo.U }
`,
},
},
{
ctxt: fakeContext(map[string][]string{
"foo": {`package foo; type T int`},
"main": {`package main
import "foo"
type V struct{ foo.T }
`},
}),
from: "(main.V).T", to: "U", // the "T" in *foo.T
want: map[string]string{
"/go/src/foo/0.go": `package foo
type U int
`,
"/go/src/main/0.go": `package main
import "foo"
type V struct{ foo.U }
`,
},
},
// Interface method renaming.
{
ctxt: fakeContext(map[string][]string{
"main": {`
package main
type I interface {
f()
}
type J interface { f(); g() }
type A int
func (A) f()
type B int
func (B) f()
func (B) g()
type C int
func (C) f()
func (C) g()
var _, _ I = A(0), B(0)
var _, _ J = B(0), C(0)
`,
},
}),
offset: "/go/src/main/0.go:#34", to: "F", // abstract method I.f
want: map[string]string{
"/go/src/main/0.go": `package main
type I interface {
F()
}
type J interface {
F()
g()
}
type A int
func (A) F()
type B int
func (B) F()
func (B) g()
type C int
func (C) F()
func (C) g()
var _, _ I = A(0), B(0)
var _, _ J = B(0), C(0)
`,
},
},
{
offset: "/go/src/main/0.go:#59", to: "F", // abstract method J.f
want: map[string]string{
"/go/src/main/0.go": `package main
type I interface {
F()
}
type J interface {
F()
g()
}
type A int
func (A) F()
type B int
func (B) F()
func (B) g()
type C int
func (C) F()
func (C) g()
var _, _ I = A(0), B(0)
var _, _ J = B(0), C(0)
`,
},
},
{
offset: "/go/src/main/0.go:#64", to: "G", // abstract method J.g
want: map[string]string{
"/go/src/main/0.go": `package main
type I interface {
f()
}
type J interface {
f()
G()
}
type A int
func (A) f()
type B int
func (B) f()
func (B) G()
type C int
func (C) f()
func (C) G()
var _, _ I = A(0), B(0)
var _, _ J = B(0), C(0)
`,
},
},
// Indirect coupling of I.f to C.f from D->I assignment and anonymous field of D.
{
ctxt: fakeContext(map[string][]string{
"main": {`
package main
type I interface {
f()
}
type C int
func (C) f()
type D struct{C}
var _ I = D{}
`,
},
}),
offset: "/go/src/main/0.go:#34", to: "F", // abstract method I.f
want: map[string]string{
"/go/src/main/0.go": `package main
type I interface {
F()
}
type C int
func (C) F()
type D struct{ C }
var _ I = D{}
`,
},
},
// Interface embedded in struct. No conflict if C need not satisfy I.
{
ctxt: fakeContext(map[string][]string{
"main": {`
package main
type I interface {
f()
}
type C struct{I}
func (C) g() int
var _ int = C{}.g()
`,
},
}),
offset: "/go/src/main/0.go:#34", to: "g", // abstract method I.f
want: map[string]string{
"/go/src/main/0.go": `package main
type I interface {
g()
}
type C struct{ I }
func (C) g() int
var _ int = C{}.g()
`,
},
},
// A type assertion causes method coupling iff signatures match.
{
ctxt: fakeContext(map[string][]string{
"main": {`package main
type I interface{
f()
}
type J interface{
f()
}
var _ = I(nil).(J)
`,
},
}),
offset: "/go/src/main/0.go:#32", to: "g", // abstract method I.f
want: map[string]string{
"/go/src/main/0.go": `package main
type I interface {
g()
}
type J interface {
g()
}
var _ = I(nil).(J)
`,
},
},
// Impossible type assertion: no method coupling.
{
ctxt: fakeContext(map[string][]string{
"main": {`package main
type I interface{
f()
}
type J interface{
f()int
}
var _ = I(nil).(J)
`,
},
}),
offset: "/go/src/main/0.go:#32", to: "g", // abstract method I.f
want: map[string]string{
"/go/src/main/0.go": `package main
type I interface {
g()
}
type J interface {
f() int
}
var _ = I(nil).(J)
`,
},
},
// Impossible type assertion: no method coupling C.f<->J.f.
{
ctxt: fakeContext(map[string][]string{
"main": {`package main
type I interface{
f()
}
type C int
func (C) f()
type J interface{
f()int
}
var _ = I(C(0)).(J)
`,
},
}),
offset: "/go/src/main/0.go:#32", to: "g", // abstract method I.f
want: map[string]string{
"/go/src/main/0.go": `package main
type I interface {
g()
}
type C int
func (C) g()
type J interface {
f() int
}
var _ = I(C(0)).(J)
`,
},
},
// Progress after "soft" type errors (Go issue 14596).
{
ctxt: fakeContext(map[string][]string{
"main": {`package main
func main() {
var unused, x int
print(x)
}
`,
},
}),
offset: "/go/src/main/0.go:#54", to: "y", // var x
want: map[string]string{
"/go/src/main/0.go": `package main
func main() {
var unused, y int
print(y)
}
`,
},
},
} {
if test.ctxt != nil {
ctxt = test.ctxt
}
got := make(map[string]string)
writeFile = func(filename string, content []byte) error {
got[filepath.ToSlash(filename)] = string(content)
return nil
}
err := Main(ctxt, test.offset, test.from, test.to)
var prefix string
if test.offset == "" {
prefix = fmt.Sprintf("-from %q -to %q", test.from, test.to)
} else {
prefix = fmt.Sprintf("-offset %q -to %q", test.offset, test.to)
}
if err != nil {
t.Errorf("%s: unexpected error: %s", prefix, err)
continue
}
for file, wantContent := range test.want {
gotContent, ok := got[file]
delete(got, file)
if !ok {
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)
}
}
}
func TestDiff(t *testing.T) {
switch runtime.GOOS {
case "windows":
if os.Getenv("GO_BUILDER_NAME") != "" {
if _, err := exec.LookPath(DiffCmd); err != nil {
t.Skipf("diff tool non-existent for %s on builders", runtime.GOOS)
}
}
case "plan9":
t.Skipf("plan9 diff tool doesn't support -u flag")
}
testenv.NeedsTool(t, DiffCmd)
testenv.NeedsTool(t, "go") // to locate the package to be renamed
defer func() {
Diff = false
stdout = os.Stdout
}()
Diff = true
stdout = new(bytes.Buffer)
// Set up a fake GOPATH in a temporary directory,
// and ensure we're in GOPATH mode.
tmpdir, err := ioutil.TempDir("", "TestDiff")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmpdir)
buildCtx := build.Default
buildCtx.GOPATH = tmpdir
pkgDir := filepath.Join(tmpdir, "src", "example.com", "rename")
if err := os.MkdirAll(pkgDir, 0777); err != nil {
t.Fatal(err)
}
prevWD, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
defer os.Chdir(prevWD)
if err := os.Chdir(pkgDir); err != nil {
t.Fatal(err)
}
const goFile = `package rename
func justHereForTestingDiff() {
justHereForTestingDiff()
}
`
if err := ioutil.WriteFile(filepath.Join(pkgDir, "rename_test.go"), []byte(goFile), 0644); err != nil {
t.Fatal(err)
}
if err := Main(&buildCtx, "", `"example.com/rename".justHereForTestingDiff`, "Foo"); err != nil {
t.Fatal(err)
}
// NB: there are tabs in the string literal!
if !strings.Contains(stdout.(fmt.Stringer).String(), `
-func justHereForTestingDiff() {
- justHereForTestingDiff()
+func Foo() {
+ Foo()
}
`) {
t.Errorf("unexpected diff:\n<<%s>>", stdout)
}
}
// ---------------------------------------------------------------------
// Simplifying wrapper around buildutil.FakeContext for packages whose
// filenames are sequentially numbered (%d.go). pkgs maps a package
// import path to its list of file contents.
func fakeContext(pkgs map[string][]string) *build.Context {
pkgs2 := make(map[string]map[string]string)
for path, files := range pkgs {
filemap := make(map[string]string)
for i, contents := range files {
filemap[fmt.Sprintf("%d.go", i)] = contents
}
pkgs2[path] = filemap
}
return buildutil.FakeContext(pkgs2)
}
// helper for single-file main packages with no imports.
func main(content string) *build.Context {
return fakeContext(map[string][]string{"main": {content}})
}