...

Source file src/github.com/google/pprof/internal/report/source_test.go

Documentation: github.com/google/pprof/internal/report

     1  //  Copyright 2017 Google Inc. All Rights Reserved.
     2  //
     3  //  Licensed under the Apache License, Version 2.0 (the "License");
     4  //  you may not use this file except in compliance with the License.
     5  //  You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  //  Unless required by applicable law or agreed to in writing, software
    10  //  distributed under the License is distributed on an "AS IS" BASIS,
    11  //  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  //  See the License for the specific language governing permissions and
    13  //  limitations under the License.
    14  
    15  package report
    16  
    17  import (
    18  	"fmt"
    19  	"os"
    20  	"path/filepath"
    21  	"regexp"
    22  	"runtime"
    23  	"strings"
    24  	"testing"
    25  
    26  	"github.com/google/pprof/internal/binutils"
    27  	"github.com/google/pprof/profile"
    28  )
    29  
    30  func TestWebList(t *testing.T) {
    31  	if runtime.GOOS != "linux" || runtime.GOARCH != "amd64" {
    32  		t.Skip("weblist only tested on x86-64 linux")
    33  	}
    34  
    35  	cpu := readProfile(filepath.Join("testdata", "sample.cpu"), t)
    36  	rpt := New(cpu, &Options{
    37  		OutputFormat: WebList,
    38  		Symbol:       regexp.MustCompile("busyLoop"),
    39  		SampleValue:  func(v []int64) int64 { return v[1] },
    40  		SampleUnit:   cpu.SampleType[1].Unit,
    41  	})
    42  	result, err := MakeWebList(rpt, &binutils.Binutils{}, -1)
    43  	if err != nil {
    44  		t.Fatalf("could not generate weblist: %v", err)
    45  	}
    46  	output := fmt.Sprint(result)
    47  
    48  	for _, expect := range []string{"func busyLoop", "call.*mapassign"} {
    49  		if match, _ := regexp.MatchString(expect, output); !match {
    50  			t.Errorf("weblist output does not contain '%s':\n%s", expect, output)
    51  		}
    52  	}
    53  }
    54  
    55  func TestSourceSyntheticAddress(t *testing.T) {
    56  	testSourceMapping(t, true)
    57  }
    58  
    59  func TestSourceMissingMapping(t *testing.T) {
    60  	testSourceMapping(t, false)
    61  }
    62  
    63  // testSourceMapping checks that source info is found even when no applicable
    64  // Mapping/objectFile exists. The locations used in the test are either zero
    65  // (if zeroAddress is true), or non-zero (otherwise).
    66  func testSourceMapping(t *testing.T, zeroAddress bool) {
    67  	nextAddr := uint64(0)
    68  
    69  	makeLoc := func(name, fname string, line int64) *profile.Location {
    70  		if !zeroAddress {
    71  			nextAddr++
    72  		}
    73  		return &profile.Location{
    74  			Address: nextAddr,
    75  			Line: []profile.Line{
    76  				{
    77  					Function: &profile.Function{Name: name, Filename: fname},
    78  					Line:     line,
    79  				},
    80  			},
    81  		}
    82  	}
    83  
    84  	// Create profile that will need synthetic addresses since it has no mappings.
    85  	foo100 := makeLoc("foo", "foo.go", 100)
    86  	bar50 := makeLoc("bar", "bar.go", 50)
    87  	prof := &profile.Profile{
    88  		Sample: []*profile.Sample{
    89  			{
    90  				Value:    []int64{9},
    91  				Location: []*profile.Location{foo100, bar50},
    92  			},
    93  			{
    94  				Value:    []int64{17},
    95  				Location: []*profile.Location{bar50},
    96  			},
    97  		},
    98  	}
    99  	rpt := &Report{
   100  		prof: prof,
   101  		options: &Options{
   102  			Symbol:      regexp.MustCompile("foo|bar"),
   103  			SampleValue: func(s []int64) int64 { return s[0] },
   104  		},
   105  		formatValue: func(v int64) string { return fmt.Sprint(v) },
   106  	}
   107  
   108  	result, err := MakeWebList(rpt, nil, -1)
   109  	if err != nil {
   110  		t.Fatalf("MakeWebList returned unexpected error: %v", err)
   111  	}
   112  	got := fmt.Sprint(result)
   113  
   114  	expect := regexp.MustCompile(
   115  		`(?s)` + // Allow "." to match newline
   116  			`bar\.go.* 50\b.* 17 +26 .*` +
   117  			`foo\.go.* 100\b.* 9 +9 `)
   118  	if !expect.MatchString(got) {
   119  		t.Errorf("expected regular expression %v does not match  output:\n%s\n", expect, got)
   120  	}
   121  }
   122  
   123  func TestOpenSourceFile(t *testing.T) {
   124  	tempdir, err := os.MkdirTemp("", "")
   125  	if err != nil {
   126  		t.Fatalf("failed to create temp dir: %v", err)
   127  	}
   128  	const lsep = string(filepath.ListSeparator)
   129  	for _, tc := range []struct {
   130  		desc       string
   131  		searchPath string
   132  		trimPath   string
   133  		fs         []string
   134  		path       string
   135  		wantPath   string // If empty, error is wanted.
   136  	}{
   137  		{
   138  			desc:     "exact absolute path is found",
   139  			fs:       []string{"foo/bar.cc"},
   140  			path:     "$dir/foo/bar.cc",
   141  			wantPath: "$dir/foo/bar.cc",
   142  		},
   143  		{
   144  			desc:       "exact relative path is found",
   145  			searchPath: "$dir",
   146  			fs:         []string{"foo/bar.cc"},
   147  			path:       "foo/bar.cc",
   148  			wantPath:   "$dir/foo/bar.cc",
   149  		},
   150  		{
   151  			desc:       "multiple search path",
   152  			searchPath: "some/path" + lsep + "$dir",
   153  			fs:         []string{"foo/bar.cc"},
   154  			path:       "foo/bar.cc",
   155  			wantPath:   "$dir/foo/bar.cc",
   156  		},
   157  		{
   158  			desc:       "relative path is found in parent dir",
   159  			searchPath: "$dir/foo/bar",
   160  			fs:         []string{"bar.cc", "foo/bar/baz.cc"},
   161  			path:       "bar.cc",
   162  			wantPath:   "$dir/bar.cc",
   163  		},
   164  		{
   165  			desc:       "trims configured prefix",
   166  			searchPath: "$dir",
   167  			trimPath:   "some-path" + lsep + "/some/remote/path",
   168  			fs:         []string{"my-project/foo/bar.cc"},
   169  			path:       "/some/remote/path/my-project/foo/bar.cc",
   170  			wantPath:   "$dir/my-project/foo/bar.cc",
   171  		},
   172  		{
   173  			desc:       "trims heuristically",
   174  			searchPath: "$dir/my-project",
   175  			fs:         []string{"my-project/foo/bar.cc"},
   176  			path:       "/some/remote/path/my-project/foo/bar.cc",
   177  			wantPath:   "$dir/my-project/foo/bar.cc",
   178  		},
   179  		{
   180  			desc: "error when not found",
   181  			path: "foo.cc",
   182  		},
   183  	} {
   184  		t.Run(tc.desc, func(t *testing.T) {
   185  			defer func() {
   186  				if err := os.RemoveAll(tempdir); err != nil {
   187  					t.Fatalf("failed to remove dir %q: %v", tempdir, err)
   188  				}
   189  			}()
   190  			for _, f := range tc.fs {
   191  				path := filepath.Join(tempdir, filepath.FromSlash(f))
   192  				dir := filepath.Dir(path)
   193  				if err := os.MkdirAll(dir, 0755); err != nil {
   194  					t.Fatalf("failed to create dir %q: %v", dir, err)
   195  				}
   196  				if err := os.WriteFile(path, nil, 0644); err != nil {
   197  					t.Fatalf("failed to create file %q: %v", path, err)
   198  				}
   199  			}
   200  			tc.searchPath = filepath.FromSlash(strings.Replace(tc.searchPath, "$dir", tempdir, -1))
   201  			tc.path = filepath.FromSlash(strings.Replace(tc.path, "$dir", tempdir, 1))
   202  			tc.wantPath = filepath.FromSlash(strings.Replace(tc.wantPath, "$dir", tempdir, 1))
   203  			if file, err := openSourceFile(tc.path, tc.searchPath, tc.trimPath); err != nil && tc.wantPath != "" {
   204  				t.Errorf("openSourceFile(%q, %q, %q) = err %v, want path %q", tc.path, tc.searchPath, tc.trimPath, err, tc.wantPath)
   205  			} else if err == nil {
   206  				defer file.Close()
   207  				gotPath := file.Name()
   208  				if tc.wantPath == "" {
   209  					t.Errorf("openSourceFile(%q, %q, %q) = %q, want error", tc.path, tc.searchPath, tc.trimPath, gotPath)
   210  				} else if gotPath != tc.wantPath {
   211  					t.Errorf("openSourceFile(%q, %q, %q) = %q, want path %q", tc.path, tc.searchPath, tc.trimPath, gotPath, tc.wantPath)
   212  				}
   213  			}
   214  		})
   215  	}
   216  }
   217  
   218  func TestIndentation(t *testing.T) {
   219  	for _, c := range []struct {
   220  		str        string
   221  		wantIndent int
   222  	}{
   223  		{"", 0},
   224  		{"foobar", 0},
   225  		{"  foo", 2},
   226  		{"\tfoo", 8},
   227  		{"\t foo", 9},
   228  		{"  \tfoo", 8},
   229  		{"       \tfoo", 8},
   230  		{"        \tfoo", 16},
   231  	} {
   232  		if n := indentation(c.str); n != c.wantIndent {
   233  			t.Errorf("indentation(%v): got %d, want %d", c.str, n, c.wantIndent)
   234  		}
   235  	}
   236  }
   237  
   238  func TestRightPad(t *testing.T) {
   239  	for _, c := range []struct {
   240  		pad    int
   241  		in     string
   242  		expect string
   243  	}{
   244  		{0, "", ""},
   245  		{4, "", "    "},
   246  		{4, "x", "x   "},
   247  		{4, "abcd", "abcd"},   // No padding because of overflow
   248  		{4, "abcde", "abcde"}, // No padding because of overflow
   249  		{10, "\tx", "        x "},
   250  		{10, "w\txy\tz", "w       xy      z"},
   251  		{20, "w\txy\tz", "w       xy      z   "},
   252  	} {
   253  		out := rightPad(c.in, c.pad)
   254  		if out != c.expect {
   255  			t.Errorf("rightPad(%q, %d): got %q, want %q", c.in, c.pad, out, c.expect)
   256  		}
   257  	}
   258  }
   259  
   260  func readProfile(fname string, t *testing.T) *profile.Profile {
   261  	file, err := os.Open(fname)
   262  	if err != nil {
   263  		t.Fatalf("%s: could not open profile: %v", fname, err)
   264  	}
   265  	defer file.Close()
   266  	p, err := profile.Parse(file)
   267  	if err != nil {
   268  		t.Fatalf("%s: could not parse profile: %v", fname, err)
   269  	}
   270  
   271  	// Fix file names so they do not include absolute path names.
   272  	fix := func(s string) string {
   273  		const testdir = "/internal/report/"
   274  		pos := strings.Index(s, testdir)
   275  		if pos == -1 {
   276  			return s
   277  		}
   278  		return s[pos+len(testdir):]
   279  	}
   280  	for _, m := range p.Mapping {
   281  		m.File = fix(m.File)
   282  	}
   283  	for _, f := range p.Function {
   284  		f.Filename = fix(f.Filename)
   285  	}
   286  
   287  	return p
   288  }
   289  

View as plain text