--- /dev/null
+// Copyright 2015 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 (
+ "fmt"
+ "go/build"
+ "go/token"
+ "io/ioutil"
+ "path/filepath"
+ "reflect"
+ "regexp"
+ "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",
+ },
+ {
+ ctxt: fakeContext(map[string][]string{
+ "foo": {``},
+ "bar": {`package bar`},
+ }),
+ from: "foo", to: "bar",
+ want: `no initial packages were loaded`,
+ },
+ }
+
+ 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) {
+ 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
+`,
+ },
+ },
+
+ // References into subpackages where directories have overlapped names
+ {
+ ctxt: fakeContext(map[string][]string{
+ "foo": {},
+ "foo/a": {`package a`},
+ "foo/aa": {`package bar`},
+ "foo/c": {`package c; import _ "foo/bar";`},
+ }),
+ from: "foo/a", to: "foo/spam",
+ want: map[string]string{
+ "/go/src/foo/spam/0.go": `package spam
+`,
+ "/go/src/foo/aa/0.go": `package bar`,
+ "/go/src/foo/c/0.go": `package c; import _ "foo/bar";`,
+ },
+ },
+
+ // 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) {
+ warning := fmt.Sprintf("%s:%d:%d: %s",
+ filepath.ToSlash(posn.Filename), // for MS Windows
+ posn.Line,
+ posn.Column,
+ message)
+ warnings = append(warnings, warning)
+
+ }
+ 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) &&
+ (len(path) == len(from) || path[len(from)] == filepath.Separator)) {
+ continue
+ }
+ 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)
+ }
+ }
+}