diff --git a/go/ssa/builder_generic_test.go b/go/ssa/builder_generic_test.go index 55dc79fe464..f5e5fa6e482 100644 --- a/go/ssa/builder_generic_test.go +++ b/go/ssa/builder_generic_test.go @@ -842,16 +842,6 @@ func TestInstructionString(t *testing.T) { } } -// packageName is a test helper to extract the package name from a string -// containing the content of a go file. -func packageName(t testing.TB, content string) string { - f, err := parser.ParseFile(token.NewFileSet(), "", content, parser.PackageClauseOnly) - if err != nil { - t.Fatalf("parsing the file %q failed with error: %s", content, err) - } - return f.Name.Name -} - func logFunction(t testing.TB, fn *ssa.Function) { // TODO: Consider adding a ssa.Function.GoString() so this can be logged to t via '%#v'. var buf bytes.Buffer diff --git a/go/ssa/builder_test.go b/go/ssa/builder_test.go index 6ef8a86d728..724cdbd47c4 100644 --- a/go/ssa/builder_test.go +++ b/go/ssa/builder_test.go @@ -556,23 +556,7 @@ func h(error) // t8 = phi [1: t7, 3: t4] #e // ... - // Parse - var conf loader.Config - f, err := conf.ParseFile("", input) - if err != nil { - t.Fatalf("parse: %v", err) - } - conf.CreateFromFiles("p", f) - - // Load - lprog, err := conf.Load() - if err != nil { - t.Fatalf("Load: %v", err) - } - - // Create and build SSA - prog := ssautil.CreateProgram(lprog, ssa.BuilderMode(0)) - p := prog.Package(lprog.Package("p").Pkg) + p := loadPackageFromSingleFile(t, input, ssa.BuilderMode(0)).spkg p.Build() g := p.Func("g") @@ -622,23 +606,7 @@ func LoadPointer(addr *unsafe.Pointer) (val unsafe.Pointer) // func init func() // var init$guard bool - // Parse - var conf loader.Config - f, err := conf.ParseFile("", input) - if err != nil { - t.Fatalf("parse: %v", err) - } - conf.CreateFromFiles("p", f) - - // Load - lprog, err := conf.Load() - if err != nil { - t.Fatalf("Load: %v", err) - } - - // Create and build SSA - prog := ssautil.CreateProgram(lprog, ssa.BuilderMode(0)) - p := prog.Package(lprog.Package("p").Pkg) + p := loadPackageFromSingleFile(t, input, ssa.BuilderMode(0)).spkg p.Build() if load := p.Func("Load"); load.Signature.TypeParams().Len() != 1 { @@ -809,31 +777,22 @@ func TestTypeparamTest(t *testing.T) { t.Logf("Input: %s\n", input) - ctx := build.Default // copy - ctx.GOROOT = "testdata" // fake goroot. Makes tests ~1s. tests take ~80s. - - reportErr := func(err error) { - t.Error(err) - } - conf := loader.Config{Build: &ctx, TypeChecker: types.Config{Error: reportErr}} - if _, err := conf.FromArgs([]string{input}, true); err != nil { - t.Fatalf("FromArgs(%s) failed: %s", input, err) - } - - iprog, err := conf.Load() - if iprog != nil { - for _, pkg := range iprog.Created { - for i, e := range pkg.Errors { - t.Errorf("Loading pkg %s error[%d]=%s", pkg, i, e) - } - } - } + pkgs, err := packages.Load(&packages.Config{ + Mode: packages.NeedSyntax | + packages.NeedTypesInfo | + packages.NeedDeps | + packages.NeedName | + packages.NeedFiles | + packages.NeedImports | + packages.NeedCompiledGoFiles | + packages.NeedTypes, + }, input) if err != nil { - t.Fatalf("conf.Load(%s) failed: %s", input, err) + t.Fatalf("fail to load pkgs from file %s", input) } mode := ssa.SanityCheckFunctions | ssa.InstantiateGenerics - prog := ssautil.CreateProgram(iprog, mode) + prog, _ := ssautil.Packages(pkgs, mode) prog.Build() }) } @@ -856,23 +815,7 @@ func sliceMax(s []int) []int { return s[a():b():c()] } ` - // Parse - var conf loader.Config - f, err := conf.ParseFile("", input) - if err != nil { - t.Fatalf("parse: %v", err) - } - conf.CreateFromFiles("p", f) - - // Load - lprog, err := conf.Load() - if err != nil { - t.Fatalf("Load: %v", err) - } - - // Create and build SSA - prog := ssautil.CreateProgram(lprog, ssa.BuilderMode(0)) - p := prog.Package(lprog.Package("p").Pkg) + p := loadPackageFromSingleFile(t, input, ssa.BuilderMode(0)).spkg p.Build() for _, item := range []struct { @@ -1176,20 +1119,7 @@ func TestLabels(t *testing.T) { func main() { _:println(1); _:println(2)}`, } for _, test := range tests { - conf := loader.Config{Fset: token.NewFileSet()} - f, err := parser.ParseFile(conf.Fset, "", test, 0) - if err != nil { - t.Errorf("parse error: %s", err) - return - } - conf.CreateFromFiles("main", f) - iprog, err := conf.Load() - if err != nil { - t.Error(err) - continue - } - prog := ssautil.CreateProgram(iprog, ssa.BuilderMode(0)) - pkg := prog.Package(iprog.Created[0].Pkg) + pkg := loadPackageFromSingleFile(t, test, ssa.BuilderMode(0)).spkg pkg.Build() } } @@ -1224,20 +1154,10 @@ func TestIssue67079(t *testing.T) { // Load the package. const src = `package p; type T int; func (T) f() {}; var _ = (*T).f` - conf := loader.Config{Fset: token.NewFileSet()} - f, err := parser.ParseFile(conf.Fset, "p.go", src, 0) - if err != nil { - t.Fatal(err) - } - conf.CreateFromFiles("p", f) - iprog, err := conf.Load() - if err != nil { - t.Fatal(err) - } - pkg := iprog.Created[0].Pkg - // Create and build SSA program. - prog := ssautil.CreateProgram(iprog, ssa.BuilderMode(0)) + p := loadPackageFromSingleFile(t, src, ssa.BuilderMode(0)).spkg + pkg := p.Pkg + prog := p.Prog prog.Build() var g errgroup.Group diff --git a/go/ssa/instantiate_test.go b/go/ssa/instantiate_test.go index fcf682c88a7..b36dc2bda21 100644 --- a/go/ssa/instantiate_test.go +++ b/go/ssa/instantiate_test.go @@ -5,6 +5,7 @@ package ssa // Note: Tests use unexported method _Instances. +// TODO(yuchen): remove deprecated loader after these test cases are moved to ssa_test. import ( "bytes" diff --git a/go/ssa/source_test.go b/go/ssa/source_test.go index 112581bb55b..3e294b95768 100644 --- a/go/ssa/source_test.go +++ b/go/ssa/source_test.go @@ -30,18 +30,34 @@ func TestObjValueLookup(t *testing.T) { t.Skipf("no testdata directory on %s", runtime.GOOS) } - conf := loader.Config{ParserMode: parser.ParseComments} - src, err := os.ReadFile("testdata/objlookup.go") + src, err := os.ReadFile("testdata/objlookup.txtar") if err != nil { t.Fatal(err) } - readFile := func(filename string) ([]byte, error) { return src, nil } - f, err := conf.ParseFile("testdata/objlookup.go", src) - if err != nil { - t.Fatal(err) + pkgs := packagesFromArchive(t, string(src)) + prog, _ := ssautil.Packages(pkgs, ssa.BuilderMode(0)) + + info := getPkgInfo(prog, pkgs, "main") + + if info == nil { + t.Fatalf("fail to get package main from loaded packages") } - conf.CreateFromFiles("main", f) + conf := info.ppkg + mainInfo := conf.TypesInfo + + f := info.file + mainPkg := info.spkg + + readFile := func(_ string) ([]byte, error) { + // split the file content to get the exact file content, + // instead of using go/printer which re-formats the file and the positions are no longer exact + strs := strings.SplitAfter(string(src), "-- objlookup.go --\n") + if len(strs) != 2 { + t.Fatalf("expect to get 2 parts after splitting but got %d", len(strs)) + } + return []byte(strs[1]), nil + } // Maps each var Ident (represented "name:linenum") to the // kind of ssa.Value we expect (represented "Constant", "&Alloc"). expectations := make(map[string]string) @@ -82,15 +98,6 @@ func TestObjValueLookup(t *testing.T) { expectations[key] = exp } - iprog, err := conf.Load() - if err != nil { - t.Error(err) - return - } - - prog := ssautil.CreateProgram(iprog, ssa.BuilderMode(0) /*|ssa.PrintFunctions*/) - mainInfo := iprog.Created[0] - mainPkg := prog.Package(mainInfo.Pkg) mainPkg.SetDebugMode(true) mainPkg.Build() @@ -222,11 +229,11 @@ func checkVarValue(t *testing.T, prog *ssa.Program, pkg *ssa.Package, ref []ast. // Ensure that, in debug mode, we can determine the ssa.Value // corresponding to every ast.Expr. func TestValueForExpr(t *testing.T) { - testValueForExpr(t, "testdata/valueforexpr.go") + testValueForExpr(t, "testdata/valueforexpr.txtar") } func TestValueForExprStructConv(t *testing.T) { - testValueForExpr(t, "testdata/structconv.go") + testValueForExpr(t, "testdata/structconv.txtar") } func testValueForExpr(t *testing.T, testfile string) { @@ -234,24 +241,23 @@ func testValueForExpr(t *testing.T, testfile string) { t.Skipf("no testdata dir on %s", runtime.GOOS) } - conf := loader.Config{ParserMode: parser.ParseComments} - f, err := conf.ParseFile(testfile, nil) + src, err := os.ReadFile(testfile) if err != nil { - t.Error(err) - return + t.Fatal(err) } - conf.CreateFromFiles("main", f) - iprog, err := conf.Load() - if err != nil { - t.Error(err) - return - } + pkgs := packagesFromArchive(t, string(src)) + prog, _ := ssautil.Packages(pkgs, ssa.BuilderMode(0)) - mainInfo := iprog.Created[0] + info := getPkgInfo(prog, pkgs, "main") + + if info == nil { + t.Fatalf("fail to get package main from loaded packages") + } + mainInfo := info.ppkg.TypesInfo + f := info.file + mainPkg := info.spkg - prog := ssautil.CreateProgram(iprog, ssa.BuilderMode(0)) - mainPkg := prog.Package(mainInfo.Pkg) mainPkg.SetDebugMode(true) mainPkg.Build() diff --git a/go/ssa/testdata/objlookup.go b/go/ssa/testdata/objlookup.txtar similarity index 98% rename from go/ssa/testdata/objlookup.go rename to go/ssa/testdata/objlookup.txtar index b040d747333..649d1f9bc8a 100644 --- a/go/ssa/testdata/objlookup.go +++ b/go/ssa/testdata/objlookup.txtar @@ -1,4 +1,8 @@ -// +build ignore +-- go.mod -- +module example.com +go 1.18 + +-- objlookup.go -- package main diff --git a/go/ssa/testdata/structconv.go b/go/ssa/testdata/structconv.txtar similarity index 78% rename from go/ssa/testdata/structconv.go rename to go/ssa/testdata/structconv.txtar index c0b4b840ee5..981a03662b8 100644 --- a/go/ssa/testdata/structconv.go +++ b/go/ssa/testdata/structconv.txtar @@ -1,6 +1,9 @@ -// +build ignore +-- go.mod -- +module example.com +go 1.18 -// This file is the input to TestValueForExprStructConv in identical_test.go, +-- structconv.go -- +// This file is the input to TestValueForExprStructConv in source_test.go, // which uses the same framework as TestValueForExpr does in source_test.go. // // In Go 1.8, struct conversions are permitted even when the struct types have diff --git a/go/ssa/testdata/valueforexpr.go b/go/ssa/testdata/valueforexpr.txtar similarity index 98% rename from go/ssa/testdata/valueforexpr.go rename to go/ssa/testdata/valueforexpr.txtar index 703c316a707..bf8543e0041 100644 --- a/go/ssa/testdata/valueforexpr.go +++ b/go/ssa/testdata/valueforexpr.txtar @@ -1,6 +1,8 @@ -//go:build ignore -// +build ignore +-- go.mod -- +module example.com +go 1.18 +-- valueforexpr.go -- package main // This file is the input to TestValueForExpr in source_test.go, which diff --git a/go/ssa/testutil_test.go b/go/ssa/testutil_test.go new file mode 100644 index 00000000000..1470a33839e --- /dev/null +++ b/go/ssa/testutil_test.go @@ -0,0 +1,119 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssa_test + +import ( + "fmt" + "go/ast" + "go/parser" + "go/token" + "testing" + + "golang.org/x/tools/go/ssa" + "golang.org/x/tools/go/ssa/ssautil" + + "golang.org/x/tools/go/packages" + "golang.org/x/tools/internal/testfiles" + "golang.org/x/tools/txtar" +) + +// loadPackageFromSingleFile is a utility function to creates a package based on the content of a go file, +// and returns the pkgInfo about the input go file. The package name is retrieved from content after parsing. +// It's useful when you want to create a ssa package and its packages.Package and ast.File representation. +func loadPackageFromSingleFile(t *testing.T, content string, mode ssa.BuilderMode) *pkgInfo { + ar := archiveFromSingleFileContent(content) + pkgs := packagesFromArchive(t, ar) + prog, _ := ssautil.Packages(pkgs, mode) + + pkgName := packageName(t, content) + pkgInfo := getPkgInfo(prog, pkgs, pkgName) + if pkgInfo == nil { + t.Fatalf("fail to get package %s from loaded packages", pkgName) + } + return pkgInfo +} + +// archiveFromSingleFileContent creates a go module named example.com +// in txtar format and put the given content in main.go file under the module. +// The package is decided by the package clause in the content. +// The content should contain no error as a typical go file. +// +// It's useful when we want to define a package in a string variable instead of putting it inside a file. +func archiveFromSingleFileContent(content string) string { + return fmt.Sprintf(` +-- go.mod -- +module example.com +go 1.18 +-- main.go -- +%s`, content) +} + +// packagesFromArchive creates the packages from archive with txtar format. +func packagesFromArchive(t *testing.T, archive string) []*packages.Package { + ar := txtar.Parse([]byte(archive)) + + fs, err := txtar.FS(ar) + if err != nil { + t.Fatal(err) + } + + dir := testfiles.CopyToTmp(t, fs) + if err != nil { + t.Fatal(err) + } + + var baseConfig = &packages.Config{ + Mode: packages.NeedSyntax | + packages.NeedTypesInfo | + packages.NeedDeps | + packages.NeedName | + packages.NeedFiles | + packages.NeedImports | + packages.NeedCompiledGoFiles | + packages.NeedTypes, + Dir: dir, + } + pkgs, err := packages.Load(baseConfig, "./...") + if err != nil { + t.Fatal(err) + } + if num := packages.PrintErrors(pkgs); num > 0 { + t.Fatalf("packages contained %d errors", num) + } + return pkgs +} + +// pkgInfo is a ssa package with its packages.Package and ast file. +// We assume the package in test only have one file. +type pkgInfo struct { + spkg *ssa.Package // ssa representation of a package + ppkg *packages.Package // packages representation of a package + file *ast.File // AST representation of the first package file +} + +// getPkgInfo retrieves the package info from the program with the given name. +// It's useful when you loaded a package from file instead of defining it directly as a string. +func getPkgInfo(prog *ssa.Program, pkgs []*packages.Package, pkgname string) *pkgInfo { + for _, pkg := range pkgs { + if pkg.Name == pkgname { + return &pkgInfo{ + spkg: prog.Package(pkg.Types), + ppkg: pkg, + file: pkg.Syntax[0], // we assume the test package is only consisted by one file + } + } + } + return nil +} + +// packageName is a test helper to extract the package name from a string +// containing the content of a go file. +func packageName(t testing.TB, content string) string { + f, err := parser.ParseFile(token.NewFileSet(), "", content, parser.PackageClauseOnly) + if err != nil { + t.Fatalf("parsing the file %q failed with error: %s", content, err) + } + return f.Name.Name +}