| // 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" | |
| "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 := io.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) | |
| } | |
| } | |
| } |