|  | // 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 err := cmd.Run(); err != nil { | 
|  | t.Fatalf("Building gorename: %v", err) | 
|  | } | 
|  | 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 | 
|  | } |