github.com/jhump/golang-x-tools@v0.0.0-20220218190644-4958d6d39439/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 "io/ioutil" 10 "os" 11 "os/exec" 12 "path/filepath" 13 "runtime" 14 "strconv" 15 "strings" 16 "testing" 17 18 "github.com/jhump/golang-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 }