blob: 2febfd1b1dbe5e237d4a530d3d6c7a6a6f2d9d5b [file] [log] [blame]
// 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 (
"bytes"
"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,
})
var buf bytes.Buffer
if err := Generate(&buf, rpt, &binutils.Binutils{}); err != nil {
t.Fatalf("could not generate weblist: %v", err)
}
output := buf.String()
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) },
}
var out bytes.Buffer
err := PrintWebList(&out, rpt, nil, -1)
if err != nil {
t.Fatalf("PrintWebList returned unexpected error: %v", err)
}
got := out.String()
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
}