Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add metricdatatest package #3025

Merged
merged 41 commits into from
Jul 21, 2022
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
47c57bc
Use export.Aggregation instead of internal
MrAlias Jul 11, 2022
9e78415
Return an export.Aggregation instead of a slice
MrAlias Jul 12, 2022
f8ec18c
Use attribute Sets instead of KeyValues for export data
MrAlias Jul 12, 2022
67fbfdd
Merge branch 'data-attr-set' into agg-test-framework
MrAlias Jul 12, 2022
2f9d30d
Add export data type comparison testing API
MrAlias Jul 13, 2022
471f91a
Add Aggregation and Value comparison funcs
MrAlias Jul 14, 2022
522bdc0
Move export testing to own pkg
MrAlias Jul 15, 2022
c964ea5
Merge branch 'new_sdk/main' into agg-test-framework
MrAlias Jul 16, 2022
38558a0
Move exporttest to metricdatatest
MrAlias Jul 16, 2022
e251fa5
Add licenses headers to files missing them
MrAlias Jul 18, 2022
041537b
Use metricdata instead of export
MrAlias Jul 18, 2022
b138608
Rename exporttest pkg to metricdatatest
MrAlias Jul 18, 2022
ddb2850
Fix spelling errors
MrAlias Jul 18, 2022
dea1b3a
Fix lint issues
MrAlias Jul 18, 2022
01c428c
Use testing pkg to error directly
MrAlias Jul 18, 2022
fcf026e
Fix CompareAggregations
MrAlias Jul 18, 2022
1de2c4e
Generalize assertions and unexport equal checks
MrAlias Jul 18, 2022
4227e72
Abstract assert tests
MrAlias Jul 18, 2022
93ac61f
Rename all exp var to r
MrAlias Jul 18, 2022
755c900
Test AssertAggregationsEqual
MrAlias Jul 18, 2022
a932325
Comment why Value and Aggregation are separate
MrAlias Jul 18, 2022
aceec74
Test AssertValuesEqual
MrAlias Jul 18, 2022
1c2f5d9
Revert changes to metricdata/temporality.go
MrAlias Jul 18, 2022
f65075c
Expand pkg doc sentence
MrAlias Jul 18, 2022
3631507
Add license header to assertion.go
MrAlias Jul 18, 2022
0d26417
Update assertion docs
MrAlias Jul 19, 2022
85d1f60
Consolidate comparisons funcs into one file
MrAlias Jul 19, 2022
ec7106d
Consolidate and fix docs
MrAlias Jul 19, 2022
ddfe5b5
Consolidate assertion.go
MrAlias Jul 19, 2022
9b50ea9
Consolidate comparisons.go
MrAlias Jul 19, 2022
3e61b4c
make lint
MrAlias Jul 19, 2022
6cce6a6
Test with relatively static times
MrAlias Jul 19, 2022
65d0f1a
Update sdk/metric/metricdata/metricdatatest/comparisons.go
MrAlias Jul 20, 2022
f70a5e5
Drop equal return from comparison funcs
MrAlias Jul 20, 2022
0dec436
Refactor AssertEqual
MrAlias Jul 20, 2022
73e319c
Remove reasN from testDatatype func params
MrAlias Jul 20, 2022
9985b46
Consolidate AssertEqual type conversions
MrAlias Jul 20, 2022
34c4d13
Fix assertion error message
MrAlias Jul 20, 2022
f894686
Add assertion failure tests
MrAlias Jul 20, 2022
82973be
Remove unneeded strings join
MrAlias Jul 20, 2022
6e54b91
Make comment include a possessive
MrAlias Jul 20, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions sdk/metric/metricdata/metricdatatest/aggregation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//go:build go1.18
// +build go1.18

package metricdatatest // import "go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest"

import (
"fmt"
"reflect"

"go.opentelemetry.io/otel/sdk/metric/metricdata"
)

// equalAggregations returns true when a and b are equal. It returns false
// when they differ, along with the reasons why they differ.
func equalAggregations(a, b metricdata.Aggregation) (equal bool, reasons []string) {
equal = true
if a == nil || b == nil {
if a != b {
equal, reasons = false, []string{notEqualStr("Aggregation", a, b)}
}
return equal, reasons
}

if reflect.TypeOf(a) != reflect.TypeOf(b) {
return false, []string{fmt.Sprintf(
"Aggregation types not equal:\nexpected: %T\nactual: %T", a, b,
)}
}

switch v := a.(type) {
case metricdata.Gauge:
var r []string
equal, r = equalGauges(v, b.(metricdata.Gauge))
if !equal {
reasons = append(reasons, "Gauge not equal:")
reasons = append(reasons, r...)
}
case metricdata.Sum:
var r []string
equal, r = equalSums(v, b.(metricdata.Sum))
if !equal {
reasons = append(reasons, "Sum not equal:")
reasons = append(reasons, r...)
}
case metricdata.Histogram:
var r []string
equal, r = equalHistograms(v, b.(metricdata.Histogram))
if !equal {
reasons = append(reasons, "Histogram not equal:")
reasons = append(reasons, r...)
}
default:
equal = false
reasons = append(reasons, fmt.Sprintf("Aggregation of unknown types %T", a))
}
return equal, reasons
}
103 changes: 103 additions & 0 deletions sdk/metric/metricdata/metricdatatest/assertion.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package metricdatatest

import (
"fmt"
"strings"
"testing"

"go.opentelemetry.io/otel/sdk/metric/metricdata"
)

// Datatypes are the concrete data-types the metricdata package provides.
type Datatypes interface {
metricdata.DataPoint |
metricdata.Float64 |
metricdata.Gauge |
metricdata.Histogram |
metricdata.HistogramDataPoint |
metricdata.Int64 |
metricdata.Metrics |
metricdata.ResourceMetrics |
metricdata.ScopeMetrics |
metricdata.Sum

// Interface types are not allowed in union types, therefore the
// Aggregation and Value type from metricdata are not included here.
}

// 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 {
t.Helper()
// Generic types cannot be type switch on. Convert them to interfaces by
// passing to assertEqual, which performs the correct functionality based
// on the type.
return assertEqual(t, expected, actual)
MrAlias marked this conversation as resolved.
Show resolved Hide resolved
}

func assertEqual(t *testing.T, expected, actual interface{}) bool {
t.Helper()
switch e := expected.(type) {
case metricdata.DataPoint:
a := actual.(metricdata.DataPoint)
return assertCompare(equalDataPoints(e, a))(t)
case metricdata.Float64:
a := actual.(metricdata.Float64)
return assertCompare(equalFloat64(e, a))(t)
case metricdata.Gauge:
a := actual.(metricdata.Gauge)
return assertCompare(equalGauges(e, a))(t)
case metricdata.Histogram:
a := actual.(metricdata.Histogram)
return assertCompare(equalHistograms(e, a))(t)
case metricdata.HistogramDataPoint:
a := actual.(metricdata.HistogramDataPoint)
return assertCompare(equalHistogramDataPoints(e, a))(t)
case metricdata.Int64:
a := actual.(metricdata.Int64)
return assertCompare(equalInt64(e, a))(t)
case metricdata.Metrics:
a := actual.(metricdata.Metrics)
return assertCompare(equalMetrics(e, a))(t)
case metricdata.ResourceMetrics:
a := actual.(metricdata.ResourceMetrics)
return assertCompare(equalResourceMetrics(e, a))(t)
case metricdata.ScopeMetrics:
a := actual.(metricdata.ScopeMetrics)
return assertCompare(equalScopeMetrics(e, a))(t)
case metricdata.Sum:
a := actual.(metricdata.Sum)
return assertCompare(equalSums(e, a))(t)
default:
panic(fmt.Sprintf("unknown types: %T", expected))
}
}

// assertCompare evaluates the return value of an equality check function. The
// return function will produce an appropriate testing error if equal is
// false.
func assertCompare(equal bool, reasons []string) func(*testing.T) bool { // nolint: revive // equal is not a control flag.
return func(t *testing.T) bool {
t.Helper()
if !equal {
if len(reasons) > 0 {
t.Error(strings.Join(reasons, "\n"))
} else {
t.Fail()
}
}
return equal
}
}

// AssertAggregationsEqual asserts that two Aggregations are equal.
func AssertAggregationsEqual(t *testing.T, expected, actual metricdata.Aggregation) bool {
t.Helper()
return assertCompare(equalAggregations(expected, actual))(t)
}

// AssertValuesEqual asserts that two Values are equal.
func AssertValuesEqual(t *testing.T, expected, actual metricdata.Value) bool {
t.Helper()
return assertCompare(equalValues(expected, actual))(t)
}
216 changes: 216 additions & 0 deletions sdk/metric/metricdata/metricdatatest/assertion_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//go:build go1.18
// +build go1.18

package metricdatatest // import "go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest"

import (
"testing"
"time"

"github.com/stretchr/testify/assert"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/metric/unit"
"go.opentelemetry.io/otel/sdk/instrumentation"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
"go.opentelemetry.io/otel/sdk/resource"
)

var (
attrA = attribute.NewSet(attribute.Bool("A", true))
attrB = attribute.NewSet(attribute.Bool("B", true))

float64A = metricdata.Float64(-1.0)
float64B = metricdata.Float64(2.0)

int64A = metricdata.Int64(-1)
int64B = metricdata.Int64(2)

dataPointsA = metricdata.DataPoint{
Attributes: attrA,
StartTime: time.Now(),
Time: time.Now(),
Value: int64A,
}
dataPointsB = metricdata.DataPoint{
Attributes: attrB,
StartTime: time.Now(),
Time: time.Now(),
Value: float64B,
}

max, min = 99.0, 3.
histogramDataPointA = metricdata.HistogramDataPoint{
Attributes: attrA,
StartTime: time.Now(),
Time: time.Now(),
Count: 2,
Bounds: []float64{0, 10},
BucketCounts: []uint64{1, 1},
Sum: 2,
}
histogramDataPointB = metricdata.HistogramDataPoint{
Attributes: attrB,
StartTime: time.Now(),
Time: time.Now(),
Count: 3,
Bounds: []float64{0, 10, 100},
BucketCounts: []uint64{1, 1, 1},
Max: &max,
Min: &min,
Sum: 3,
}

gaugeA = metricdata.Gauge{DataPoints: []metricdata.DataPoint{dataPointsA}}
gaugeB = metricdata.Gauge{DataPoints: []metricdata.DataPoint{dataPointsB}}

sumA = metricdata.Sum{
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
DataPoints: []metricdata.DataPoint{dataPointsA},
}
sumB = metricdata.Sum{
Temporality: metricdata.DeltaTemporality,
IsMonotonic: false,
DataPoints: []metricdata.DataPoint{dataPointsB},
}

histogramA = metricdata.Histogram{
Temporality: metricdata.CumulativeTemporality,
DataPoints: []metricdata.HistogramDataPoint{histogramDataPointA},
}
histogramB = metricdata.Histogram{
Temporality: metricdata.DeltaTemporality,
DataPoints: []metricdata.HistogramDataPoint{histogramDataPointB},
}

metricsA = metricdata.Metrics{
Name: "A",
Description: "A desc",
Unit: unit.Dimensionless,
Data: sumA,
}
metricsB = metricdata.Metrics{
Name: "B",
Description: "B desc",
Unit: unit.Bytes,
Data: gaugeB,
}

scopeMetricsA = metricdata.ScopeMetrics{
Scope: instrumentation.Scope{Name: "A"},
Metrics: []metricdata.Metrics{metricsA},
}
scopeMetricsB = metricdata.ScopeMetrics{
Scope: instrumentation.Scope{Name: "B"},
Metrics: []metricdata.Metrics{metricsB},
}

resourceMetricsA = metricdata.ResourceMetrics{
Resource: resource.NewSchemaless(attribute.String("resource", "A")),
ScopeMetrics: []metricdata.ScopeMetrics{scopeMetricsA},
}
resourceMetricsB = metricdata.ResourceMetrics{
Resource: resource.NewSchemaless(attribute.String("resource", "B")),
ScopeMetrics: []metricdata.ScopeMetrics{scopeMetricsB},
}
)

type equalFunc[T Datatypes] func(T, T) (bool, []string)

func testDatatype[T Datatypes](a, b T, f equalFunc[T], reasN int) func(*testing.T) {
return func(t *testing.T) {
AssertEqual(t, a, a)
AssertEqual(t, b, b)

e, r := f(a, b)
assert.Falsef(t, e, "%v != %v", a, b)
assert.Len(t, r, reasN, "number or reasons not equal")
}
}

func TestAssertEqual(t *testing.T) {
t.Run("ResourceMetrics", testDatatype(resourceMetricsA, resourceMetricsB, equalResourceMetrics, 2))
t.Run("ScopeMetrics", testDatatype(scopeMetricsA, scopeMetricsB, equalScopeMetrics, 2))
t.Run("Metrics", testDatatype(metricsA, metricsB, equalMetrics, 5))
t.Run("Histogram", testDatatype(histogramA, histogramB, equalHistograms, 2))
t.Run("Sum", testDatatype(sumA, sumB, equalSums, 3))
t.Run("Gauge", testDatatype(gaugeA, gaugeB, equalGauges, 1))
t.Run("HistogramDataPoint", testDatatype(histogramDataPointA, histogramDataPointB, equalHistogramDataPoints, 9))
t.Run("DataPoint", testDatatype(dataPointsA, dataPointsB, equalDataPoints, 5))
t.Run("Int64", testDatatype(int64A, int64B, equalInt64, 1))
t.Run("Float64", testDatatype(float64A, float64B, equalFloat64, 1))
}

type unknownAggregation struct {
metricdata.Aggregation
}

func TestAssertAggregationsEqual(t *testing.T) {
AssertAggregationsEqual(t, nil, nil)
AssertAggregationsEqual(t, sumA, sumA)
AssertAggregationsEqual(t, gaugeA, gaugeA)
AssertAggregationsEqual(t, histogramA, histogramA)

e, r := equalAggregations(sumA, nil)
assert.False(t, e, "nil comparison")
assert.Len(t, r, 1, "should return nil comparison mismatch only")

e, r = equalAggregations(sumA, gaugeA)
assert.Falsef(t, e, "%v != %v", sumA, gaugeA)
assert.Len(t, r, 1, "should return with type mismatch only")

e, r = equalAggregations(unknownAggregation{}, unknownAggregation{})
assert.False(t, e, "unknown aggregation")
assert.Len(t, r, 1, "should return with unknown aggregation only")

e, _ = equalAggregations(sumA, sumB)
assert.Falsef(t, e, "%v != %v", sumA, sumB)

e, _ = equalAggregations(gaugeA, gaugeB)
assert.Falsef(t, e, "%v != %v", gaugeA, gaugeB)

e, _ = equalAggregations(histogramA, histogramB)
assert.Falsef(t, e, "%v != %v", histogramA, histogramB)
}

type unknownValue struct {
metricdata.Value
}

func TestAssertValuesEqual(t *testing.T) {
AssertValuesEqual(t, nil, nil)
AssertValuesEqual(t, int64A, int64A)
AssertValuesEqual(t, float64A, float64A)

e, r := equalValues(int64A, nil)
assert.False(t, e, "nil comparison")
assert.Len(t, r, 1, "should return nil comparison mismatch only")

e, r = equalValues(int64A, float64A)
assert.Falsef(t, e, "%v != %v", sumA, gaugeA)
assert.Len(t, r, 1, "should return with type mismatch only")

e, r = equalValues(unknownValue{}, unknownValue{})
assert.False(t, e, "unknown value")
assert.Len(t, r, 1, "should return with unknown value only")

e, _ = equalValues(int64A, int64B)
assert.Falsef(t, e, "%v != %v", int64A, int64B)

e, _ = equalValues(float64A, float64B)
assert.Falsef(t, e, "%v != %v", float64A, float64B)
}
Loading