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