1 // Copyright 2023 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
17 // testPGODevirtualize tests that specific PGO devirtualize rewrites are performed.
18 func testPGODevirtualize(t *testing.T, dir string) {
19 testenv.MustHaveGoRun(t)
22 const pkg = "example.com/pgo/devirtualize"
24 // Add a go.mod so we have a consistent symbol names in this temp dir.
25 goMod := fmt.Sprintf(`module %s
28 if err := os.WriteFile(filepath.Join(dir, "go.mod"), []byte(goMod), 0644); err != nil {
29 t.Fatalf("error writing go.mod: %v", err)
32 // Run the test without PGO to ensure that the test assertions are
33 // correct even in the non-optimized version.
34 cmd := testenv.CleanCmdEnv(testenv.Command(t, testenv.GoToolPath(t), "test", "."))
36 b, err := cmd.CombinedOutput()
37 t.Logf("Test without PGO:\n%s", b)
39 t.Fatalf("Test failed without PGO: %v", err)
42 // Build the test with the profile.
43 pprof := filepath.Join(dir, "devirt.pprof")
44 gcflag := fmt.Sprintf("-gcflags=-m=2 -pgoprofile=%s -d=pgodebug=3", pprof)
45 out := filepath.Join(dir, "test.exe")
46 cmd = testenv.CleanCmdEnv(testenv.Command(t, testenv.GoToolPath(t), "test", "-o", out, gcflag, "."))
49 pr, pw, err := os.Pipe()
51 t.Fatalf("error creating pipe: %v", err)
60 t.Fatalf("error starting go test: %v", err)
63 type devirtualization struct {
68 want := []devirtualization{
71 pos: "./devirt.go:101:20",
72 callee: "mult.Mult.Multiply",
75 pos: "./devirt.go:101:39",
78 // ExerciseFuncConcrete
80 pos: "./devirt.go:178:18",
83 // TODO(prattmic): Export data lookup for function value callees not implemented.
85 // pos: "./devirt.go:179:15",
86 // callee: "mult.MultFn",
90 pos: "./devirt.go:218:13",
93 // TODO(prattmic): Export data lookup for function value callees not implemented.
95 // pos: "./devirt.go:219:19",
96 // callee: "mult.MultFn",
98 // ExerciseFuncClosure
99 // TODO(prattmic): Closure callees not implemented.
101 // pos: "./devirt.go:266:9",
102 // callee: "AddClosure.func1",
105 // pos: "./devirt.go:267:15",
106 // callee: "mult.MultClosure.func1",
110 got := make(map[devirtualization]struct{})
112 devirtualizedLine := regexp.MustCompile(`(.*): PGO devirtualizing \w+ call .* to (.*)`)
114 scanner := bufio.NewScanner(pr)
116 line := scanner.Text()
117 t.Logf("child: %s", line)
119 m := devirtualizedLine.FindStringSubmatch(line)
124 d := devirtualization{
130 if err := cmd.Wait(); err != nil {
131 t.Fatalf("error running go test: %v", err)
133 if err := scanner.Err(); err != nil {
134 t.Fatalf("error reading go test output: %v", err)
137 if len(got) != len(want) {
138 t.Errorf("mismatched devirtualization count; got %v want %v", got, want)
140 for _, w := range want {
141 if _, ok := got[w]; ok {
144 t.Errorf("devirtualization %v missing; got %v", w, got)
147 // Run test with PGO to ensure the assertions are still true.
148 cmd = testenv.CleanCmdEnv(testenv.Command(t, out))
150 b, err = cmd.CombinedOutput()
151 t.Logf("Test with PGO:\n%s", b)
153 t.Fatalf("Test failed without PGO: %v", err)
157 // TestPGODevirtualize tests that specific functions are devirtualized when PGO
158 // is applied to the exact source that was profiled.
159 func TestPGODevirtualize(t *testing.T) {
160 wd, err := os.Getwd()
162 t.Fatalf("error getting wd: %v", err)
164 srcDir := filepath.Join(wd, "testdata", "pgo", "devirtualize")
166 // Copy the module to a scratch location so we can add a go.mod.
168 if err := os.Mkdir(filepath.Join(dir, "mult.pkg"), 0755); err != nil {
169 t.Fatalf("error creating dir: %v", err)
171 for _, file := range []string{"devirt.go", "devirt_test.go", "devirt.pprof", filepath.Join("mult.pkg", "mult.go")} {
172 if err := copyFile(filepath.Join(dir, file), filepath.Join(srcDir, file)); err != nil {
173 t.Fatalf("error copying %s: %v", file, err)
177 testPGODevirtualize(t, dir)