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 8package gcimporter 9 10import ( 11 "bytes" 12 "fmt" 13 "go/types" 14 "io/ioutil" 15 "os" 16 "os/exec" 17 "path/filepath" 18 "runtime" 19 "strings" 20 "testing" 21 "time" 22 23 "golang.org/x/tools/internal/testenv" 24) 25 26func TestMain(m *testing.M) { 27 testenv.ExitIfSmallMachine() 28 os.Exit(m.Run()) 29} 30 31// ---------------------------------------------------------------------------- 32// The following three functions (Builder, HasGoBuild, MustHaveGoBuild) were 33// copied from $GOROOT/src/internal/testenv since that package is not available 34// in x/tools. 35 36// Builder reports the name of the builder running this test 37// (for example, "linux-amd64" or "windows-386-gce"). 38// If the test is not running on the build infrastructure, 39// Builder returns the empty string. 40func Builder() string { 41 return os.Getenv("GO_BUILDER_NAME") 42} 43 44// HasGoBuild reports whether the current system can build programs with ``go build'' 45// and then run them with os.StartProcess or exec.Command. 46func HasGoBuild() bool { 47 switch runtime.GOOS { 48 case "android", "nacl": 49 return false 50 case "darwin": 51 if strings.HasPrefix(runtime.GOARCH, "arm") { 52 return false 53 } 54 } 55 return true 56} 57 58// MustHaveGoBuild checks that the current system can build programs with ``go build'' 59// and then run them with os.StartProcess or exec.Command. 60// If not, MustHaveGoBuild calls t.Skip with an explanation. 61func MustHaveGoBuild(t *testing.T) { 62 testenv.NeedsTool(t, "go") 63 if !HasGoBuild() { 64 t.Skipf("skipping test: 'go build' not available on %s/%s", runtime.GOOS, runtime.GOARCH) 65 } 66} 67 68// ---------------------------------------------------------------------------- 69 70// skipSpecialPlatforms causes the test to be skipped for platforms where 71// builders (build.golang.org) don't have access to compiled packages for 72// import. 73func skipSpecialPlatforms(t *testing.T) { 74 switch platform := runtime.GOOS + "-" + runtime.GOARCH; platform { 75 case "nacl-amd64p32", 76 "nacl-386", 77 "nacl-arm", 78 "darwin-arm", 79 "darwin-arm64": 80 t.Skipf("no compiled packages available for import on %s", platform) 81 } 82} 83 84// compile runs the compiler on filename, with dirname as the working directory, 85// and writes the output file to outdirname. 86func compile(t *testing.T, dirname, filename, outdirname string) string { 87 /* testenv. */ MustHaveGoBuild(t) 88 // filename must end with ".go" 89 if !strings.HasSuffix(filename, ".go") { 90 t.Fatalf("filename doesn't end in .go: %s", filename) 91 } 92 basename := filepath.Base(filename) 93 outname := filepath.Join(outdirname, basename[:len(basename)-2]+"o") 94 cmd := exec.Command("go", "tool", "compile", "-o", outname, filename) 95 cmd.Dir = dirname 96 out, err := cmd.CombinedOutput() 97 if err != nil { 98 t.Logf("%s", out) 99 t.Fatalf("go tool compile %s failed: %s", filename, err) 100 } 101 return outname 102} 103 104func testPath(t *testing.T, path, srcDir string) *types.Package { 105 t0 := time.Now() 106 pkg, err := Import(make(map[string]*types.Package), path, srcDir, nil) 107 if err != nil { 108 t.Errorf("testPath(%s): %s", path, err) 109 return nil 110 } 111 t.Logf("testPath(%s): %v", path, time.Since(t0)) 112 return pkg 113} 114 115const maxTime = 30 * time.Second 116 117func testDir(t *testing.T, dir string, endTime time.Time) (nimports int) { 118 dirname := filepath.Join(runtime.GOROOT(), "pkg", runtime.GOOS+"_"+runtime.GOARCH, dir) 119 list, err := ioutil.ReadDir(dirname) 120 if err != nil { 121 t.Fatalf("testDir(%s): %s", dirname, err) 122 } 123 for _, f := range list { 124 if time.Now().After(endTime) { 125 t.Log("testing time used up") 126 return 127 } 128 switch { 129 case !f.IsDir(): 130 // try extensions 131 for _, ext := range pkgExts { 132 if strings.HasSuffix(f.Name(), ext) { 133 name := f.Name()[0 : len(f.Name())-len(ext)] // remove extension 134 if testPath(t, filepath.Join(dir, name), dir) != nil { 135 nimports++ 136 } 137 } 138 } 139 case f.IsDir(): 140 nimports += testDir(t, filepath.Join(dir, f.Name()), endTime) 141 } 142 } 143 return 144} 145 146func mktmpdir(t *testing.T) string { 147 tmpdir, err := ioutil.TempDir("", "gcimporter_test") 148 if err != nil { 149 t.Fatal("mktmpdir:", err) 150 } 151 if err := os.Mkdir(filepath.Join(tmpdir, "testdata"), 0700); err != nil { 152 os.RemoveAll(tmpdir) 153 t.Fatal("mktmpdir:", err) 154 } 155 return tmpdir 156} 157 158const testfile = "exports.go" 159 160func TestImportTestdata(t *testing.T) { 161 // This package only handles gc export data. 162 if runtime.Compiler != "gc" { 163 t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler) 164 } 165 166 tmpdir := mktmpdir(t) 167 defer os.RemoveAll(tmpdir) 168 169 compile(t, "testdata", testfile, filepath.Join(tmpdir, "testdata")) 170 171 // filename should end with ".go" 172 filename := testfile[:len(testfile)-3] 173 if pkg := testPath(t, "./testdata/"+filename, tmpdir); pkg != nil { 174 // The package's Imports list must include all packages 175 // explicitly imported by testfile, plus all packages 176 // referenced indirectly via exported objects in testfile. 177 // With the textual export format (when run against Go1.6), 178 // the list may also include additional packages that are 179 // not strictly required for import processing alone (they 180 // are exported to err "on the safe side"). 181 // For now, we just test the presence of a few packages 182 // that we know are there for sure. 183 got := fmt.Sprint(pkg.Imports()) 184 for _, want := range []string{"go/ast", "go/token"} { 185 if !strings.Contains(got, want) { 186 t.Errorf(`Package("exports").Imports() = %s, does not contain %s`, got, want) 187 } 188 } 189 } 190} 191 192func TestVersionHandling(t *testing.T) { 193 skipSpecialPlatforms(t) // we really only need to exclude nacl platforms, but this is fine 194 195 // This package only handles gc export data. 196 if runtime.Compiler != "gc" { 197 t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler) 198 } 199 200 const dir = "./testdata/versions" 201 list, err := ioutil.ReadDir(dir) 202 if err != nil { 203 t.Fatal(err) 204 } 205 206 tmpdir := mktmpdir(t) 207 defer os.RemoveAll(tmpdir) 208 corruptdir := filepath.Join(tmpdir, "testdata", "versions") 209 if err := os.Mkdir(corruptdir, 0700); err != nil { 210 t.Fatal(err) 211 } 212 213 for _, f := range list { 214 name := f.Name() 215 if !strings.HasSuffix(name, ".a") { 216 continue // not a package file 217 } 218 if strings.Contains(name, "corrupted") { 219 continue // don't process a leftover corrupted file 220 } 221 pkgpath := "./" + name[:len(name)-2] 222 223 if testing.Verbose() { 224 t.Logf("importing %s", name) 225 } 226 227 // test that export data can be imported 228 _, err := Import(make(map[string]*types.Package), pkgpath, dir, nil) 229 if err != nil { 230 // ok to fail if it fails with a newer version error for select files 231 if strings.Contains(err.Error(), "newer version") { 232 switch name { 233 case "test_go1.11_999b.a", "test_go1.11_999i.a": 234 continue 235 } 236 // fall through 237 } 238 t.Errorf("import %q failed: %v", pkgpath, err) 239 continue 240 } 241 242 // create file with corrupted export data 243 // 1) read file 244 data, err := ioutil.ReadFile(filepath.Join(dir, name)) 245 if err != nil { 246 t.Fatal(err) 247 } 248 // 2) find export data 249 i := bytes.Index(data, []byte("\n$$B\n")) + 5 250 j := bytes.Index(data[i:], []byte("\n$$\n")) + i 251 if i < 0 || j < 0 || i > j { 252 t.Fatalf("export data section not found (i = %d, j = %d)", i, j) 253 } 254 // 3) corrupt the data (increment every 7th byte) 255 for k := j - 13; k >= i; k -= 7 { 256 data[k]++ 257 } 258 // 4) write the file 259 pkgpath += "_corrupted" 260 filename := filepath.Join(corruptdir, pkgpath) + ".a" 261 ioutil.WriteFile(filename, data, 0666) 262 263 // test that importing the corrupted file results in an error 264 _, err = Import(make(map[string]*types.Package), pkgpath, corruptdir, nil) 265 if err == nil { 266 t.Errorf("import corrupted %q succeeded", pkgpath) 267 } else if msg := err.Error(); !strings.Contains(msg, "version skew") { 268 t.Errorf("import %q error incorrect (%s)", pkgpath, msg) 269 } 270 } 271} 272 273func TestImportStdLib(t *testing.T) { 274 skipSpecialPlatforms(t) 275 276 // This package only handles gc export data. 277 if runtime.Compiler != "gc" { 278 t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler) 279 } 280 281 dt := maxTime 282 if testing.Short() && /* testenv. */ Builder() == "" { 283 dt = 10 * time.Millisecond 284 } 285 nimports := testDir(t, "", time.Now().Add(dt)) // installed packages 286 t.Logf("tested %d imports", nimports) 287} 288 289func TestIssue5815(t *testing.T) { 290 skipSpecialPlatforms(t) 291 292 // This package only handles gc export data. 293 if runtime.Compiler != "gc" { 294 t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler) 295 } 296 297 pkg := importPkg(t, "strings", ".") 298 299 scope := pkg.Scope() 300 for _, name := range scope.Names() { 301 obj := scope.Lookup(name) 302 if obj.Pkg() == nil { 303 t.Errorf("no pkg for %s", obj) 304 } 305 if tname, _ := obj.(*types.TypeName); tname != nil { 306 named := tname.Type().(*types.Named) 307 for i := 0; i < named.NumMethods(); i++ { 308 m := named.Method(i) 309 if m.Pkg() == nil { 310 t.Errorf("no pkg for %s", m) 311 } 312 } 313 } 314 } 315} 316 317// Smoke test to ensure that imported methods get the correct package. 318func TestCorrectMethodPackage(t *testing.T) { 319 skipSpecialPlatforms(t) 320 321 // This package only handles gc export data. 322 if runtime.Compiler != "gc" { 323 t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler) 324 } 325 326 imports := make(map[string]*types.Package) 327 _, err := Import(imports, "net/http", ".", nil) 328 if err != nil { 329 t.Fatal(err) 330 } 331 332 mutex := imports["sync"].Scope().Lookup("Mutex").(*types.TypeName).Type() 333 mset := types.NewMethodSet(types.NewPointer(mutex)) // methods of *sync.Mutex 334 sel := mset.Lookup(nil, "Lock") 335 lock := sel.Obj().(*types.Func) 336 if got, want := lock.Pkg().Path(), "sync"; got != want { 337 t.Errorf("got package path %q; want %q", got, want) 338 } 339} 340 341func TestIssue13566(t *testing.T) { 342 skipSpecialPlatforms(t) 343 344 // This package only handles gc export data. 345 if runtime.Compiler != "gc" { 346 t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler) 347 } 348 349 // On windows, we have to set the -D option for the compiler to avoid having a drive 350 // letter and an illegal ':' in the import path - just skip it (see also issue #3483). 351 if runtime.GOOS == "windows" { 352 t.Skip("avoid dealing with relative paths/drive letters on windows") 353 } 354 355 tmpdir := mktmpdir(t) 356 defer os.RemoveAll(tmpdir) 357 testoutdir := filepath.Join(tmpdir, "testdata") 358 359 // b.go needs to be compiled from the output directory so that the compiler can 360 // find the compiled package a. We pass the full path to compile() so that we 361 // don't have to copy the file to that directory. 362 bpath, err := filepath.Abs(filepath.Join("testdata", "b.go")) 363 if err != nil { 364 t.Fatal(err) 365 } 366 compile(t, "testdata", "a.go", testoutdir) 367 compile(t, testoutdir, bpath, testoutdir) 368 369 // import must succeed (test for issue at hand) 370 pkg := importPkg(t, "./testdata/b", tmpdir) 371 372 // make sure all indirectly imported packages have names 373 for _, imp := range pkg.Imports() { 374 if imp.Name() == "" { 375 t.Errorf("no name for %s package", imp.Path()) 376 } 377 } 378} 379 380func TestIssue13898(t *testing.T) { 381 skipSpecialPlatforms(t) 382 383 // This package only handles gc export data. 384 if runtime.Compiler != "gc" { 385 t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler) 386 } 387 388 // import go/internal/gcimporter which imports go/types partially 389 imports := make(map[string]*types.Package) 390 _, err := Import(imports, "go/internal/gcimporter", ".", nil) 391 if err != nil { 392 t.Fatal(err) 393 } 394 395 // look for go/types package 396 var goTypesPkg *types.Package 397 for path, pkg := range imports { 398 if path == "go/types" { 399 goTypesPkg = pkg 400 break 401 } 402 } 403 if goTypesPkg == nil { 404 t.Fatal("go/types not found") 405 } 406 407 // look for go/types.Object type 408 obj := lookupObj(t, goTypesPkg.Scope(), "Object") 409 typ, ok := obj.Type().(*types.Named) 410 if !ok { 411 t.Fatalf("go/types.Object type is %v; wanted named type", typ) 412 } 413 414 // lookup go/types.Object.Pkg method 415 m, index, indirect := types.LookupFieldOrMethod(typ, false, nil, "Pkg") 416 if m == nil { 417 t.Fatalf("go/types.Object.Pkg not found (index = %v, indirect = %v)", index, indirect) 418 } 419 420 // the method must belong to go/types 421 if m.Pkg().Path() != "go/types" { 422 t.Fatalf("found %v; want go/types", m.Pkg()) 423 } 424} 425 426func TestIssue15517(t *testing.T) { 427 skipSpecialPlatforms(t) 428 429 // This package only handles gc export data. 430 if runtime.Compiler != "gc" { 431 t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler) 432 } 433 434 // On windows, we have to set the -D option for the compiler to avoid having a drive 435 // letter and an illegal ':' in the import path - just skip it (see also issue #3483). 436 if runtime.GOOS == "windows" { 437 t.Skip("avoid dealing with relative paths/drive letters on windows") 438 } 439 440 tmpdir := mktmpdir(t) 441 defer os.RemoveAll(tmpdir) 442 443 compile(t, "testdata", "p.go", filepath.Join(tmpdir, "testdata")) 444 445 // Multiple imports of p must succeed without redeclaration errors. 446 // We use an import path that's not cleaned up so that the eventual 447 // file path for the package is different from the package path; this 448 // will expose the error if it is present. 449 // 450 // (Issue: Both the textual and the binary importer used the file path 451 // of the package to be imported as key into the shared packages map. 452 // However, the binary importer then used the package path to identify 453 // the imported package to mark it as complete; effectively marking the 454 // wrong package as complete. By using an "unclean" package path, the 455 // file and package path are different, exposing the problem if present. 456 // The same issue occurs with vendoring.) 457 imports := make(map[string]*types.Package) 458 for i := 0; i < 3; i++ { 459 if _, err := Import(imports, "./././testdata/p", tmpdir, nil); err != nil { 460 t.Fatal(err) 461 } 462 } 463} 464 465func TestIssue15920(t *testing.T) { 466 skipSpecialPlatforms(t) 467 468 // This package only handles gc export data. 469 if runtime.Compiler != "gc" { 470 t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler) 471 } 472 473 // On windows, we have to set the -D option for the compiler to avoid having a drive 474 // letter and an illegal ':' in the import path - just skip it (see also issue #3483). 475 if runtime.GOOS == "windows" { 476 t.Skip("avoid dealing with relative paths/drive letters on windows") 477 } 478 479 compileAndImportPkg(t, "issue15920") 480} 481 482func TestIssue20046(t *testing.T) { 483 skipSpecialPlatforms(t) 484 485 // This package only handles gc export data. 486 if runtime.Compiler != "gc" { 487 t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler) 488 } 489 490 // On windows, we have to set the -D option for the compiler to avoid having a drive 491 // letter and an illegal ':' in the import path - just skip it (see also issue #3483). 492 if runtime.GOOS == "windows" { 493 t.Skip("avoid dealing with relative paths/drive letters on windows") 494 } 495 496 // "./issue20046".V.M must exist 497 pkg := compileAndImportPkg(t, "issue20046") 498 obj := lookupObj(t, pkg.Scope(), "V") 499 if m, index, indirect := types.LookupFieldOrMethod(obj.Type(), false, nil, "M"); m == nil { 500 t.Fatalf("V.M not found (index = %v, indirect = %v)", index, indirect) 501 } 502} 503 504func importPkg(t *testing.T, path, srcDir string) *types.Package { 505 pkg, err := Import(make(map[string]*types.Package), path, srcDir, nil) 506 if err != nil { 507 t.Fatal(err) 508 } 509 return pkg 510} 511 512func compileAndImportPkg(t *testing.T, name string) *types.Package { 513 tmpdir := mktmpdir(t) 514 defer os.RemoveAll(tmpdir) 515 compile(t, "testdata", name+".go", filepath.Join(tmpdir, "testdata")) 516 return importPkg(t, "./testdata/"+name, tmpdir) 517} 518 519func lookupObj(t *testing.T, scope *types.Scope, name string) types.Object { 520 if obj := scope.Lookup(name); obj != nil { 521 return obj 522 } 523 t.Fatalf("%s not found", name) 524 return nil 525} 526