blob: 70bb613ceff898d79cb6313cc06d99f9d9e90715 [file] [log] [blame] [edit]
package browsertests
import (
"fmt"
"io"
"net/http"
"net/http/httptest"
"regexp"
"runtime"
"testing"
"time"
"github.com/google/pprof/driver"
"github.com/google/pprof/profile"
)
func makeTestServer(t testing.TB, prof *profile.Profile) *httptest.Server {
if runtime.GOOS == "nacl" || runtime.GOOS == "js" {
t.Skip("test assumes tcp available")
}
// Custom http server creator
var server *httptest.Server
serverCreated := make(chan bool)
creator := func(a *driver.HTTPServerArgs) error {
server = httptest.NewServer(http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
if h := a.Handlers[r.URL.Path]; h != nil {
h.ServeHTTP(w, r)
}
}))
serverCreated <- true
return nil
}
// Start server and wait for it to be initialized
go func() {
err := driver.PProf(&driver.Options{
Obj: fakeObjTool{},
UI: testUI{t},
Fetch: testFetcher{prof},
HTTPServer: creator,
Flagset: testFlags{
"http": "unused:1234",
"no_browser": true,
},
})
if err != nil {
panic(err)
}
}()
<-serverCreated
// Close the server when the test is done.
t.Cleanup(server.Close)
return server
}
// Fake test implementations of types needed by pprof driver.
const addrBase = 0x1000
const fakeSource = "testdata/file1000.src"
type fakeObj struct{}
func (f fakeObj) Close() error { return nil }
func (f fakeObj) Name() string { return "testbin" }
func (f fakeObj) ObjAddr(addr uint64) (uint64, error) { return addr, nil }
func (f fakeObj) BuildID() string { return "" }
func (f fakeObj) SourceLine(addr uint64) ([]driver.Frame, error) {
return nil, fmt.Errorf("SourceLine unimplemented")
}
func (f fakeObj) Symbols(r *regexp.Regexp, addr uint64) ([]*driver.Sym, error) {
return []*driver.Sym{
{
Name: []string{"F1"}, File: fakeSource,
Start: addrBase, End: addrBase + 10,
},
{
Name: []string{"F2"}, File: fakeSource,
Start: addrBase + 10, End: addrBase + 20,
},
{
Name: []string{"F3"}, File: fakeSource,
Start: addrBase + 20, End: addrBase + 30,
},
}, nil
}
type fakeObjTool struct{}
func (obj fakeObjTool) Open(file string, start, limit, offset uint64, relocationSymbol string) (driver.ObjFile, error) {
return fakeObj{}, nil
}
func (obj fakeObjTool) Disasm(file string, start, end uint64, intelSyntax bool) ([]driver.Inst, error) {
return []driver.Inst{
{Addr: addrBase + 10, Text: "f1:asm", Function: "F1", Line: 3},
{Addr: addrBase + 20, Text: "f2:asm", Function: "F2", Line: 11},
{Addr: addrBase + 30, Text: "d3:asm", Function: "F3", Line: 22},
}, nil
}
func makeFakeProfile() *profile.Profile {
// Three functions: F1, F2, F3 with three lines, 11, 22, 33.
funcs := []*profile.Function{
{ID: 1, Name: "F1", Filename: fakeSource, StartLine: 3},
{ID: 2, Name: "F2", Filename: fakeSource, StartLine: 5},
{ID: 3, Name: "F3", Filename: fakeSource, StartLine: 7},
}
lines := []profile.Line{
{Function: funcs[0], Line: 11},
{Function: funcs[1], Line: 22},
{Function: funcs[2], Line: 33},
}
mapping := []*profile.Mapping{
{
ID: 1,
Start: addrBase,
Limit: addrBase + 100,
Offset: 0,
File: "testbin",
HasFunctions: true,
HasFilenames: true,
HasLineNumbers: true,
},
}
// Three interesting addresses: base+{10,20,30}
locs := []*profile.Location{
{ID: 1, Address: addrBase + 10, Line: lines[0:1], Mapping: mapping[0]},
{ID: 2, Address: addrBase + 20, Line: lines[1:2], Mapping: mapping[0]},
{ID: 3, Address: addrBase + 30, Line: lines[2:3], Mapping: mapping[0]},
}
// Two stack traces.
return &profile.Profile{
PeriodType: &profile.ValueType{Type: "cpu", Unit: "milliseconds"},
Period: 1,
DurationNanos: 10e9,
SampleType: []*profile.ValueType{
{Type: "cpu", Unit: "milliseconds"},
},
Sample: []*profile.Sample{
{
Location: []*profile.Location{locs[2], locs[1], locs[0]},
Value: []int64{100},
},
{
Location: []*profile.Location{locs[1], locs[0]},
Value: []int64{200},
},
},
Location: locs,
Function: funcs,
Mapping: mapping,
}
}
type testFlags map[string]any
func (flags testFlags) Bool(name string, def bool, usage string) *bool {
return getFlag(flags, name, def)
}
func (flags testFlags) Int(name string, def int, usage string) *int {
return getFlag(flags, name, def)
}
func (flags testFlags) Float64(name string, def float64, usage string) *float64 {
return getFlag(flags, name, def)
}
func (flags testFlags) String(name string, def string, usage string) *string {
return getFlag(flags, name, def)
}
func (flags testFlags) StringList(name string, def string, usage string) *[]*string {
return getFlag(flags, name, []*string{}) // Not supported, so return an empty list.
}
func (flags testFlags) ExtraUsage() string { return "" }
func (flags testFlags) AddExtraUsage(eu string) {}
func (flags testFlags) Parse(usage func()) []string { return []string{"test", "bin"} }
var _ driver.FlagSet = testFlags{}
func getFlag[T any](flags testFlags, name string, def T) *T {
result := &def
if v, ok := flags[name]; ok {
*result = v.(T)
}
return result
}
type testUI struct {
T testing.TB
}
func (ui testUI) ReadLine(_ string) (string, error) { return "", io.EOF }
func (ui testUI) IsTerminal() bool { return false }
func (ui testUI) WantBrowser() bool { return false }
func (ui testUI) SetAutoComplete(_ func(string) string) {}
func (ui testUI) Print(args ...interface{}) {} // discard
func (ui testUI) PrintErr(args ...interface{}) {
ui.T.Error("unexpected error: " + fmt.Sprint(args...))
}
var _ driver.UI = testUI{}
type testFetcher struct {
profile *profile.Profile
}
func (f testFetcher) Fetch(source string, duration, timeout time.Duration) (*profile.Profile, string, error) {
// http://pproftest.local prevents file from being saved.
return f.profile, "http://pproftest.local", nil
}
var _ driver.Fetcher = testFetcher{}