mirror of
https://github.com/langhuihui/monibuca.git
synced 2025-12-24 13:48:04 +08:00
289 lines
7.9 KiB
Go
289 lines
7.9 KiB
Go
// 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"
|
|
|
|
"m7s.live/v5/plugin/debug/pkg/internal/binutils"
|
|
"m7s.live/v5/plugin/debug/pkg/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
|
|
}
|