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:173:36",
84 pos: "./devirt.go:173:15",
85 callee: "mult.MultFn",
89 pos: "./devirt.go:207:35",
93 pos: "./devirt.go:207:19",
94 callee: "mult.MultFn",
96 // ExerciseFuncClosure
97 // TODO(prattmic): Closure callees not implemented.
99 // pos: "./devirt.go:249:27",
100 // callee: "AddClosure.func1",
103 // pos: "./devirt.go:249:15",
104 // callee: "mult.MultClosure.func1",
108 got := make(map[devirtualization]struct{})
110 devirtualizedLine := regexp.MustCompile(`(.*): PGO devirtualizing \w+ call .* to (.*)`)
112 scanner := bufio.NewScanner(pr)
114 line := scanner.Text()
115 t.Logf("child: %s", line)
117 m := devirtualizedLine.FindStringSubmatch(line)
122 d := devirtualization{
128 if err := cmd.Wait(); err != nil {
129 t.Fatalf("error running go test: %v", err)
131 if err := scanner.Err(); err != nil {
132 t.Fatalf("error reading go test output: %v", err)
135 if len(got) != len(want) {
136 t.Errorf("mismatched devirtualization count; got %v want %v", got, want)
138 for _, w := range want {
139 if _, ok := got[w]; ok {
142 t.Errorf("devirtualization %v missing; got %v", w, got)
145 // Run test with PGO to ensure the assertions are still true.
146 cmd = testenv.CleanCmdEnv(testenv.Command(t, out))
148 b, err = cmd.CombinedOutput()
149 t.Logf("Test with PGO:\n%s", b)
151 t.Fatalf("Test failed without PGO: %v", err)
155 // TestPGODevirtualize tests that specific functions are devirtualized when PGO
156 // is applied to the exact source that was profiled.
157 func TestPGODevirtualize(t *testing.T) {
158 wd, err := os.Getwd()
160 t.Fatalf("error getting wd: %v", err)
162 srcDir := filepath.Join(wd, "testdata", "pgo", "devirtualize")
164 // Copy the module to a scratch location so we can add a go.mod.
166 if err := os.Mkdir(filepath.Join(dir, "mult.pkg"), 0755); err != nil {
167 t.Fatalf("error creating dir: %v", err)
169 for _, file := range []string{"devirt.go", "devirt_test.go", "devirt.pprof", filepath.Join("mult.pkg", "mult.go")} {
170 if err := copyFile(filepath.Join(dir, file), filepath.Join(srcDir, file)); err != nil {
171 t.Fatalf("error copying %s: %v", file, err)
175 testPGODevirtualize(t, dir)