]> Cypherpunks repositories - gostls13.git/commitdiff
go/internal/gcimporter: add import tests for type parameters
authorRobert Findley <rfindley@google.com>
Wed, 1 Sep 2021 20:51:17 +0000 (16:51 -0400)
committerRobert Findley <rfindley@google.com>
Thu, 2 Sep 2021 14:05:36 +0000 (14:05 +0000)
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>
src/go/internal/gcimporter/gcimporter_test.go
src/go/types/struct.go

index 3c76aafde39947ef49d9e42dd8107b5d753c210b..9f4345d8f9c06463c288d9afb37901fbdc1fd388 100644 (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)
 
index 48b07346dc30cc7c3ba3038ca7b1a780bd41829d..f6e6f2a5e653f1db3dcac8af19db164e0933aef0 100644 (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
 }