1
2
3
4
5 package rename
6
7 import (
8 "fmt"
9 "go/build"
10 "go/token"
11 "io"
12 "path/filepath"
13 "reflect"
14 "regexp"
15 "strings"
16 "testing"
17
18 "golang.org/x/tools/go/buildutil"
19 )
20
21 func TestErrors(t *testing.T) {
22 tests := []struct {
23 ctxt *build.Context
24 from, to string
25 want string
26 }{
27
28 {
29 ctxt: fakeContext(map[string][]string{
30 "foo": {`package foo; type T int`},
31 "bar": {`package bar`},
32 "main": {`package main
33
34 import "foo"
35
36 var _ foo.T
37 `},
38 }),
39 from: "foo", to: "bar",
40 want: `invalid move destination: bar conflicts with directory .go.src.bar`,
41 },
42
43 {
44 ctxt: fakeContext(map[string][]string{
45 "foo": {`package foo; type T int`},
46 "foo/sub": {`package sub`},
47 "bar/sub": {`package sub`},
48 "main": {`package main
49
50 import "foo"
51
52 var _ foo.T
53 `},
54 }),
55 from: "foo", to: "bar",
56 want: "invalid move destination: bar; package or subpackage bar/sub already exists",
57 },
58
59 {
60 ctxt: fakeContext(map[string][]string{
61 "foo": {`package foo; type T int`},
62 "main": {`package main
63
64 import "foo"
65
66 var _ foo.T
67 `},
68 }),
69 from: "foo", to: "bar-v2.0",
70 want: "invalid move destination: bar-v2.0; gomvpkg does not " +
71 "support move destinations whose base names are not valid " +
72 "go identifiers",
73 },
74 {
75 ctxt: fakeContext(map[string][]string{
76 "foo": {``},
77 "bar": {`package bar`},
78 }),
79 from: "foo", to: "bar",
80 want: `no initial packages were loaded`,
81 },
82 }
83
84 for _, test := range tests {
85 ctxt := test.ctxt
86
87 got := make(map[string]string)
88 writeFile = func(filename string, content []byte) error {
89 got[filename] = string(content)
90 return nil
91 }
92 moveDirectory = func(from, to string) error {
93 for path, contents := range got {
94 if strings.HasPrefix(path, from) {
95 newPath := strings.Replace(path, from, to, 1)
96 delete(got, path)
97 got[newPath] = contents
98 }
99 }
100 return nil
101 }
102
103 err := Move(ctxt, test.from, test.to, "")
104 prefix := fmt.Sprintf("-from %q -to %q", test.from, test.to)
105 if err == nil {
106 t.Errorf("%s: nil error. Expected error: %s", prefix, test.want)
107 continue
108 }
109 matched, err2 := regexp.MatchString(test.want, err.Error())
110 if err2 != nil {
111 t.Errorf("regexp.MatchString failed %s", err2)
112 continue
113 }
114 if !matched {
115 t.Errorf("%s: conflict does not match expectation:\n"+
116 "Error: %q\n"+
117 "Pattern: %q",
118 prefix, err.Error(), test.want)
119 }
120 }
121 }
122
123 func TestMoves(t *testing.T) {
124 tests := []struct {
125 ctxt *build.Context
126 from, to string
127 want map[string]string
128 wantWarnings []string
129 }{
130
131 {
132 ctxt: fakeContext(map[string][]string{
133 "foo": {`package foo; type T int`},
134 "main": {`package main
135
136 import "foo"
137
138 var _ foo.T
139 `},
140 }),
141 from: "foo", to: "bar",
142 want: map[string]string{
143 "/go/src/main/0.go": `package main
144
145 import "bar"
146
147 var _ bar.T
148 `,
149 "/go/src/bar/0.go": `package bar
150
151 type T int
152 `,
153 },
154 },
155
156
157 {
158 ctxt: fakeContext(map[string][]string{
159 "foo": {`package foo; type T int`},
160 "foo/sub": {`package sub; type T int`},
161 "main": {`package main
162
163 import "foo"
164 import "foo/sub"
165
166 var _ foo.T
167 var _ sub.T
168 `},
169 }),
170 from: "foo", to: "bar",
171 want: map[string]string{
172 "/go/src/main/0.go": `package main
173
174 import "bar"
175 import "bar/sub"
176
177 var _ bar.T
178 var _ sub.T
179 `,
180 "/go/src/bar/0.go": `package bar
181
182 type T int
183 `,
184 "/go/src/bar/sub/0.go": `package sub; type T int`,
185 },
186 },
187
188
189 {
190 ctxt: fakeContext(map[string][]string{
191 "foo": {`package foo; import "foo/a"; var _ a.T`},
192 "foo/a": {`package a; type T int`},
193 "foo/b": {`package b; import "foo/a"; var _ a.T`},
194 }),
195 from: "foo", to: "bar",
196 want: map[string]string{
197 "/go/src/bar/0.go": `package bar
198
199 import "bar/a"
200
201 var _ a.T
202 `,
203 "/go/src/bar/a/0.go": `package a; type T int`,
204 "/go/src/bar/b/0.go": `package b
205
206 import "bar/a"
207
208 var _ a.T
209 `,
210 },
211 },
212
213
214 {
215 ctxt: fakeContext(map[string][]string{
216 "foo": {},
217 "foo/a": {`package a`},
218 "foo/aa": {`package bar`},
219 "foo/c": {`package c; import _ "foo/bar";`},
220 }),
221 from: "foo/a", to: "foo/spam",
222 want: map[string]string{
223 "/go/src/foo/spam/0.go": `package spam
224 `,
225 "/go/src/foo/aa/0.go": `package bar`,
226 "/go/src/foo/c/0.go": `package c; import _ "foo/bar";`,
227 },
228 },
229
230
231 {
232 ctxt: buildutil.FakeContext(map[string]map[string]string{
233 "foo": {
234 "0.go": `package foo; type T int`,
235 "0_test.go": `package foo_test; import "foo"; var _ foo.T`,
236 },
237 "baz": {
238 "0_test.go": `package baz_test; import "foo"; var _ foo.T`,
239 },
240 }),
241 from: "foo", to: "bar",
242 want: map[string]string{
243 "/go/src/bar/0.go": `package bar
244
245 type T int
246 `,
247 "/go/src/bar/0_test.go": `package bar_test
248
249 import "bar"
250
251 var _ bar.T
252 `,
253 "/go/src/baz/0_test.go": `package baz_test
254
255 import "bar"
256
257 var _ bar.T
258 `,
259 },
260 },
261
262 {
263 ctxt: fakeContext(map[string][]string{"foo": {`package foo // import "baz"`}}),
264 from: "foo", to: "bar",
265 want: map[string]string{"/go/src/bar/0.go": `package bar // import "bar"
266 `},
267 },
268 {
269 ctxt: fakeContext(map[string][]string{"foo": {`package foo /* import "baz" */`}}),
270 from: "foo", to: "bar",
271 want: map[string]string{"/go/src/bar/0.go": `package bar /* import "bar" */
272 `},
273 },
274 {
275 ctxt: fakeContext(map[string][]string{"foo": {`package foo // import "baz"`}}),
276 from: "foo", to: "bar",
277 want: map[string]string{"/go/src/bar/0.go": `package bar // import "bar"
278 `},
279 },
280 {
281 ctxt: fakeContext(map[string][]string{"foo": {`package foo
282 // import " this is not an import comment`}}),
283 from: "foo", to: "bar",
284 want: map[string]string{"/go/src/bar/0.go": `package bar
285
286 // import " this is not an import comment
287 `},
288 },
289 {
290 ctxt: fakeContext(map[string][]string{"foo": {`package foo
291 /* import " this is not an import comment */`}}),
292 from: "foo", to: "bar",
293 want: map[string]string{"/go/src/bar/0.go": `package bar
294
295 /* import " this is not an import comment */
296 `},
297 },
298
299 {
300 ctxt: fakeContext(map[string][]string{
301 "x": {},
302 "a": {`package a; type A int`},
303 "b": {`package b; type B int`},
304 "conflict": {`package conflict
305
306 import "a"
307 import "b"
308 var _ a.A
309 var _ b.B
310 `},
311 "ok": {`package ok
312 import "b"
313 var _ b.B
314 `},
315 }),
316 from: "b", to: "x/a",
317 want: map[string]string{
318 "/go/src/a/0.go": `package a; type A int`,
319 "/go/src/ok/0.go": `package ok
320
321 import "x/a"
322
323 var _ a.B
324 `,
325 "/go/src/conflict/0.go": `package conflict
326
327 import "a"
328 import "x/a"
329
330 var _ a.A
331 var _ b.B
332 `,
333 "/go/src/x/a/0.go": `package a
334
335 type B int
336 `,
337 },
338 wantWarnings: []string{
339 `/go/src/conflict/0.go:4:8: renaming this imported package name "b" to "a"`,
340 `/go/src/conflict/0.go:3:8: conflicts with imported package name in same block`,
341 `/go/src/conflict/0.go:3:8: skipping update of this file`,
342 },
343 },
344
345 {
346 ctxt: fakeContext(map[string][]string{
347 "x": {},
348 "y": {},
349 "x/foo": {`package foo
350
351 type T int
352 `},
353 "main": {`package main; import "x/foo"; var _ foo.T`},
354 }),
355 from: "x/foo", to: "y/foo",
356 want: map[string]string{
357 "/go/src/y/foo/0.go": `package foo
358
359 type T int
360 `,
361 "/go/src/main/0.go": `package main
362
363 import "y/foo"
364
365 var _ foo.T
366 `,
367 },
368 },
369 }
370
371 for _, test := range tests {
372 ctxt := test.ctxt
373
374 got := make(map[string]string)
375
376
377 buildutil.ForEachPackage(ctxt, func(importPath string, err error) {
378 if err != nil {
379 return
380 }
381 path := filepath.Join("/go/src", importPath, "0.go")
382 if !buildutil.FileExists(ctxt, path) {
383 return
384 }
385 f, err := ctxt.OpenFile(path)
386 if err != nil {
387 t.Errorf("unexpected error opening file: %s", err)
388 return
389 }
390 bytes, err := io.ReadAll(f)
391 f.Close()
392 if err != nil {
393 t.Errorf("unexpected error reading file: %s", err)
394 return
395 }
396 got[path] = string(bytes)
397 })
398 var warnings []string
399 reportError = func(posn token.Position, message string) {
400 warning := fmt.Sprintf("%s:%d:%d: %s",
401 filepath.ToSlash(posn.Filename),
402 posn.Line,
403 posn.Column,
404 message)
405 warnings = append(warnings, warning)
406
407 }
408 writeFile = func(filename string, content []byte) error {
409 got[filename] = string(content)
410 return nil
411 }
412 moveDirectory = func(from, to string) error {
413 for path, contents := range got {
414 if !(strings.HasPrefix(path, from) &&
415 (len(path) == len(from) || path[len(from)] == filepath.Separator)) {
416 continue
417 }
418 newPath := strings.Replace(path, from, to, 1)
419 delete(got, path)
420 got[newPath] = contents
421 }
422 return nil
423 }
424
425 err := Move(ctxt, test.from, test.to, "")
426 prefix := fmt.Sprintf("-from %q -to %q", test.from, test.to)
427 if err != nil {
428 t.Errorf("%s: unexpected error: %s", prefix, err)
429 continue
430 }
431
432 if !reflect.DeepEqual(warnings, test.wantWarnings) {
433 t.Errorf("%s: unexpected warnings:\n%s\nwant:\n%s",
434 prefix,
435 strings.Join(warnings, "\n"),
436 strings.Join(test.wantWarnings, "\n"))
437 }
438
439 for file, wantContent := range test.want {
440 k := filepath.FromSlash(file)
441 gotContent, ok := got[k]
442 delete(got, k)
443 if !ok {
444
445
446 t.Errorf("%s: file %s not rewritten", prefix, file)
447 continue
448 }
449 if gotContent != wantContent {
450 t.Errorf("%s: rewritten file %s does not match expectation; got <<<%s>>>\n"+
451 "want <<<%s>>>", prefix, file, gotContent, wantContent)
452 }
453 }
454
455 for file := range got {
456 t.Errorf("%s: unexpected rewrite of file %s", prefix, file)
457 }
458 }
459 }
460
View as plain text