1
2
3
4
5
6
7
8
9
10
11
12
13
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
64
65
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
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)` +
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
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"},
248 {4, "abcde", "abcde"},
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
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