| // Copyright 2017 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package main_test |
| |
| import ( |
| "fmt" |
| "io/ioutil" |
| "os" |
| "os/exec" |
| "path/filepath" |
| "runtime" |
| "strconv" |
| "strings" |
| "testing" |
| |
| "golang.org/x/tools/internal/testenv" |
| ) |
| |
| type test struct { |
| offset, from, to string // specify the arguments |
| fileSpecified bool // true if the offset or from args specify a specific file |
| pkgs map[string][]string |
| wantErr bool |
| wantOut string // a substring expected to be in the output |
| 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 |
| } |
| |
| // Test that renaming that would modify cgo files will produce an error and not modify the file. |
| func TestGeneratedFiles(t *testing.T) { |
| testenv.NeedsTool(t, "go") |
| testenv.NeedsTool(t, "cgo") |
| |
| tmp, bin, cleanup := buildGorename(t) |
| defer cleanup() |
| |
| srcDir := filepath.Join(tmp, "src") |
| err := os.Mkdir(srcDir, os.ModePerm) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| var env = []string{fmt.Sprintf("GOPATH=%s", tmp)} |
| for _, envVar := range os.Environ() { |
| if !strings.HasPrefix(envVar, "GOPATH=") { |
| env = append(env, envVar) |
| } |
| } |
| // gorename currently requires GOPATH mode. |
| env = append(env, "GO111MODULE=off") |
| |
| // Testing renaming in packages that include cgo files: |
| for iter, renameTest := range []test{ |
| { |
| // Test: variable not used in any cgo file -> no error |
| from: `"mytest"::f`, to: "g", |
| packages: map[string][]string{ |
| "mytest": []string{`package mytest; func f() {}`, |
| `package mytest |
| // #include <stdio.h> |
| import "C" |
| |
| func z() {C.puts(nil)}`}, |
| }, |
| wantErr: false, |
| wantOut: "Renamed 1 occurrence in 1 file in 1 package.", |
| }, { |
| // Test: to name used in cgo file -> rename error |
| from: `"mytest"::f`, to: "g", |
| packages: map[string][]string{ |
| "mytest": []string{`package mytest; func f() {}`, |
| `package mytest |
| // #include <stdio.h> |
| import "C" |
| |
| func g() {C.puts(nil)}`}, |
| }, |
| wantErr: true, |
| wantOut: "conflicts with func in same block", |
| }, |
| { |
| // Test: from name in package in cgo file -> error |
| from: `"mytest"::f`, to: "g", |
| packages: map[string][]string{ |
| "mytest": []string{`package mytest |
| |
| // #include <stdio.h> |
| import "C" |
| |
| func f() { C.puts(nil); } |
| `}, |
| }, |
| wantErr: true, |
| wantOut: "gorename: refusing to modify generated file containing DO NOT EDIT marker:", |
| }, { |
| // Test: from name in cgo file -> error |
| from: filepath.Join("mytest", "0.go") + `::f`, to: "g", |
| fileSpecified: true, |
| packages: map[string][]string{ |
| "mytest": []string{`package mytest |
| |
| // #include <stdio.h> |
| import "C" |
| |
| func f() { C.puts(nil); } |
| `}, |
| }, |
| wantErr: true, |
| wantOut: "gorename: refusing to modify generated file containing DO NOT EDIT marker:", |
| }, { |
| // Test: offset in cgo file -> identifier in cgo error |
| offset: filepath.Join("main", "0.go") + `:#78`, to: "bar", |
| fileSpecified: true, |
| wantErr: true, |
| packages: map[string][]string{ |
| "main": {`package main |
| |
| // #include <unistd.h> |
| import "C" |
| import "fmt" |
| |
| func main() { |
| foo := 1 |
| C.close(2) |
| fmt.Println(foo) |
| } |
| `}, |
| }, |
| wantOut: "cannot rename identifiers in generated file containing DO NOT EDIT marker:", |
| }, { |
| // Test: from identifier appears in cgo file in another package -> error |
| from: `"test"::Foo`, to: "Bar", |
| packages: map[string][]string{ |
| "test": []string{ |
| `package test |
| |
| func Foo(x int) (int){ |
| return x * 2 |
| } |
| `, |
| }, |
| "main": []string{ |
| `package main |
| |
| import "test" |
| import "fmt" |
| // #include <unistd.h> |
| import "C" |
| |
| func fun() { |
| x := test.Foo(3) |
| C.close(3) |
| fmt.Println(x) |
| } |
| `, |
| }, |
| }, |
| wantErr: true, |
| wantOut: "gorename: refusing to modify generated file containing DO NOT EDIT marker:", |
| }, { |
| // Test: from identifier doesn't appear in cgo file that includes modified package -> rename successful |
| from: `"test".Foo::x`, to: "y", |
| packages: map[string][]string{ |
| "test": []string{ |
| `package test |
| |
| func Foo(x int) (int){ |
| return x * 2 |
| } |
| `, |
| }, |
| "main": []string{ |
| `package main |
| import "test" |
| import "fmt" |
| // #include <unistd.h> |
| import "C" |
| |
| func fun() { |
| x := test.Foo(3) |
| C.close(3) |
| fmt.Println(x) |
| } |
| `, |
| }, |
| }, |
| wantErr: false, |
| wantOut: "Renamed 2 occurrences in 1 file in 1 package.", |
| }, { |
| // Test: from name appears in cgo file in same package -> error |
| from: `"mytest"::f`, to: "g", |
| packages: map[string][]string{ |
| "mytest": []string{`package mytest; func f() {}`, |
| `package mytest |
| // #include <stdio.h> |
| import "C" |
| |
| func z() {C.puts(nil); f()}`, |
| `package mytest |
| // #include <unistd.h> |
| import "C" |
| |
| func foo() {C.close(3); f()}`, |
| }, |
| }, |
| wantErr: true, |
| wantOut: "gorename: refusing to modify generated files containing DO NOT EDIT marker:", |
| }, { |
| // Test: from name in file, identifier not used in cgo file -> rename successful |
| from: filepath.Join("mytest", "0.go") + `::f`, to: "g", |
| fileSpecified: true, |
| packages: map[string][]string{ |
| "mytest": []string{`package mytest; func f() {}`, |
| `package mytest |
| // #include <stdio.h> |
| import "C" |
| |
| func z() {C.puts(nil)}`}, |
| }, |
| wantErr: false, |
| wantOut: "Renamed 1 occurrence in 1 file in 1 package.", |
| }, { |
| // Test: from identifier imported to another package but does not modify cgo file -> rename successful |
| from: `"test".Foo`, to: "Bar", |
| packages: map[string][]string{ |
| "test": []string{ |
| `package test |
| |
| func Foo(x int) (int){ |
| return x * 2 |
| } |
| `, |
| }, |
| "main": []string{ |
| `package main |
| // #include <unistd.h> |
| import "C" |
| |
| func fun() { |
| C.close(3) |
| } |
| `, |
| `package main |
| import "test" |
| import "fmt" |
| func g() { fmt.Println(test.Foo(3)) } |
| `, |
| }, |
| }, |
| wantErr: false, |
| wantOut: "Renamed 2 occurrences in 2 files in 2 packages.", |
| }, |
| } { |
| // Write the test files |
| testCleanup := setUpPackages(t, srcDir, renameTest.packages) |
| |
| // Set up arguments |
| var args []string |
| |
| var arg, val string |
| if renameTest.offset != "" { |
| arg, val = "-offset", renameTest.offset |
| } else { |
| arg, val = "-from", renameTest.from |
| } |
| |
| prefix := fmt.Sprintf("%d: %s %q -to %q", iter, arg, val, renameTest.to) |
| |
| if renameTest.fileSpecified { |
| // add the src dir to the value of the argument |
| val = filepath.Join(srcDir, val) |
| } |
| |
| args = append(args, arg, val, "-to", renameTest.to) |
| |
| // Run command |
| cmd := exec.Command(bin, args...) |
| cmd.Args[0] = "gorename" |
| cmd.Env = env |
| |
| // Check the output |
| out, err := cmd.CombinedOutput() |
| // errors should result in no changes to files |
| if err != nil { |
| if !renameTest.wantErr { |
| t.Errorf("%s: received unexpected error %s", prefix, err) |
| } |
| // Compare output |
| if ok := strings.Contains(string(out), renameTest.wantOut); !ok { |
| t.Errorf("%s: unexpected command output: %s (want: %s)", prefix, out, renameTest.wantOut) |
| } |
| // Check that no files were modified |
| if modified := modifiedFiles(t, srcDir, renameTest.packages); len(modified) != 0 { |
| t.Errorf("%s: files unexpectedly modified: %s", prefix, modified) |
| } |
| |
| } else { |
| if !renameTest.wantErr { |
| if ok := strings.Contains(string(out), renameTest.wantOut); !ok { |
| t.Errorf("%s: unexpected command output: %s (want: %s)", prefix, out, renameTest.wantOut) |
| } |
| } else { |
| t.Errorf("%s: command succeeded unexpectedly, output: %s", prefix, out) |
| } |
| } |
| testCleanup() |
| } |
| } |
| |
| // buildGorename builds the gorename executable. |
| // It returns its path, and a cleanup function. |
| func buildGorename(t *testing.T) (tmp, bin string, cleanup func()) { |
| if runtime.GOOS == "android" { |
| t.Skipf("the dependencies are not available on android") |
| } |
| |
| tmp, err := ioutil.TempDir("", "gorename-regtest-") |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| defer func() { |
| if cleanup == nil { // probably, go build failed. |
| os.RemoveAll(tmp) |
| } |
| }() |
| |
| bin = filepath.Join(tmp, "gorename") |
| if runtime.GOOS == "windows" { |
| bin += ".exe" |
| } |
| cmd := exec.Command("go", "build", "-o", bin) |
| if out, err := cmd.CombinedOutput(); err != nil { |
| t.Fatalf("Building gorename: %v\n%s", err, out) |
| } |
| return tmp, bin, func() { os.RemoveAll(tmp) } |
| } |
| |
| // setUpPackages sets up the files in a temporary directory provided by arguments. |
| func setUpPackages(t *testing.T, dir string, packages map[string][]string) (cleanup func()) { |
| var pkgDirs []string |
| |
| for pkgName, files := range packages { |
| // Create a directory for the package. |
| pkgDir := filepath.Join(dir, pkgName) |
| pkgDirs = append(pkgDirs, pkgDir) |
| |
| if err := os.Mkdir(pkgDir, os.ModePerm); err != nil { |
| t.Fatal(err) |
| } |
| // Write the packages files |
| for i, val := range files { |
| file := filepath.Join(pkgDir, strconv.Itoa(i)+".go") |
| if err := ioutil.WriteFile(file, []byte(val), os.ModePerm); err != nil { |
| t.Fatal(err) |
| } |
| } |
| } |
| return func() { |
| for _, dir := range pkgDirs { |
| os.RemoveAll(dir) |
| } |
| } |
| } |
| |
| // modifiedFiles returns a list of files that were renamed (without the prefix dir). |
| func modifiedFiles(t *testing.T, dir string, packages map[string][]string) (results []string) { |
| |
| for pkgName, files := range packages { |
| pkgDir := filepath.Join(dir, pkgName) |
| |
| for i, val := range files { |
| file := filepath.Join(pkgDir, strconv.Itoa(i)+".go") |
| // read file contents and compare to val |
| if contents, err := ioutil.ReadFile(file); err != nil { |
| t.Fatalf("File missing: %s", err) |
| } else if string(contents) != val { |
| results = append(results, strings.TrimPrefix(dir, file)) |
| } |
| } |
| } |
| return results |
| } |