go/internal/gcimporter: add import tests for type parameters

Add a new test TestImportTypeparamTests that compiles and imports
packages contained in test/typeparam, and compares the resulting package
scope with the scope produced by type-checking directly.

In the process, fix a bug in go/types affecting embedded instances with
more than one type argument. This was uncovered by smoketest.go.

To enable this new test it was easiest to move gcimporter_test.go to an
external test, which required copying the pkgExts variable.

Fixes #48101

Change-Id: Ie4d981bf463e886a8d141809805d184dbbf64607
Reviewed-on: https://go-review.googlesource.com/c/go/+/347070
Trust: Robert Findley <rfindley@google.com>
Run-TryBot: Robert Findley <rfindley@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Robert Griesemer <gri@golang.org>
This commit is contained in:
Robert Findley 2021-09-01 16:51:17 -04:00
parent b8420baf46
commit 4fb79569d2
2 changed files with 134 additions and 1 deletions

View file

@ -2,11 +2,12 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package gcimporter
package gcimporter_test
import (
"bytes"
"fmt"
"internal/goexperiment"
"internal/testenv"
"os"
"os/exec"
@ -16,8 +17,13 @@ import (
"testing"
"time"
"go/ast"
"go/importer"
"go/parser"
"go/token"
"go/types"
. "go/internal/gcimporter"
)
// skipSpecialPlatforms causes the test to be skipped for platforms where
@ -63,6 +69,8 @@ func testPath(t *testing.T, path, srcDir string) *types.Package {
const maxTime = 30 * time.Second
var pkgExts = [...]string{".a", ".o"} // keep in sync with gcimporter.go
func testDir(t *testing.T, dir string, endTime time.Time) (nimports int) {
dirname := filepath.Join(runtime.GOROOT(), "pkg", runtime.GOOS+"_"+runtime.GOARCH, dir)
list, err := os.ReadDir(dirname)
@ -134,6 +142,129 @@ func TestImportTestdata(t *testing.T) {
}
}
func TestImportTypeparamTests(t *testing.T) {
// This test doesn't yet work with the unified export format.
if goexperiment.Unified {
t.Skip("unified export data format is currently unsupported")
}
// This package only handles gc export data.
if runtime.Compiler != "gc" {
t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler)
}
tmpdir := mktmpdir(t)
defer os.RemoveAll(tmpdir)
// Check go files in test/typeparam, except those that fail for a known
// reason.
rootDir := filepath.Join(runtime.GOROOT(), "test", "typeparam")
list, err := os.ReadDir(rootDir)
if err != nil {
t.Fatal(err)
}
skip := map[string]string{
"equal.go": "inconsistent embedded sorting", // TODO(rfindley): investigate this.
"nested.go": "fails to compile", // TODO(rfindley): investigate this.
}
for _, entry := range list {
if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".go") {
// For now, only consider standalone go files.
continue
}
t.Run(entry.Name(), func(t *testing.T) {
if reason, ok := skip[entry.Name()]; ok {
t.Skip(reason)
}
filename := filepath.Join(rootDir, entry.Name())
src, err := os.ReadFile(filename)
if err != nil {
t.Fatal(err)
}
if !bytes.HasPrefix(src, []byte("// run")) && !bytes.HasPrefix(src, []byte("// compile")) {
// We're bypassing the logic of run.go here, so be conservative about
// the files we consider in an attempt to make this test more robust to
// changes in test/typeparams.
t.Skipf("not detected as a run test")
}
// Compile and import, and compare the resulting package with the package
// that was type-checked directly.
compile(t, rootDir, entry.Name(), filepath.Join(tmpdir, "testdata"))
pkgName := strings.TrimSuffix(entry.Name(), ".go")
imported := importPkg(t, "./testdata/"+pkgName, tmpdir)
checked := checkFile(t, filename, src)
seen := make(map[string]bool)
for _, name := range imported.Scope().Names() {
if !token.IsExported(name) {
continue // ignore synthetic names like .inittask and .dict.*
}
seen[name] = true
importedObj := imported.Scope().Lookup(name)
got := types.ObjectString(importedObj, types.RelativeTo(imported))
got = sanitizeObjectString(got)
checkedObj := checked.Scope().Lookup(name)
if checkedObj == nil {
t.Fatalf("imported object %q was not type-checked", name)
}
want := types.ObjectString(checkedObj, types.RelativeTo(checked))
want = sanitizeObjectString(want)
if got != want {
t.Errorf("imported %q as %q, want %q", name, got, want)
}
}
for _, name := range checked.Scope().Names() {
if !token.IsExported(name) || seen[name] {
continue
}
t.Errorf("did not import object %q", name)
}
})
}
}
// sanitizeObjectString removes type parameter debugging markers from an object
// string, to normalize it for comparison.
// TODO(rfindley): this should not be necessary.
func sanitizeObjectString(s string) string {
var runes []rune
for _, r := range s {
if r == '#' {
continue // trim instance markers
}
if '₀' <= r && r < '₀'+10 {
continue // trim type parameter subscripts
}
runes = append(runes, r)
}
return string(runes)
}
func checkFile(t *testing.T, filename string, src []byte) *types.Package {
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, filename, src, 0)
if err != nil {
t.Fatal(err)
}
config := types.Config{
Importer: importer.Default(),
}
pkg, err := config.Check("", fset, []*ast.File{f}, nil)
if err != nil {
t.Fatal(err)
}
return pkg
}
func TestVersionHandling(t *testing.T) {
skipSpecialPlatforms(t)

View file

@ -176,6 +176,8 @@ func embeddedFieldIdent(e ast.Expr) *ast.Ident {
return e.Sel
case *ast.IndexExpr:
return embeddedFieldIdent(e.X)
case *ast.MultiIndexExpr:
return embeddedFieldIdent(e.X)
}
return nil // invalid embedded field
}