go/src/go/internal/gcimporter/gcimporter_test.go
Robert Griesemer 756088549f go/importer: associate exported field and interface methods with correct package
In gc export data, exported struct field and interface method names appear
in unqualified form (i.e., w/o package name). The (gc)importer assumed that
unqualified exported names automatically belong to the package being imported.
This is not the case if the field or method belongs to a struct or interface
that was declared in another package and re-exported.

The issue becomes visible if a type T (say an interface with a method M)
is declared in a package A, indirectly re-exported by a package B (which
imports A), and then imported in C. If C imports both A and B, if A is
imported before B, T.M gets associated with the correct package A. If B
is imported before A, T.M appears to be exported by B (even though T itself
is correctly marked as coming from A). If T.M is imported again via the
import of A if gets dropped (as it should) because it was imported already.

The fix is to pass down the parent package when we parse imported types
so that the importer can use the correct package when creating fields
and methods.

Fixes #13898.

Change-Id: I7ec2ee2dda15859c582b65db221c3841899776e1
Reviewed-on: https://go-review.googlesource.com/18549
Reviewed-by: Alan Donovan <adonovan@google.com>
2016-01-13 18:34:14 +00:00

363 lines
9.7 KiB
Go

// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package gcimporter
import (
"fmt"
"internal/testenv"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"testing"
"time"
"go/types"
)
// skipSpecialPlatforms causes the test to be skipped for platforms where
// builders (build.golang.org) don't have access to compiled packages for
// import.
func skipSpecialPlatforms(t *testing.T) {
switch platform := runtime.GOOS + "-" + runtime.GOARCH; platform {
case "nacl-amd64p32",
"nacl-386",
"nacl-arm",
"darwin-arm",
"darwin-arm64":
t.Skipf("no compiled packages available for import on %s", platform)
}
}
func compile(t *testing.T, dirname, filename string) string {
testenv.MustHaveGoBuild(t)
cmd := exec.Command("go", "tool", "compile", filename)
cmd.Dir = dirname
out, err := cmd.CombinedOutput()
if err != nil {
t.Logf("%s", out)
t.Fatalf("go tool compile %s failed: %s", filename, err)
}
// filename should end with ".go"
return filepath.Join(dirname, filename[:len(filename)-2]+"o")
}
// TODO(gri) Remove this function once we switched to new export format by default.
func compileNewExport(t *testing.T, dirname, filename string) string {
testenv.MustHaveGoBuild(t)
cmd := exec.Command("go", "tool", "compile", "-newexport", filename)
cmd.Dir = dirname
out, err := cmd.CombinedOutput()
if err != nil {
t.Logf("%s", out)
t.Fatalf("go tool compile %s failed: %s", filename, err)
}
// filename should end with ".go"
return filepath.Join(dirname, filename[:len(filename)-2]+"o")
}
func testPath(t *testing.T, path, srcDir string) *types.Package {
t0 := time.Now()
pkg, err := Import(make(map[string]*types.Package), path, srcDir)
if err != nil {
t.Errorf("testPath(%s): %s", path, err)
return nil
}
t.Logf("testPath(%s): %v", path, time.Since(t0))
return pkg
}
const maxTime = 30 * time.Second
func testDir(t *testing.T, dir string, endTime time.Time) (nimports int) {
dirname := filepath.Join(runtime.GOROOT(), "pkg", runtime.GOOS+"_"+runtime.GOARCH, dir)
list, err := ioutil.ReadDir(dirname)
if err != nil {
t.Fatalf("testDir(%s): %s", dirname, err)
}
for _, f := range list {
if time.Now().After(endTime) {
t.Log("testing time used up")
return
}
switch {
case !f.IsDir():
// try extensions
for _, ext := range pkgExts {
if strings.HasSuffix(f.Name(), ext) {
name := f.Name()[0 : len(f.Name())-len(ext)] // remove extension
if testPath(t, filepath.Join(dir, name), dir) != nil {
nimports++
}
}
}
case f.IsDir():
nimports += testDir(t, filepath.Join(dir, f.Name()), endTime)
}
}
return
}
func TestImportTestdata(t *testing.T) {
// This package only handles gc export data.
if runtime.Compiler != "gc" {
t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler)
return
}
if outFn := compile(t, "testdata", "exports.go"); outFn != "" {
defer os.Remove(outFn)
}
if pkg := testPath(t, "./testdata/exports", "."); pkg != nil {
// The package's Imports list must include all packages
// explicitly imported by exports.go, plus all packages
// referenced indirectly via exported objects in exports.go.
// With the textual export format, the list may also include
// additional packages that are not strictly required for
// import processing alone (they are exported to err "on
// the safe side").
got := fmt.Sprint(pkg.Imports())
for _, want := range []string{"go/ast", "go/token"} {
if !strings.Contains(got, want) {
t.Errorf(`Package("exports").Imports() = %s, does not contain %s`, got, want)
}
}
}
}
// TODO(gri) Remove this function once we switched to new export format by default
// (and update the comment and want list in TestImportTestdata).
func TestImportTestdataNewExport(t *testing.T) {
// This package only handles gc export data.
if runtime.Compiler != "gc" {
t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler)
return
}
if outFn := compileNewExport(t, "testdata", "exports.go"); outFn != "" {
defer os.Remove(outFn)
}
if pkg := testPath(t, "./testdata/exports", "."); pkg != nil {
// The package's Imports list must include all packages
// explicitly imported by exports.go, plus all packages
// referenced indirectly via exported objects in exports.go.
want := `[package ast ("go/ast") package token ("go/token")]`
got := fmt.Sprint(pkg.Imports())
if got != want {
t.Errorf(`Package("exports").Imports() = %s, want %s`, got, want)
}
}
}
func TestImportStdLib(t *testing.T) {
skipSpecialPlatforms(t)
// This package only handles gc export data.
if runtime.Compiler != "gc" {
t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler)
return
}
dt := maxTime
if testing.Short() && testenv.Builder() == "" {
dt = 10 * time.Millisecond
}
nimports := testDir(t, "", time.Now().Add(dt)) // installed packages
t.Logf("tested %d imports", nimports)
}
var importedObjectTests = []struct {
name string
want string
}{
{"math.Pi", "const Pi untyped float"},
{"io.Reader", "type Reader interface{Read(p []byte) (n int, err error)}"},
{"io.ReadWriter", "type ReadWriter interface{Read(p []byte) (n int, err error); Write(p []byte) (n int, err error)}"},
{"math.Sin", "func Sin(x float64) float64"},
// TODO(gri) add more tests
}
func TestImportedTypes(t *testing.T) {
skipSpecialPlatforms(t)
// This package only handles gc export data.
if runtime.Compiler != "gc" {
t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler)
return
}
for _, test := range importedObjectTests {
s := strings.Split(test.name, ".")
if len(s) != 2 {
t.Fatal("inconsistent test data")
}
importPath := s[0]
objName := s[1]
pkg, err := Import(make(map[string]*types.Package), importPath, ".")
if err != nil {
t.Error(err)
continue
}
obj := pkg.Scope().Lookup(objName)
if obj == nil {
t.Errorf("%s: object not found", test.name)
continue
}
got := types.ObjectString(obj, types.RelativeTo(pkg))
if got != test.want {
t.Errorf("%s: got %q; want %q", test.name, got, test.want)
}
}
}
func TestIssue5815(t *testing.T) {
skipSpecialPlatforms(t)
// This package only handles gc export data.
if runtime.Compiler != "gc" {
t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler)
return
}
pkg, err := Import(make(map[string]*types.Package), "strings", ".")
if err != nil {
t.Fatal(err)
}
scope := pkg.Scope()
for _, name := range scope.Names() {
obj := scope.Lookup(name)
if obj.Pkg() == nil {
t.Errorf("no pkg for %s", obj)
}
if tname, _ := obj.(*types.TypeName); tname != nil {
named := tname.Type().(*types.Named)
for i := 0; i < named.NumMethods(); i++ {
m := named.Method(i)
if m.Pkg() == nil {
t.Errorf("no pkg for %s", m)
}
}
}
}
}
// Smoke test to ensure that imported methods get the correct package.
func TestCorrectMethodPackage(t *testing.T) {
skipSpecialPlatforms(t)
// This package only handles gc export data.
if runtime.Compiler != "gc" {
t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler)
return
}
imports := make(map[string]*types.Package)
_, err := Import(imports, "net/http", ".")
if err != nil {
t.Fatal(err)
}
mutex := imports["sync"].Scope().Lookup("Mutex").(*types.TypeName).Type()
mset := types.NewMethodSet(types.NewPointer(mutex)) // methods of *sync.Mutex
sel := mset.Lookup(nil, "Lock")
lock := sel.Obj().(*types.Func)
if got, want := lock.Pkg().Path(), "sync"; got != want {
t.Errorf("got package path %q; want %q", got, want)
}
}
func TestIssue13566(t *testing.T) {
skipSpecialPlatforms(t)
// This package only handles gc export data.
if runtime.Compiler != "gc" {
t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler)
return
}
// On windows, we have to set the -D option for the compiler to avoid having a drive
// letter and an illegal ':' in the import path - just skip it (see also issue #3483).
if runtime.GOOS == "windows" {
t.Skip("avoid dealing with relative paths/drive letters on windows")
}
if f := compile(t, "testdata", "a.go"); f != "" {
defer os.Remove(f)
}
if f := compile(t, "testdata", "b.go"); f != "" {
defer os.Remove(f)
}
// import must succeed (test for issue at hand)
pkg, err := Import(make(map[string]*types.Package), "./testdata/b", ".")
if err != nil {
t.Fatal(err)
}
// make sure all indirectly imported packages have names
for _, imp := range pkg.Imports() {
if imp.Name() == "" {
t.Errorf("no name for %s package", imp.Path())
}
}
}
func TestIssue13898(t *testing.T) {
skipSpecialPlatforms(t)
// This package only handles gc export data.
if runtime.Compiler != "gc" {
t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler)
return
}
// import go/internal/gcimporter which imports go/types partially
imports := make(map[string]*types.Package)
_, err := Import(imports, "go/internal/gcimporter", ".")
if err != nil {
t.Fatal(err)
}
// look for go/types package
var goTypesPkg *types.Package
for path, pkg := range imports {
if path == "go/types" {
goTypesPkg = pkg
break
}
}
if goTypesPkg == nil {
t.Fatal("go/types not found")
}
// look for go/types.Object type
obj := goTypesPkg.Scope().Lookup("Object")
if obj == nil {
t.Fatal("go/types.Object not found")
}
typ, ok := obj.Type().(*types.Named)
if !ok {
t.Fatalf("go/types.Object type is %v; wanted named type", typ)
}
// lookup go/types.Object.Pkg method
m, _, _ := types.LookupFieldOrMethod(typ, false, nil, "Pkg")
if m == nil {
t.Fatal("go/types.Object.Pkg not found")
}
// the method must belong to go/types
if m.Pkg().Path() != "go/types" {
t.Fatalf("found %v; want go/types", m.Pkg())
}
}