diff --git a/build/build_test.go b/build/build_test.go index 343e8b933..7bda7f54a 100644 --- a/build/build_test.go +++ b/build/build_test.go @@ -418,18 +418,17 @@ func TestOverlayAugmentation(t *testing.T) { test.want = test.src } - fsetSrc := token.NewFileSet() - fileSrc := srctesting.Parse(t, fsetSrc, pkgName+test.src) + f := srctesting.New(t) + fileSrc := f.Parse("test.go", pkgName+test.src) overrides := map[string]overrideInfo{} augmentOverlayFile(fileSrc, overrides) pruneImports(fileSrc) - got := srctesting.Format(t, fsetSrc, fileSrc) + got := srctesting.Format(t, f.FileSet, fileSrc) - fsetWant := token.NewFileSet() - fileWant := srctesting.Parse(t, fsetWant, pkgName+test.want) - want := srctesting.Format(t, fsetWant, fileWant) + fileWant := f.Parse("test.go", pkgName+test.want) + want := srctesting.Format(t, f.FileSet, fileWant) if got != want { t.Errorf("augmentOverlayFile and pruneImports got unexpected code:\n"+ @@ -720,18 +719,17 @@ func TestOriginalAugmentation(t *testing.T) { t.Run(test.desc, func(t *testing.T) { pkgName := "package testpackage\n\n" importPath := `math/rand` - fsetSrc := token.NewFileSet() - fileSrc := srctesting.Parse(t, fsetSrc, pkgName+test.src) + f := srctesting.New(t) + fileSrc := f.Parse("test.go", pkgName+test.src) augmentOriginalImports(importPath, fileSrc) augmentOriginalFile(fileSrc, test.info) pruneImports(fileSrc) - got := srctesting.Format(t, fsetSrc, fileSrc) + got := srctesting.Format(t, f.FileSet, fileSrc) - fsetWant := token.NewFileSet() - fileWant := srctesting.Parse(t, fsetWant, pkgName+test.want) - want := srctesting.Format(t, fsetWant, fileWant) + fileWant := f.Parse("test.go", pkgName+test.want) + want := srctesting.Format(t, f.FileSet, fileWant) if got != want { t.Errorf("augmentOriginalImports, augmentOriginalFile, and pruneImports got unexpected code:\n"+ diff --git a/compiler/analysis/info_test.go b/compiler/analysis/info_test.go index 723208255..588f09a6c 100644 --- a/compiler/analysis/info_test.go +++ b/compiler/analysis/info_test.go @@ -2,7 +2,6 @@ package analysis import ( "go/ast" - "go/token" "go/types" "testing" @@ -34,11 +33,11 @@ func notBlocking() { func() { println() } () } ` - fset := token.NewFileSet() - file := srctesting.Parse(t, fset, src) - typesInfo, typesPkg := srctesting.Check(t, fset, file) + f := srctesting.New(t) + file := f.Parse("test.go", src) + typesInfo, typesPkg := f.Check("pkg/test", file) - pkgInfo := AnalyzePkg([]*ast.File{file}, fset, typesInfo, typesPkg, func(f *types.Func) bool { + pkgInfo := AnalyzePkg([]*ast.File{file}, f.FileSet, typesInfo, typesPkg, func(f *types.Func) bool { panic("isBlocking() should be never called for imported functions in this test.") }) diff --git a/compiler/astutil/astutil_test.go b/compiler/astutil/astutil_test.go index 28528a2b3..56dabc510 100644 --- a/compiler/astutil/astutil_test.go +++ b/compiler/astutil/astutil_test.go @@ -2,7 +2,6 @@ package astutil import ( "go/ast" - "go/token" "strconv" "testing" @@ -44,8 +43,7 @@ func TestImportsUnsafe(t *testing.T) { for _, test := range tests { t.Run(test.desc, func(t *testing.T) { src := "package testpackage\n\n" + test.imports - fset := token.NewFileSet() - file := srctesting.Parse(t, fset, src) + file := srctesting.New(t).Parse("test.go", src) got := ImportsUnsafe(file) if got != test.want { t.Fatalf("ImportsUnsafe() returned %t, want %t", got, test.want) @@ -81,8 +79,7 @@ func TestImportName(t *testing.T) { for _, test := range tests { t.Run(test.desc, func(t *testing.T) { src := "package testpackage\n\n" + test.src - fset := token.NewFileSet() - file := srctesting.Parse(t, fset, src) + file := srctesting.New(t).Parse("test.go", src) if len(file.Imports) != 1 { t.Fatal(`expected one and only one import`) } @@ -399,8 +396,7 @@ func TestHasDirectiveOnFile(t *testing.T) { for _, test := range tests { t.Run(test.desc, func(t *testing.T) { const action = `do-stuff` - fset := token.NewFileSet() - file := srctesting.Parse(t, fset, test.src) + file := srctesting.New(t).Parse("test.go", test.src) if got := hasDirective(file, action); got != test.want { t.Errorf(`hasDirective(%T, %q) returned %t, want %t`, file, action, got, test.want) } diff --git a/compiler/internal/symbol/symbol_test.go b/compiler/internal/symbol/symbol_test.go index d4fc4196a..778e3b1e0 100644 --- a/compiler/internal/symbol/symbol_test.go +++ b/compiler/internal/symbol/symbol_test.go @@ -1,7 +1,6 @@ package symbol import ( - "go/token" "go/types" "testing" @@ -18,8 +17,8 @@ func TestName(t *testing.T) { var AVariable int32 ` - fset := token.NewFileSet() - _, pkg := srctesting.Check(t, fset, srctesting.Parse(t, fset, src)) + f := srctesting.New(t) + _, pkg := f.Check("pkg/test", f.Parse("test.go", src)) tests := []struct { obj types.Object @@ -27,19 +26,19 @@ func TestName(t *testing.T) { }{ { obj: pkg.Scope().Lookup("AFunction"), - want: Name{PkgPath: "test", Name: "AFunction"}, + want: Name{PkgPath: "pkg/test", Name: "AFunction"}, }, { obj: pkg.Scope().Lookup("AType"), - want: Name{PkgPath: "test", Name: "AType"}, + want: Name{PkgPath: "pkg/test", Name: "AType"}, }, { obj: types.NewMethodSet(pkg.Scope().Lookup("AType").Type()).Lookup(pkg, "AMethod").Obj(), - want: Name{PkgPath: "test", Name: "AType.AMethod"}, + want: Name{PkgPath: "pkg/test", Name: "AType.AMethod"}, }, { obj: types.NewMethodSet(types.NewPointer(pkg.Scope().Lookup("AType").Type())).Lookup(pkg, "APointerMethod").Obj(), - want: Name{PkgPath: "test", Name: "(*AType).APointerMethod"}, + want: Name{PkgPath: "pkg/test", Name: "(*AType).APointerMethod"}, }, { obj: pkg.Scope().Lookup("AVariable"), - want: Name{PkgPath: "test", Name: "AVariable"}, + want: Name{PkgPath: "pkg/test", Name: "AVariable"}, }, } diff --git a/compiler/internal/typeparams/collect_test.go b/compiler/internal/typeparams/collect_test.go index 64aa43778..99b288552 100644 --- a/compiler/internal/typeparams/collect_test.go +++ b/compiler/internal/typeparams/collect_test.go @@ -2,7 +2,6 @@ package typeparams import ( "go/ast" - "go/token" "go/types" "testing" @@ -80,9 +79,9 @@ func TestVisitor(t *testing.T) { type entry5 = typ[int, F] ` - fset := token.NewFileSet() - file := srctesting.Parse(t, fset, src) - info, pkg := srctesting.Check(t, fset, file) + f := srctesting.New(t) + file := f.Parse("test.go", src) + info, pkg := f.Check("pkg/test", file) lookupObj := func(name string) types.Object { return srctesting.LookupObj(pkg, name) @@ -280,9 +279,9 @@ func TestSeedVisitor(t *testing.T) { func e() { var _ typ[int64] } ` - fset := token.NewFileSet() - file := srctesting.Parse(t, fset, src) - info, pkg := srctesting.Check(t, fset, file) + f := srctesting.New(t) + file := f.Parse("test.go", src) + info, pkg := f.Check("pkg/test", file) sv := seedVisitor{ visitor: visitor{ @@ -343,9 +342,9 @@ func TestCollector(t *testing.T) { } ` - fset := token.NewFileSet() - file := srctesting.Parse(t, fset, src) - info, pkg := srctesting.Check(t, fset, file) + f := srctesting.New(t) + file := f.Parse("test.go", src) + info, pkg := f.Check("pkg/test", file) c := Collector{ TContext: types.NewContext(), @@ -396,7 +395,7 @@ func TestResolver_SubstituteSelection(t *testing.T) { func (_ g[T]) Method(t T) string { return t.String() }`, - wantObj: "func (test.x).String() string", + wantObj: "func (pkg/test.x).String() string", wantSig: "func() string", }, { descr: "generic receiver type with type parameter", @@ -407,8 +406,8 @@ func TestResolver_SubstituteSelection(t *testing.T) { func (_ g[T]) Method(t T) string { return g[T]{}.Method(t) }`, - wantObj: "func (test.g[test.x]).Method(t test.x) string", - wantSig: "func(t test.x) string", + wantObj: "func (pkg/test.g[pkg/test.x]).Method(t pkg/test.x) string", + wantSig: "func(t pkg/test.x) string", }, { descr: "method expression", src: `package test @@ -418,15 +417,15 @@ func TestResolver_SubstituteSelection(t *testing.T) { func (recv g[T]) Method(t T) string { return g[T].Method(recv, t) }`, - wantObj: "func (test.g[test.x]).Method(t test.x) string", - wantSig: "func(recv test.g[test.x], t test.x) string", + wantObj: "func (pkg/test.g[pkg/test.x]).Method(t pkg/test.x) string", + wantSig: "func(recv pkg/test.g[pkg/test.x], t pkg/test.x) string", }} for _, test := range tests { t.Run(test.descr, func(t *testing.T) { - fset := token.NewFileSet() - file := srctesting.Parse(t, fset, test.src) - info, pkg := srctesting.Check(t, fset, file) + f := srctesting.New(t) + file := f.Parse("test.go", test.src) + info, pkg := f.Check("pkg/test", file) method := srctesting.LookupObj(pkg, "g.Method").(*types.Func).Type().(*types.Signature) resolver := NewResolver(nil, ToSlice(method.RecvTypeParams()), []types.Type{srctesting.LookupObj(pkg, "x").Type()}) diff --git a/compiler/internal/typeparams/instance_test.go b/compiler/internal/typeparams/instance_test.go index d00b58beb..a5273f883 100644 --- a/compiler/internal/typeparams/instance_test.go +++ b/compiler/internal/typeparams/instance_test.go @@ -1,7 +1,6 @@ package typeparams import ( - "go/token" "go/types" "testing" @@ -38,8 +37,8 @@ func TestInstanceString(t *testing.T) { func Fun[U any, W any](x, y U) {} func fun[U any, W any](x, y U) {} ` - fset := token.NewFileSet() - _, pkg := srctesting.Check(t, fset, srctesting.Parse(t, fset, src)) + f := srctesting.New(t) + _, pkg := f.Check("pkg/test", f.Parse("test.go", src)) mustType := testingx.Must[types.Type](t) tests := []struct { @@ -53,7 +52,7 @@ func TestInstanceString(t *testing.T) { Object: pkg.Scope().Lookup("Typ"), TArgs: []types.Type{types.Typ[types.Int], types.Typ[types.String]}, }, - wantStr: "test.Typ", + wantStr: "pkg/test.Typ", wantTypeString: "testcase.Typ[int, string]", }, { descr: "exported method", @@ -61,21 +60,21 @@ func TestInstanceString(t *testing.T) { Object: pkg.Scope().Lookup("Typ").Type().(*types.Named).Method(0), TArgs: []types.Type{types.Typ[types.Int], types.Typ[types.String]}, }, - wantStr: "test.Typ.Method", + wantStr: "pkg/test.Typ.Method", }, { descr: "exported function", instance: Instance{ Object: pkg.Scope().Lookup("Fun"), TArgs: []types.Type{types.Typ[types.Int], types.Typ[types.String]}, }, - wantStr: "test.Fun", + wantStr: "pkg/test.Fun", }, { descr: "unexported type", instance: Instance{ Object: pkg.Scope().Lookup("typ"), TArgs: []types.Type{types.Typ[types.Int], types.Typ[types.String]}, }, - wantStr: "test.typ", + wantStr: "pkg/test.typ", wantTypeString: "testcase.typ[int, string]", }, { descr: "unexported method", @@ -83,20 +82,20 @@ func TestInstanceString(t *testing.T) { Object: pkg.Scope().Lookup("typ").Type().(*types.Named).Method(0), TArgs: []types.Type{types.Typ[types.Int], types.Typ[types.String]}, }, - wantStr: "test.typ.method", + wantStr: "pkg/test.typ.method", }, { descr: "unexported function", instance: Instance{ Object: pkg.Scope().Lookup("fun"), TArgs: []types.Type{types.Typ[types.Int], types.Typ[types.String]}, }, - wantStr: "test.fun", + wantStr: "pkg/test.fun", }, { descr: "no type params", instance: Instance{ Object: pkg.Scope().Lookup("Ints"), }, - wantStr: "test.Ints", + wantStr: "pkg/test.Ints", wantTypeString: "testcase.Ints", }, { descr: "complex parameter type", @@ -110,7 +109,7 @@ func TestInstanceString(t *testing.T) { }, true)), }, }, - wantStr: "test.fun<[]int, test.typ[int, string]>", + wantStr: "pkg/test.fun<[]int, pkg/test.typ[int, string]>", }} for _, test := range tests { @@ -134,8 +133,8 @@ func TestInstanceQueue(t *testing.T) { type Typ[T any, V any] []T func Fun[U any, W any](x, y U) {} ` - fset := token.NewFileSet() - _, pkg := srctesting.Check(t, fset, srctesting.Parse(t, fset, src)) + f := srctesting.New(t) + _, pkg := f.Check("pkg/test", f.Parse("test.go", src)) i1 := Instance{ Object: pkg.Scope().Lookup("Typ"), diff --git a/compiler/typesutil/typenames_test.go b/compiler/typesutil/typenames_test.go index 332a5973f..1e8a4b994 100644 --- a/compiler/typesutil/typenames_test.go +++ b/compiler/typesutil/typenames_test.go @@ -1,7 +1,6 @@ package typesutil import ( - "go/token" "go/types" "testing" @@ -24,8 +23,8 @@ func TestTypeNames(t *testing.T) { type B int type C int ` - fset := token.NewFileSet() - _, pkg := srctesting.Check(t, fset, srctesting.Parse(t, fset, src)) + f := srctesting.New(t) + _, pkg := f.Check("pkg/test", f.Parse("test.go", src)) A := srctesting.LookupObj(pkg, "A").(*types.TypeName) B := srctesting.LookupObj(pkg, "B").(*types.TypeName) C := srctesting.LookupObj(pkg, "C").(*types.TypeName) diff --git a/internal/srctesting/srctesting.go b/internal/srctesting/srctesting.go index 37ee248e9..4bae410e4 100644 --- a/internal/srctesting/srctesting.go +++ b/internal/srctesting/srctesting.go @@ -4,6 +4,7 @@ package srctesting import ( "bytes" + "fmt" "go/ast" "go/format" "go/parser" @@ -13,41 +14,67 @@ import ( "testing" ) +// Fixture provides utilities for parsing and type checking Go code in tests. +type Fixture struct { + T *testing.T + FileSet *token.FileSet + Info *types.Info + Packages map[string]*types.Package +} + +// New creates a fresh Fixture. +func New(t *testing.T) *Fixture { + return &Fixture{ + T: t, + FileSet: token.NewFileSet(), + Info: &types.Info{ + Types: make(map[ast.Expr]types.TypeAndValue), + Defs: make(map[*ast.Ident]types.Object), + Uses: make(map[*ast.Ident]types.Object), + Implicits: make(map[ast.Node]types.Object), + Selections: make(map[*ast.SelectorExpr]*types.Selection), + Scopes: make(map[ast.Node]*types.Scope), + Instances: make(map[*ast.Ident]types.Instance), + }, + Packages: map[string]*types.Package{}, + } +} + // Parse source from the string and return complete AST. -// -// Assumes source file name `test.go`. Fails the test on parsing error. -func Parse(t *testing.T, fset *token.FileSet, src string) *ast.File { - t.Helper() - f, err := parser.ParseFile(fset, "test.go", src, parser.ParseComments) +func (f *Fixture) Parse(name, src string) *ast.File { + f.T.Helper() + file, err := parser.ParseFile(f.FileSet, name, src, parser.ParseComments) if err != nil { - t.Fatalf("Failed to parse test source: %s", err) + f.T.Fatalf("Failed to parse test source: %s", err) } - return f + return file } // Check type correctness of the provided AST. // -// Assumes "test" package import path. Fails the test if type checking fails. -// Provided AST is expected not to have any imports. -func Check(t *testing.T, fset *token.FileSet, files ...*ast.File) (*types.Info, *types.Package) { - t.Helper() - typesInfo := &types.Info{ - Types: make(map[ast.Expr]types.TypeAndValue), - Defs: make(map[*ast.Ident]types.Object), - Uses: make(map[*ast.Ident]types.Object), - Implicits: make(map[ast.Node]types.Object), - Selections: make(map[*ast.SelectorExpr]*types.Selection), - Scopes: make(map[ast.Node]*types.Scope), - Instances: make(map[*ast.Ident]types.Instance), - } +// Fails the test if type checking fails. Provided AST is expected not to have +// any imports. +func (f *Fixture) Check(importPath string, files ...*ast.File) (*types.Info, *types.Package) { + f.T.Helper() config := &types.Config{ - Sizes: &types.StdSizes{WordSize: 4, MaxAlign: 8}, + Sizes: &types.StdSizes{WordSize: 4, MaxAlign: 8}, + Importer: f, } - typesPkg, err := config.Check("test", fset, files, typesInfo) + pkg, err := config.Check(importPath, f.FileSet, files, f.Info) if err != nil { - t.Fatalf("Filed to type check test source: %s", err) + f.T.Fatalf("Filed to type check test source: %s", err) + } + f.Packages[importPath] = pkg + return f.Info, pkg +} + +// Import implements types.Importer. +func (f *Fixture) Import(path string) (*types.Package, error) { + pkg, ok := f.Packages[path] + if !ok { + return nil, fmt.Errorf("missing type info for package %q", path) } - return typesInfo, typesPkg + return pkg, nil } // ParseFuncDecl parses source with a single function defined and returns the @@ -70,8 +97,7 @@ func ParseFuncDecl(t *testing.T, src string) *ast.FuncDecl { // Fails the test if there isn't exactly one declaration in the source. func ParseDecl(t *testing.T, src string) ast.Decl { t.Helper() - fset := token.NewFileSet() - file := Parse(t, fset, src) + file := New(t).Parse("test.go", src) if l := len(file.Decls); l != 1 { t.Fatalf(`Got %d decls in the sources, expected exactly 1`, l) } diff --git a/internal/srctesting/srctesting_test.go b/internal/srctesting/srctesting_test.go new file mode 100644 index 000000000..44fa51ead --- /dev/null +++ b/internal/srctesting/srctesting_test.go @@ -0,0 +1,28 @@ +package srctesting + +import "testing" + +func TestFixture(t *testing.T) { + f := New(t) + + const src1 = `package foo + type X int + ` + _, foo := f.Check("pkg/foo", f.Parse("foo.go", src1)) + + if !foo.Complete() { + t.Fatalf("Got: incomplete package pkg/foo: %s. Want: complete package.", foo) + } + + const src2 = `package bar + import "pkg/foo" + func Fun() foo.X { return 0 } + ` + + // Should type check successfully with dependency on pkg/foo. + _, bar := f.Check("pkg/bar", f.Parse("bar.go", src2)) + + if !bar.Complete() { + t.Fatalf("Got: incomplete package pkg/bar: %s. Want: complete package.", foo) + } +}