Skip to content

Commit

Permalink
Add metricdatatest package (#3025)
Browse files Browse the repository at this point in the history
* Use export.Aggregation instead of internal

* Return an export.Aggregation instead of a slice

* Use attribute Sets instead of KeyValues for export data

Attribute Sets have stronger guarantees about the uniqueness of their
keys and more functionality. We already ensure attributes are stored as
Sets by the aggregator which will produce these data types. Instead of
converting to a KeyValue slice, keep the data as a Set.

Any user of the data can always call the ToSlice method to use the data
as a slice of KeyValues.

* Add export data type comparison testing API

* Add Aggregation and Value comparison funcs

* Move export testing to own pkg

* Move exporttest to metricdatatest

* Add licenses headers to files missing them

* Use metricdata instead of export

Fix merge of new_sdk/main

* Rename exporttest pkg to metricdatatest

* Fix spelling errors

* Fix lint issues

* Use testing pkg to error directly

Include Helper() method calls to correct the call-stack.

* Fix CompareAggregations

Set equal to true by default

* Generalize assertions and unexport equal checks

* Abstract assert tests

* Rename all exp var to r

* Test AssertAggregationsEqual

* Comment why Value and Aggregation are separate

* Test AssertValuesEqual

* Revert changes to metricdata/temporality.go

* Expand pkg doc sentence

* Add license header to assertion.go

* Update assertion docs

* Consolidate comparisons funcs into one file

* Consolidate and fix docs

* Consolidate assertion.go

* Consolidate comparisons.go

* make lint

* Test with relatively static times

* Update sdk/metric/metricdata/metricdatatest/comparisons.go

Co-authored-by: Aaron Clawson <[email protected]>

* Drop equal return from comparison funcs

* Refactor AssertEqual

* Remove reasN from testDatatype func params

* Consolidate AssertEqual type conversions

* Fix assertion error message

* Add assertion failure tests

* Remove unneeded strings join

* Make comment include a possessive

Co-authored-by: Aaron Clawson <[email protected]>
  • Loading branch information
MrAlias and MadVikingGod authored Jul 21, 2022
1 parent 98f88ca commit 1dbe1fc
Show file tree
Hide file tree
Showing 4 changed files with 776 additions and 0 deletions.
98 changes: 98 additions & 0 deletions sdk/metric/metricdata/metricdatatest/assertion.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// 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 provides testing functionality for use with the
// metricdata package.
package metricdatatest // import "go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest"

import (
"fmt"
"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 asserted. Use an interface instead.
aIface := interface{}(actual)

var r []string
switch e := interface{}(expected).(type) {
case metricdata.DataPoint:
r = equalDataPoints(e, aIface.(metricdata.DataPoint))
case metricdata.Float64:
r = equalFloat64(e, aIface.(metricdata.Float64))
case metricdata.Gauge:
r = equalGauges(e, aIface.(metricdata.Gauge))
case metricdata.Histogram:
r = equalHistograms(e, aIface.(metricdata.Histogram))
case metricdata.HistogramDataPoint:
r = equalHistogramDataPoints(e, aIface.(metricdata.HistogramDataPoint))
case metricdata.Int64:
r = equalInt64(e, aIface.(metricdata.Int64))
case metricdata.Metrics:
r = equalMetrics(e, aIface.(metricdata.Metrics))
case metricdata.ResourceMetrics:
r = equalResourceMetrics(e, aIface.(metricdata.ResourceMetrics))
case metricdata.ScopeMetrics:
r = equalScopeMetrics(e, aIface.(metricdata.ScopeMetrics))
case metricdata.Sum:
r = equalSums(e, aIface.(metricdata.Sum))
default:
// We control all types passed to this, panic to signal developers
// early they changed things in an incompatible way.
panic(fmt.Sprintf("unknown types: %T", expected))
}

if len(r) > 0 {
t.Error(r)
return false
}
return true
}

// AssertAggregationsEqual asserts that two Aggregations are equal.
func AssertAggregationsEqual(t *testing.T, expected, actual metricdata.Aggregation) bool {
t.Helper()
if r := equalAggregations(expected, actual); len(r) > 0 {
t.Error(r)
return false
}
return true
}

// AssertValuesEqual asserts that two Values are equal.
func AssertValuesEqual(t *testing.T, expected, actual metricdata.Value) bool {
t.Helper()
if r := equalValues(expected, actual); len(r) > 0 {
t.Error(r)
return false
}
return true
}
63 changes: 63 additions & 0 deletions sdk/metric/metricdata/metricdatatest/assertion_fail_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// 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 && tests_fail
// +build go1.18,tests_fail

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

import (
"testing"
)

// These tests are used to develop the failure messages of this package's
// assertions. They can be run with the following.
//
// go test -tags tests_fail ./...

func testFailDatatype[T Datatypes](a, b T) func(*testing.T) {
return func(t *testing.T) {
AssertEqual(t, a, b)
}
}

func TestFailAssertEqual(t *testing.T) {
t.Run("ResourceMetrics", testFailDatatype(resourceMetricsA, resourceMetricsB))
t.Run("ScopeMetrics", testFailDatatype(scopeMetricsA, scopeMetricsB))
t.Run("Metrics", testFailDatatype(metricsA, metricsB))
t.Run("Histogram", testFailDatatype(histogramA, histogramB))
t.Run("Sum", testFailDatatype(sumA, sumB))
t.Run("Gauge", testFailDatatype(gaugeA, gaugeB))
t.Run("HistogramDataPoint", testFailDatatype(histogramDataPointA, histogramDataPointB))
t.Run("DataPoint", testFailDatatype(dataPointsA, dataPointsB))
t.Run("Int64", testFailDatatype(int64A, int64B))
t.Run("Float64", testFailDatatype(float64A, float64B))
}

func TestFailAssertAggregationsEqual(t *testing.T) {
AssertAggregationsEqual(t, sumA, nil)
AssertAggregationsEqual(t, sumA, gaugeA)
AssertAggregationsEqual(t, unknownAggregation{}, unknownAggregation{})
AssertAggregationsEqual(t, sumA, sumB)
AssertAggregationsEqual(t, gaugeA, gaugeB)
AssertAggregationsEqual(t, histogramA, histogramB)
}

func TestFailAssertValuesEqual(t *testing.T) {
AssertValuesEqual(t, int64A, nil)
AssertValuesEqual(t, int64A, float64A)
AssertValuesEqual(t, unknownValue{}, unknownValue{})
AssertValuesEqual(t, int64A, int64B)
AssertValuesEqual(t, float64A, float64B)
}
215 changes: 215 additions & 0 deletions sdk/metric/metricdata/metricdatatest/assertion_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
// 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)

startA = time.Now()
startB = startA.Add(time.Millisecond)
endA = startA.Add(time.Second)
endB = startB.Add(time.Second)

dataPointsA = metricdata.DataPoint{
Attributes: attrA,
StartTime: startA,
Time: endA,
Value: int64A,
}
dataPointsB = metricdata.DataPoint{
Attributes: attrB,
StartTime: startB,
Time: endB,
Value: float64B,
}

max, min = 99.0, 3.
histogramDataPointA = metricdata.HistogramDataPoint{
Attributes: attrA,
StartTime: startA,
Time: endA,
Count: 2,
Bounds: []float64{0, 10},
BucketCounts: []uint64{1, 1},
Sum: 2,
}
histogramDataPointB = metricdata.HistogramDataPoint{
Attributes: attrB,
StartTime: startB,
Time: endB,
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) []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)
assert.Greaterf(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))
t.Run("Metrics", testDatatype(metricsA, metricsB, equalMetrics))
t.Run("Histogram", testDatatype(histogramA, histogramB, equalHistograms))
t.Run("Sum", testDatatype(sumA, sumB, equalSums))
t.Run("Gauge", testDatatype(gaugeA, gaugeB, equalGauges))
t.Run("HistogramDataPoint", testDatatype(histogramDataPointA, histogramDataPointB, equalHistogramDataPoints))
t.Run("DataPoint", testDatatype(dataPointsA, dataPointsB, equalDataPoints))
t.Run("Int64", testDatatype(int64A, int64B, equalInt64))
t.Run("Float64", testDatatype(float64A, float64B, equalFloat64))
}

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)

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

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

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

r = equalAggregations(sumA, sumB)
assert.Greaterf(t, len(r), 0, "%v == %v", sumA, sumB)

r = equalAggregations(gaugeA, gaugeB)
assert.Greaterf(t, len(r), 0, "%v == %v", gaugeA, gaugeB)

r = equalAggregations(histogramA, histogramB)
assert.Greaterf(t, len(r), 0, "%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)

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

r = equalValues(int64A, float64A)
assert.Len(t, r, 1, "should return with type mismatch only")

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

r = equalValues(int64A, int64B)
assert.Greaterf(t, len(r), 0, "%v == %v", int64A, int64B)

r = equalValues(float64A, float64B)
assert.Greaterf(t, len(r), 0, "%v == %v", float64A, float64B)
}
Loading

0 comments on commit 1dbe1fc

Please sign in to comment.