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  }