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