github.com/powerman/golang-tools@v0.1.11-0.20220410185822-5ad214d8d803/refactor/rename/mvpkg_test.go (about)

     1  // Copyright 2015 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 rename
     6  
     7  import (
     8  	"fmt"
     9  	"go/build"
    10  	"go/token"
    11  	"io/ioutil"
    12  	"path/filepath"
    13  	"reflect"
    14  	"regexp"
    15  	"strings"
    16  	"testing"
    17  
    18  	"github.com/powerman/golang-tools/go/buildutil"
    19  )
    20  
    21  func TestErrors(t *testing.T) {
    22  	tests := []struct {
    23  		ctxt     *build.Context
    24  		from, to string
    25  		want     string // regexp to match error, or "OK"
    26  	}{
    27  		// Simple example.
    28  		{
    29  			ctxt: fakeContext(map[string][]string{
    30  				"foo": {`package foo; type T int`},
    31  				"bar": {`package bar`},
    32  				"main": {`package main
    33  
    34  import "foo"
    35  
    36  var _ foo.T
    37  `},
    38  			}),
    39  			from: "foo", to: "bar",
    40  			want: `invalid move destination: bar conflicts with directory .go.src.bar`,
    41  		},
    42  		// Subpackage already exists.
    43  		{
    44  			ctxt: fakeContext(map[string][]string{
    45  				"foo":     {`package foo; type T int`},
    46  				"foo/sub": {`package sub`},
    47  				"bar/sub": {`package sub`},
    48  				"main": {`package main
    49  
    50  import "foo"
    51  
    52  var _ foo.T
    53  `},
    54  			}),
    55  			from: "foo", to: "bar",
    56  			want: "invalid move destination: bar; package or subpackage bar/sub already exists",
    57  		},
    58  		// Invalid base name.
    59  		{
    60  			ctxt: fakeContext(map[string][]string{
    61  				"foo": {`package foo; type T int`},
    62  				"main": {`package main
    63  
    64  import "foo"
    65  
    66  var _ foo.T
    67  `},
    68  			}),
    69  			from: "foo", to: "bar-v2.0",
    70  			want: "invalid move destination: bar-v2.0; gomvpkg does not " +
    71  				"support move destinations whose base names are not valid " +
    72  				"go identifiers",
    73  		},
    74  		{
    75  			ctxt: fakeContext(map[string][]string{
    76  				"foo": {``},
    77  				"bar": {`package bar`},
    78  			}),
    79  			from: "foo", to: "bar",
    80  			want: `no initial packages were loaded`,
    81  		},
    82  	}
    83  
    84  	for _, test := range tests {
    85  		ctxt := test.ctxt
    86  
    87  		got := make(map[string]string)
    88  		writeFile = func(filename string, content []byte) error {
    89  			got[filename] = string(content)
    90  			return nil
    91  		}
    92  		moveDirectory = func(from, to string) error {
    93  			for path, contents := range got {
    94  				if strings.HasPrefix(path, from) {
    95  					newPath := strings.Replace(path, from, to, 1)
    96  					delete(got, path)
    97  					got[newPath] = contents
    98  				}
    99  			}
   100  			return nil
   101  		}
   102  
   103  		err := Move(ctxt, test.from, test.to, "")
   104  		prefix := fmt.Sprintf("-from %q -to %q", test.from, test.to)
   105  		if err == nil {
   106  			t.Errorf("%s: nil error. Expected error: %s", prefix, test.want)
   107  			continue
   108  		}
   109  		matched, err2 := regexp.MatchString(test.want, err.Error())
   110  		if err2 != nil {
   111  			t.Errorf("regexp.MatchString failed %s", err2)
   112  			continue
   113  		}
   114  		if !matched {
   115  			t.Errorf("%s: conflict does not match expectation:\n"+
   116  				"Error: %q\n"+
   117  				"Pattern: %q",
   118  				prefix, err.Error(), test.want)
   119  		}
   120  	}
   121  }
   122  
   123  func TestMoves(t *testing.T) {
   124  	tests := []struct {
   125  		ctxt         *build.Context
   126  		from, to     string
   127  		want         map[string]string
   128  		wantWarnings []string
   129  	}{
   130  		// Simple example.
   131  		{
   132  			ctxt: fakeContext(map[string][]string{
   133  				"foo": {`package foo; type T int`},
   134  				"main": {`package main
   135  
   136  import "foo"
   137  
   138  var _ foo.T
   139  `},
   140  			}),
   141  			from: "foo", to: "bar",
   142  			want: map[string]string{
   143  				"/go/src/main/0.go": `package main
   144  
   145  import "bar"
   146  
   147  var _ bar.T
   148  `,
   149  				"/go/src/bar/0.go": `package bar
   150  
   151  type T int
   152  `,
   153  			},
   154  		},
   155  
   156  		// Example with subpackage.
   157  		{
   158  			ctxt: fakeContext(map[string][]string{
   159  				"foo":     {`package foo; type T int`},
   160  				"foo/sub": {`package sub; type T int`},
   161  				"main": {`package main
   162  
   163  import "foo"
   164  import "foo/sub"
   165  
   166  var _ foo.T
   167  var _ sub.T
   168  `},
   169  			}),
   170  			from: "foo", to: "bar",
   171  			want: map[string]string{
   172  				"/go/src/main/0.go": `package main
   173  
   174  import "bar"
   175  import "bar/sub"
   176  
   177  var _ bar.T
   178  var _ sub.T
   179  `,
   180  				"/go/src/bar/0.go": `package bar
   181  
   182  type T int
   183  `,
   184  				"/go/src/bar/sub/0.go": `package sub; type T int`,
   185  			},
   186  		},
   187  
   188  		// References into subpackages
   189  		{
   190  			ctxt: fakeContext(map[string][]string{
   191  				"foo":   {`package foo; import "foo/a"; var _ a.T`},
   192  				"foo/a": {`package a; type T int`},
   193  				"foo/b": {`package b; import "foo/a"; var _ a.T`},
   194  			}),
   195  			from: "foo", to: "bar",
   196  			want: map[string]string{
   197  				"/go/src/bar/0.go": `package bar
   198  
   199  import "bar/a"
   200  
   201  var _ a.T
   202  `,
   203  				"/go/src/bar/a/0.go": `package a; type T int`,
   204  				"/go/src/bar/b/0.go": `package b
   205  
   206  import "bar/a"
   207  
   208  var _ a.T
   209  `,
   210  			},
   211  		},
   212  
   213  		// References into subpackages where directories have overlapped names
   214  		{
   215  			ctxt: fakeContext(map[string][]string{
   216  				"foo":    {},
   217  				"foo/a":  {`package a`},
   218  				"foo/aa": {`package bar`},
   219  				"foo/c":  {`package c; import _ "foo/bar";`},
   220  			}),
   221  			from: "foo/a", to: "foo/spam",
   222  			want: map[string]string{
   223  				"/go/src/foo/spam/0.go": `package spam
   224  `,
   225  				"/go/src/foo/aa/0.go": `package bar`,
   226  				"/go/src/foo/c/0.go":  `package c; import _ "foo/bar";`,
   227  			},
   228  		},
   229  
   230  		// External test packages
   231  		{
   232  			ctxt: buildutil.FakeContext(map[string]map[string]string{
   233  				"foo": {
   234  					"0.go":      `package foo; type T int`,
   235  					"0_test.go": `package foo_test; import "foo"; var _ foo.T`,
   236  				},
   237  				"baz": {
   238  					"0_test.go": `package baz_test; import "foo"; var _ foo.T`,
   239  				},
   240  			}),
   241  			from: "foo", to: "bar",
   242  			want: map[string]string{
   243  				"/go/src/bar/0.go": `package bar
   244  
   245  type T int
   246  `,
   247  				"/go/src/bar/0_test.go": `package bar_test
   248  
   249  import "bar"
   250  
   251  var _ bar.T
   252  `,
   253  				"/go/src/baz/0_test.go": `package baz_test
   254  
   255  import "bar"
   256  
   257  var _ bar.T
   258  `,
   259  			},
   260  		},
   261  		// package import comments
   262  		{
   263  			ctxt: fakeContext(map[string][]string{"foo": {`package foo // import "baz"`}}),
   264  			from: "foo", to: "bar",
   265  			want: map[string]string{"/go/src/bar/0.go": `package bar // import "bar"
   266  `},
   267  		},
   268  		{
   269  			ctxt: fakeContext(map[string][]string{"foo": {`package foo /* import "baz" */`}}),
   270  			from: "foo", to: "bar",
   271  			want: map[string]string{"/go/src/bar/0.go": `package bar /* import "bar" */
   272  `},
   273  		},
   274  		{
   275  			ctxt: fakeContext(map[string][]string{"foo": {`package foo       // import "baz"`}}),
   276  			from: "foo", to: "bar",
   277  			want: map[string]string{"/go/src/bar/0.go": `package bar // import "bar"
   278  `},
   279  		},
   280  		{
   281  			ctxt: fakeContext(map[string][]string{"foo": {`package foo
   282  // import " this is not an import comment`}}),
   283  			from: "foo", to: "bar",
   284  			want: map[string]string{"/go/src/bar/0.go": `package bar
   285  
   286  // import " this is not an import comment
   287  `},
   288  		},
   289  		{
   290  			ctxt: fakeContext(map[string][]string{"foo": {`package foo
   291  /* import " this is not an import comment */`}}),
   292  			from: "foo", to: "bar",
   293  			want: map[string]string{"/go/src/bar/0.go": `package bar
   294  
   295  /* import " this is not an import comment */
   296  `},
   297  		},
   298  		// Import name conflict generates a warning, not an error.
   299  		{
   300  			ctxt: fakeContext(map[string][]string{
   301  				"x": {},
   302  				"a": {`package a; type A int`},
   303  				"b": {`package b; type B int`},
   304  				"conflict": {`package conflict
   305  
   306  import "a"
   307  import "b"
   308  var _ a.A
   309  var _ b.B
   310  `},
   311  				"ok": {`package ok
   312  import "b"
   313  var _ b.B
   314  `},
   315  			}),
   316  			from: "b", to: "x/a",
   317  			want: map[string]string{
   318  				"/go/src/a/0.go": `package a; type A int`,
   319  				"/go/src/ok/0.go": `package ok
   320  
   321  import "x/a"
   322  
   323  var _ a.B
   324  `,
   325  				"/go/src/conflict/0.go": `package conflict
   326  
   327  import "a"
   328  import "x/a"
   329  
   330  var _ a.A
   331  var _ b.B
   332  `,
   333  				"/go/src/x/a/0.go": `package a
   334  
   335  type B int
   336  `,
   337  			},
   338  			wantWarnings: []string{
   339  				`/go/src/conflict/0.go:4:8: renaming this imported package name "b" to "a"`,
   340  				`/go/src/conflict/0.go:3:8: 	conflicts with imported package name in same block`,
   341  				`/go/src/conflict/0.go:3:8: skipping update of this file`,
   342  			},
   343  		},
   344  		// Rename with same base name.
   345  		{
   346  			ctxt: fakeContext(map[string][]string{
   347  				"x": {},
   348  				"y": {},
   349  				"x/foo": {`package foo
   350  
   351  type T int
   352  `},
   353  				"main": {`package main; import "x/foo"; var _ foo.T`},
   354  			}),
   355  			from: "x/foo", to: "y/foo",
   356  			want: map[string]string{
   357  				"/go/src/y/foo/0.go": `package foo
   358  
   359  type T int
   360  `,
   361  				"/go/src/main/0.go": `package main
   362  
   363  import "y/foo"
   364  
   365  var _ foo.T
   366  `,
   367  			},
   368  		},
   369  	}
   370  
   371  	for _, test := range tests {
   372  		ctxt := test.ctxt
   373  
   374  		got := make(map[string]string)
   375  		// Populate got with starting file set. rewriteFile and moveDirectory
   376  		// will mutate got to produce resulting file set.
   377  		buildutil.ForEachPackage(ctxt, func(importPath string, err error) {
   378  			if err != nil {
   379  				return
   380  			}
   381  			path := filepath.Join("/go/src", importPath, "0.go")
   382  			if !buildutil.FileExists(ctxt, path) {
   383  				return
   384  			}
   385  			f, err := ctxt.OpenFile(path)
   386  			if err != nil {
   387  				t.Errorf("unexpected error opening file: %s", err)
   388  				return
   389  			}
   390  			bytes, err := ioutil.ReadAll(f)
   391  			f.Close()
   392  			if err != nil {
   393  				t.Errorf("unexpected error reading file: %s", err)
   394  				return
   395  			}
   396  			got[path] = string(bytes)
   397  		})
   398  		var warnings []string
   399  		reportError = func(posn token.Position, message string) {
   400  			warning := fmt.Sprintf("%s:%d:%d: %s",
   401  				filepath.ToSlash(posn.Filename), // for MS Windows
   402  				posn.Line,
   403  				posn.Column,
   404  				message)
   405  			warnings = append(warnings, warning)
   406  
   407  		}
   408  		writeFile = func(filename string, content []byte) error {
   409  			got[filename] = string(content)
   410  			return nil
   411  		}
   412  		moveDirectory = func(from, to string) error {
   413  			for path, contents := range got {
   414  				if !(strings.HasPrefix(path, from) &&
   415  					(len(path) == len(from) || path[len(from)] == filepath.Separator)) {
   416  					continue
   417  				}
   418  				newPath := strings.Replace(path, from, to, 1)
   419  				delete(got, path)
   420  				got[newPath] = contents
   421  			}
   422  			return nil
   423  		}
   424  
   425  		err := Move(ctxt, test.from, test.to, "")
   426  		prefix := fmt.Sprintf("-from %q -to %q", test.from, test.to)
   427  		if err != nil {
   428  			t.Errorf("%s: unexpected error: %s", prefix, err)
   429  			continue
   430  		}
   431  
   432  		if !reflect.DeepEqual(warnings, test.wantWarnings) {
   433  			t.Errorf("%s: unexpected warnings:\n%s\nwant:\n%s",
   434  				prefix,
   435  				strings.Join(warnings, "\n"),
   436  				strings.Join(test.wantWarnings, "\n"))
   437  		}
   438  
   439  		for file, wantContent := range test.want {
   440  			k := filepath.FromSlash(file)
   441  			gotContent, ok := got[k]
   442  			delete(got, k)
   443  			if !ok {
   444  				// TODO(matloob): some testcases might have files that won't be
   445  				// rewritten
   446  				t.Errorf("%s: file %s not rewritten", prefix, file)
   447  				continue
   448  			}
   449  			if gotContent != wantContent {
   450  				t.Errorf("%s: rewritten file %s does not match expectation; got <<<%s>>>\n"+
   451  					"want <<<%s>>>", prefix, file, gotContent, wantContent)
   452  			}
   453  		}
   454  		// got should now be empty
   455  		for file := range got {
   456  			t.Errorf("%s: unexpected rewrite of file %s", prefix, file)
   457  		}
   458  	}
   459  }