// Copyright 2017 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package report import ( "fmt" "os" "path/filepath" "regexp" "runtime" "strings" "testing" "github.com/google/pprof/internal/binutils" "github.com/google/pprof/profile" ) func TestWebList(t *testing.T) { if runtime.GOOS != "linux" || runtime.GOARCH != "amd64" { t.Skip("weblist only tested on x86-64 linux") } cpu := readProfile(filepath.Join("testdata", "sample.cpu"), t) rpt := New(cpu, &Options{ OutputFormat: WebList, Symbol: regexp.MustCompile("busyLoop"), SampleValue: func(v []int64) int64 { return v[1] }, SampleUnit: cpu.SampleType[1].Unit, }) result, err := MakeWebList(rpt, &binutils.Binutils{}, -1) if err != nil { t.Fatalf("could not generate weblist: %v", err) } output := fmt.Sprint(result) for _, expect := range []string{"func busyLoop", "call.*mapassign"} { if match, _ := regexp.MatchString(expect, output); !match { t.Errorf("weblist output does not contain '%s':\n%s", expect, output) } } } func TestSourceSyntheticAddress(t *testing.T) { testSourceMapping(t, true) } func TestSourceMissingMapping(t *testing.T) { testSourceMapping(t, false) } // testSourceMapping checks that source info is found even when no applicable // Mapping/objectFile exists. The locations used in the test are either zero // (if zeroAddress is true), or non-zero (otherwise). func testSourceMapping(t *testing.T, zeroAddress bool) { nextAddr := uint64(0) makeLoc := func(name, fname string, line int64) *profile.Location { if !zeroAddress { nextAddr++ } return &profile.Location{ Address: nextAddr, Line: []profile.Line{ { Function: &profile.Function{Name: name, Filename: fname}, Line: line, }, }, } } // Create profile that will need synthetic addresses since it has no mappings. foo100 := makeLoc("foo", "foo.go", 100) bar50 := makeLoc("bar", "bar.go", 50) prof := &profile.Profile{ Sample: []*profile.Sample{ { Value: []int64{9}, Location: []*profile.Location{foo100, bar50}, }, { Value: []int64{17}, Location: []*profile.Location{bar50}, }, }, } rpt := &Report{ prof: prof, options: &Options{ Symbol: regexp.MustCompile("foo|bar"), SampleValue: func(s []int64) int64 { return s[0] }, }, formatValue: func(v int64) string { return fmt.Sprint(v) }, } result, err := MakeWebList(rpt, nil, -1) if err != nil { t.Fatalf("MakeWebList returned unexpected error: %v", err) } got := fmt.Sprint(result) expect := regexp.MustCompile( `(?s)` + // Allow "." to match newline `bar\.go.* 50\b.* 17 +26 .*` + `foo\.go.* 100\b.* 9 +9 `) if !expect.MatchString(got) { t.Errorf("expected regular expression %v does not match output:\n%s\n", expect, got) } } func TestOpenSourceFile(t *testing.T) { tempdir, err := os.MkdirTemp("", "") if err != nil { t.Fatalf("failed to create temp dir: %v", err) } const lsep = string(filepath.ListSeparator) for _, tc := range []struct { desc string searchPath string trimPath string fs []string path string wantPath string // If empty, error is wanted. }{ { desc: "exact absolute path is found", fs: []string{"foo/bar.cc"}, path: "$dir/foo/bar.cc", wantPath: "$dir/foo/bar.cc", }, { desc: "exact relative path is found", searchPath: "$dir", fs: []string{"foo/bar.cc"}, path: "foo/bar.cc", wantPath: "$dir/foo/bar.cc", }, { desc: "multiple search path", searchPath: "some/path" + lsep + "$dir", fs: []string{"foo/bar.cc"}, path: "foo/bar.cc", wantPath: "$dir/foo/bar.cc", }, { desc: "relative path is found in parent dir", searchPath: "$dir/foo/bar", fs: []string{"bar.cc", "foo/bar/baz.cc"}, path: "bar.cc", wantPath: "$dir/bar.cc", }, { desc: "trims configured prefix", searchPath: "$dir", trimPath: "some-path" + lsep + "/some/remote/path", fs: []string{"my-project/foo/bar.cc"}, path: "/some/remote/path/my-project/foo/bar.cc", wantPath: "$dir/my-project/foo/bar.cc", }, { desc: "trims heuristically", searchPath: "$dir/my-project", fs: []string{"my-project/foo/bar.cc"}, path: "/some/remote/path/my-project/foo/bar.cc", wantPath: "$dir/my-project/foo/bar.cc", }, { desc: "error when not found", path: "foo.cc", }, } { t.Run(tc.desc, func(t *testing.T) { defer func() { if err := os.RemoveAll(tempdir); err != nil { t.Fatalf("failed to remove dir %q: %v", tempdir, err) } }() for _, f := range tc.fs { path := filepath.Join(tempdir, filepath.FromSlash(f)) dir := filepath.Dir(path) if err := os.MkdirAll(dir, 0755); err != nil { t.Fatalf("failed to create dir %q: %v", dir, err) } if err := os.WriteFile(path, nil, 0644); err != nil { t.Fatalf("failed to create file %q: %v", path, err) } } tc.searchPath = filepath.FromSlash(strings.Replace(tc.searchPath, "$dir", tempdir, -1)) tc.path = filepath.FromSlash(strings.Replace(tc.path, "$dir", tempdir, 1)) tc.wantPath = filepath.FromSlash(strings.Replace(tc.wantPath, "$dir", tempdir, 1)) if file, err := openSourceFile(tc.path, tc.searchPath, tc.trimPath); err != nil && tc.wantPath != "" { t.Errorf("openSourceFile(%q, %q, %q) = err %v, want path %q", tc.path, tc.searchPath, tc.trimPath, err, tc.wantPath) } else if err == nil { defer file.Close() gotPath := file.Name() if tc.wantPath == "" { t.Errorf("openSourceFile(%q, %q, %q) = %q, want error", tc.path, tc.searchPath, tc.trimPath, gotPath) } else if gotPath != tc.wantPath { t.Errorf("openSourceFile(%q, %q, %q) = %q, want path %q", tc.path, tc.searchPath, tc.trimPath, gotPath, tc.wantPath) } } }) } } func TestIndentation(t *testing.T) { for _, c := range []struct { str string wantIndent int }{ {"", 0}, {"foobar", 0}, {" foo", 2}, {"\tfoo", 8}, {"\t foo", 9}, {" \tfoo", 8}, {" \tfoo", 8}, {" \tfoo", 16}, } { if n := indentation(c.str); n != c.wantIndent { t.Errorf("indentation(%v): got %d, want %d", c.str, n, c.wantIndent) } } } func TestRightPad(t *testing.T) { for _, c := range []struct { pad int in string expect string }{ {0, "", ""}, {4, "", " "}, {4, "x", "x "}, {4, "abcd", "abcd"}, // No padding because of overflow {4, "abcde", "abcde"}, // No padding because of overflow {10, "\tx", " x "}, {10, "w\txy\tz", "w xy z"}, {20, "w\txy\tz", "w xy z "}, } { out := rightPad(c.in, c.pad) if out != c.expect { t.Errorf("rightPad(%q, %d): got %q, want %q", c.in, c.pad, out, c.expect) } } } func readProfile(fname string, t *testing.T) *profile.Profile { file, err := os.Open(fname) if err != nil { t.Fatalf("%s: could not open profile: %v", fname, err) } defer file.Close() p, err := profile.Parse(file) if err != nil { t.Fatalf("%s: could not parse profile: %v", fname, err) } // Fix file names so they do not include absolute path names. fix := func(s string) string { const testdir = "/internal/report/" pos := strings.Index(s, testdir) if pos == -1 { return s } return s[pos+len(testdir):] } for _, m := range p.Mapping { m.File = fix(m.File) } for _, f := range p.Function { f.Filename = fix(f.Filename) } return p }