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
+}