| // 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") | |
| } | |
| } |