// Copyright 2026 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package pgo import ( "bytes" "internal/profile" "strings" "testing" ) func testProfile(sampleTypes []*profile.ValueType, defaultSampleType string, values []int64) *profile.Profile { main := &profile.Function{ID: 1, Name: "main.main", StartLine: 10} foo := &profile.Function{ID: 2, Name: "main.foo", StartLine: 20} mainLoc := &profile.Location{ID: 1, Line: []profile.Line{{Function: main, Line: 12}}} fooLoc := &profile.Location{ID: 2, Line: []profile.Line{{Function: foo, Line: 22}}} return &profile.Profile{ SampleType: sampleTypes, DefaultSampleType: defaultSampleType, Function: []*profile.Function{main, foo}, Location: []*profile.Location{mainLoc, fooLoc}, Sample: []*profile.Sample{ // Stacks are leaf-first: foo called from main. {Location: []*profile.Location{fooLoc, mainLoc}, Value: values}, }, } } func serializeProfile(t *testing.T, p *profile.Profile) *bytes.Reader { t.Helper() var buf bytes.Buffer if err := p.Write(&buf); err != nil { t.Fatalf("writing profile: %v", err) } return bytes.NewReader(buf.Bytes()) } func TestFromPProfSampleType(t *testing.T) { perf := []*profile.ValueType{ {Type: "cycles_sample", Unit: "count"}, {Type: "cycles_event", Unit: "count"}, } tests := []struct { name string sampleTypes []*profile.ValueType defaultType string values []int64 wantErr string // substring of the expected error, if any wantTotalWeight int64 }{ { name: "native samples/count", sampleTypes: []*profile.ValueType{{Type: "samples", Unit: "count"}, {Type: "cpu", Unit: "nanoseconds"}}, values: []int64{5, 500}, wantTotalWeight: 5, }, { name: "native cpu/nanoseconds", sampleTypes: []*profile.ValueType{{Type: "cpu", Unit: "nanoseconds"}}, values: []int64{500}, wantTotalWeight: 500, }, { name: "perf via default sample type", sampleTypes: perf, defaultType: "cycles_event", values: []int64{5, 500}, wantTotalWeight: 500, }, { name: "perf without default uses last value type", sampleTypes: perf, values: []int64{5, 500}, wantTotalWeight: 500, }, { name: "heap profile rejected", sampleTypes: []*profile.ValueType{{Type: "alloc_objects", Unit: "count"}, {Type: "inuse_space", Unit: "bytes"}}, defaultType: "inuse_space", values: []int64{1, 2}, wantErr: "Go heap profile", }, { name: "mutex profile rejected", sampleTypes: []*profile.ValueType{{Type: "contentions", Unit: "count"}, {Type: "delay", Unit: "nanoseconds"}}, values: []int64{1, 2}, wantErr: "Go mutex or block profile", }, { name: "goroutine profile rejected", sampleTypes: []*profile.ValueType{{Type: "goroutine", Unit: "count"}}, values: []int64{1}, wantErr: "Go goroutine profile", }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { got, err := FromPProf(serializeProfile(t, testProfile(tc.sampleTypes, tc.defaultType, tc.values))) if tc.wantErr != "" { if err == nil || !strings.Contains(err.Error(), tc.wantErr) { t.Fatalf("FromPProf got err %v, want error containing %q", err, tc.wantErr) } return } if err != nil { t.Fatalf("FromPProf: %v", err) } if len(got.NamedEdgeMap.ByWeight) == 0 { t.Error("empty edge map, want a call edge") } if got.TotalWeight != tc.wantTotalWeight { t.Errorf("TotalWeight = %d, want %d", got.TotalWeight, tc.wantTotalWeight) } }) } } func TestSampleValueIndexNoSampleTypes(t *testing.T) { if _, err := sampleValueIndex(&profile.Profile{}); err == nil { t.Error("sampleValueIndex succeeded on a profile with no sample types, want error") } }