diff --git a/sdk/metric/metricdata/metricdatatest/assertion.go b/sdk/metric/metricdata/metricdatatest/assertion.go index c66409f4814..4c519d15d49 100644 --- a/sdk/metric/metricdata/metricdatatest/assertion.go +++ b/sdk/metric/metricdata/metricdatatest/assertion.go @@ -44,38 +44,66 @@ type Datatypes interface { // Aggregation and Value type from metricdata are not included here. } +type config struct { + ignoreTimestamp bool +} + +// Option allows for fine grain control over how AssertEqual operates. +type Option interface { + apply(cfg config) config +} + +type fnOption func(cfg config) config + +func (fn fnOption) apply(cfg config) config { + return fn(cfg) +} + +// IgnoreTimestamp disables checking if timestamps are different. +func IgnoreTimestamp() Option { + return fnOption(func(cfg config) config { + cfg.ignoreTimestamp = true + return cfg + }) +} + // AssertEqual asserts that the two concrete data-types from the metricdata // package are equal. -func AssertEqual[T Datatypes](t *testing.T, expected, actual T) bool { +func AssertEqual[T Datatypes](t *testing.T, expected, actual T, opts ...Option) bool { t.Helper() + cfg := config{} + for _, opt := range opts { + cfg = opt.apply(cfg) + } + // Generic types cannot be type asserted. Use an interface instead. aIface := interface{}(actual) var r []string switch e := interface{}(expected).(type) { case metricdata.DataPoint[int64]: - r = equalDataPoints(e, aIface.(metricdata.DataPoint[int64])) + r = equalDataPoints(e, aIface.(metricdata.DataPoint[int64]), cfg) case metricdata.DataPoint[float64]: - r = equalDataPoints(e, aIface.(metricdata.DataPoint[float64])) + r = equalDataPoints(e, aIface.(metricdata.DataPoint[float64]), cfg) case metricdata.Gauge[int64]: - r = equalGauges(e, aIface.(metricdata.Gauge[int64])) + r = equalGauges(e, aIface.(metricdata.Gauge[int64]), cfg) case metricdata.Gauge[float64]: - r = equalGauges(e, aIface.(metricdata.Gauge[float64])) + r = equalGauges(e, aIface.(metricdata.Gauge[float64]), cfg) case metricdata.Histogram: - r = equalHistograms(e, aIface.(metricdata.Histogram)) + r = equalHistograms(e, aIface.(metricdata.Histogram), cfg) case metricdata.HistogramDataPoint: - r = equalHistogramDataPoints(e, aIface.(metricdata.HistogramDataPoint)) + r = equalHistogramDataPoints(e, aIface.(metricdata.HistogramDataPoint), cfg) case metricdata.Metrics: - r = equalMetrics(e, aIface.(metricdata.Metrics)) + r = equalMetrics(e, aIface.(metricdata.Metrics), cfg) case metricdata.ResourceMetrics: - r = equalResourceMetrics(e, aIface.(metricdata.ResourceMetrics)) + r = equalResourceMetrics(e, aIface.(metricdata.ResourceMetrics), cfg) case metricdata.ScopeMetrics: - r = equalScopeMetrics(e, aIface.(metricdata.ScopeMetrics)) + r = equalScopeMetrics(e, aIface.(metricdata.ScopeMetrics), cfg) case metricdata.Sum[int64]: - r = equalSums(e, aIface.(metricdata.Sum[int64])) + r = equalSums(e, aIface.(metricdata.Sum[int64]), cfg) case metricdata.Sum[float64]: - r = equalSums(e, aIface.(metricdata.Sum[float64])) + r = equalSums(e, aIface.(metricdata.Sum[float64]), cfg) default: // We control all types passed to this, panic to signal developers // early they changed things in an incompatible way. @@ -90,9 +118,15 @@ func AssertEqual[T Datatypes](t *testing.T, expected, actual T) bool { } // AssertAggregationsEqual asserts that two Aggregations are equal. -func AssertAggregationsEqual(t *testing.T, expected, actual metricdata.Aggregation) bool { +func AssertAggregationsEqual(t *testing.T, expected, actual metricdata.Aggregation, opts ...Option) bool { t.Helper() - if r := equalAggregations(expected, actual); len(r) > 0 { + + cfg := config{} + for _, opt := range opts { + cfg = opt.apply(cfg) + } + + if r := equalAggregations(expected, actual, cfg); len(r) > 0 { t.Error(r) return false } diff --git a/sdk/metric/metricdata/metricdatatest/assertion_test.go b/sdk/metric/metricdata/metricdatatest/assertion_test.go index 79657dd15fb..71331f5a945 100644 --- a/sdk/metric/metricdata/metricdatatest/assertion_test.go +++ b/sdk/metric/metricdata/metricdatatest/assertion_test.go @@ -63,6 +63,18 @@ var ( Time: endB, Value: 2.0, } + dataPointInt64C = metricdata.DataPoint[int64]{ + Attributes: attrA, + StartTime: startB, + Time: endB, + Value: -1, + } + dataPointFloat64C = metricdata.DataPoint[float64]{ + Attributes: attrA, + StartTime: startB, + Time: endB, + Value: -1.0, + } max, min = 99.0, 3. histogramDataPointA = metricdata.HistogramDataPoint{ @@ -85,6 +97,15 @@ var ( Min: &min, Sum: 3, } + histogramDataPointC = metricdata.HistogramDataPoint{ + Attributes: attrA, + StartTime: startB, + Time: endB, + Count: 2, + Bounds: []float64{0, 10}, + BucketCounts: []uint64{1, 1}, + Sum: 2, + } gaugeInt64A = metricdata.Gauge[int64]{ DataPoints: []metricdata.DataPoint[int64]{dataPointInt64A}, @@ -98,6 +119,12 @@ var ( gaugeFloat64B = metricdata.Gauge[float64]{ DataPoints: []metricdata.DataPoint[float64]{dataPointFloat64B}, } + gaugeInt64C = metricdata.Gauge[int64]{ + DataPoints: []metricdata.DataPoint[int64]{dataPointInt64C}, + } + gaugeFloat64C = metricdata.Gauge[float64]{ + DataPoints: []metricdata.DataPoint[float64]{dataPointFloat64C}, + } sumInt64A = metricdata.Sum[int64]{ Temporality: metricdata.CumulativeTemporality, @@ -119,6 +146,16 @@ var ( IsMonotonic: true, DataPoints: []metricdata.DataPoint[float64]{dataPointFloat64B}, } + sumInt64C = metricdata.Sum[int64]{ + Temporality: metricdata.CumulativeTemporality, + IsMonotonic: true, + DataPoints: []metricdata.DataPoint[int64]{dataPointInt64C}, + } + sumFloat64C = metricdata.Sum[float64]{ + Temporality: metricdata.CumulativeTemporality, + IsMonotonic: true, + DataPoints: []metricdata.DataPoint[float64]{dataPointFloat64C}, + } histogramA = metricdata.Histogram{ Temporality: metricdata.CumulativeTemporality, @@ -128,6 +165,10 @@ var ( Temporality: metricdata.DeltaTemporality, DataPoints: []metricdata.HistogramDataPoint{histogramDataPointB}, } + histogramC = metricdata.Histogram{ + Temporality: metricdata.CumulativeTemporality, + DataPoints: []metricdata.HistogramDataPoint{histogramDataPointC}, + } metricsA = metricdata.Metrics{ Name: "A", @@ -141,6 +182,12 @@ var ( Unit: unit.Bytes, Data: gaugeFloat64B, } + metricsC = metricdata.Metrics{ + Name: "A", + Description: "A desc", + Unit: unit.Dimensionless, + Data: sumInt64C, + } scopeMetricsA = metricdata.ScopeMetrics{ Scope: instrumentation.Scope{Name: "A"}, @@ -150,6 +197,10 @@ var ( Scope: instrumentation.Scope{Name: "B"}, Metrics: []metricdata.Metrics{metricsB}, } + scopeMetricsC = metricdata.ScopeMetrics{ + Scope: instrumentation.Scope{Name: "A"}, + Metrics: []metricdata.Metrics{metricsC}, + } resourceMetricsA = metricdata.ResourceMetrics{ Resource: resource.NewSchemaless(attribute.String("resource", "A")), @@ -159,20 +210,34 @@ var ( Resource: resource.NewSchemaless(attribute.String("resource", "B")), ScopeMetrics: []metricdata.ScopeMetrics{scopeMetricsB}, } + resourceMetricsC = metricdata.ResourceMetrics{ + Resource: resource.NewSchemaless(attribute.String("resource", "A")), + ScopeMetrics: []metricdata.ScopeMetrics{scopeMetricsC}, + } ) -type equalFunc[T Datatypes] func(T, T) []string +type equalFunc[T Datatypes] func(T, T, config) []string func testDatatype[T Datatypes](a, b T, f equalFunc[T]) func(*testing.T) { return func(t *testing.T) { AssertEqual(t, a, a) AssertEqual(t, b, b) - r := f(a, b) + r := f(a, b, config{}) assert.Greaterf(t, len(r), 0, "%v == %v", a, b) } } +func testDatatypeIgnoreTime[T Datatypes](a, b T, f equalFunc[T]) func(*testing.T) { + return func(t *testing.T) { + AssertEqual(t, a, a) + AssertEqual(t, b, b) + + r := f(a, b, config{ignoreTimestamp: true}) + assert.Equalf(t, len(r), 0, "%v == %v", a, b) + } +} + func TestAssertEqual(t *testing.T) { t.Run("ResourceMetrics", testDatatype(resourceMetricsA, resourceMetricsB, equalResourceMetrics)) t.Run("ScopeMetrics", testDatatype(scopeMetricsA, scopeMetricsB, equalScopeMetrics)) @@ -187,6 +252,20 @@ func TestAssertEqual(t *testing.T) { t.Run("DataPointFloat64", testDatatype(dataPointFloat64A, dataPointFloat64B, equalDataPoints[float64])) } +func TestAssertEqualIgnoreTime(t *testing.T) { + t.Run("ResourceMetrics", testDatatypeIgnoreTime(resourceMetricsA, resourceMetricsC, equalResourceMetrics)) + t.Run("ScopeMetrics", testDatatypeIgnoreTime(scopeMetricsA, scopeMetricsC, equalScopeMetrics)) + t.Run("Metrics", testDatatypeIgnoreTime(metricsA, metricsC, equalMetrics)) + t.Run("Histogram", testDatatypeIgnoreTime(histogramA, histogramC, equalHistograms)) + t.Run("SumInt64", testDatatypeIgnoreTime(sumInt64A, sumInt64C, equalSums[int64])) + t.Run("SumFloat64", testDatatypeIgnoreTime(sumFloat64A, sumFloat64C, equalSums[float64])) + t.Run("GaugeInt64", testDatatypeIgnoreTime(gaugeInt64A, gaugeInt64C, equalGauges[int64])) + t.Run("GaugeFloat64", testDatatypeIgnoreTime(gaugeFloat64A, gaugeFloat64C, equalGauges[float64])) + t.Run("HistogramDataPoint", testDatatypeIgnoreTime(histogramDataPointA, histogramDataPointC, equalHistogramDataPoints)) + t.Run("DataPointInt64", testDatatypeIgnoreTime(dataPointInt64A, dataPointInt64C, equalDataPoints[int64])) + t.Run("DataPointFloat64", testDatatypeIgnoreTime(dataPointFloat64A, dataPointFloat64C, equalDataPoints[float64])) +} + type unknownAggregation struct { metricdata.Aggregation } @@ -199,27 +278,42 @@ func TestAssertAggregationsEqual(t *testing.T) { AssertAggregationsEqual(t, gaugeFloat64A, gaugeFloat64A) AssertAggregationsEqual(t, histogramA, histogramA) - r := equalAggregations(sumInt64A, nil) + r := equalAggregations(sumInt64A, nil, config{}) assert.Len(t, r, 1, "should return nil comparison mismatch only") - r = equalAggregations(sumInt64A, gaugeInt64A) + r = equalAggregations(sumInt64A, gaugeInt64A, config{}) assert.Len(t, r, 1, "should return with type mismatch only") - r = equalAggregations(unknownAggregation{}, unknownAggregation{}) + r = equalAggregations(unknownAggregation{}, unknownAggregation{}, config{}) assert.Len(t, r, 1, "should return with unknown aggregation only") - r = equalAggregations(sumInt64A, sumInt64B) + r = equalAggregations(sumInt64A, sumInt64B, config{}) assert.Greaterf(t, len(r), 0, "%v == %v", sumInt64A, sumInt64B) - r = equalAggregations(sumFloat64A, sumFloat64B) + r = equalAggregations(sumInt64A, sumInt64C, config{ignoreTimestamp: true}) + assert.Equalf(t, len(r), 0, "%v == %v", sumInt64A, sumInt64C) + + r = equalAggregations(sumFloat64A, sumFloat64B, config{}) assert.Greaterf(t, len(r), 0, "%v == %v", sumFloat64A, sumFloat64B) - r = equalAggregations(gaugeInt64A, gaugeInt64B) + r = equalAggregations(sumFloat64A, sumFloat64C, config{ignoreTimestamp: true}) + assert.Equalf(t, len(r), 0, "%v == %v", sumFloat64A, sumFloat64C) + + r = equalAggregations(gaugeInt64A, gaugeInt64B, config{}) assert.Greaterf(t, len(r), 0, "%v == %v", gaugeInt64A, gaugeInt64B) - r = equalAggregations(gaugeFloat64A, gaugeFloat64B) + r = equalAggregations(gaugeInt64A, gaugeInt64C, config{ignoreTimestamp: true}) + assert.Equalf(t, len(r), 0, "%v == %v", gaugeInt64A, gaugeInt64C) + + r = equalAggregations(gaugeFloat64A, gaugeFloat64B, config{}) assert.Greaterf(t, len(r), 0, "%v == %v", gaugeFloat64A, gaugeFloat64B) - r = equalAggregations(histogramA, histogramB) + r = equalAggregations(gaugeFloat64A, gaugeFloat64C, config{ignoreTimestamp: true}) + assert.Equalf(t, len(r), 0, "%v == %v", gaugeFloat64A, gaugeFloat64C) + + r = equalAggregations(histogramA, histogramB, config{}) assert.Greaterf(t, len(r), 0, "%v == %v", histogramA, histogramB) + + r = equalAggregations(histogramA, histogramC, config{ignoreTimestamp: true}) + assert.Equalf(t, len(r), 0, "%v == %v", histogramA, histogramC) } diff --git a/sdk/metric/metricdata/metricdatatest/comparisons.go b/sdk/metric/metricdata/metricdatatest/comparisons.go index 171e9a50749..4c40f3bf67d 100644 --- a/sdk/metric/metricdata/metricdatatest/comparisons.go +++ b/sdk/metric/metricdata/metricdatatest/comparisons.go @@ -31,7 +31,7 @@ import ( // // The ScopeMetrics each ResourceMetrics contains are compared based on // containing the same ScopeMetrics, not the order they are stored in. -func equalResourceMetrics(a, b metricdata.ResourceMetrics) (reasons []string) { +func equalResourceMetrics(a, b metricdata.ResourceMetrics, cfg config) (reasons []string) { if !a.Resource.Equal(b.Resource) { reasons = append(reasons, notEqualStr("Resources", a.Resource, b.Resource)) } @@ -40,7 +40,7 @@ func equalResourceMetrics(a, b metricdata.ResourceMetrics) (reasons []string) { a.ScopeMetrics, b.ScopeMetrics, func(a, b metricdata.ScopeMetrics) bool { - r := equalScopeMetrics(a, b) + r := equalScopeMetrics(a, b, cfg) return len(r) == 0 }, )) @@ -55,7 +55,7 @@ func equalResourceMetrics(a, b metricdata.ResourceMetrics) (reasons []string) { // // The Metrics each ScopeMetrics contains are compared based on containing the // same Metrics, not the order they are stored in. -func equalScopeMetrics(a, b metricdata.ScopeMetrics) (reasons []string) { +func equalScopeMetrics(a, b metricdata.ScopeMetrics, cfg config) (reasons []string) { if a.Scope != b.Scope { reasons = append(reasons, notEqualStr("Scope", a.Scope, b.Scope)) } @@ -64,7 +64,7 @@ func equalScopeMetrics(a, b metricdata.ScopeMetrics) (reasons []string) { a.Metrics, b.Metrics, func(a, b metricdata.Metrics) bool { - r := equalMetrics(a, b) + r := equalMetrics(a, b, cfg) return len(r) == 0 }, )) @@ -76,7 +76,7 @@ func equalScopeMetrics(a, b metricdata.ScopeMetrics) (reasons []string) { // equalMetrics returns reasons Metrics are not equal. If they are equal, the // returned reasons will be empty. -func equalMetrics(a, b metricdata.Metrics) (reasons []string) { +func equalMetrics(a, b metricdata.Metrics, cfg config) (reasons []string) { if a.Name != b.Name { reasons = append(reasons, notEqualStr("Name", a.Name, b.Name)) } @@ -87,7 +87,7 @@ func equalMetrics(a, b metricdata.Metrics) (reasons []string) { reasons = append(reasons, notEqualStr("Unit", a.Unit, b.Unit)) } - r := equalAggregations(a.Data, b.Data) + r := equalAggregations(a.Data, b.Data, cfg) if len(r) > 0 { reasons = append(reasons, "Metrics Data not equal:") reasons = append(reasons, r...) @@ -97,7 +97,7 @@ func equalMetrics(a, b metricdata.Metrics) (reasons []string) { // equalAggregations returns reasons a and b are not equal. If they are equal, // the returned reasons will be empty. -func equalAggregations(a, b metricdata.Aggregation) (reasons []string) { +func equalAggregations(a, b metricdata.Aggregation, cfg config) (reasons []string) { if a == nil || b == nil { if a != b { return []string{notEqualStr("Aggregation", a, b)} @@ -111,31 +111,31 @@ func equalAggregations(a, b metricdata.Aggregation) (reasons []string) { switch v := a.(type) { case metricdata.Gauge[int64]: - r := equalGauges(v, b.(metricdata.Gauge[int64])) + r := equalGauges(v, b.(metricdata.Gauge[int64]), cfg) if len(r) > 0 { reasons = append(reasons, "Gauge[int64] not equal:") reasons = append(reasons, r...) } case metricdata.Gauge[float64]: - r := equalGauges(v, b.(metricdata.Gauge[float64])) + r := equalGauges(v, b.(metricdata.Gauge[float64]), cfg) if len(r) > 0 { reasons = append(reasons, "Gauge[float64] not equal:") reasons = append(reasons, r...) } case metricdata.Sum[int64]: - r := equalSums(v, b.(metricdata.Sum[int64])) + r := equalSums(v, b.(metricdata.Sum[int64]), cfg) if len(r) > 0 { reasons = append(reasons, "Sum[int64] not equal:") reasons = append(reasons, r...) } case metricdata.Sum[float64]: - r := equalSums(v, b.(metricdata.Sum[float64])) + r := equalSums(v, b.(metricdata.Sum[float64]), cfg) if len(r) > 0 { reasons = append(reasons, "Sum[float64] not equal:") reasons = append(reasons, r...) } case metricdata.Histogram: - r := equalHistograms(v, b.(metricdata.Histogram)) + r := equalHistograms(v, b.(metricdata.Histogram), cfg) if len(r) > 0 { reasons = append(reasons, "Histogram not equal:") reasons = append(reasons, r...) @@ -151,12 +151,12 @@ func equalAggregations(a, b metricdata.Aggregation) (reasons []string) { // // The DataPoints each Gauge contains are compared based on containing the // same DataPoints, not the order they are stored in. -func equalGauges[N int64 | float64](a, b metricdata.Gauge[N]) (reasons []string) { +func equalGauges[N int64 | float64](a, b metricdata.Gauge[N], cfg config) (reasons []string) { r := compareDiff(diffSlices( a.DataPoints, b.DataPoints, func(a, b metricdata.DataPoint[N]) bool { - r := equalDataPoints(a, b) + r := equalDataPoints(a, b, cfg) return len(r) == 0 }, )) @@ -171,7 +171,7 @@ func equalGauges[N int64 | float64](a, b metricdata.Gauge[N]) (reasons []string) // // The DataPoints each Sum contains are compared based on containing the same // DataPoints, not the order they are stored in. -func equalSums[N int64 | float64](a, b metricdata.Sum[N]) (reasons []string) { +func equalSums[N int64 | float64](a, b metricdata.Sum[N], cfg config) (reasons []string) { if a.Temporality != b.Temporality { reasons = append(reasons, notEqualStr("Temporality", a.Temporality, b.Temporality)) } @@ -183,7 +183,7 @@ func equalSums[N int64 | float64](a, b metricdata.Sum[N]) (reasons []string) { a.DataPoints, b.DataPoints, func(a, b metricdata.DataPoint[N]) bool { - r := equalDataPoints(a, b) + r := equalDataPoints(a, b, cfg) return len(r) == 0 }, )) @@ -198,7 +198,7 @@ func equalSums[N int64 | float64](a, b metricdata.Sum[N]) (reasons []string) { // // The DataPoints each Histogram contains are compared based on containing the // same HistogramDataPoint, not the order they are stored in. -func equalHistograms(a, b metricdata.Histogram) (reasons []string) { +func equalHistograms(a, b metricdata.Histogram, cfg config) (reasons []string) { if a.Temporality != b.Temporality { reasons = append(reasons, notEqualStr("Temporality", a.Temporality, b.Temporality)) } @@ -207,7 +207,7 @@ func equalHistograms(a, b metricdata.Histogram) (reasons []string) { a.DataPoints, b.DataPoints, func(a, b metricdata.HistogramDataPoint) bool { - r := equalHistogramDataPoints(a, b) + r := equalHistogramDataPoints(a, b, cfg) return len(r) == 0 }, )) @@ -219,7 +219,7 @@ func equalHistograms(a, b metricdata.Histogram) (reasons []string) { // equalDataPoints returns reasons DataPoints are not equal. If they are // equal, the returned reasons will be empty. -func equalDataPoints[N int64 | float64](a, b metricdata.DataPoint[N]) (reasons []string) { +func equalDataPoints[N int64 | float64](a, b metricdata.DataPoint[N], cfg config) (reasons []string) { // nolint: revive // Intentional internal control flag if !a.Attributes.Equals(&b.Attributes) { reasons = append(reasons, notEqualStr( "Attributes", @@ -227,11 +227,14 @@ func equalDataPoints[N int64 | float64](a, b metricdata.DataPoint[N]) (reasons [ b.Attributes.Encoded(attribute.DefaultEncoder()), )) } - if !a.StartTime.Equal(b.StartTime) { - reasons = append(reasons, notEqualStr("StartTime", a.StartTime.UnixNano(), b.StartTime.UnixNano())) - } - if !a.Time.Equal(b.Time) { - reasons = append(reasons, notEqualStr("Time", a.Time.UnixNano(), b.Time.UnixNano())) + + if !cfg.ignoreTimestamp { + if !a.StartTime.Equal(b.StartTime) { + reasons = append(reasons, notEqualStr("StartTime", a.StartTime.UnixNano(), b.StartTime.UnixNano())) + } + if !a.Time.Equal(b.Time) { + reasons = append(reasons, notEqualStr("Time", a.Time.UnixNano(), b.Time.UnixNano())) + } } if a.Value != b.Value { @@ -242,7 +245,7 @@ func equalDataPoints[N int64 | float64](a, b metricdata.DataPoint[N]) (reasons [ // equalHistogramDataPoints returns reasons HistogramDataPoints are not equal. // If they are equal, the returned reasons will be empty. -func equalHistogramDataPoints(a, b metricdata.HistogramDataPoint) (reasons []string) { +func equalHistogramDataPoints(a, b metricdata.HistogramDataPoint, cfg config) (reasons []string) { // nolint: revive // Intentional internal control flag if !a.Attributes.Equals(&b.Attributes) { reasons = append(reasons, notEqualStr( "Attributes", @@ -250,11 +253,13 @@ func equalHistogramDataPoints(a, b metricdata.HistogramDataPoint) (reasons []str b.Attributes.Encoded(attribute.DefaultEncoder()), )) } - if !a.StartTime.Equal(b.StartTime) { - reasons = append(reasons, notEqualStr("StartTime", a.StartTime.UnixNano(), b.StartTime.UnixNano())) - } - if !a.Time.Equal(b.Time) { - reasons = append(reasons, notEqualStr("Time", a.Time.UnixNano(), b.Time.UnixNano())) + if !cfg.ignoreTimestamp { + if !a.StartTime.Equal(b.StartTime) { + reasons = append(reasons, notEqualStr("StartTime", a.StartTime.UnixNano(), b.StartTime.UnixNano())) + } + if !a.Time.Equal(b.Time) { + reasons = append(reasons, notEqualStr("Time", a.Time.UnixNano(), b.Time.UnixNano())) + } } if a.Count != b.Count { reasons = append(reasons, notEqualStr("Count", a.Count, b.Count))