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.
14package exporterhelper
15
16import (
17	"context"
18	"errors"
19	"testing"
20
21	"github.com/stretchr/testify/assert"
22	"github.com/stretchr/testify/require"
23	"go.opentelemetry.io/otel"
24	"go.opentelemetry.io/otel/attribute"
25	"go.opentelemetry.io/otel/oteltest"
26	"go.opentelemetry.io/otel/trace"
27
28	"go.opentelemetry.io/collector/component"
29	"go.opentelemetry.io/collector/component/componenttest"
30	"go.opentelemetry.io/collector/config"
31	"go.opentelemetry.io/collector/consumer"
32	"go.opentelemetry.io/collector/consumer/consumererror"
33	"go.opentelemetry.io/collector/consumer/consumerhelper"
34	"go.opentelemetry.io/collector/internal/obsreportconfig/obsmetrics"
35	"go.opentelemetry.io/collector/internal/testdata"
36	"go.opentelemetry.io/collector/model/pdata"
37	"go.opentelemetry.io/collector/obsreport/obsreporttest"
38)
39
40const (
41	fakeTraceParentSpanName = "fake_trace_parent_span_name"
42)
43
44var (
45	fakeTracesExporterName   = config.NewIDWithName("fake_traces_exporter", "with_name")
46	fakeTracesExporterConfig = config.NewExporterSettings(fakeTracesExporterName)
47)
48
49func TestTracesRequest(t *testing.T) {
50	mr := newTracesRequest(context.Background(), testdata.GenerateTracesOneSpan(), nil)
51
52	traceErr := consumererror.NewTraces(errors.New("some error"), pdata.NewTraces())
53	assert.EqualValues(t, newTracesRequest(context.Background(), pdata.NewTraces(), nil), mr.onError(traceErr))
54}
55
56func TestTracesExporter_InvalidName(t *testing.T) {
57	te, err := NewTracesExporter(nil, componenttest.NewNopExporterCreateSettings(), newTraceDataPusher(nil))
58	require.Nil(t, te)
59	require.Equal(t, errNilConfig, err)
60}
61
62func TestTracesExporter_NilLogger(t *testing.T) {
63	te, err := NewTracesExporter(&fakeTracesExporterConfig, component.ExporterCreateSettings{}, newTraceDataPusher(nil))
64	require.Nil(t, te)
65	require.Equal(t, errNilLogger, err)
66}
67
68func TestTracesExporter_NilPushTraceData(t *testing.T) {
69	te, err := NewTracesExporter(&fakeTracesExporterConfig, componenttest.NewNopExporterCreateSettings(), nil)
70	require.Nil(t, te)
71	require.Equal(t, errNilPushTraceData, err)
72}
73
74func TestTracesExporter_Default(t *testing.T) {
75	td := pdata.NewTraces()
76	te, err := NewTracesExporter(&fakeTracesExporterConfig, componenttest.NewNopExporterCreateSettings(), newTraceDataPusher(nil))
77	assert.NotNil(t, te)
78	assert.NoError(t, err)
79
80	assert.Equal(t, consumer.Capabilities{MutatesData: false}, te.Capabilities())
81	assert.NoError(t, te.Start(context.Background(), componenttest.NewNopHost()))
82	assert.NoError(t, te.ConsumeTraces(context.Background(), td))
83	assert.NoError(t, te.Shutdown(context.Background()))
84}
85
86func TestTracesExporter_WithCapabilities(t *testing.T) {
87	capabilities := consumer.Capabilities{MutatesData: true}
88	te, err := NewTracesExporter(&fakeTracesExporterConfig, componenttest.NewNopExporterCreateSettings(), newTraceDataPusher(nil), WithCapabilities(capabilities))
89	assert.NotNil(t, te)
90	assert.NoError(t, err)
91
92	assert.Equal(t, capabilities, te.Capabilities())
93}
94
95func TestTracesExporter_Default_ReturnError(t *testing.T) {
96	td := pdata.NewTraces()
97	want := errors.New("my_error")
98	te, err := NewTracesExporter(&fakeTracesExporterConfig, componenttest.NewNopExporterCreateSettings(), newTraceDataPusher(want))
99	require.NoError(t, err)
100	require.NotNil(t, te)
101
102	err = te.ConsumeTraces(context.Background(), td)
103	require.Equal(t, want, err)
104}
105
106func TestTracesExporter_WithRecordMetrics(t *testing.T) {
107	te, err := NewTracesExporter(&fakeTracesExporterConfig, componenttest.NewNopExporterCreateSettings(), newTraceDataPusher(nil))
108	require.NoError(t, err)
109	require.NotNil(t, te)
110
111	checkRecordedMetricsForTracesExporter(t, te, nil)
112}
113
114func TestTracesExporter_WithRecordMetrics_ReturnError(t *testing.T) {
115	want := errors.New("my_error")
116	te, err := NewTracesExporter(&fakeTracesExporterConfig, componenttest.NewNopExporterCreateSettings(), newTraceDataPusher(want))
117	require.NoError(t, err)
118	require.NotNil(t, te)
119
120	checkRecordedMetricsForTracesExporter(t, te, want)
121}
122
123func TestTracesExporter_WithRecordEnqueueFailedMetrics(t *testing.T) {
124	doneFn, err := obsreporttest.SetupRecordedMetricsTest()
125	require.NoError(t, err)
126	defer doneFn()
127
128	rCfg := DefaultRetrySettings()
129	qCfg := DefaultQueueSettings()
130	qCfg.NumConsumers = 1
131	qCfg.QueueSize = 2
132	wantErr := errors.New("some-error")
133	te, err := NewTracesExporter(&fakeTracesExporterConfig, componenttest.NewNopExporterCreateSettings(), newTraceDataPusher(wantErr), WithRetry(rCfg), WithQueue(qCfg))
134	require.NoError(t, err)
135	require.NotNil(t, te)
136
137	td := testdata.GenerateTracesTwoSpansSameResource()
138	const numBatches = 7
139	for i := 0; i < numBatches; i++ {
140		te.ConsumeTraces(context.Background(), td)
141	}
142
143	// 2 batched must be in queue, and 5 batches (10 spans) rejected due to queue overflow
144	checkExporterEnqueueFailedTracesStats(t, fakeTracesExporterName, int64(10))
145}
146
147func TestTracesExporter_WithSpan(t *testing.T) {
148	sr := new(oteltest.SpanRecorder)
149	tp := oteltest.NewTracerProvider(oteltest.WithSpanRecorder(sr))
150	otel.SetTracerProvider(tp)
151	defer otel.SetTracerProvider(trace.NewNoopTracerProvider())
152
153	te, err := NewTracesExporter(&fakeTracesExporterConfig, componenttest.NewNopExporterCreateSettings(), newTraceDataPusher(nil))
154	require.NoError(t, err)
155	require.NotNil(t, te)
156
157	checkWrapSpanForTracesExporter(t, sr, tp.Tracer("test"), te, nil, 1)
158}
159
160func TestTracesExporter_WithSpan_ReturnError(t *testing.T) {
161	sr := new(oteltest.SpanRecorder)
162	tp := oteltest.NewTracerProvider(oteltest.WithSpanRecorder(sr))
163	otel.SetTracerProvider(tp)
164	defer otel.SetTracerProvider(trace.NewNoopTracerProvider())
165
166	want := errors.New("my_error")
167	te, err := NewTracesExporter(&fakeTracesExporterConfig, componenttest.NewNopExporterCreateSettings(), newTraceDataPusher(want))
168	require.NoError(t, err)
169	require.NotNil(t, te)
170
171	checkWrapSpanForTracesExporter(t, sr, tp.Tracer("test"), te, want, 1)
172}
173
174func TestTracesExporter_WithShutdown(t *testing.T) {
175	shutdownCalled := false
176	shutdown := func(context.Context) error { shutdownCalled = true; return nil }
177
178	te, err := NewTracesExporter(&fakeTracesExporterConfig, componenttest.NewNopExporterCreateSettings(), newTraceDataPusher(nil), WithShutdown(shutdown))
179	assert.NotNil(t, te)
180	assert.NoError(t, err)
181
182	assert.NoError(t, te.Start(context.Background(), componenttest.NewNopHost()))
183	assert.NoError(t, te.Shutdown(context.Background()))
184	assert.True(t, shutdownCalled)
185}
186
187func TestTracesExporter_WithShutdown_ReturnError(t *testing.T) {
188	want := errors.New("my_error")
189	shutdownErr := func(context.Context) error { return want }
190
191	te, err := NewTracesExporter(&fakeTracesExporterConfig, componenttest.NewNopExporterCreateSettings(), newTraceDataPusher(nil), WithShutdown(shutdownErr))
192	assert.NotNil(t, te)
193	assert.NoError(t, err)
194
195	assert.NoError(t, te.Start(context.Background(), componenttest.NewNopHost()))
196	assert.Equal(t, te.Shutdown(context.Background()), want)
197}
198
199func newTraceDataPusher(retError error) consumerhelper.ConsumeTracesFunc {
200	return func(ctx context.Context, td pdata.Traces) error {
201		return retError
202	}
203}
204
205func checkRecordedMetricsForTracesExporter(t *testing.T, te component.TracesExporter, wantError error) {
206	doneFn, err := obsreporttest.SetupRecordedMetricsTest()
207	require.NoError(t, err)
208	defer doneFn()
209
210	td := testdata.GenerateTracesTwoSpansSameResource()
211	const numBatches = 7
212	for i := 0; i < numBatches; i++ {
213		require.Equal(t, wantError, te.ConsumeTraces(context.Background(), td))
214	}
215
216	// TODO: When the new metrics correctly count partial dropped fix this.
217	if wantError != nil {
218		obsreporttest.CheckExporterTraces(t, fakeTracesExporterName, 0, int64(numBatches*td.SpanCount()))
219	} else {
220		obsreporttest.CheckExporterTraces(t, fakeTracesExporterName, int64(numBatches*td.SpanCount()), 0)
221	}
222}
223
224func generateTraceTraffic(t *testing.T, tracer trace.Tracer, te component.TracesExporter, numRequests int, wantError error) {
225	td := pdata.NewTraces()
226	td.ResourceSpans().AppendEmpty().InstrumentationLibrarySpans().AppendEmpty().Spans().AppendEmpty()
227	ctx, span := tracer.Start(context.Background(), fakeTraceParentSpanName)
228	defer span.End()
229	for i := 0; i < numRequests; i++ {
230		require.Equal(t, wantError, te.ConsumeTraces(ctx, td))
231	}
232}
233
234func checkWrapSpanForTracesExporter(t *testing.T, sr *oteltest.SpanRecorder, tracer trace.Tracer, te component.TracesExporter, wantError error, numSpans int64) {
235	const numRequests = 5
236	generateTraceTraffic(t, tracer, te, numRequests, wantError)
237
238	// Inspection time!
239	gotSpanData := sr.Completed()
240	require.Equal(t, numRequests+1, len(gotSpanData))
241
242	parentSpan := gotSpanData[numRequests]
243	require.Equalf(t, fakeTraceParentSpanName, parentSpan.Name(), "SpanData %v", parentSpan)
244
245	for _, sd := range gotSpanData[:numRequests] {
246		require.Equalf(t, parentSpan.SpanContext().SpanID(), sd.ParentSpanID(), "Exporter span not a child\nSpanData %v", sd)
247		checkStatus(t, sd, wantError)
248
249		sentSpans := numSpans
250		var failedToSendSpans int64
251		if wantError != nil {
252			sentSpans = 0
253			failedToSendSpans = numSpans
254		}
255
256		require.Equalf(t, attribute.Int64Value(sentSpans), sd.Attributes()[obsmetrics.SentSpansKey], "SpanData %v", sd)
257		require.Equalf(t, attribute.Int64Value(failedToSendSpans), sd.Attributes()[obsmetrics.FailedToSendSpansKey], "SpanData %v", sd)
258	}
259}
260