1 | // Copyright 2015 The Go Authors. All rights reserved. |
---|---|
2 | // Use of this source code is governed by a BSD-style |
3 | // license that can be found in the LICENSE file. |
4 | |
5 | package rename |
6 | |
7 | import ( |
8 | "fmt" |
9 | "go/build" |
10 | "go/token" |
11 | "io/ioutil" |
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 // regexp to match error, or "OK" |
26 | }{ |
27 | // Simple example. |
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 | // Subpackage already exists. |
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 | // Invalid base name. |
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 | // Simple example. |
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 | // Example with subpackage. |
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 | // References into subpackages |
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 | // References into subpackages where directories have overlapped names |
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 | // External test packages |
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 | // package import comments |
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 | // Import name conflict generates a warning, not an error. |
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 | // Rename with same base name. |
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 | // Populate got with starting file set. rewriteFile and moveDirectory |
376 | // will mutate got to produce resulting file set. |
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 := ioutil.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), // for MS Windows |
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 | // TODO(matloob): some testcases might have files that won't be |
445 | // rewritten |
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 | // got should now be empty |
455 | for file := range got { |
456 | t.Errorf("%s: unexpected rewrite of file %s", prefix, file) |
457 | } |
458 | } |
459 | } |
460 |
Members