• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2017 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
5package srcimporter
6
7import (
8	"flag"
9	"go/build"
10	"go/token"
11	"go/types"
12	"internal/testenv"
13	"os"
14	"path"
15	"path/filepath"
16	"strings"
17	"testing"
18	"time"
19)
20
21func TestMain(m *testing.M) {
22	flag.Parse()
23	build.Default.GOROOT = testenv.GOROOT(nil)
24	os.Exit(m.Run())
25}
26
27const maxTime = 2 * time.Second
28
29var importer = New(&build.Default, token.NewFileSet(), make(map[string]*types.Package))
30
31func doImport(t *testing.T, path, srcDir string) {
32	t0 := time.Now()
33	if _, err := importer.ImportFrom(path, srcDir, 0); err != nil {
34		// don't report an error if there's no buildable Go files
35		if _, nogo := err.(*build.NoGoError); !nogo {
36			t.Errorf("import %q failed (%v)", path, err)
37		}
38		return
39	}
40	t.Logf("import %q: %v", path, time.Since(t0))
41}
42
43// walkDir imports the all the packages with the given path
44// prefix recursively. It returns the number of packages
45// imported and whether importing was aborted because time
46// has passed endTime.
47func walkDir(t *testing.T, path string, endTime time.Time) (int, bool) {
48	if time.Now().After(endTime) {
49		t.Log("testing time used up")
50		return 0, true
51	}
52
53	// ignore fake packages and testdata directories
54	if path == "builtin" || path == "unsafe" || strings.HasSuffix(path, "testdata") {
55		return 0, false
56	}
57
58	list, err := os.ReadDir(filepath.Join(testenv.GOROOT(t), "src", path))
59	if err != nil {
60		t.Fatalf("walkDir %s failed (%v)", path, err)
61	}
62
63	nimports := 0
64	hasGoFiles := false
65	for _, f := range list {
66		if f.IsDir() {
67			n, abort := walkDir(t, filepath.Join(path, f.Name()), endTime)
68			nimports += n
69			if abort {
70				return nimports, true
71			}
72		} else if strings.HasSuffix(f.Name(), ".go") {
73			hasGoFiles = true
74		}
75	}
76
77	if hasGoFiles {
78		doImport(t, path, "")
79		nimports++
80	}
81
82	return nimports, false
83}
84
85func TestImportStdLib(t *testing.T) {
86	if !testenv.HasSrc() {
87		t.Skip("no source code available")
88	}
89
90	if testing.Short() && testenv.Builder() == "" {
91		t.Skip("skipping in -short mode")
92	}
93	dt := maxTime
94	nimports, _ := walkDir(t, "", time.Now().Add(dt)) // installed packages
95	t.Logf("tested %d imports", nimports)
96}
97
98var importedObjectTests = []struct {
99	name string
100	want string
101}{
102	{"flag.Bool", "func Bool(name string, value bool, usage string) *bool"},
103	{"io.Reader", "type Reader interface{Read(p []byte) (n int, err error)}"},
104	{"io.ReadWriter", "type ReadWriter interface{Reader; Writer}"}, // go/types.gcCompatibilityMode is off => interface not flattened
105	{"math.Pi", "const Pi untyped float"},
106	{"math.Sin", "func Sin(x float64) float64"},
107	{"math/big.Int", "type Int struct{neg bool; abs nat}"},
108	{"golang.org/x/text/unicode/norm.MaxSegmentSize", "const MaxSegmentSize untyped int"},
109}
110
111func TestImportedTypes(t *testing.T) {
112	if !testenv.HasSrc() {
113		t.Skip("no source code available")
114	}
115
116	for _, test := range importedObjectTests {
117		i := strings.LastIndex(test.name, ".")
118		if i < 0 {
119			t.Fatal("invalid test data format")
120		}
121		importPath := test.name[:i]
122		objName := test.name[i+1:]
123
124		pkg, err := importer.ImportFrom(importPath, ".", 0)
125		if err != nil {
126			t.Error(err)
127			continue
128		}
129
130		obj := pkg.Scope().Lookup(objName)
131		if obj == nil {
132			t.Errorf("%s: object not found", test.name)
133			continue
134		}
135
136		got := types.ObjectString(obj, types.RelativeTo(pkg))
137		if got != test.want {
138			t.Errorf("%s: got %q; want %q", test.name, got, test.want)
139		}
140
141		if named, _ := obj.Type().(*types.Named); named != nil {
142			verifyInterfaceMethodRecvs(t, named, 0)
143		}
144	}
145}
146
147// verifyInterfaceMethodRecvs verifies that method receiver types
148// are named if the methods belong to a named interface type.
149func verifyInterfaceMethodRecvs(t *testing.T, named *types.Named, level int) {
150	// avoid endless recursion in case of an embedding bug that lead to a cycle
151	if level > 10 {
152		t.Errorf("%s: embeds itself", named)
153		return
154	}
155
156	iface, _ := named.Underlying().(*types.Interface)
157	if iface == nil {
158		return // not an interface
159	}
160
161	// check explicitly declared methods
162	for i := 0; i < iface.NumExplicitMethods(); i++ {
163		m := iface.ExplicitMethod(i)
164		recv := m.Type().(*types.Signature).Recv()
165		if recv == nil {
166			t.Errorf("%s: missing receiver type", m)
167			continue
168		}
169		if recv.Type() != named {
170			t.Errorf("%s: got recv type %s; want %s", m, recv.Type(), named)
171		}
172	}
173
174	// check embedded interfaces (they are named, too)
175	for i := 0; i < iface.NumEmbeddeds(); i++ {
176		// embedding of interfaces cannot have cycles; recursion will terminate
177		verifyInterfaceMethodRecvs(t, iface.Embedded(i), level+1)
178	}
179}
180
181func TestReimport(t *testing.T) {
182	if !testenv.HasSrc() {
183		t.Skip("no source code available")
184	}
185
186	// Reimporting a partially imported (incomplete) package is not supported (see issue #19337).
187	// Make sure we recognize the situation and report an error.
188
189	mathPkg := types.NewPackage("math", "math") // incomplete package
190	importer := New(&build.Default, token.NewFileSet(), map[string]*types.Package{mathPkg.Path(): mathPkg})
191	_, err := importer.ImportFrom("math", ".", 0)
192	if err == nil || !strings.HasPrefix(err.Error(), "reimport") {
193		t.Errorf("got %v; want reimport error", err)
194	}
195}
196
197func TestIssue20855(t *testing.T) {
198	if !testenv.HasSrc() {
199		t.Skip("no source code available")
200	}
201
202	pkg, err := importer.ImportFrom("go/internal/srcimporter/testdata/issue20855", ".", 0)
203	if err == nil || !strings.Contains(err.Error(), "missing function body") {
204		t.Fatalf("got unexpected or no error: %v", err)
205	}
206	if pkg == nil {
207		t.Error("got no package despite no hard errors")
208	}
209}
210
211func testImportPath(t *testing.T, pkgPath string) {
212	if !testenv.HasSrc() {
213		t.Skip("no source code available")
214	}
215
216	pkgName := path.Base(pkgPath)
217
218	pkg, err := importer.Import(pkgPath)
219	if err != nil {
220		t.Fatal(err)
221	}
222
223	if pkg.Name() != pkgName {
224		t.Errorf("got %q; want %q", pkg.Name(), pkgName)
225	}
226
227	if pkg.Path() != pkgPath {
228		t.Errorf("got %q; want %q", pkg.Path(), pkgPath)
229	}
230}
231
232// TestIssue23092 tests relative imports.
233func TestIssue23092(t *testing.T) {
234	testImportPath(t, "./testdata/issue23092")
235}
236
237// TestIssue24392 tests imports against a path containing 'testdata'.
238func TestIssue24392(t *testing.T) {
239	testImportPath(t, "go/internal/srcimporter/testdata/issue24392")
240}
241
242func TestCgo(t *testing.T) {
243	testenv.MustHaveGoBuild(t)
244	testenv.MustHaveCGO(t)
245
246	buildCtx := build.Default
247	importer := New(&buildCtx, token.NewFileSet(), make(map[string]*types.Package))
248	_, err := importer.ImportFrom("cmd/cgo/internal/test", buildCtx.Dir, 0)
249	if err != nil {
250		t.Fatalf("Import failed: %v", err)
251	}
252}
253