github.com/powerman/golang-tools@v0.1.11-0.20220410185822-5ad214d8d803/go/internal/gcimporter/gcimporter_test.go (about) 1 // Copyright 2011 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 // This file is a copy of $GOROOT/src/go/internal/gcimporter/gcimporter_test.go, 6 // adjusted to make it build with code from (std lib) internal/testenv copied. 7 8 package gcimporter 9 10 import ( 11 "bytes" 12 "fmt" 13 "go/build" 14 "go/constant" 15 "go/types" 16 "io/ioutil" 17 "os" 18 "os/exec" 19 "path/filepath" 20 "runtime" 21 "strings" 22 "testing" 23 "time" 24 25 "github.com/powerman/golang-tools/internal/testenv" 26 ) 27 28 func TestMain(m *testing.M) { 29 testenv.ExitIfSmallMachine() 30 os.Exit(m.Run()) 31 } 32 33 // ---------------------------------------------------------------------------- 34 35 func needsCompiler(t *testing.T, compiler string) { 36 if runtime.Compiler == compiler { 37 return 38 } 39 switch compiler { 40 case "gc": 41 t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler) 42 } 43 } 44 45 // compile runs the compiler on filename, with dirname as the working directory, 46 // and writes the output file to outdirname. 47 func compile(t *testing.T, dirname, filename, outdirname string) string { 48 testenv.NeedsGoBuild(t) 49 50 // filename must end with ".go" 51 if !strings.HasSuffix(filename, ".go") { 52 t.Fatalf("filename doesn't end in .go: %s", filename) 53 } 54 basename := filepath.Base(filename) 55 outname := filepath.Join(outdirname, basename[:len(basename)-2]+"o") 56 cmd := exec.Command("go", "tool", "compile", "-p=p", "-o", outname, filename) 57 cmd.Dir = dirname 58 out, err := cmd.CombinedOutput() 59 if err != nil { 60 t.Logf("%s", out) 61 t.Fatalf("go tool compile %s failed: %s", filename, err) 62 } 63 return outname 64 } 65 66 func testPath(t *testing.T, path, srcDir string) *types.Package { 67 t0 := time.Now() 68 pkg, err := Import(make(map[string]*types.Package), path, srcDir, nil) 69 if err != nil { 70 t.Errorf("testPath(%s): %s", path, err) 71 return nil 72 } 73 t.Logf("testPath(%s): %v", path, time.Since(t0)) 74 return pkg 75 } 76 77 const maxTime = 30 * time.Second 78 79 func testDir(t *testing.T, dir string, endTime time.Time) (nimports int) { 80 dirname := filepath.Join(runtime.GOROOT(), "pkg", runtime.GOOS+"_"+runtime.GOARCH, dir) 81 list, err := ioutil.ReadDir(dirname) 82 if err != nil { 83 t.Fatalf("testDir(%s): %s", dirname, err) 84 } 85 for _, f := range list { 86 if time.Now().After(endTime) { 87 t.Log("testing time used up") 88 return 89 } 90 switch { 91 case !f.IsDir(): 92 // try extensions 93 for _, ext := range pkgExts { 94 if strings.HasSuffix(f.Name(), ext) { 95 name := f.Name()[0 : len(f.Name())-len(ext)] // remove extension 96 if testPath(t, filepath.Join(dir, name), dir) != nil { 97 nimports++ 98 } 99 } 100 } 101 case f.IsDir(): 102 nimports += testDir(t, filepath.Join(dir, f.Name()), endTime) 103 } 104 } 105 return 106 } 107 108 func mktmpdir(t *testing.T) string { 109 tmpdir, err := ioutil.TempDir("", "gcimporter_test") 110 if err != nil { 111 t.Fatal("mktmpdir:", err) 112 } 113 if err := os.Mkdir(filepath.Join(tmpdir, "testdata"), 0700); err != nil { 114 os.RemoveAll(tmpdir) 115 t.Fatal("mktmpdir:", err) 116 } 117 return tmpdir 118 } 119 120 const testfile = "exports.go" 121 122 func TestImportTestdata(t *testing.T) { 123 needsCompiler(t, "gc") 124 125 tmpdir := mktmpdir(t) 126 defer os.RemoveAll(tmpdir) 127 128 compile(t, "testdata", testfile, filepath.Join(tmpdir, "testdata")) 129 130 // filename should end with ".go" 131 filename := testfile[:len(testfile)-3] 132 if pkg := testPath(t, "./testdata/"+filename, tmpdir); pkg != nil { 133 // The package's Imports list must include all packages 134 // explicitly imported by testfile, plus all packages 135 // referenced indirectly via exported objects in testfile. 136 // With the textual export format (when run against Go1.6), 137 // the list may also include additional packages that are 138 // not strictly required for import processing alone (they 139 // are exported to err "on the safe side"). 140 // For now, we just test the presence of a few packages 141 // that we know are there for sure. 142 got := fmt.Sprint(pkg.Imports()) 143 for _, want := range []string{"go/ast", "go/token"} { 144 if !strings.Contains(got, want) { 145 t.Errorf(`Package("exports").Imports() = %s, does not contain %s`, got, want) 146 } 147 } 148 } 149 } 150 151 func TestVersionHandling(t *testing.T) { 152 if debug { 153 t.Skip("TestVersionHandling panics in debug mode") 154 } 155 156 // This package only handles gc export data. 157 needsCompiler(t, "gc") 158 159 const dir = "./testdata/versions" 160 list, err := ioutil.ReadDir(dir) 161 if err != nil { 162 t.Fatal(err) 163 } 164 165 tmpdir := mktmpdir(t) 166 defer os.RemoveAll(tmpdir) 167 corruptdir := filepath.Join(tmpdir, "testdata", "versions") 168 if err := os.Mkdir(corruptdir, 0700); err != nil { 169 t.Fatal(err) 170 } 171 172 for _, f := range list { 173 name := f.Name() 174 if !strings.HasSuffix(name, ".a") { 175 continue // not a package file 176 } 177 if strings.Contains(name, "corrupted") { 178 continue // don't process a leftover corrupted file 179 } 180 pkgpath := "./" + name[:len(name)-2] 181 182 if testing.Verbose() { 183 t.Logf("importing %s", name) 184 } 185 186 // test that export data can be imported 187 _, err := Import(make(map[string]*types.Package), pkgpath, dir, nil) 188 if err != nil { 189 // ok to fail if it fails with a newer version error for select files 190 if strings.Contains(err.Error(), "newer version") { 191 switch name { 192 case "test_go1.11_999b.a", "test_go1.11_999i.a": 193 continue 194 } 195 // fall through 196 } 197 t.Errorf("import %q failed: %v", pkgpath, err) 198 continue 199 } 200 201 // create file with corrupted export data 202 // 1) read file 203 data, err := ioutil.ReadFile(filepath.Join(dir, name)) 204 if err != nil { 205 t.Fatal(err) 206 } 207 // 2) find export data 208 i := bytes.Index(data, []byte("\n$$B\n")) + 5 209 j := bytes.Index(data[i:], []byte("\n$$\n")) + i 210 if i < 0 || j < 0 || i > j { 211 t.Fatalf("export data section not found (i = %d, j = %d)", i, j) 212 } 213 // 3) corrupt the data (increment every 7th byte) 214 for k := j - 13; k >= i; k -= 7 { 215 data[k]++ 216 } 217 // 4) write the file 218 pkgpath += "_corrupted" 219 filename := filepath.Join(corruptdir, pkgpath) + ".a" 220 ioutil.WriteFile(filename, data, 0666) 221 222 // test that importing the corrupted file results in an error 223 _, err = Import(make(map[string]*types.Package), pkgpath, corruptdir, nil) 224 if err == nil { 225 t.Errorf("import corrupted %q succeeded", pkgpath) 226 } else if msg := err.Error(); !strings.Contains(msg, "version skew") { 227 t.Errorf("import %q error incorrect (%s)", pkgpath, msg) 228 } 229 } 230 } 231 232 func TestImportStdLib(t *testing.T) { 233 // This package only handles gc export data. 234 needsCompiler(t, "gc") 235 236 dt := maxTime 237 if testing.Short() && os.Getenv("GO_BUILDER_NAME") == "" { 238 dt = 10 * time.Millisecond 239 } 240 nimports := testDir(t, "", time.Now().Add(dt)) // installed packages 241 t.Logf("tested %d imports", nimports) 242 } 243 244 var importedObjectTests = []struct { 245 name string 246 want string 247 }{ 248 // non-interfaces 249 {"crypto.Hash", "type Hash uint"}, 250 {"go/ast.ObjKind", "type ObjKind int"}, 251 {"go/types.Qualifier", "type Qualifier func(*Package) string"}, 252 {"go/types.Comparable", "func Comparable(T Type) bool"}, 253 {"math.Pi", "const Pi untyped float"}, 254 {"math.Sin", "func Sin(x float64) float64"}, 255 {"go/ast.NotNilFilter", "func NotNilFilter(_ string, v reflect.Value) bool"}, 256 {"go/internal/gcimporter.FindPkg", "func FindPkg(path string, srcDir string) (filename string, id string)"}, 257 258 // interfaces 259 {"context.Context", "type Context interface{Deadline() (deadline time.Time, ok bool); Done() <-chan struct{}; Err() error; Value(key any) any}"}, 260 {"crypto.Decrypter", "type Decrypter interface{Decrypt(rand io.Reader, msg []byte, opts DecrypterOpts) (plaintext []byte, err error); Public() PublicKey}"}, 261 {"encoding.BinaryMarshaler", "type BinaryMarshaler interface{MarshalBinary() (data []byte, err error)}"}, 262 {"io.Reader", "type Reader interface{Read(p []byte) (n int, err error)}"}, 263 {"io.ReadWriter", "type ReadWriter interface{Reader; Writer}"}, 264 {"go/ast.Node", "type Node interface{End() go/token.Pos; Pos() go/token.Pos}"}, 265 {"go/types.Type", "type Type interface{String() string; Underlying() Type}"}, 266 } 267 268 // TODO(rsc): Delete this init func after x/tools no longer needs to test successfully with Go 1.17. 269 func init() { 270 if build.Default.ReleaseTags[len(build.Default.ReleaseTags)-1] <= "go1.17" { 271 for i := range importedObjectTests { 272 if importedObjectTests[i].name == "context.Context" { 273 // Expand any to interface{}. 274 importedObjectTests[i].want = "type Context interface{Deadline() (deadline time.Time, ok bool); Done() <-chan struct{}; Err() error; Value(key interface{}) interface{}}" 275 } 276 } 277 } 278 } 279 280 func TestImportedTypes(t *testing.T) { 281 testenv.NeedsGo1Point(t, 11) 282 // This package only handles gc export data. 283 needsCompiler(t, "gc") 284 285 for _, test := range importedObjectTests { 286 obj := importObject(t, test.name) 287 if obj == nil { 288 continue // error reported elsewhere 289 } 290 got := types.ObjectString(obj, types.RelativeTo(obj.Pkg())) 291 292 // TODO(rsc): Delete this block once go.dev/cl/368254 lands. 293 if got != test.want && test.want == strings.ReplaceAll(got, "interface{}", "any") { 294 got = test.want 295 } 296 297 if got != test.want { 298 t.Errorf("%s: got %q; want %q", test.name, got, test.want) 299 } 300 301 if named, _ := obj.Type().(*types.Named); named != nil { 302 verifyInterfaceMethodRecvs(t, named, 0) 303 } 304 } 305 } 306 307 func TestImportedConsts(t *testing.T) { 308 testenv.NeedsGo1Point(t, 11) 309 tests := []struct { 310 name string 311 want constant.Kind 312 }{ 313 {"math.Pi", constant.Float}, 314 {"math.MaxFloat64", constant.Float}, 315 {"math.MaxInt64", constant.Int}, 316 } 317 318 for _, test := range tests { 319 obj := importObject(t, test.name) 320 if got := obj.(*types.Const).Val().Kind(); got != test.want { 321 t.Errorf("%s: imported as constant.Kind(%v), want constant.Kind(%v)", test.name, got, test.want) 322 } 323 } 324 } 325 326 // importObject imports the object specified by a name of the form 327 // <import path>.<object name>, e.g. go/types.Type. 328 // 329 // If any errors occur they are reported via t and the resulting object will 330 // be nil. 331 func importObject(t *testing.T, name string) types.Object { 332 s := strings.Split(name, ".") 333 if len(s) != 2 { 334 t.Fatal("inconsistent test data") 335 } 336 importPath := s[0] 337 objName := s[1] 338 339 pkg, err := Import(make(map[string]*types.Package), importPath, ".", nil) 340 if err != nil { 341 t.Error(err) 342 return nil 343 } 344 345 obj := pkg.Scope().Lookup(objName) 346 if obj == nil { 347 t.Errorf("%s: object not found", name) 348 return nil 349 } 350 return obj 351 } 352 353 // verifyInterfaceMethodRecvs verifies that method receiver types 354 // are named if the methods belong to a named interface type. 355 func verifyInterfaceMethodRecvs(t *testing.T, named *types.Named, level int) { 356 // avoid endless recursion in case of an embedding bug that lead to a cycle 357 if level > 10 { 358 t.Errorf("%s: embeds itself", named) 359 return 360 } 361 362 iface, _ := named.Underlying().(*types.Interface) 363 if iface == nil { 364 return // not an interface 365 } 366 367 // check explicitly declared methods 368 for i := 0; i < iface.NumExplicitMethods(); i++ { 369 m := iface.ExplicitMethod(i) 370 recv := m.Type().(*types.Signature).Recv() 371 if recv == nil { 372 t.Errorf("%s: missing receiver type", m) 373 continue 374 } 375 if recv.Type() != named { 376 t.Errorf("%s: got recv type %s; want %s", m, recv.Type(), named) 377 } 378 } 379 380 // check embedded interfaces (if they are named, too) 381 for i := 0; i < iface.NumEmbeddeds(); i++ { 382 // embedding of interfaces cannot have cycles; recursion will terminate 383 if etype, _ := iface.EmbeddedType(i).(*types.Named); etype != nil { 384 verifyInterfaceMethodRecvs(t, etype, level+1) 385 } 386 } 387 } 388 389 func TestIssue5815(t *testing.T) { 390 // This package only handles gc export data. 391 needsCompiler(t, "gc") 392 393 pkg := importPkg(t, "strings", ".") 394 395 scope := pkg.Scope() 396 for _, name := range scope.Names() { 397 obj := scope.Lookup(name) 398 if obj.Pkg() == nil { 399 t.Errorf("no pkg for %s", obj) 400 } 401 if tname, _ := obj.(*types.TypeName); tname != nil { 402 named := tname.Type().(*types.Named) 403 for i := 0; i < named.NumMethods(); i++ { 404 m := named.Method(i) 405 if m.Pkg() == nil { 406 t.Errorf("no pkg for %s", m) 407 } 408 } 409 } 410 } 411 } 412 413 // Smoke test to ensure that imported methods get the correct package. 414 func TestCorrectMethodPackage(t *testing.T) { 415 // This package only handles gc export data. 416 needsCompiler(t, "gc") 417 418 imports := make(map[string]*types.Package) 419 _, err := Import(imports, "net/http", ".", nil) 420 if err != nil { 421 t.Fatal(err) 422 } 423 424 mutex := imports["sync"].Scope().Lookup("Mutex").(*types.TypeName).Type() 425 mset := types.NewMethodSet(types.NewPointer(mutex)) // methods of *sync.Mutex 426 sel := mset.Lookup(nil, "Lock") 427 lock := sel.Obj().(*types.Func) 428 if got, want := lock.Pkg().Path(), "sync"; got != want { 429 t.Errorf("got package path %q; want %q", got, want) 430 } 431 } 432 433 func TestIssue13566(t *testing.T) { 434 // This package only handles gc export data. 435 needsCompiler(t, "gc") 436 437 // On windows, we have to set the -D option for the compiler to avoid having a drive 438 // letter and an illegal ':' in the import path - just skip it (see also issue #3483). 439 if runtime.GOOS == "windows" { 440 t.Skip("avoid dealing with relative paths/drive letters on windows") 441 } 442 443 tmpdir := mktmpdir(t) 444 defer os.RemoveAll(tmpdir) 445 testoutdir := filepath.Join(tmpdir, "testdata") 446 447 // b.go needs to be compiled from the output directory so that the compiler can 448 // find the compiled package a. We pass the full path to compile() so that we 449 // don't have to copy the file to that directory. 450 bpath, err := filepath.Abs(filepath.Join("testdata", "b.go")) 451 if err != nil { 452 t.Fatal(err) 453 } 454 compile(t, "testdata", "a.go", testoutdir) 455 compile(t, testoutdir, bpath, testoutdir) 456 457 // import must succeed (test for issue at hand) 458 pkg := importPkg(t, "./testdata/b", tmpdir) 459 460 // make sure all indirectly imported packages have names 461 for _, imp := range pkg.Imports() { 462 if imp.Name() == "" { 463 t.Errorf("no name for %s package", imp.Path()) 464 } 465 } 466 } 467 468 func TestIssue13898(t *testing.T) { 469 // This package only handles gc export data. 470 needsCompiler(t, "gc") 471 472 // import go/internal/gcimporter which imports go/types partially 473 imports := make(map[string]*types.Package) 474 _, err := Import(imports, "go/internal/gcimporter", ".", nil) 475 if err != nil { 476 t.Fatal(err) 477 } 478 479 // look for go/types package 480 var goTypesPkg *types.Package 481 for path, pkg := range imports { 482 if path == "go/types" { 483 goTypesPkg = pkg 484 break 485 } 486 } 487 if goTypesPkg == nil { 488 t.Fatal("go/types not found") 489 } 490 491 // look for go/types.Object type 492 obj := lookupObj(t, goTypesPkg.Scope(), "Object") 493 typ, ok := obj.Type().(*types.Named) 494 if !ok { 495 t.Fatalf("go/types.Object type is %v; wanted named type", typ) 496 } 497 498 // lookup go/types.Object.Pkg method 499 m, index, indirect := types.LookupFieldOrMethod(typ, false, nil, "Pkg") 500 if m == nil { 501 t.Fatalf("go/types.Object.Pkg not found (index = %v, indirect = %v)", index, indirect) 502 } 503 504 // the method must belong to go/types 505 if m.Pkg().Path() != "go/types" { 506 t.Fatalf("found %v; want go/types", m.Pkg()) 507 } 508 } 509 510 func TestIssue15517(t *testing.T) { 511 // This package only handles gc export data. 512 needsCompiler(t, "gc") 513 514 // On windows, we have to set the -D option for the compiler to avoid having a drive 515 // letter and an illegal ':' in the import path - just skip it (see also issue #3483). 516 if runtime.GOOS == "windows" { 517 t.Skip("avoid dealing with relative paths/drive letters on windows") 518 } 519 520 tmpdir := mktmpdir(t) 521 defer os.RemoveAll(tmpdir) 522 523 compile(t, "testdata", "p.go", filepath.Join(tmpdir, "testdata")) 524 525 // Multiple imports of p must succeed without redeclaration errors. 526 // We use an import path that's not cleaned up so that the eventual 527 // file path for the package is different from the package path; this 528 // will expose the error if it is present. 529 // 530 // (Issue: Both the textual and the binary importer used the file path 531 // of the package to be imported as key into the shared packages map. 532 // However, the binary importer then used the package path to identify 533 // the imported package to mark it as complete; effectively marking the 534 // wrong package as complete. By using an "unclean" package path, the 535 // file and package path are different, exposing the problem if present. 536 // The same issue occurs with vendoring.) 537 imports := make(map[string]*types.Package) 538 for i := 0; i < 3; i++ { 539 if _, err := Import(imports, "./././testdata/p", tmpdir, nil); err != nil { 540 t.Fatal(err) 541 } 542 } 543 } 544 545 func TestIssue15920(t *testing.T) { 546 // This package only handles gc export data. 547 needsCompiler(t, "gc") 548 549 // On windows, we have to set the -D option for the compiler to avoid having a drive 550 // letter and an illegal ':' in the import path - just skip it (see also issue #3483). 551 if runtime.GOOS == "windows" { 552 t.Skip("avoid dealing with relative paths/drive letters on windows") 553 } 554 555 compileAndImportPkg(t, "issue15920") 556 } 557 558 func TestIssue20046(t *testing.T) { 559 // This package only handles gc export data. 560 needsCompiler(t, "gc") 561 562 // On windows, we have to set the -D option for the compiler to avoid having a drive 563 // letter and an illegal ':' in the import path - just skip it (see also issue #3483). 564 if runtime.GOOS == "windows" { 565 t.Skip("avoid dealing with relative paths/drive letters on windows") 566 } 567 568 // "./issue20046".V.M must exist 569 pkg := compileAndImportPkg(t, "issue20046") 570 obj := lookupObj(t, pkg.Scope(), "V") 571 if m, index, indirect := types.LookupFieldOrMethod(obj.Type(), false, nil, "M"); m == nil { 572 t.Fatalf("V.M not found (index = %v, indirect = %v)", index, indirect) 573 } 574 } 575 576 func TestIssue25301(t *testing.T) { 577 testenv.NeedsGo1Point(t, 11) 578 // This package only handles gc export data. 579 needsCompiler(t, "gc") 580 581 // On windows, we have to set the -D option for the compiler to avoid having a drive 582 // letter and an illegal ':' in the import path - just skip it (see also issue #3483). 583 if runtime.GOOS == "windows" { 584 t.Skip("avoid dealing with relative paths/drive letters on windows") 585 } 586 587 compileAndImportPkg(t, "issue25301") 588 } 589 590 func importPkg(t *testing.T, path, srcDir string) *types.Package { 591 pkg, err := Import(make(map[string]*types.Package), path, srcDir, nil) 592 if err != nil { 593 t.Fatal(err) 594 } 595 return pkg 596 } 597 598 func compileAndImportPkg(t *testing.T, name string) *types.Package { 599 tmpdir := mktmpdir(t) 600 defer os.RemoveAll(tmpdir) 601 compile(t, "testdata", name+".go", filepath.Join(tmpdir, "testdata")) 602 return importPkg(t, "./testdata/"+name, tmpdir) 603 } 604 605 func lookupObj(t *testing.T, scope *types.Scope, name string) types.Object { 606 if obj := scope.Lookup(name); obj != nil { 607 return obj 608 } 609 t.Fatalf("%s not found", name) 610 return nil 611 }