diff --git a/.chloggen/profiles-validation.yaml b/.chloggen/profiles-validation.yaml new file mode 100644 index 0000000000000..99078c78ace9b --- /dev/null +++ b/.chloggen/profiles-validation.yaml @@ -0,0 +1,27 @@ +# Use this changelog template to create an entry for release notes. + +# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix' +change_type: 'enhancement' + +# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver) +component: pkg/pdatatest + +# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). +note: Add ValidateProfile() function to validate pprofile.Profile. + +# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists. +issues: [38452] + +# (Optional) One or more lines of additional information to render under the primary note. +# These lines will be padded with 2 spaces and then inserted directly into the document. +# Use pipe (|) for multiline entries. +subtext: + +# If your change doesn't affect end users or the exported elements of any package, +# you should instead start your pull request title with [chore] or use the "Skip Changelog" label. +# Optional: The change log or logs in which this entry should be included. +# e.g. '[user]' or '[user, api]' +# Include 'user' if the change is relevant to end users. +# Include 'api' if there is a change to a library API. +# Default: '[user]' +change_logs: [api] diff --git a/pkg/pdatatest/pprofiletest/profiles.go b/pkg/pdatatest/pprofiletest/profiles.go index 15dcbc61d8b5a..9f6301fbe7d66 100644 --- a/pkg/pdatatest/pprofiletest/profiles.go +++ b/pkg/pdatatest/pprofiletest/profiles.go @@ -181,11 +181,13 @@ func CompareScopeProfiles(expected, actual pprofile.ScopeProfiles) error { var outOfOrderErrs error for e := 0; e < numProfiles; e++ { elr := expected.Profiles().At(e) + errs = multierr.Append(errs, ValidateProfile(elr)) em := profileAttributesToMap(elr) var foundMatch bool for a := 0; a < numProfiles; a++ { alr := actual.Profiles().At(a) + errs = multierr.Append(errs, ValidateProfile(alr)) if _, ok := matchingProfiles[alr]; ok { continue } @@ -266,7 +268,7 @@ func CompareProfile(expected, actual pprofile.Profile) error { } if !reflect.DeepEqual(expected.StringTable(), actual.StringTable()) { - errs = multierr.Append(errs, fmt.Errorf("stringTable does not match expected")) + errs = multierr.Append(errs, fmt.Errorf("stringTable '%v' does not match expected '%v'", actual.StringTable().AsRaw(), expected.StringTable().AsRaw())) } if expected.OriginalPayloadFormat() != actual.OriginalPayloadFormat() { diff --git a/pkg/pdatatest/pprofiletest/profiles_test.go b/pkg/pdatatest/pprofiletest/profiles_test.go index 344eda1cbcb05..012669596fdd7 100644 --- a/pkg/pdatatest/pprofiletest/profiles_test.go +++ b/pkg/pdatatest/pprofiletest/profiles_test.go @@ -707,7 +707,7 @@ func TestCompareProfile(t *testing.T) { }(), err: multierr.Combine( errors.New(`attributes don't match expected: map[key:val], actual: map[key1:val1]`), - errors.New(`stringTable does not match expected`), + errors.New(`stringTable '[ cpu1 nanoseconds1 samples count samples1 count1 cpu2 nanoseconds2]' does not match expected '[ cpu nanoseconds samples count]'`), errors.New(`period does not match expected '1', actual '2'`), fmt.Errorf(`sampleType: %w`, fmt.Errorf(`missing expected valueType "unit: 4, type: 3, aggregationTemporality: 1"`)), fmt.Errorf(`sampleType: %w`, fmt.Errorf(`unexpected valueType "unit: 6, type: 5, aggregationTemporality: 1"`)), diff --git a/pkg/pdatatest/pprofiletest/validate.go b/pkg/pdatatest/pprofiletest/validate.go new file mode 100644 index 0000000000000..dcab32cd8ba8f --- /dev/null +++ b/pkg/pdatatest/pprofiletest/validate.go @@ -0,0 +1,260 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package pprofiletest // import "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatatest/pprofiletest" +import ( + "errors" + "fmt" + + "go.opentelemetry.io/collector/pdata/pcommon" + "go.opentelemetry.io/collector/pdata/pprofile" +) + +func ValidateProfile(pp pprofile.Profile) error { + var errs error + + stLen := pp.StringTable().Len() + if stLen < 1 { + // Return here to avoid panicking when accessing the string table. + return fmt.Errorf("empty string table, must at least contain the empty string") + } + + if pp.StringTable().At(0) != "" { + errs = errors.Join(errs, fmt.Errorf("string table must start with the empty string")) + } + + if pp.SampleType().Len() < 1 { + // Since the proto field 'default_sample_type_index' is always valid, there must be at least + // one sample type in the profile. + errs = errors.Join(errs, fmt.Errorf("missing sample type, need at least a default")) + } + + errs = errors.Join(errs, validateSampleType(pp)) + + errs = errors.Join(errs, validateSamples(pp)) + + if err := validateValueType(stLen, pp.PeriodType()); err != nil { + errs = errors.Join(errs, fmt.Errorf("period_type: %w", err)) + } + + if err := validateIndex(stLen, pp.DefaultSampleTypeStrindex()); err != nil { + errs = errors.Join(errs, fmt.Errorf("default_sample_type_strindex: %w", err)) + } + + if err := validateIndices(stLen, pp.CommentStrindices()); err != nil { + errs = errors.Join(errs, fmt.Errorf("comment_strindices: %w", err)) + } + + if err := validateIndices(pp.AttributeTable().Len(), pp.AttributeIndices()); err != nil { + errs = errors.Join(errs, fmt.Errorf("attribute_indices: %w", err)) + } + + errs = errors.Join(errs, validateAttributeUnits(pp)) + + return errs +} + +func validateIndices(length int, indices pcommon.Int32Slice) error { + var errs error + + for i := range indices.Len() { + if err := validateIndex(length, indices.At(i)); err != nil { + errs = errors.Join(errs, fmt.Errorf("[%d]: %w", i, err)) + } + } + + return errs +} + +func validateIndex(length int, idx int32) error { + if idx < 0 || int(idx) >= length { + return fmt.Errorf("index %d is out of range [0..%d)", + idx, length) + } + return nil +} + +func validateSampleType(pp pprofile.Profile) error { + var errs error + + stLen := pp.StringTable().Len() + for i := range pp.SampleType().Len() { + if err := validateValueType(stLen, pp.SampleType().At(i)); err != nil { + errs = errors.Join(errs, fmt.Errorf("sample_type[%d]: %w", i, err)) + } + } + + return errs +} + +func validateValueType(stLen int, pvt pprofile.ValueType) error { + var errs error + + if err := validateIndex(stLen, pvt.TypeStrindex()); err != nil { + errs = errors.Join(errs, fmt.Errorf("type_strindex: %w", err)) + } + + if err := validateIndex(stLen, pvt.UnitStrindex()); err != nil { + errs = errors.Join(errs, fmt.Errorf("unit_strindex: %w", err)) + } + + if pvt.AggregationTemporality() != pprofile.AggregationTemporalityDelta && + pvt.AggregationTemporality() != pprofile.AggregationTemporalityCumulative { + errs = errors.Join(errs, fmt.Errorf("aggregation_temporality %d is invalid", + pvt.AggregationTemporality())) + } + + return errs +} + +func validateSamples(pp pprofile.Profile) error { + var errs error + + for i := range pp.Sample().Len() { + if err := validateSample(pp, pp.Sample().At(i)); err != nil { + errs = errors.Join(errs, fmt.Errorf("sample[%d]: %w", i, err)) + } + } + + return errs +} + +func validateSample(pp pprofile.Profile, sample pprofile.Sample) error { + var errs error + + length := sample.LocationsLength() + if length < 0 { + errs = errors.Join(errs, fmt.Errorf("locations_length %d is negative", length)) + } + + if length > 0 { + start := sample.LocationsStartIndex() + if err := validateIndex(pp.LocationIndices().Len(), start); err != nil { + errs = errors.Join(errs, fmt.Errorf("locations_start_index: %w", err)) + } + + end := start + length + if err := validateIndex(pp.LocationIndices().Len(), end-1); err != nil { + errs = errors.Join(errs, fmt.Errorf("locations end (%d+%d): %w", start, length, err)) + } + + if errs != nil { + // Return here to avoid panicking when accessing the location indices. + return errs + } + + for i := start; i < end; i++ { + locIdx := pp.LocationIndices().At(int(i)) + if err := validateIndex(pp.LocationTable().Len(), locIdx); err != nil { + errs = errors.Join(errs, fmt.Errorf("location_indices[%d]: %w", i, err)) + continue + } + if err := validateLocation(pp, pp.LocationTable().At(int(locIdx))); err != nil { + errs = errors.Join(errs, fmt.Errorf("locations[%d]: %w", i, err)) + } + } + } + + numValues := pp.SampleType().Len() + if sample.Value().Len() != numValues { + errs = errors.Join(errs, fmt.Errorf("value length %d does not match sample_type length=%d", + sample.Value().Len(), numValues)) + } + + if err := validateIndices(pp.AttributeTable().Len(), sample.AttributeIndices()); err != nil { + errs = errors.Join(errs, fmt.Errorf("attribute_indices: %w", err)) + } + + startTime := uint64(pp.Time().AsTime().UnixNano()) + endTime := startTime + uint64(pp.Duration().AsTime().UnixNano()) + for i := range sample.TimestampsUnixNano().Len() { + ts := sample.TimestampsUnixNano().At(i) + if ts < startTime || ts >= endTime { + errs = errors.Join(errs, fmt.Errorf("timestamps_unix_nano[%d] %d is out of range [%d..%d)", + i, ts, startTime, endTime)) + } + } + + if sample.HasLinkIndex() { + if err := validateIndex(pp.LinkTable().Len(), sample.LinkIndex()); err != nil { + errs = errors.Join(errs, fmt.Errorf("link_index: %w", err)) + } + } + + return errs +} + +func validateLocation(pp pprofile.Profile, loc pprofile.Location) error { + var errs error + + if loc.HasMappingIndex() { + if err := validateIndex(pp.MappingTable().Len(), loc.MappingIndex()); err != nil { + // Continuing would run into a panic. + return fmt.Errorf("mapping_index: %w", err) + } + + if err := validateMapping(pp, pp.MappingTable().At(int(loc.MappingIndex()))); err != nil { + errs = errors.Join(errs, fmt.Errorf("mapping: %w", err)) + } + } + + for i := range loc.Line().Len() { + if err := validateLine(pp, loc.Line().At(i)); err != nil { + errs = errors.Join(errs, fmt.Errorf("line[%d]: %w", i, err)) + } + } + + if err := validateIndices(pp.AttributeTable().Len(), loc.AttributeIndices()); err != nil { + errs = errors.Join(errs, fmt.Errorf("attribute_indices: %w", err)) + } + + return errs +} + +func validateLine(pp pprofile.Profile, line pprofile.Line) error { + if err := validateIndex(pp.FunctionTable().Len(), line.FunctionIndex()); err != nil { + return fmt.Errorf("function_index: %w", err) + } + + return nil +} + +func validateMapping(pp pprofile.Profile, mapping pprofile.Mapping) error { + var errs error + + if err := validateIndex(pp.StringTable().Len(), mapping.FilenameStrindex()); err != nil { + errs = errors.Join(errs, fmt.Errorf("filename_strindex: %w", err)) + } + + if err := validateIndices(pp.AttributeTable().Len(), mapping.AttributeIndices()); err != nil { + errs = errors.Join(errs, fmt.Errorf("attribute_indices: %w", err)) + } + + return errs +} + +func validateAttributeUnits(pp pprofile.Profile) error { + var errs error + + for i := range pp.AttributeUnits().Len() { + if err := validateAttributeUnit(pp, pp.AttributeUnits().At(i)); err != nil { + errs = errors.Join(errs, fmt.Errorf("attribute_units[%d]: %w", i, err)) + } + } + + return errs +} + +func validateAttributeUnit(pp pprofile.Profile, au pprofile.AttributeUnit) error { + var errs error + + if err := validateIndex(pp.StringTable().Len(), au.AttributeKeyStrindex()); err != nil { + errs = errors.Join(errs, fmt.Errorf("attribute_key: %w", err)) + } + + if err := validateIndex(pp.StringTable().Len(), au.UnitStrindex()); err != nil { + errs = errors.Join(errs, fmt.Errorf("unit: %w", err)) + } + + return errs +} diff --git a/pkg/pdatatest/pprofiletest/validate_test.go b/pkg/pdatatest/pprofiletest/validate_test.go new file mode 100644 index 0000000000000..0156b1b950699 --- /dev/null +++ b/pkg/pdatatest/pprofiletest/validate_test.go @@ -0,0 +1,762 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package pprofiletest + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "go.opentelemetry.io/collector/pdata/pcommon" + "go.opentelemetry.io/collector/pdata/pprofile" +) + +func Test_validateProfile(t *testing.T) { + tests := []struct { + name string + profile pprofile.Profile + wantErr assert.ErrorAssertionFunc + }{ + { + name: "empty string table", + profile: pprofile.NewProfile(), + wantErr: assert.Error, + }, + { + name: "no empty string at index 0", + profile: func() pprofile.Profile { + pp := pprofile.NewProfile() + pp.StringTable().Append("x") + return pp + }(), + wantErr: assert.Error, + }, + { + name: "empty sample type", + profile: func() pprofile.Profile { + pp := pprofile.NewProfile() + pp.StringTable().Append("") + return pp + }(), + wantErr: assert.Error, + }, + { + name: "invalid sample type", + profile: func() pprofile.Profile { + pp := pprofile.NewProfile() + pp.StringTable().Append("") + pp.SampleType().AppendEmpty() + return pp + }(), + wantErr: assert.Error, + }, + { + name: "invalid sample", + profile: func() pprofile.Profile { + pp := pprofile.NewProfile() + pp.StringTable().Append("") + st := pp.SampleType().AppendEmpty() + st.SetAggregationTemporality(pprofile.AggregationTemporalityDelta) + pp.PeriodType().SetAggregationTemporality(pprofile.AggregationTemporalityDelta) + pp.Sample().AppendEmpty() + return pp + }(), + wantErr: assert.Error, + }, + { + name: "invalid default sample type string index", + profile: func() pprofile.Profile { + pp := pprofile.NewProfile() + pp.StringTable().Append("") + st := pp.SampleType().AppendEmpty() + st.SetAggregationTemporality(pprofile.AggregationTemporalityDelta) + pp.PeriodType().SetAggregationTemporality(pprofile.AggregationTemporalityDelta) + s := pp.Sample().AppendEmpty() + s.Value().Append(0) + pp.SetDefaultSampleTypeStrindex(1) + return pp + }(), + wantErr: assert.Error, + }, + { + name: "invalid comment string index", + profile: func() pprofile.Profile { + pp := pprofile.NewProfile() + pp.StringTable().Append("") + st := pp.SampleType().AppendEmpty() + st.SetAggregationTemporality(pprofile.AggregationTemporalityDelta) + pp.PeriodType().SetAggregationTemporality(pprofile.AggregationTemporalityDelta) + s := pp.Sample().AppendEmpty() + s.Value().Append(0) + pp.SetDefaultSampleTypeStrindex(0) + pp.CommentStrindices().Append(1) + return pp + }(), + wantErr: assert.Error, + }, + { + name: "invalid attribute index", + profile: func() pprofile.Profile { + pp := pprofile.NewProfile() + pp.StringTable().Append("") + st := pp.SampleType().AppendEmpty() + st.SetAggregationTemporality(pprofile.AggregationTemporalityDelta) + pp.PeriodType().SetAggregationTemporality(pprofile.AggregationTemporalityDelta) + s := pp.Sample().AppendEmpty() + s.Value().Append(0) + pp.SetDefaultSampleTypeStrindex(0) + pp.CommentStrindices().Append(0) + pp.AttributeIndices().Append(1) + return pp + }(), + wantErr: assert.Error, + }, + { + name: "invalid attribute unit index", + profile: func() pprofile.Profile { + pp := pprofile.NewProfile() + pp.StringTable().Append("") + st := pp.SampleType().AppendEmpty() + st.SetAggregationTemporality(pprofile.AggregationTemporalityDelta) + pp.PeriodType().SetAggregationTemporality(pprofile.AggregationTemporalityDelta) + s := pp.Sample().AppendEmpty() + s.Value().Append(0) + pp.SetDefaultSampleTypeStrindex(0) + pp.CommentStrindices().Append(0) + pp.AttributeIndices().Append(0) + au := pp.AttributeUnits().AppendEmpty() + au.SetAttributeKeyStrindex(1) + return pp + }(), + wantErr: assert.Error, + }, + { + name: "valid", + profile: func() pprofile.Profile { + pp := pprofile.NewProfile() + pp.StringTable().Append("") + st := pp.SampleType().AppendEmpty() + st.SetAggregationTemporality(pprofile.AggregationTemporalityDelta) + pp.PeriodType().SetAggregationTemporality(pprofile.AggregationTemporalityDelta) + s := pp.Sample().AppendEmpty() + s.Value().Append(0) + pp.SetDefaultSampleTypeStrindex(0) + pp.CommentStrindices().Append(0) + pp.AttributeIndices().Append(0) + au := pp.AttributeUnits().AppendEmpty() + au.SetAttributeKeyStrindex(0) + return pp + }(), + wantErr: assert.Error, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.wantErr(t, ValidateProfile(tt.profile)) + }) + } +} + +func Test_validateIndices(t *testing.T) { + indices := pcommon.NewInt32Slice() + indices.Append(0, 1) + + assert.Error(t, validateIndices(0, indices)) + assert.Error(t, validateIndices(1, indices)) + assert.NoError(t, validateIndices(2, indices)) + assert.NoError(t, validateIndices(3, indices)) +} + +func Test_validateIndex(t *testing.T) { + assert.Error(t, validateIndex(0, 0)) + assert.NoError(t, validateIndex(1, 0)) + assert.Error(t, validateIndex(1, 1)) +} + +func Test_validateSampleTypes(t *testing.T) { + tests := []struct { + name string + profile pprofile.Profile + wantErr assert.ErrorAssertionFunc + }{ + { + name: "empty", + profile: pprofile.NewProfile(), + wantErr: assert.NoError, + }, + { + name: "valid", + profile: func() pprofile.Profile { + pp := pprofile.NewProfile() + pp.StringTable().Append("") + s := pp.SampleType().AppendEmpty() + s.SetAggregationTemporality(pprofile.AggregationTemporalityDelta) + s = pp.SampleType().AppendEmpty() + s.SetAggregationTemporality(pprofile.AggregationTemporalityCumulative) + return pp + }(), + wantErr: assert.NoError, + }, + { + name: "invalid", + profile: func() pprofile.Profile { + pp := pprofile.NewProfile() + pp.StringTable().Append("") + s := pp.SampleType().AppendEmpty() + s.SetAggregationTemporality(pprofile.AggregationTemporalityDelta) + s = pp.SampleType().AppendEmpty() + s.SetAggregationTemporality(3) + return pp + }(), + wantErr: assert.Error, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.wantErr(t, validateSampleType(tt.profile)) + }) + } +} + +func Test_validateValueType(t *testing.T) { + tests := []struct { + name string + profile pprofile.Profile + valueType pprofile.ValueType + wantErr assert.ErrorAssertionFunc + }{ + { + name: "type string index out of range", + profile: pprofile.NewProfile(), + valueType: pprofile.NewValueType(), + wantErr: assert.Error, + }, + { + name: "invalid aggregation temporality", + profile: func() pprofile.Profile { + pp := pprofile.NewProfile() + pp.StringTable().Append("") + return pp + }(), + valueType: pprofile.NewValueType(), + wantErr: assert.Error, + }, + { + name: "unit string index out of range", + profile: func() pprofile.Profile { + pp := pprofile.NewProfile() + pp.StringTable().Append("") + return pp + }(), + valueType: func() pprofile.ValueType { + pp := pprofile.NewValueType() + pp.SetAggregationTemporality(pprofile.AggregationTemporalityDelta) + pp.SetUnitStrindex(1) + return pp + }(), + wantErr: assert.Error, + }, + { + name: "valid delta", + profile: func() pprofile.Profile { + pp := pprofile.NewProfile() + pp.StringTable().Append("") + return pp + }(), + valueType: func() pprofile.ValueType { + pp := pprofile.NewValueType() + pp.SetAggregationTemporality(pprofile.AggregationTemporalityDelta) + return pp + }(), + wantErr: assert.NoError, + }, + { + name: "valid cumulative", + profile: func() pprofile.Profile { + pp := pprofile.NewProfile() + pp.StringTable().Append("") + return pp + }(), + valueType: func() pprofile.ValueType { + pp := pprofile.NewValueType() + pp.SetAggregationTemporality(pprofile.AggregationTemporalityCumulative) + return pp + }(), + wantErr: assert.NoError, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.wantErr(t, validateValueType(tt.profile.StringTable().Len(), tt.valueType)) + }) + } +} + +func Test_validateSamples(t *testing.T) { + tests := []struct { + name string + profile pprofile.Profile + wantErr assert.ErrorAssertionFunc + }{ + { + name: "no samples", + profile: pprofile.NewProfile(), + wantErr: assert.NoError, + }, + { + name: "valid samples", + profile: func() pprofile.Profile { + pp := pprofile.NewProfile() + pp.Sample().AppendEmpty() + pp.Sample().AppendEmpty() + return pp + }(), + wantErr: assert.NoError, + }, + { + name: "invalid sample", + profile: func() pprofile.Profile { + pp := pprofile.NewProfile() + pp.Sample().AppendEmpty() + s := pp.Sample().AppendEmpty() + s.TimestampsUnixNano().Append(123) + return pp + }(), + wantErr: assert.Error, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.wantErr(t, validateSamples(tt.profile)) + }) + } +} + +func Test_validateSample(t *testing.T) { + tests := []struct { + name string + profile pprofile.Profile + sample pprofile.Sample + wantErr assert.ErrorAssertionFunc + }{ + { + name: "empty", + profile: pprofile.NewProfile(), + sample: pprofile.NewSample(), + wantErr: assert.NoError, + }, + { + name: "negative location length", + profile: pprofile.NewProfile(), + sample: func() pprofile.Sample { + s := pprofile.NewSample() + s.SetLocationsLength(-1) + return s + }(), + wantErr: assert.Error, + }, + { + name: "location length out of range", + profile: pprofile.NewProfile(), + sample: func() pprofile.Sample { + s := pprofile.NewSample() + s.SetLocationsStartIndex(0) + s.SetLocationsLength(1) + return s + }(), + wantErr: assert.Error, + }, + { + name: "location start plus location length in range", + profile: func() pprofile.Profile { + pp := pprofile.NewProfile() + pp.LocationTable().AppendEmpty() + pp.LocationIndices().Append(0) + return pp + }(), + sample: func() pprofile.Sample { + s := pprofile.NewSample() + s.SetLocationsStartIndex(0) + s.SetLocationsLength(1) + return s + }(), + wantErr: assert.NoError, + }, + { + name: "location start plus location length out of range", + profile: func() pprofile.Profile { + pp := pprofile.NewProfile() + pp.LocationTable().AppendEmpty() + pp.LocationIndices().Append(0) + return pp + }(), + sample: func() pprofile.Sample { + s := pprofile.NewSample() + s.SetLocationsStartIndex(0) + s.SetLocationsLength(2) + return s + }(), + wantErr: assert.Error, + }, + { + name: "location index out of range", + profile: func() pprofile.Profile { + pp := pprofile.NewProfile() + pp.LocationTable().AppendEmpty() + pp.LocationIndices().Append(1) + return pp + }(), + sample: func() pprofile.Sample { + s := pprofile.NewSample() + s.SetLocationsStartIndex(0) + s.SetLocationsLength(1) + return s + }(), + wantErr: assert.Error, + }, + { + name: "sample type length does not match", + profile: pprofile.NewProfile(), + sample: func() pprofile.Sample { + s := pprofile.NewSample() + s.Value().Append(123) + return s + }(), + wantErr: assert.Error, + }, + { + name: "attribute in range", + profile: func() pprofile.Profile { + pp := pprofile.NewProfile() + pp.AttributeTable().AppendEmpty() + return pp + }(), + sample: func() pprofile.Sample { + s := pprofile.NewSample() + s.AttributeIndices().Append(0) + return s + }(), + wantErr: assert.NoError, + }, + { + name: "attribute out of range", + profile: func() pprofile.Profile { + pp := pprofile.NewProfile() + pp.AttributeTable().AppendEmpty() + return pp + }(), + sample: func() pprofile.Sample { + s := pprofile.NewSample() + s.AttributeIndices().Append(1) + return s + }(), + wantErr: assert.Error, + }, + { + name: "timestamp in range", + profile: func() pprofile.Profile { + pp := pprofile.NewProfile() + pp.SetTime(1) + pp.SetDuration(1) + return pp + }(), + sample: func() pprofile.Sample { + s := pprofile.NewSample() + s.TimestampsUnixNano().Append(1) + return s + }(), + wantErr: assert.NoError, + }, + { + name: "timestamp too small", + profile: func() pprofile.Profile { + pp := pprofile.NewProfile() + pp.SetTime(1) + pp.SetDuration(1) + return pp + }(), + sample: func() pprofile.Sample { + s := pprofile.NewSample() + s.TimestampsUnixNano().Append(0) + return s + }(), + wantErr: assert.Error, + }, + { + name: "timestamp too high", + profile: func() pprofile.Profile { + pp := pprofile.NewProfile() + pp.SetTime(1) + pp.SetDuration(1) + return pp + }(), + sample: func() pprofile.Sample { + s := pprofile.NewSample() + s.TimestampsUnixNano().Append(2) + return s + }(), + wantErr: assert.Error, + }, + { + name: "link in range", + profile: func() pprofile.Profile { + pp := pprofile.NewProfile() + pp.LinkTable().AppendEmpty() + return pp + }(), + sample: func() pprofile.Sample { + s := pprofile.NewSample() + s.SetLinkIndex(0) + return s + }(), + wantErr: assert.NoError, + }, + { + name: "link out of range", + profile: func() pprofile.Profile { + pp := pprofile.NewProfile() + pp.LinkTable().AppendEmpty() + return pp + }(), + sample: func() pprofile.Sample { + s := pprofile.NewSample() + s.SetLinkIndex(1) + return s + }(), + wantErr: assert.Error, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.wantErr(t, validateSample(tt.profile, tt.sample)) + }) + } +} + +func Test_validateLocation(t *testing.T) { + tests := []struct { + name string + profile pprofile.Profile + location pprofile.Location + wantErr assert.ErrorAssertionFunc + }{ + { + name: "empty", + profile: pprofile.NewProfile(), + location: pprofile.NewLocation(), + wantErr: assert.NoError, + }, + { + name: "mapping index in range", + profile: func() pprofile.Profile { + pp := pprofile.NewProfile() + pp.StringTable().Append("") + pp.MappingTable().AppendEmpty() + return pp + }(), + location: func() pprofile.Location { + l := pprofile.NewLocation() + l.SetMappingIndex(0) + return l + }(), + wantErr: assert.NoError, + }, + { + name: "with line and attribute", + profile: func() pprofile.Profile { + pp := pprofile.NewProfile() + pp.StringTable().Append("") + pp.MappingTable().AppendEmpty() + pp.AttributeTable().AppendEmpty() + pp.FunctionTable().AppendEmpty() + return pp + }(), + location: func() pprofile.Location { + l := pprofile.NewLocation() + l.AttributeIndices().Append(0) + l.Line().AppendEmpty() + return l + }(), + wantErr: assert.NoError, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.wantErr(t, validateLocation(tt.profile, tt.location)) + }) + } +} + +func Test_validateLine(t *testing.T) { + tests := []struct { + name string + profile pprofile.Profile + line pprofile.Line + wantErr assert.ErrorAssertionFunc + }{ + { + name: "function index out of range", + profile: pprofile.NewProfile(), + line: pprofile.NewLine(), + wantErr: assert.Error, + }, + { + name: "function index in range", + profile: func() pprofile.Profile { + pp := pprofile.NewProfile() + pp.StringTable().Append("") + pp.FunctionTable().AppendEmpty() + return pp + }(), + line: pprofile.NewLine(), + wantErr: assert.NoError, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.wantErr(t, validateLine(tt.profile, tt.line)) + }) + } +} + +func Test_validateMapping(t *testing.T) { + tests := []struct { + name string + profile pprofile.Profile + mapping pprofile.Mapping + wantErr assert.ErrorAssertionFunc + }{ + { + name: "filename index out of range", + profile: pprofile.NewProfile(), + mapping: pprofile.NewMapping(), + wantErr: assert.Error, + }, + { + name: "filename index in range", + profile: func() pprofile.Profile { + pp := pprofile.NewProfile() + pp.StringTable().Append("") + return pp + }(), + mapping: pprofile.NewMapping(), + wantErr: assert.NoError, + }, + { + name: "attribute out of range", + profile: func() pprofile.Profile { + pp := pprofile.NewProfile() + pp.StringTable().Append("") + return pp + }(), + mapping: func() pprofile.Mapping { + m := pprofile.NewMapping() + m.AttributeIndices().Append(0) + return m + }(), + wantErr: assert.Error, + }, + { + name: "attribute in range", + profile: func() pprofile.Profile { + pp := pprofile.NewProfile() + pp.StringTable().Append("") + pp.AttributeTable().AppendEmpty() + return pp + }(), + mapping: func() pprofile.Mapping { + m := pprofile.NewMapping() + m.AttributeIndices().Append(0) + return m + }(), + wantErr: assert.NoError, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.wantErr(t, validateMapping(tt.profile, tt.mapping)) + }) + } +} + +func Test_validateAttributeUnits(t *testing.T) { + tests := []struct { + name string + profile pprofile.Profile + wantErr assert.ErrorAssertionFunc + }{ + { + name: "empty", + profile: pprofile.NewProfile(), + wantErr: assert.NoError, + }, + { + name: "in range", + profile: func() pprofile.Profile { + pp := pprofile.NewProfile() + pp.StringTable().Append("") + pp.AttributeUnits().AppendEmpty() + return pp + }(), + wantErr: assert.NoError, + }, + { + name: "unit index out of range", + profile: func() pprofile.Profile { + pp := pprofile.NewProfile() + pp.StringTable().Append("") + au := pp.AttributeUnits().AppendEmpty() + au.SetUnitStrindex(1) + return pp + }(), + wantErr: assert.Error, + }, + { + name: "attribute key index out of range", + profile: func() pprofile.Profile { + pp := pprofile.NewProfile() + pp.StringTable().Append("") + au := pp.AttributeUnits().AppendEmpty() + au.SetAttributeKeyStrindex(1) + return pp + }(), + wantErr: assert.Error, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.wantErr(t, validateAttributeUnits(tt.profile)) + }) + } +} + +func Test_validateAttributeUnitAt(t *testing.T) { + tests := []struct { + name string + profile pprofile.Profile + attrUnit pprofile.AttributeUnit + wantErr assert.ErrorAssertionFunc + }{ + { + name: "out of range", + profile: pprofile.NewProfile(), + attrUnit: pprofile.NewAttributeUnit(), + wantErr: assert.Error, + }, + { + name: "in range", + profile: func() pprofile.Profile { + pp := pprofile.NewProfile() + pp.StringTable().Append("") + return pp + }(), + attrUnit: pprofile.NewAttributeUnit(), + wantErr: assert.NoError, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.wantErr(t, validateAttributeUnit(tt.profile, tt.attrUnit)) + }) + } +}