Home | History | Annotate | Download | only in gcimporter
      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 package gcimporter
      6 
      7 import (
      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.
     26 func 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 
     37 func 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 
     49 func 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 
     60 const maxTime = 30 * time.Second
     61 
     62 func 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 
     91 func 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 
    120 func 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 
    183 func 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 
    199 var 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 
    210 func 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 
    245 func 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.
    274 func 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 
    297 func 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 
    329 func 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 
    375 func 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 
    413 func 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 
    434 func 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 
    460 func 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 
    468 func 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