1 | // Copyright 2017 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 main_test |
6 | |
7 | import ( |
8 | "fmt" |
9 | "io/ioutil" |
10 | "os" |
11 | "os/exec" |
12 | "path/filepath" |
13 | "runtime" |
14 | "strconv" |
15 | "strings" |
16 | "testing" |
17 | |
18 | "golang.org/x/tools/internal/testenv" |
19 | ) |
20 | |
21 | type test struct { |
22 | offset, from, to string // specify the arguments |
23 | fileSpecified bool // true if the offset or from args specify a specific file |
24 | pkgs map[string][]string |
25 | wantErr bool |
26 | wantOut string // a substring expected to be in the output |
27 | packages map[string][]string // a map of the package name to the files contained within, which will be numbered by i.go where i is the index |
28 | } |
29 | |
30 | // Test that renaming that would modify cgo files will produce an error and not modify the file. |
31 | func TestGeneratedFiles(t *testing.T) { |
32 | testenv.NeedsTool(t, "go") |
33 | testenv.NeedsTool(t, "cgo") |
34 | |
35 | tmp, bin, cleanup := buildGorename(t) |
36 | defer cleanup() |
37 | |
38 | srcDir := filepath.Join(tmp, "src") |
39 | err := os.Mkdir(srcDir, os.ModePerm) |
40 | if err != nil { |
41 | t.Fatal(err) |
42 | } |
43 | |
44 | var env = []string{fmt.Sprintf("GOPATH=%s", tmp)} |
45 | for _, envVar := range os.Environ() { |
46 | if !strings.HasPrefix(envVar, "GOPATH=") { |
47 | env = append(env, envVar) |
48 | } |
49 | } |
50 | // gorename currently requires GOPATH mode. |
51 | env = append(env, "GO111MODULE=off") |
52 | |
53 | // Testing renaming in packages that include cgo files: |
54 | for iter, renameTest := range []test{ |
55 | { |
56 | // Test: variable not used in any cgo file -> no error |
57 | from: `"mytest"::f`, to: "g", |
58 | packages: map[string][]string{ |
59 | "mytest": []string{`package mytest; func f() {}`, |
60 | `package mytest |
61 | // #include <stdio.h> |
62 | import "C" |
63 | |
64 | func z() {C.puts(nil)}`}, |
65 | }, |
66 | wantErr: false, |
67 | wantOut: "Renamed 1 occurrence in 1 file in 1 package.", |
68 | }, { |
69 | // Test: to name used in cgo file -> rename error |
70 | from: `"mytest"::f`, to: "g", |
71 | packages: map[string][]string{ |
72 | "mytest": []string{`package mytest; func f() {}`, |
73 | `package mytest |
74 | // #include <stdio.h> |
75 | import "C" |
76 | |
77 | func g() {C.puts(nil)}`}, |
78 | }, |
79 | wantErr: true, |
80 | wantOut: "conflicts with func in same block", |
81 | }, |
82 | { |
83 | // Test: from name in package in cgo file -> error |
84 | from: `"mytest"::f`, to: "g", |
85 | packages: map[string][]string{ |
86 | "mytest": []string{`package mytest |
87 | |
88 | // #include <stdio.h> |
89 | import "C" |
90 | |
91 | func f() { C.puts(nil); } |
92 | `}, |
93 | }, |
94 | wantErr: true, |
95 | wantOut: "gorename: refusing to modify generated file containing DO NOT EDIT marker:", |
96 | }, { |
97 | // Test: from name in cgo file -> error |
98 | from: filepath.Join("mytest", "0.go") + `::f`, to: "g", |
99 | fileSpecified: true, |
100 | packages: map[string][]string{ |
101 | "mytest": []string{`package mytest |
102 | |
103 | // #include <stdio.h> |
104 | import "C" |
105 | |
106 | func f() { C.puts(nil); } |
107 | `}, |
108 | }, |
109 | wantErr: true, |
110 | wantOut: "gorename: refusing to modify generated file containing DO NOT EDIT marker:", |
111 | }, { |
112 | // Test: offset in cgo file -> identifier in cgo error |
113 | offset: filepath.Join("main", "0.go") + `:#78`, to: "bar", |
114 | fileSpecified: true, |
115 | wantErr: true, |
116 | packages: map[string][]string{ |
117 | "main": {`package main |
118 | |
119 | // #include <unistd.h> |
120 | import "C" |
121 | import "fmt" |
122 | |
123 | func main() { |
124 | foo := 1 |
125 | C.close(2) |
126 | fmt.Println(foo) |
127 | } |
128 | `}, |
129 | }, |
130 | wantOut: "cannot rename identifiers in generated file containing DO NOT EDIT marker:", |
131 | }, { |
132 | // Test: from identifier appears in cgo file in another package -> error |
133 | from: `"test"::Foo`, to: "Bar", |
134 | packages: map[string][]string{ |
135 | "test": []string{ |
136 | `package test |
137 | |
138 | func Foo(x int) (int){ |
139 | return x * 2 |
140 | } |
141 | `, |
142 | }, |
143 | "main": []string{ |
144 | `package main |
145 | |
146 | import "test" |
147 | import "fmt" |
148 | // #include <unistd.h> |
149 | import "C" |
150 | |
151 | func fun() { |
152 | x := test.Foo(3) |
153 | C.close(3) |
154 | fmt.Println(x) |
155 | } |
156 | `, |
157 | }, |
158 | }, |
159 | wantErr: true, |
160 | wantOut: "gorename: refusing to modify generated file containing DO NOT EDIT marker:", |
161 | }, { |
162 | // Test: from identifier doesn't appear in cgo file that includes modified package -> rename successful |
163 | from: `"test".Foo::x`, to: "y", |
164 | packages: map[string][]string{ |
165 | "test": []string{ |
166 | `package test |
167 | |
168 | func Foo(x int) (int){ |
169 | return x * 2 |
170 | } |
171 | `, |
172 | }, |
173 | "main": []string{ |
174 | `package main |
175 | import "test" |
176 | import "fmt" |
177 | // #include <unistd.h> |
178 | import "C" |
179 | |
180 | func fun() { |
181 | x := test.Foo(3) |
182 | C.close(3) |
183 | fmt.Println(x) |
184 | } |
185 | `, |
186 | }, |
187 | }, |
188 | wantErr: false, |
189 | wantOut: "Renamed 2 occurrences in 1 file in 1 package.", |
190 | }, { |
191 | // Test: from name appears in cgo file in same package -> error |
192 | from: `"mytest"::f`, to: "g", |
193 | packages: map[string][]string{ |
194 | "mytest": []string{`package mytest; func f() {}`, |
195 | `package mytest |
196 | // #include <stdio.h> |
197 | import "C" |
198 | |
199 | func z() {C.puts(nil); f()}`, |
200 | `package mytest |
201 | // #include <unistd.h> |
202 | import "C" |
203 | |
204 | func foo() {C.close(3); f()}`, |
205 | }, |
206 | }, |
207 | wantErr: true, |
208 | wantOut: "gorename: refusing to modify generated files containing DO NOT EDIT marker:", |
209 | }, { |
210 | // Test: from name in file, identifier not used in cgo file -> rename successful |
211 | from: filepath.Join("mytest", "0.go") + `::f`, to: "g", |
212 | fileSpecified: true, |
213 | packages: map[string][]string{ |
214 | "mytest": []string{`package mytest; func f() {}`, |
215 | `package mytest |
216 | // #include <stdio.h> |
217 | import "C" |
218 | |
219 | func z() {C.puts(nil)}`}, |
220 | }, |
221 | wantErr: false, |
222 | wantOut: "Renamed 1 occurrence in 1 file in 1 package.", |
223 | }, { |
224 | // Test: from identifier imported to another package but does not modify cgo file -> rename successful |
225 | from: `"test".Foo`, to: "Bar", |
226 | packages: map[string][]string{ |
227 | "test": []string{ |
228 | `package test |
229 | |
230 | func Foo(x int) (int){ |
231 | return x * 2 |
232 | } |
233 | `, |
234 | }, |
235 | "main": []string{ |
236 | `package main |
237 | // #include <unistd.h> |
238 | import "C" |
239 | |
240 | func fun() { |
241 | C.close(3) |
242 | } |
243 | `, |
244 | `package main |
245 | import "test" |
246 | import "fmt" |
247 | func g() { fmt.Println(test.Foo(3)) } |
248 | `, |
249 | }, |
250 | }, |
251 | wantErr: false, |
252 | wantOut: "Renamed 2 occurrences in 2 files in 2 packages.", |
253 | }, |
254 | } { |
255 | // Write the test files |
256 | testCleanup := setUpPackages(t, srcDir, renameTest.packages) |
257 | |
258 | // Set up arguments |
259 | var args []string |
260 | |
261 | var arg, val string |
262 | if renameTest.offset != "" { |
263 | arg, val = "-offset", renameTest.offset |
264 | } else { |
265 | arg, val = "-from", renameTest.from |
266 | } |
267 | |
268 | prefix := fmt.Sprintf("%d: %s %q -to %q", iter, arg, val, renameTest.to) |
269 | |
270 | if renameTest.fileSpecified { |
271 | // add the src dir to the value of the argument |
272 | val = filepath.Join(srcDir, val) |
273 | } |
274 | |
275 | args = append(args, arg, val, "-to", renameTest.to) |
276 | |
277 | // Run command |
278 | cmd := exec.Command(bin, args...) |
279 | cmd.Args[0] = "gorename" |
280 | cmd.Env = env |
281 | |
282 | // Check the output |
283 | out, err := cmd.CombinedOutput() |
284 | // errors should result in no changes to files |
285 | if err != nil { |
286 | if !renameTest.wantErr { |
287 | t.Errorf("%s: received unexpected error %s", prefix, err) |
288 | } |
289 | // Compare output |
290 | if ok := strings.Contains(string(out), renameTest.wantOut); !ok { |
291 | t.Errorf("%s: unexpected command output: %s (want: %s)", prefix, out, renameTest.wantOut) |
292 | } |
293 | // Check that no files were modified |
294 | if modified := modifiedFiles(t, srcDir, renameTest.packages); len(modified) != 0 { |
295 | t.Errorf("%s: files unexpectedly modified: %s", prefix, modified) |
296 | } |
297 | |
298 | } else { |
299 | if !renameTest.wantErr { |
300 | if ok := strings.Contains(string(out), renameTest.wantOut); !ok { |
301 | t.Errorf("%s: unexpected command output: %s (want: %s)", prefix, out, renameTest.wantOut) |
302 | } |
303 | } else { |
304 | t.Errorf("%s: command succeeded unexpectedly, output: %s", prefix, out) |
305 | } |
306 | } |
307 | testCleanup() |
308 | } |
309 | } |
310 | |
311 | // buildGorename builds the gorename executable. |
312 | // It returns its path, and a cleanup function. |
313 | func buildGorename(t *testing.T) (tmp, bin string, cleanup func()) { |
314 | if runtime.GOOS == "android" { |
315 | t.Skipf("the dependencies are not available on android") |
316 | } |
317 | |
318 | tmp, err := ioutil.TempDir("", "gorename-regtest-") |
319 | if err != nil { |
320 | t.Fatal(err) |
321 | } |
322 | |
323 | defer func() { |
324 | if cleanup == nil { // probably, go build failed. |
325 | os.RemoveAll(tmp) |
326 | } |
327 | }() |
328 | |
329 | bin = filepath.Join(tmp, "gorename") |
330 | if runtime.GOOS == "windows" { |
331 | bin += ".exe" |
332 | } |
333 | cmd := exec.Command("go", "build", "-o", bin) |
334 | if out, err := cmd.CombinedOutput(); err != nil { |
335 | t.Fatalf("Building gorename: %v\n%s", err, out) |
336 | } |
337 | return tmp, bin, func() { os.RemoveAll(tmp) } |
338 | } |
339 | |
340 | // setUpPackages sets up the files in a temporary directory provided by arguments. |
341 | func setUpPackages(t *testing.T, dir string, packages map[string][]string) (cleanup func()) { |
342 | var pkgDirs []string |
343 | |
344 | for pkgName, files := range packages { |
345 | // Create a directory for the package. |
346 | pkgDir := filepath.Join(dir, pkgName) |
347 | pkgDirs = append(pkgDirs, pkgDir) |
348 | |
349 | if err := os.Mkdir(pkgDir, os.ModePerm); err != nil { |
350 | t.Fatal(err) |
351 | } |
352 | // Write the packages files |
353 | for i, val := range files { |
354 | file := filepath.Join(pkgDir, strconv.Itoa(i)+".go") |
355 | if err := ioutil.WriteFile(file, []byte(val), os.ModePerm); err != nil { |
356 | t.Fatal(err) |
357 | } |
358 | } |
359 | } |
360 | return func() { |
361 | for _, dir := range pkgDirs { |
362 | os.RemoveAll(dir) |
363 | } |
364 | } |
365 | } |
366 | |
367 | // modifiedFiles returns a list of files that were renamed (without the prefix dir). |
368 | func modifiedFiles(t *testing.T, dir string, packages map[string][]string) (results []string) { |
369 | |
370 | for pkgName, files := range packages { |
371 | pkgDir := filepath.Join(dir, pkgName) |
372 | |
373 | for i, val := range files { |
374 | file := filepath.Join(pkgDir, strconv.Itoa(i)+".go") |
375 | // read file contents and compare to val |
376 | if contents, err := ioutil.ReadFile(file); err != nil { |
377 | t.Fatalf("File missing: %s", err) |
378 | } else if string(contents) != val { |
379 | results = append(results, strings.TrimPrefix(dir, file)) |
380 | } |
381 | } |
382 | } |
383 | return results |
384 | } |
385 |
Members