blob: 56549434d14d3ec15611d46a8b265d1fc212b7e2 [file] [log] [blame]
// Copyright 2017 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 srcimporter
import (
"go/build"
"go/token"
"go/types"
"internal/testenv"
"io/ioutil"
"path"
"path/filepath"
"runtime"
"strings"
"testing"
"time"
)
const maxTime = 2 * time.Second
var importer = New(&build.Default, token.NewFileSet(), make(map[string]*types.Package))
func doImport(t *testing.T, path, srcDir string) {
t0 := time.Now()
if _, err := importer.ImportFrom(path, srcDir, 0); err != nil {
// don't report an error if there's no buildable Go files
if _, nogo := err.(*build.NoGoError); !nogo {
t.Errorf("import %q failed (%v)", path, err)
}
return
}
t.Logf("import %q: %v", path, time.Since(t0))
}
// walkDir imports the all the packages with the given path
// prefix recursively. It returns the number of packages
// imported and whether importing was aborted because time
// has passed endTime.
func walkDir(t *testing.T, path string, endTime time.Time) (int, bool) {
if time.Now().After(endTime) {
t.Log("testing time used up")
return 0, true
}
// ignore fake packages and testdata directories
if path == "builtin" || path == "unsafe" || strings.HasSuffix(path, "testdata") {
return 0, false
}
list, err := ioutil.ReadDir(filepath.Join(runtime.GOROOT(), "src", path))
if err != nil {
t.Fatalf("walkDir %s failed (%v)", path, err)
}
nimports := 0
hasGoFiles := false
for _, f := range list {
if f.IsDir() {
n, abort := walkDir(t, filepath.Join(path, f.Name()), endTime)
nimports += n
if abort {
return nimports, true
}
} else if strings.HasSuffix(f.Name(), ".go") {
hasGoFiles = true
}
}
if hasGoFiles {
doImport(t, path, "")
nimports++
}
return nimports, false
}
func TestImportStdLib(t *testing.T) {
if !testenv.HasSrc() {
t.Skip("no source code available")
}
if testing.Short() && testenv.Builder() == "" {
t.Skip("skipping in -short mode")
}
dt := maxTime
nimports, _ := walkDir(t, "", time.Now().Add(dt)) // installed packages
t.Logf("tested %d imports", nimports)
}
var importedObjectTests = []struct {
name string
want string
}{
{"flag.Bool", "func Bool(name string, value bool, usage string) *bool"},
{"io.Reader", "type Reader interface{Read(p []byte) (n int, err error)}"},
{"io.ReadWriter", "type ReadWriter interface{Reader; Writer}"}, // go/types.gcCompatibilityMode is off => interface not flattened
{"math.Pi", "const Pi untyped float"},
{"math.Sin", "func Sin(x float64) float64"},
{"math/big.Int", "type Int struct{neg bool; abs nat}"},
{"golang.org/x/text/unicode/norm.MaxSegmentSize", "const MaxSegmentSize untyped int"},
}
func TestImportedTypes(t *testing.T) {
if !testenv.HasSrc() {
t.Skip("no source code available")
}
for _, test := range importedObjectTests {
i := strings.LastIndex(test.name, ".")
if i < 0 {
t.Fatal("invalid test data format")
}
importPath := test.name[:i]
objName := test.name[i+1:]
pkg, err := importer.ImportFrom(importPath, ".", 0)
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)
}
if named, _ := obj.Type().(*types.Named); named != nil {
verifyInterfaceMethodRecvs(t, named, 0)
}
}
}
// verifyInterfaceMethodRecvs verifies that method receiver types
// are named if the methods belong to a named interface type.
func verifyInterfaceMethodRecvs(t *testing.T, named *types.Named, level int) {
// avoid endless recursion in case of an embedding bug that lead to a cycle
if level > 10 {
t.Errorf("%s: embeds itself", named)
return
}
iface, _ := named.Underlying().(*types.Interface)
if iface == nil {
return // not an interface
}
// check explicitly declared methods
for i := 0; i < iface.NumExplicitMethods(); i++ {
m := iface.ExplicitMethod(i)
recv := m.Type().(*types.Signature).Recv()
if recv == nil {
t.Errorf("%s: missing receiver type", m)
continue
}
if recv.Type() != named {
t.Errorf("%s: got recv type %s; want %s", m, recv.Type(), named)
}
}
// check embedded interfaces (they are named, too)
for i := 0; i < iface.NumEmbeddeds(); i++ {
// embedding of interfaces cannot have cycles; recursion will terminate
verifyInterfaceMethodRecvs(t, iface.Embedded(i), level+1)
}
}
func TestReimport(t *testing.T) {
if !testenv.HasSrc() {
t.Skip("no source code available")
}
// Reimporting a partially imported (incomplete) package is not supported (see issue #19337).
// Make sure we recognize the situation and report an error.
mathPkg := types.NewPackage("math", "math") // incomplete package
importer := New(&build.Default, token.NewFileSet(), map[string]*types.Package{mathPkg.Path(): mathPkg})
_, err := importer.ImportFrom("math", ".", 0)
if err == nil || !strings.HasPrefix(err.Error(), "reimport") {
t.Errorf("got %v; want reimport error", err)
}
}
func TestIssue20855(t *testing.T) {
if !testenv.HasSrc() {
t.Skip("no source code available")
}
pkg, err := importer.ImportFrom("go/internal/srcimporter/testdata/issue20855", ".", 0)
if err == nil || !strings.Contains(err.Error(), "missing function body") {
t.Fatalf("got unexpected or no error: %v", err)
}
if pkg == nil {
t.Error("got no package despite no hard errors")
}
}
func testImportPath(t *testing.T, pkgPath string) {
if !testenv.HasSrc() {
t.Skip("no source code available")
}
pkgName := path.Base(pkgPath)
pkg, err := importer.Import(pkgPath)
if err != nil {
t.Fatal(err)
}
if pkg.Name() != pkgName {
t.Errorf("got %q; want %q", pkg.Name(), pkgName)
}
if pkg.Path() != pkgPath {
t.Errorf("got %q; want %q", pkg.Path(), pkgPath)
}
}
// TestIssue23092 tests relative imports.
func TestIssue23092(t *testing.T) {
testImportPath(t, "./testdata/issue23092")
}
// TestIssue24392 tests imports against a path containing 'testdata'.
func TestIssue24392(t *testing.T) {
testImportPath(t, "go/internal/srcimporter/testdata/issue24392")
}