1  
     2  
     3  
     4  
     5  package test
     6  
     7  import (
     8  	"bufio"
     9  	"bytes"
    10  	"fmt"
    11  	"internal/profile"
    12  	"internal/testenv"
    13  	"io"
    14  	"os"
    15  	"path/filepath"
    16  	"regexp"
    17  	"strings"
    18  	"testing"
    19  )
    20  
    21  func buildPGOInliningTest(t *testing.T, dir string, gcflag string) []byte {
    22  	const pkg = "example.com/pgo/inline"
    23  
    24  	
    25  	goMod := fmt.Sprintf(`module %s
    26  go 1.19
    27  `, pkg)
    28  	if err := os.WriteFile(filepath.Join(dir, "go.mod"), []byte(goMod), 0644); err != nil {
    29  		t.Fatalf("error writing go.mod: %v", err)
    30  	}
    31  
    32  	exe := filepath.Join(dir, "test.exe")
    33  	args := []string{"test", "-c", "-o", exe, "-gcflags=" + gcflag}
    34  	cmd := testenv.Command(t, testenv.GoToolPath(t), args...)
    35  	cmd.Dir = dir
    36  	cmd = testenv.CleanCmdEnv(cmd)
    37  	t.Log(cmd)
    38  	out, err := cmd.CombinedOutput()
    39  	if err != nil {
    40  		t.Fatalf("build failed: %v, output:\n%s", err, out)
    41  	}
    42  	return out
    43  }
    44  
    45  
    46  func testPGOIntendedInlining(t *testing.T, dir string) {
    47  	testenv.MustHaveGoRun(t)
    48  	t.Parallel()
    49  
    50  	const pkg = "example.com/pgo/inline"
    51  
    52  	want := []string{
    53  		"(*BS).NS",
    54  	}
    55  
    56  	
    57  	wantNot := []string{
    58  		
    59  		
    60  		"A",
    61  		
    62  		
    63  		"benchmarkB",
    64  	}
    65  
    66  	must := map[string]bool{
    67  		"(*BS).NS": true,
    68  	}
    69  
    70  	notInlinedReason := make(map[string]string)
    71  	for _, fname := range want {
    72  		fullName := pkg + "." + fname
    73  		if _, ok := notInlinedReason[fullName]; ok {
    74  			t.Errorf("duplicate func: %s", fullName)
    75  		}
    76  		notInlinedReason[fullName] = "unknown reason"
    77  	}
    78  
    79  	
    80  	
    81  	expectedNotInlinedList := make(map[string]struct{})
    82  	for _, fname := range wantNot {
    83  		fullName := pkg + "." + fname
    84  		expectedNotInlinedList[fullName] = struct{}{}
    85  	}
    86  
    87  	
    88  	
    89  	pprof := filepath.Join(dir, "inline_hot.pprof")
    90  	gcflag := fmt.Sprintf("-m -m -pgoprofile=%s -d=pgoinlinebudget=160,pgoinlinecdfthreshold=90", pprof)
    91  	out := buildPGOInliningTest(t, dir, gcflag)
    92  
    93  	scanner := bufio.NewScanner(bytes.NewReader(out))
    94  	curPkg := ""
    95  	canInline := regexp.MustCompile(`: can inline ([^ ]*)`)
    96  	haveInlined := regexp.MustCompile(`: inlining call to ([^ ]*)`)
    97  	cannotInline := regexp.MustCompile(`: cannot inline ([^ ]*): (.*)`)
    98  	for scanner.Scan() {
    99  		line := scanner.Text()
   100  		t.Logf("child: %s", line)
   101  		if strings.HasPrefix(line, "# ") {
   102  			curPkg = line[2:]
   103  			splits := strings.Split(curPkg, " ")
   104  			curPkg = splits[0]
   105  			continue
   106  		}
   107  		if m := haveInlined.FindStringSubmatch(line); m != nil {
   108  			fname := m[1]
   109  			delete(notInlinedReason, curPkg+"."+fname)
   110  			continue
   111  		}
   112  		if m := canInline.FindStringSubmatch(line); m != nil {
   113  			fname := m[1]
   114  			fullname := curPkg + "." + fname
   115  			
   116  			if _, ok := must[fullname]; !ok {
   117  				delete(notInlinedReason, fullname)
   118  				continue
   119  			}
   120  		}
   121  		if m := cannotInline.FindStringSubmatch(line); m != nil {
   122  			fname, reason := m[1], m[2]
   123  			fullName := curPkg + "." + fname
   124  			if _, ok := notInlinedReason[fullName]; ok {
   125  				
   126  				notInlinedReason[fullName] = reason
   127  			}
   128  			delete(expectedNotInlinedList, fullName)
   129  			continue
   130  		}
   131  	}
   132  	if err := scanner.Err(); err != nil {
   133  		t.Fatalf("error reading output: %v", err)
   134  	}
   135  	for fullName, reason := range notInlinedReason {
   136  		t.Errorf("%s was not inlined: %s", fullName, reason)
   137  	}
   138  
   139  	
   140  	
   141  	for fullName, _ := range expectedNotInlinedList {
   142  		t.Errorf("%s was expected not inlined", fullName)
   143  	}
   144  }
   145  
   146  
   147  
   148  func TestPGOIntendedInlining(t *testing.T) {
   149  	wd, err := os.Getwd()
   150  	if err != nil {
   151  		t.Fatalf("error getting wd: %v", err)
   152  	}
   153  	srcDir := filepath.Join(wd, "testdata/pgo/inline")
   154  
   155  	
   156  	dir := t.TempDir()
   157  
   158  	for _, file := range []string{"inline_hot.go", "inline_hot_test.go", "inline_hot.pprof"} {
   159  		if err := copyFile(filepath.Join(dir, file), filepath.Join(srcDir, file)); err != nil {
   160  			t.Fatalf("error copying %s: %v", file, err)
   161  		}
   162  	}
   163  
   164  	testPGOIntendedInlining(t, dir)
   165  }
   166  
   167  
   168  
   169  func TestPGOIntendedInliningShiftedLines(t *testing.T) {
   170  	wd, err := os.Getwd()
   171  	if err != nil {
   172  		t.Fatalf("error getting wd: %v", err)
   173  	}
   174  	srcDir := filepath.Join(wd, "testdata/pgo/inline")
   175  
   176  	
   177  	dir := t.TempDir()
   178  
   179  	
   180  	for _, file := range []string{"inline_hot_test.go", "inline_hot.pprof"} {
   181  		if err := copyFile(filepath.Join(dir, file), filepath.Join(srcDir, file)); err != nil {
   182  			t.Fatalf("error copying %s : %v", file, err)
   183  		}
   184  	}
   185  
   186  	
   187  	
   188  	src, err := os.Open(filepath.Join(srcDir, "inline_hot.go"))
   189  	if err != nil {
   190  		t.Fatalf("error opening src inline_hot.go: %v", err)
   191  	}
   192  	defer src.Close()
   193  
   194  	dst, err := os.Create(filepath.Join(dir, "inline_hot.go"))
   195  	if err != nil {
   196  		t.Fatalf("error creating dst inline_hot.go: %v", err)
   197  	}
   198  	defer dst.Close()
   199  
   200  	if _, err := io.WriteString(dst, `// Autogenerated
   201  // Lines
   202  `); err != nil {
   203  		t.Fatalf("error writing comments to dst: %v", err)
   204  	}
   205  
   206  	if _, err := io.Copy(dst, src); err != nil {
   207  		t.Fatalf("error copying inline_hot.go: %v", err)
   208  	}
   209  
   210  	dst.Close()
   211  
   212  	testPGOIntendedInlining(t, dir)
   213  }
   214  
   215  
   216  
   217  
   218  func TestPGOSingleIndex(t *testing.T) {
   219  	for _, tc := range []struct {
   220  		originalIndex int
   221  	}{{
   222  		
   223  		
   224  		
   225  		
   226  		
   227  		
   228  		originalIndex: 0,
   229  	}, {
   230  		originalIndex: 1,
   231  	}} {
   232  		t.Run(fmt.Sprintf("originalIndex=%d", tc.originalIndex), func(t *testing.T) {
   233  			wd, err := os.Getwd()
   234  			if err != nil {
   235  				t.Fatalf("error getting wd: %v", err)
   236  			}
   237  			srcDir := filepath.Join(wd, "testdata/pgo/inline")
   238  
   239  			
   240  			dir := t.TempDir()
   241  
   242  			originalPprofFile, err := os.Open(filepath.Join(srcDir, "inline_hot.pprof"))
   243  			if err != nil {
   244  				t.Fatalf("error opening inline_hot.pprof: %v", err)
   245  			}
   246  			defer originalPprofFile.Close()
   247  
   248  			p, err := profile.Parse(originalPprofFile)
   249  			if err != nil {
   250  				t.Fatalf("error parsing inline_hot.pprof: %v", err)
   251  			}
   252  
   253  			
   254  			p.SampleType = []*profile.ValueType{p.SampleType[tc.originalIndex]}
   255  
   256  			
   257  			for _, s := range p.Sample {
   258  				s.Value = []int64{s.Value[tc.originalIndex]}
   259  			}
   260  
   261  			modifiedPprofFile, err := os.Create(filepath.Join(dir, "inline_hot.pprof"))
   262  			if err != nil {
   263  				t.Fatalf("error creating inline_hot.pprof: %v", err)
   264  			}
   265  			defer modifiedPprofFile.Close()
   266  
   267  			if err := p.Write(modifiedPprofFile); err != nil {
   268  				t.Fatalf("error writing inline_hot.pprof: %v", err)
   269  			}
   270  
   271  			for _, file := range []string{"inline_hot.go", "inline_hot_test.go"} {
   272  				if err := copyFile(filepath.Join(dir, file), filepath.Join(srcDir, file)); err != nil {
   273  					t.Fatalf("error copying %s: %v", file, err)
   274  				}
   275  			}
   276  
   277  			testPGOIntendedInlining(t, dir)
   278  		})
   279  	}
   280  }
   281  
   282  func copyFile(dst, src string) error {
   283  	s, err := os.Open(src)
   284  	if err != nil {
   285  		return err
   286  	}
   287  	defer s.Close()
   288  
   289  	d, err := os.Create(dst)
   290  	if err != nil {
   291  		return err
   292  	}
   293  	defer d.Close()
   294  
   295  	_, err = io.Copy(d, s)
   296  	return err
   297  }
   298  
   299  
   300  func TestPGOHash(t *testing.T) {
   301  	testenv.MustHaveGoRun(t)
   302  	t.Parallel()
   303  
   304  	const pkg = "example.com/pgo/inline"
   305  
   306  	wd, err := os.Getwd()
   307  	if err != nil {
   308  		t.Fatalf("error getting wd: %v", err)
   309  	}
   310  	srcDir := filepath.Join(wd, "testdata/pgo/inline")
   311  
   312  	
   313  	dir := t.TempDir()
   314  
   315  	for _, file := range []string{"inline_hot.go", "inline_hot_test.go", "inline_hot.pprof"} {
   316  		if err := copyFile(filepath.Join(dir, file), filepath.Join(srcDir, file)); err != nil {
   317  			t.Fatalf("error copying %s: %v", file, err)
   318  		}
   319  	}
   320  
   321  	pprof := filepath.Join(dir, "inline_hot.pprof")
   322  	
   323  	
   324  	gcflag0 := fmt.Sprintf("-pgoprofile=%s -trimpath %s=>%s -d=pgoinlinebudget=160,pgoinlinecdfthreshold=90,pgodebug=1", pprof, dir, pkg)
   325  
   326  	
   327  	const srcPos = "example.com/pgo/inline/inline_hot.go:81:19"
   328  	const hashMatch = "pgohash triggered " + srcPos + " (inline)"
   329  	pgoDebugRE := regexp.MustCompile(`hot-budget check allows inlining for call .* at ` + strings.ReplaceAll(srcPos, ".", "\\."))
   330  	hash := "v1" 
   331  	gcflag := gcflag0 + ",pgohash=" + hash
   332  	out := buildPGOInliningTest(t, dir, gcflag)
   333  	if !bytes.Contains(out, []byte(hashMatch)) || !pgoDebugRE.Match(out) {
   334  		t.Errorf("output does not contain expected source line, out:\n%s", out)
   335  	}
   336  
   337  	
   338  	hash = "v0" 
   339  	gcflag = gcflag0 + ",pgohash=" + hash
   340  	out = buildPGOInliningTest(t, dir, gcflag)
   341  	if bytes.Contains(out, []byte(hashMatch)) || pgoDebugRE.Match(out) {
   342  		t.Errorf("output contains unexpected source line, out:\n%s", out)
   343  	}
   344  }
   345  
View as plain text