github.com/jd-ly/tools@v0.5.7/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 // licence 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/jd-ly/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 }