1// Copyright The OpenTelemetry Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package internal_test
16
17import (
18	"context"
19	"errors"
20	"testing"
21
22	"github.com/stretchr/testify/require"
23
24	"go.opentelemetry.io/otel/api/global"
25	"go.opentelemetry.io/otel/api/global/internal"
26	"go.opentelemetry.io/otel/api/metric"
27	metrictest "go.opentelemetry.io/otel/internal/metric"
28	"go.opentelemetry.io/otel/label"
29)
30
31var Must = metric.Must
32
33// Note: Maybe this should be factored into ../../../internal/metric?
34type measured struct {
35	Name                   string
36	InstrumentationName    string
37	InstrumentationVersion string
38	Labels                 map[label.Key]label.Value
39	Number                 metric.Number
40}
41
42func asStructs(batches []metrictest.Batch) []measured {
43	var r []measured
44	for _, batch := range batches {
45		for _, m := range batch.Measurements {
46			r = append(r, measured{
47				Name:                   m.Instrument.Descriptor().Name(),
48				InstrumentationName:    m.Instrument.Descriptor().InstrumentationName(),
49				InstrumentationVersion: m.Instrument.Descriptor().InstrumentationVersion(),
50				Labels:                 asMap(batch.Labels...),
51				Number:                 m.Number,
52			})
53		}
54	}
55	return r
56}
57
58func asMap(kvs ...label.KeyValue) map[label.Key]label.Value {
59	m := map[label.Key]label.Value{}
60	for _, label := range kvs {
61		m[label.Key] = label.Value
62	}
63	return m
64}
65
66var asInt = metric.NewInt64Number
67var asFloat = metric.NewFloat64Number
68
69func TestDirect(t *testing.T) {
70	internal.ResetForTest()
71
72	ctx := context.Background()
73	meter1 := global.Meter("test1", metric.WithInstrumentationVersion("semver:v1.0.0"))
74	meter2 := global.Meter("test2")
75	labels1 := []label.KeyValue{label.String("A", "B")}
76	labels2 := []label.KeyValue{label.String("C", "D")}
77	labels3 := []label.KeyValue{label.String("E", "F")}
78
79	counter := Must(meter1).NewInt64Counter("test.counter")
80	counter.Add(ctx, 1, labels1...)
81	counter.Add(ctx, 1, labels1...)
82
83	valuerecorder := Must(meter1).NewFloat64ValueRecorder("test.valuerecorder")
84	valuerecorder.Record(ctx, 1, labels1...)
85	valuerecorder.Record(ctx, 2, labels1...)
86
87	_ = Must(meter1).NewFloat64ValueObserver("test.valueobserver.float", func(_ context.Context, result metric.Float64ObserverResult) {
88		result.Observe(1., labels1...)
89		result.Observe(2., labels2...)
90	})
91
92	_ = Must(meter1).NewInt64ValueObserver("test.valueobserver.int", func(_ context.Context, result metric.Int64ObserverResult) {
93		result.Observe(1, labels1...)
94		result.Observe(2, labels2...)
95	})
96
97	second := Must(meter2).NewFloat64ValueRecorder("test.second")
98	second.Record(ctx, 1, labels3...)
99	second.Record(ctx, 2, labels3...)
100
101	mock, provider := metrictest.NewProvider()
102	global.SetMeterProvider(provider)
103
104	counter.Add(ctx, 1, labels1...)
105	valuerecorder.Record(ctx, 3, labels1...)
106	second.Record(ctx, 3, labels3...)
107
108	mock.RunAsyncInstruments()
109
110	measurements := asStructs(mock.MeasurementBatches)
111
112	require.EqualValues(t,
113		[]measured{
114			{
115				Name:                   "test.counter",
116				InstrumentationName:    "test1",
117				InstrumentationVersion: "semver:v1.0.0",
118				Labels:                 asMap(labels1...),
119				Number:                 asInt(1),
120			},
121			{
122				Name:                   "test.valuerecorder",
123				InstrumentationName:    "test1",
124				InstrumentationVersion: "semver:v1.0.0",
125				Labels:                 asMap(labels1...),
126				Number:                 asFloat(3),
127			},
128			{
129				Name:                "test.second",
130				InstrumentationName: "test2",
131				Labels:              asMap(labels3...),
132				Number:              asFloat(3),
133			},
134			{
135				Name:                   "test.valueobserver.float",
136				InstrumentationName:    "test1",
137				InstrumentationVersion: "semver:v1.0.0",
138				Labels:                 asMap(labels1...),
139				Number:                 asFloat(1),
140			},
141			{
142				Name:                   "test.valueobserver.float",
143				InstrumentationName:    "test1",
144				InstrumentationVersion: "semver:v1.0.0",
145				Labels:                 asMap(labels2...),
146				Number:                 asFloat(2),
147			},
148			{
149				Name:                   "test.valueobserver.int",
150				InstrumentationName:    "test1",
151				InstrumentationVersion: "semver:v1.0.0",
152				Labels:                 asMap(labels1...),
153				Number:                 asInt(1),
154			},
155			{
156				Name:                   "test.valueobserver.int",
157				InstrumentationName:    "test1",
158				InstrumentationVersion: "semver:v1.0.0",
159				Labels:                 asMap(labels2...),
160				Number:                 asInt(2),
161			},
162		},
163		measurements,
164	)
165}
166
167func TestBound(t *testing.T) {
168	internal.ResetForTest()
169
170	// Note: this test uses opposite Float64/Int64 number kinds
171	// vs. the above, to cover all the instruments.
172	ctx := context.Background()
173	glob := global.Meter("test")
174	labels1 := []label.KeyValue{label.String("A", "B")}
175
176	counter := Must(glob).NewFloat64Counter("test.counter")
177	boundC := counter.Bind(labels1...)
178	boundC.Add(ctx, 1)
179	boundC.Add(ctx, 1)
180
181	valuerecorder := Must(glob).NewInt64ValueRecorder("test.valuerecorder")
182	boundM := valuerecorder.Bind(labels1...)
183	boundM.Record(ctx, 1)
184	boundM.Record(ctx, 2)
185
186	mock, provider := metrictest.NewProvider()
187	global.SetMeterProvider(provider)
188
189	boundC.Add(ctx, 1)
190	boundM.Record(ctx, 3)
191
192	require.EqualValues(t,
193		[]measured{
194			{
195				Name:                "test.counter",
196				InstrumentationName: "test",
197				Labels:              asMap(labels1...),
198				Number:              asFloat(1),
199			},
200			{
201				Name:                "test.valuerecorder",
202				InstrumentationName: "test",
203				Labels:              asMap(labels1...),
204				Number:              asInt(3),
205			},
206		},
207		asStructs(mock.MeasurementBatches))
208
209	boundC.Unbind()
210	boundM.Unbind()
211}
212
213func TestUnbind(t *testing.T) {
214	// Tests Unbind with SDK never installed.
215	internal.ResetForTest()
216
217	glob := global.Meter("test")
218	labels1 := []label.KeyValue{label.String("A", "B")}
219
220	counter := Must(glob).NewFloat64Counter("test.counter")
221	boundC := counter.Bind(labels1...)
222
223	valuerecorder := Must(glob).NewInt64ValueRecorder("test.valuerecorder")
224	boundM := valuerecorder.Bind(labels1...)
225
226	boundC.Unbind()
227	boundM.Unbind()
228}
229
230func TestUnbindThenRecordOne(t *testing.T) {
231	internal.ResetForTest()
232
233	ctx := context.Background()
234	mock, provider := metrictest.NewProvider()
235
236	meter := global.Meter("test")
237	counter := Must(meter).NewInt64Counter("test.counter")
238	boundC := counter.Bind()
239	global.SetMeterProvider(provider)
240	boundC.Unbind()
241
242	require.NotPanics(t, func() {
243		boundC.Add(ctx, 1)
244	})
245	require.Equal(t, 0, len(mock.MeasurementBatches))
246}
247
248type meterProviderWithConstructorError struct {
249	metric.Provider
250}
251
252type meterWithConstructorError struct {
253	metric.MeterImpl
254}
255
256func (m *meterProviderWithConstructorError) Meter(iName string, opts ...metric.MeterOption) metric.Meter {
257	return metric.WrapMeterImpl(&meterWithConstructorError{m.Provider.Meter(iName, opts...).MeterImpl()}, iName, opts...)
258}
259
260func (m *meterWithConstructorError) NewSyncInstrument(_ metric.Descriptor) (metric.SyncImpl, error) {
261	return metric.NoopSync{}, errors.New("constructor error")
262}
263
264func TestErrorInDeferredConstructor(t *testing.T) {
265	internal.ResetForTest()
266
267	ctx := context.Background()
268	meter := global.MeterProvider().Meter("builtin")
269
270	c1 := Must(meter).NewInt64Counter("test")
271	c2 := Must(meter).NewInt64Counter("test")
272
273	_, provider := metrictest.NewProvider()
274	sdk := &meterProviderWithConstructorError{provider}
275
276	require.Panics(t, func() {
277		global.SetMeterProvider(sdk)
278	})
279
280	c1.Add(ctx, 1)
281	c2.Add(ctx, 2)
282}
283
284func TestImplementationIndirection(t *testing.T) {
285	internal.ResetForTest()
286
287	// Test that Implementation() does the proper indirection, i.e.,
288	// returns the implementation interface not the global, after
289	// registered.
290
291	meter1 := global.Meter("test1")
292
293	// Sync: no SDK yet
294	counter := Must(meter1).NewInt64Counter("interface.counter")
295
296	ival := counter.Measurement(1).SyncImpl().Implementation()
297	require.NotNil(t, ival)
298
299	_, ok := ival.(*metrictest.Sync)
300	require.False(t, ok)
301
302	// Async: no SDK yet
303	valueobserver := Must(meter1).NewFloat64ValueObserver(
304		"interface.valueobserver",
305		func(_ context.Context, result metric.Float64ObserverResult) {},
306	)
307
308	ival = valueobserver.AsyncImpl().Implementation()
309	require.NotNil(t, ival)
310
311	_, ok = ival.(*metrictest.Async)
312	require.False(t, ok)
313
314	// Register the SDK
315	_, provider := metrictest.NewProvider()
316	global.SetMeterProvider(provider)
317
318	// Repeat the above tests
319
320	// Sync
321	ival = counter.Measurement(1).SyncImpl().Implementation()
322	require.NotNil(t, ival)
323
324	_, ok = ival.(*metrictest.Sync)
325	require.True(t, ok)
326
327	// Async
328	ival = valueobserver.AsyncImpl().Implementation()
329	require.NotNil(t, ival)
330
331	_, ok = ival.(*metrictest.Async)
332	require.True(t, ok)
333}
334
335func TestRecordBatchMock(t *testing.T) {
336	internal.ResetForTest()
337
338	meter := global.MeterProvider().Meter("builtin")
339
340	counter := Must(meter).NewInt64Counter("test.counter")
341
342	meter.RecordBatch(context.Background(), nil, counter.Measurement(1))
343
344	mock, provider := metrictest.NewProvider()
345	global.SetMeterProvider(provider)
346
347	meter.RecordBatch(context.Background(), nil, counter.Measurement(1))
348
349	require.EqualValues(t,
350		[]measured{
351			{
352				Name:                "test.counter",
353				InstrumentationName: "builtin",
354				Labels:              asMap(),
355				Number:              asInt(1),
356			},
357		},
358		asStructs(mock.MeasurementBatches))
359}
360