@@ -6,6 +6,7 @@ package test
6
6
7
7
import (
8
8
"bufio"
9
+ "bytes"
9
10
"fmt"
10
11
"internal/profile"
11
12
"internal/testenv"
@@ -17,11 +18,7 @@ import (
17
18
"testing"
18
19
)
19
20
20
- // testPGOIntendedInlining tests that specific functions are inlined.
21
- func testPGOIntendedInlining (t * testing.T , dir string ) {
22
- testenv .MustHaveGoRun (t )
23
- t .Parallel ()
24
-
21
+ func buildPGOInliningTest (t * testing.T , dir string , flags ... string ) []byte {
25
22
const pkg = "example.com/pgo/inline"
26
23
27
24
// Add a go.mod so we have a consistent symbol names in this temp dir.
@@ -32,6 +29,25 @@ go 1.19
32
29
t .Fatalf ("error writing go.mod: %v" , err )
33
30
}
34
31
32
+ exe := filepath .Join (dir , "test.exe" )
33
+ args := []string {"test" , "-c" , "-o" , exe }
34
+ args = append (args , flags ... )
35
+ cmd := testenv .CleanCmdEnv (testenv .Command (t , testenv .GoToolPath (t ), args ... ))
36
+ cmd .Dir = dir
37
+ out , err := cmd .CombinedOutput ()
38
+ if err != nil {
39
+ t .Fatalf ("build failed: %v, output:\n %s" , err , out )
40
+ }
41
+ return out
42
+ }
43
+
44
+ // testPGOIntendedInlining tests that specific functions are inlined.
45
+ func testPGOIntendedInlining (t * testing.T , dir string ) {
46
+ testenv .MustHaveGoRun (t )
47
+ t .Parallel ()
48
+
49
+ const pkg = "example.com/pgo/inline"
50
+
35
51
want := []string {
36
52
"(*BS).NS" ,
37
53
}
@@ -71,25 +87,9 @@ go 1.19
71
87
// TODO: maybe adjust the test to work with default threshold.
72
88
pprof := filepath .Join (dir , "inline_hot.pprof" )
73
89
gcflag := fmt .Sprintf ("-gcflags=-m -m -pgoprofile=%s -d=pgoinlinebudget=160,pgoinlinecdfthreshold=90" , pprof )
74
- out := filepath .Join (dir , "test.exe" )
75
- cmd := testenv .CleanCmdEnv (testenv .Command (t , testenv .GoToolPath (t ), "test" , "-c" , "-o" , out , gcflag , "." ))
76
- cmd .Dir = dir
77
-
78
- pr , pw , err := os .Pipe ()
79
- if err != nil {
80
- t .Fatalf ("error creating pipe: %v" , err )
81
- }
82
- defer pr .Close ()
83
- cmd .Stdout = pw
84
- cmd .Stderr = pw
85
-
86
- err = cmd .Start ()
87
- pw .Close ()
88
- if err != nil {
89
- t .Fatalf ("error starting go test: %v" , err )
90
- }
90
+ out := buildPGOInliningTest (t , dir , gcflag )
91
91
92
- scanner := bufio .NewScanner (pr )
92
+ scanner := bufio .NewScanner (bytes . NewReader ( out ) )
93
93
curPkg := ""
94
94
canInline := regexp .MustCompile (`: can inline ([^ ]*)` )
95
95
haveInlined := regexp .MustCompile (`: inlining call to ([^ ]*)` )
@@ -128,11 +128,8 @@ go 1.19
128
128
continue
129
129
}
130
130
}
131
- if err := cmd .Wait (); err != nil {
132
- t .Fatalf ("error running go test: %v" , err )
133
- }
134
131
if err := scanner .Err (); err != nil {
135
- t .Fatalf ("error reading go test output: %v" , err )
132
+ t .Fatalf ("error reading output: %v" , err )
136
133
}
137
134
for fullName , reason := range notInlinedReason {
138
135
t .Errorf ("%s was not inlined: %s" , fullName , reason )
@@ -297,3 +294,48 @@ func copyFile(dst, src string) error {
297
294
_ , err = io .Copy (d , s )
298
295
return err
299
296
}
297
+
298
+ // TestPGOHash tests that PGO optimization decisions can be selected by pgohash.
299
+ func TestPGOHash (t * testing.T ) {
300
+ testenv .MustHaveGoRun (t )
301
+ t .Parallel ()
302
+
303
+ wd , err := os .Getwd ()
304
+ if err != nil {
305
+ t .Fatalf ("error getting wd: %v" , err )
306
+ }
307
+ srcDir := filepath .Join (wd , "testdata/pgo/inline" )
308
+
309
+ // Copy the module to a scratch location so we can add a go.mod.
310
+ dir := t .TempDir ()
311
+
312
+ for _ , file := range []string {"inline_hot.go" , "inline_hot_test.go" , "inline_hot.pprof" } {
313
+ if err := copyFile (filepath .Join (dir , file ), filepath .Join (srcDir , file )); err != nil {
314
+ t .Fatalf ("error copying %s: %v" , file , err )
315
+ }
316
+ }
317
+
318
+ pprof := filepath .Join (dir , "inline_hot.pprof" )
319
+ gcflag0 := fmt .Sprintf ("-gcflags=-pgoprofile=%s -d=pgoinlinebudget=160,pgoinlinecdfthreshold=90,pgodebug=1," , pprof )
320
+
321
+ // Check that a hash match allows PGO inlining.
322
+ const srcPos = "example.com/pgo/inline/inline_hot.go:81:19"
323
+ const hashMatch = "pgohash triggered " + srcPos + " (inline)"
324
+ pgoDebugRE := regexp .MustCompile (`hot-budget check allows inlining for call .* at ` + strings .ReplaceAll (srcPos , "." , "\\ ." ))
325
+ hash := "v1" // 1 matches srcPos, v for verbose (print source location)
326
+ gcflag := gcflag0 + ",pgohash=" + hash
327
+ // build with -trimpath so the source location (thus the hash)
328
+ // does not depend on the temporary directory path.
329
+ out := buildPGOInliningTest (t , dir , gcflag , "-trimpath" )
330
+ if ! bytes .Contains (out , []byte (hashMatch )) || ! pgoDebugRE .Match (out ) {
331
+ t .Errorf ("output does not contain expected source line, out:\n %s" , out )
332
+ }
333
+
334
+ // Check that a hash mismatch turns off PGO inlining.
335
+ hash = "v0" // 0 should not match srcPos
336
+ gcflag = gcflag0 + ",pgohash=" + hash
337
+ out = buildPGOInliningTest (t , dir , gcflag , "-trimpath" )
338
+ if bytes .Contains (out , []byte (hashMatch )) || pgoDebugRE .Match (out ) {
339
+ t .Errorf ("output contains unexpected source line, out:\n %s" , out )
340
+ }
341
+ }
0 commit comments