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