...

Source file src/golang.org/x/tools/cmd/gorename/gorename_test.go

Documentation: golang.org/x/tools/cmd/gorename

     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  }
   384  

View as plain text