blob: 5d068e04178947007cc30530702276ce993a2f9a [file] [log] [blame]
// 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"
"os"
"os/exec"
"path/filepath"
"regexp"
"runtime"
"strings"
"testing"
"golang.org/x/tools/go/buildutil"
"golang.org/x/tools/internal/aliases"
"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
alias bool // requires materialized aliases
}{
// 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
`,
},
},
// Renaming of embedded field alias.
{
alias: true,
ctxt: main(`package main
type T int
type A = T
type U struct{ A }
var _ = U{}.A
var a A
`),
offset: "/go/src/main/0.go:#68", to: "A2", // A in "U{}.A"
want: map[string]string{
"/go/src/main/0.go": `package main
type T int
type A2 = T
type U struct{ A2 }
var _ = U{}.A2
var a A2
`,
},
},
// Renaming of embedded field pointer to alias.
{
alias: true,
ctxt: main(`package main
type T int
type A = T
type U struct{ *A }
var _ = U{}.A
var a A
`),
offset: "/go/src/main/0.go:#69", to: "A2", // A in "U{}.A"
want: map[string]string{
"/go/src/main/0.go": `package main
type T int
type A2 = T
type U struct{ *A2 }
var _ = U{}.A2
var a A2
`,
},
},
// Renaming of alias
{
ctxt: main(`package main
type A = int
func _() A {
return A(0)
}
`),
offset: "/go/src/main/0.go:#49", to: "A2", // A in "A(0)"
want: map[string]string{
"/go/src/main/0.go": `package main
type A2 = int
func _() A2 {
return A2(0)
}
`,
},
},
// 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
}
// Skip tests that require aliases when not enables.
// (No test requires _no_ aliases,
// so there is no contrapositive case.)
if test.alias && !aliases.Enabled() {
t.Log("test requires aliases")
continue
}
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 := os.MkdirTemp("", "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 modFile = `module example.com/rename
go 1.15
`
if err := os.WriteFile(filepath.Join(pkgDir, "go.mod"), []byte(modFile), 0644); err != nil {
t.Fatal(err)
}
const goFile = `package rename
func justHereForTestingDiff() {
justHereForTestingDiff()
}
`
if err := os.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}})
}