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