Skip to content

Commit

Permalink
WIP to add SDK integration testing
Browse files Browse the repository at this point in the history
  • Loading branch information
MrAlias committed Oct 28, 2022
1 parent 94ae231 commit 4fe220c
Show file tree
Hide file tree
Showing 7 changed files with 538 additions and 0 deletions.
61 changes: 61 additions & 0 deletions sdk/metric/metrictest/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// 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.

package metrictest // import "go.opentelemetry.io/otel/sdk/metric/metrictest"

import (
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/sdk/instrumentation"
"go.opentelemetry.io/otel/sdk/metric/aggregation"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
"go.opentelemetry.io/otel/sdk/resource"
)

const (
pkgName = "go.opentelemetry.io/otel/sdk/metric/metrictest"
version = "v0.1.0"
schemaURL = "https://opentelemetry.io/schemas/1.0.0"
)

var (
alice = []attribute.KeyValue{
attribute.String("username", "alice"),
attribute.Int("id", 3214986),
attribute.Bool("active", true),
}
bob = []attribute.KeyValue{
attribute.String("username", "bob"),
attribute.Int("id", -1),
attribute.Bool("active", false),
}

defaultRes = resource.NewSchemaless(
attribute.String("company", "globotron"),
)
defualtConf = Config{
Resource: defaultRes,
}

defualtScope = instrumentation.Scope{
Name: pkgName,
Version: version,
SchemaURL: schemaURL,
}
)

type Config struct {
Resource *resource.Resource
Temporality metricdata.Temporality
Aggregation aggregation.Aggregation
}
43 changes: 43 additions & 0 deletions sdk/metric/metrictest/meter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// 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.

package metrictest // import "go.opentelemetry.io/otel/sdk/metric/metrictest"

import (
"sync"
"testing"

"github.com/stretchr/testify/assert"
"go.opentelemetry.io/otel/metric"
)

func testMeter(f Factory) func(*testing.T) {
return func(t *testing.T) {
t.Run("CreationConcurrentSafe", func(t *testing.T) {
mp, _ := f(defualtConf)

var wg sync.WaitGroup
wg.Add(1)
var asyncMeter metric.Meter
go func() {
defer wg.Done()
asyncMeter = mp.Meter(pkgName)
}()
syncMeter := mp.Meter(pkgName)
wg.Wait()

assert.Same(t, asyncMeter, syncMeter)
})
}
}
55 changes: 55 additions & 0 deletions sdk/metric/metrictest/storage.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// 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.

package metrictest // import "go.opentelemetry.io/otel/sdk/metric/metrictest"

import (
"context"
"sync"

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

type Gatherer interface {
Gather(context.Context) error
Storage() *Storage
}

// Storage stores exported metric data.
type Storage struct {
dataMu sync.Mutex
data []metricdata.ResourceMetrics
}

// NewStorage returns a configures Storage ready to store exported metric data.
func NewStorage() *Storage {
return &Storage{}
}

// Add adds the request to the Storage.
func (s *Storage) Add(data metricdata.ResourceMetrics) {
s.dataMu.Lock()
defer s.dataMu.Unlock()
s.data = append(s.data, data)
}

// dump returns all added metric data and clears the storage.
func (s *Storage) dump() []metricdata.ResourceMetrics {
s.dataMu.Lock()
defer s.dataMu.Unlock()

var data []metricdata.ResourceMetrics
data, s.data = s.data, []metricdata.ResourceMetrics{}
return data
}
31 changes: 31 additions & 0 deletions sdk/metric/metrictest/suite.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// 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.

package metrictest // import "go.opentelemetry.io/otel/sdk/metric/metrictest"

import (
"testing"

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

type Factory func(Config) (metric.MeterProvider, Gatherer)

func Run(t *testing.T, factory Factory) {
t.Helper()

t.Run("Meter", testMeter(factory))
t.Run("SyncInt64Counter", testSyncInt64Counter(factory))
t.Run("SyncInt64UpDownCounter", testSyncInt64UpDownCounter(factory))
}
181 changes: 181 additions & 0 deletions sdk/metric/metrictest/syncint64counter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
// 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.

package metrictest // import "go.opentelemetry.io/otel/sdk/metric/metrictest"

import (
"context"
"sync"
"testing"

"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/metric/instrument"
"go.opentelemetry.io/otel/metric/unit"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
"go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest"
)

func testSyncInt64Counter(f Factory) func(*testing.T) {
return func(t *testing.T) {
t.Run("Default", testSyncInt64CounterDefault(f))
t.Run("Delta", testSyncInt64CounterDelta(f))
}
}

func testSyncInt64CounterDefault(f Factory) func(*testing.T) {
const (
goroutines = 30
cycles = 3
name = "logins"
desc = "Number of user logins"
u = unit.Dimensionless
)

return func(t *testing.T) {
mp, g := f(defualtConf)
m := mp.Meter(
pkgName,
metric.WithInstrumentationVersion(version),
metric.WithSchemaURL(schemaURL),
)
logins, err := m.SyncInt64().Counter(
name,
instrument.WithDescription(desc),
instrument.WithUnit(u),
)
require.NoError(t, err)

for cycle := 0; cycle < cycles; cycle++ {
ctx := context.Background()
var wg sync.WaitGroup
for n := 0; n < goroutines; n++ {
wg.Add(1)
go func() {
defer wg.Done()
logins.Add(ctx, 0, bob...) // No-op.
logins.Add(ctx, 1, alice...)
logins.Add(ctx, 2, bob...)
}()
}
wg.Wait()

require.NoError(t, g.Gather(ctx))
}

got := g.Storage().dump()
require.Len(t, got, cycles, "wrong number of collection cycles")

want := metricdata.ResourceMetrics{
Resource: defaultRes,
ScopeMetrics: []metricdata.ScopeMetrics{{
Scope: defualtScope,
Metrics: []metricdata.Metrics{{
Name: name,
Description: desc,
Unit: u,
Data: metricdata.Sum[int64]{
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
DataPoints: []metricdata.DataPoint[int64]{{
Attributes: attribute.NewSet(alice...),
}, {
Attributes: attribute.NewSet(bob...),
}},
},
}},
}},
}
for c := 0; c < cycles; c++ {
dpts := want.ScopeMetrics[0].Metrics[0].Data.(metricdata.Sum[int64]).DataPoints
dpts[0].Value = int64(1 * goroutines * (c + 1))
dpts[1].Value = int64(2 * goroutines * (c + 1))
metricdatatest.AssertEqual(t, got[c], want, metricdatatest.IgnoreTimestamp())
}
}
}

func testSyncInt64CounterDelta(f Factory) func(*testing.T) {
const (
goroutines = 30
name = "logins"
desc = "Number of user logins"
u = unit.Dimensionless
)

return func(t *testing.T) {
conf := defualtConf
conf.Temporality = metricdata.DeltaTemporality
mp, coll := f(conf)
m := mp.Meter(
pkgName,
metric.WithInstrumentationVersion(version),
metric.WithSchemaURL(schemaURL),
)
c, err := m.SyncInt64().Counter(
name,
instrument.WithDescription(desc),
instrument.WithUnit(u),
)
require.NoError(t, err)

ctx := context.Background()
var wg sync.WaitGroup
for n := 0; n < goroutines; n++ {
wg.Add(1)
go func() {
defer wg.Done()
c.Add(ctx, 0, bob...) // No-op.
c.Add(ctx, 1, alice...)
c.Add(ctx, 2, bob...)
}()
}
wg.Wait()

if mps, ok := mp.(interface{ Shutdown(context.Context) error }); ok {
require.NoError(t, mps.Shutdown(ctx))
}

got := coll.Storage().dump()
require.Len(t, got, 1, "one export expected")
metricdatatest.AssertEqual(
t,
metricdata.ResourceMetrics{
Resource: defaultRes,
ScopeMetrics: []metricdata.ScopeMetrics{{
Scope: defualtScope,
Metrics: []metricdata.Metrics{{
Name: name,
Description: desc,
Unit: u,
Data: metricdata.Sum[int64]{
Temporality: metricdata.DeltaTemporality,
IsMonotonic: true,
DataPoints: []metricdata.DataPoint[int64]{{
Attributes: attribute.NewSet(alice...),
Value: goroutines,
}, {
Attributes: attribute.NewSet(bob...),
Value: 2 * goroutines,
}},
},
}},
}},
},
got[0],
metricdatatest.IgnoreTimestamp(),
)
}
}
Loading

0 comments on commit 4fe220c

Please sign in to comment.