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