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 jaeger
16
17import (
18	"math"
19	"os"
20	"testing"
21
22	"github.com/stretchr/testify/assert"
23	"github.com/stretchr/testify/require"
24
25	"go.opentelemetry.io/otel/attribute"
26	ottest "go.opentelemetry.io/otel/internal/internaltest"
27)
28
29func Test_parseTags(t *testing.T) {
30	envStore, err := ottest.SetEnvVariables(map[string]string{
31		"existing": "not-default",
32	})
33	require.NoError(t, err)
34	defer func() {
35		require.NoError(t, envStore.Restore())
36	}()
37
38	testCases := []struct {
39		name          string
40		tagStr        string
41		expectedTags  []attribute.KeyValue
42		expectedError error
43	}{
44		{
45			name:   "string",
46			tagStr: "key=value",
47			expectedTags: []attribute.KeyValue{
48				{
49					Key:   "key",
50					Value: attribute.StringValue("value"),
51				},
52			},
53		},
54		{
55			name:   "int64",
56			tagStr: "k=9223372036854775807,k2=-9223372036854775808",
57			expectedTags: []attribute.KeyValue{
58				{
59					Key:   "k",
60					Value: attribute.Int64Value(math.MaxInt64),
61				},
62				{
63					Key:   "k2",
64					Value: attribute.Int64Value(math.MinInt64),
65				},
66			},
67		},
68		{
69			name:   "float64",
70			tagStr: "k=1.797693134862315708145274237317043567981e+308,k2=4.940656458412465441765687928682213723651e-324,k3=-1.2",
71			expectedTags: []attribute.KeyValue{
72				{
73					Key:   "k",
74					Value: attribute.Float64Value(math.MaxFloat64),
75				},
76				{
77					Key:   "k2",
78					Value: attribute.Float64Value(math.SmallestNonzeroFloat64),
79				},
80				{
81					Key:   "k3",
82					Value: attribute.Float64Value(-1.2),
83				},
84			},
85		},
86		{
87			name:   "multiple type values",
88			tagStr: "k=v,k2=123, k3=v3 ,k4=-1.2, k5=${existing:default},k6=${nonExisting:default}",
89			expectedTags: []attribute.KeyValue{
90				{
91					Key:   "k",
92					Value: attribute.StringValue("v"),
93				},
94				{
95					Key:   "k2",
96					Value: attribute.Int64Value(123),
97				},
98				{
99					Key:   "k3",
100					Value: attribute.StringValue("v3"),
101				},
102				{
103					Key:   "k4",
104					Value: attribute.Float64Value(-1.2),
105				},
106				{
107					Key:   "k5",
108					Value: attribute.StringValue("not-default"),
109				},
110				{
111					Key:   "k6",
112					Value: attribute.StringValue("default"),
113				},
114			},
115		},
116		{
117			name:          "malformed: only have key",
118			tagStr:        "key",
119			expectedError: errTagValueNotFound,
120		},
121		{
122			name:          "malformed: environment key has no default value",
123			tagStr:        "key=${foo}",
124			expectedError: errTagEnvironmentDefaultValueNotFound,
125		},
126	}
127
128	for _, tc := range testCases {
129		t.Run(tc.name, func(t *testing.T) {
130			tags, err := parseTags(tc.tagStr)
131			if tc.expectedError == nil {
132				assert.NoError(t, err)
133				assert.Equal(t, tc.expectedTags, tags)
134			} else {
135				assert.Error(t, err)
136				assert.Equal(t, tc.expectedError, err)
137				assert.Equal(t, tc.expectedTags, tags)
138			}
139		})
140	}
141}
142
143func Test_parseValue(t *testing.T) {
144	testCases := []struct {
145		name     string
146		str      string
147		expected attribute.Value
148	}{
149		{
150			name:     "bool: true",
151			str:      "true",
152			expected: attribute.BoolValue(true),
153		},
154		{
155			name:     "bool: false",
156			str:      "false",
157			expected: attribute.BoolValue(false),
158		},
159		{
160			name:     "int64: 012340",
161			str:      "012340",
162			expected: attribute.Int64Value(12340),
163		},
164		{
165			name:     "int64: -012340",
166			str:      "-012340",
167			expected: attribute.Int64Value(-12340),
168		},
169		{
170			name:     "int64: 0",
171			str:      "0",
172			expected: attribute.Int64Value(0),
173		},
174		{
175			name:     "float64: -0.1",
176			str:      "-0.1",
177			expected: attribute.Float64Value(-0.1),
178		},
179		{
180			name:     "float64: 00.001",
181			str:      "00.001",
182			expected: attribute.Float64Value(0.001),
183		},
184		{
185			name:     "float64: 1E23",
186			str:      "1E23",
187			expected: attribute.Float64Value(1e23),
188		},
189		{
190			name:     "string: foo",
191			str:      "foo",
192			expected: attribute.StringValue("foo"),
193		},
194	}
195
196	for _, tc := range testCases {
197		t.Run(tc.name, func(t *testing.T) {
198			v := parseValue(tc.str)
199			assert.Equal(t, tc.expected, v)
200		})
201	}
202}
203
204func TestNewRawExporterWithEnv(t *testing.T) {
205	const (
206		collectorEndpoint = "http://localhost"
207		username          = "user"
208		password          = "password"
209		serviceName       = "test-service"
210		disabled          = "false"
211		tags              = "key=value"
212	)
213
214	envStore, err := ottest.SetEnvVariables(map[string]string{
215		envEndpoint:    collectorEndpoint,
216		envUser:        username,
217		envPassword:    password,
218		envDisabled:    disabled,
219		envServiceName: serviceName,
220		envTags:        tags,
221	})
222	require.NoError(t, err)
223	defer func() {
224		require.NoError(t, envStore.Restore())
225	}()
226
227	// Create Jaeger Exporter with environment variables
228	exp, err := NewRawExporter(
229		WithCollectorEndpoint(CollectorEndpointFromEnv(), WithCollectorEndpointOptionFromEnv()),
230		WithDisabled(true),
231		WithDisabledFromEnv(),
232		WithProcessFromEnv(),
233	)
234
235	assert.NoError(t, err)
236	assert.Equal(t, false, exp.o.Disabled)
237	assert.EqualValues(t, serviceName, exp.process.ServiceName)
238	assert.Len(t, exp.process.Tags, 1)
239
240	require.IsType(t, &collectorUploader{}, exp.uploader)
241	uploader := exp.uploader.(*collectorUploader)
242	assert.Equal(t, collectorEndpoint, uploader.endpoint)
243	assert.Equal(t, username, uploader.username)
244	assert.Equal(t, password, uploader.password)
245}
246
247func TestNewRawExporterWithEnvImplicitly(t *testing.T) {
248	const (
249		collectorEndpoint = "http://localhost"
250		username          = "user"
251		password          = "password"
252		serviceName       = "test-service"
253		disabled          = "false"
254		tags              = "key=value"
255	)
256
257	envStore, err := ottest.SetEnvVariables(map[string]string{
258		envEndpoint:    collectorEndpoint,
259		envUser:        username,
260		envPassword:    password,
261		envDisabled:    disabled,
262		envServiceName: serviceName,
263		envTags:        tags,
264	})
265	require.NoError(t, err)
266	defer func() {
267		require.NoError(t, envStore.Restore())
268	}()
269
270	// Create Jaeger Exporter with environment variables
271	exp, err := NewRawExporter(
272		WithCollectorEndpoint("should be overwritten"),
273		WithDisabled(true),
274	)
275
276	assert.NoError(t, err)
277	// NewRawExporter will ignore Disabled env
278	assert.Equal(t, true, exp.o.Disabled)
279	assert.EqualValues(t, serviceName, exp.process.ServiceName)
280	assert.Len(t, exp.process.Tags, 1)
281
282	require.IsType(t, &collectorUploader{}, exp.uploader)
283	uploader := exp.uploader.(*collectorUploader)
284	assert.Equal(t, collectorEndpoint, uploader.endpoint)
285	assert.Equal(t, username, uploader.username)
286	assert.Equal(t, password, uploader.password)
287}
288
289func TestCollectorEndpointFromEnv(t *testing.T) {
290	const (
291		collectorEndpoint = "http://localhost"
292	)
293
294	envStore, err := ottest.SetEnvVariables(map[string]string{
295		envEndpoint: collectorEndpoint,
296	})
297	require.NoError(t, err)
298	defer func() {
299		require.NoError(t, envStore.Restore())
300	}()
301
302	assert.Equal(t, collectorEndpoint, CollectorEndpointFromEnv())
303}
304
305func TestWithCollectorEndpointOptionFromEnv(t *testing.T) {
306	testCases := []struct {
307		name                             string
308		envUsername                      string
309		envPassword                      string
310		collectorEndpointOptions         CollectorEndpointOptions
311		expectedCollectorEndpointOptions CollectorEndpointOptions
312	}{
313		{
314			name:        "overrides value via environment variables",
315			envUsername: "username",
316			envPassword: "password",
317			collectorEndpointOptions: CollectorEndpointOptions{
318				username: "foo",
319				password: "bar",
320			},
321			expectedCollectorEndpointOptions: CollectorEndpointOptions{
322				username: "username",
323				password: "password",
324			},
325		},
326		{
327			name:        "environment variables is empty, will not overwrite value",
328			envUsername: "",
329			envPassword: "",
330			collectorEndpointOptions: CollectorEndpointOptions{
331				username: "foo",
332				password: "bar",
333			},
334			expectedCollectorEndpointOptions: CollectorEndpointOptions{
335				username: "foo",
336				password: "bar",
337			},
338		},
339	}
340
341	envStore := ottest.NewEnvStore()
342	envStore.Record(envUser)
343	envStore.Record(envPassword)
344	defer func() {
345		require.NoError(t, envStore.Restore())
346	}()
347	for _, tc := range testCases {
348		t.Run(tc.name, func(t *testing.T) {
349			require.NoError(t, os.Setenv(envUser, tc.envUsername))
350			require.NoError(t, os.Setenv(envPassword, tc.envPassword))
351
352			f := WithCollectorEndpointOptionFromEnv()
353			f(&tc.collectorEndpointOptions)
354
355			assert.Equal(t, tc.expectedCollectorEndpointOptions, tc.collectorEndpointOptions)
356		})
357	}
358}
359
360func TestWithDisabledFromEnv(t *testing.T) {
361	testCases := []struct {
362		name            string
363		env             string
364		options         options
365		expectedOptions options
366	}{
367		{
368			name:            "overwriting",
369			env:             "true",
370			options:         options{},
371			expectedOptions: options{Disabled: true},
372		},
373		{
374			name:            "no overwriting",
375			env:             "",
376			options:         options{Disabled: true},
377			expectedOptions: options{Disabled: true},
378		},
379	}
380
381	envStore := ottest.NewEnvStore()
382	envStore.Record(envDisabled)
383	defer func() {
384		require.NoError(t, envStore.Restore())
385	}()
386	for _, tc := range testCases {
387		t.Run(tc.name, func(t *testing.T) {
388			require.NoError(t, os.Setenv(envDisabled, tc.env))
389
390			f := WithDisabledFromEnv()
391			f(&tc.options)
392
393			assert.Equal(t, tc.expectedOptions, tc.options)
394		})
395	}
396}
397
398func TestProcessFromEnv(t *testing.T) {
399	testCases := []struct {
400		name            string
401		serviceName     string
402		tags            string
403		expectedProcess Process
404	}{
405		{
406			name:        "set process",
407			serviceName: "test-service",
408			tags:        "key=value,key2=123",
409			expectedProcess: Process{
410				ServiceName: "test-service",
411				Tags: []attribute.KeyValue{
412					attribute.String("key", "value"),
413					attribute.Int64("key2", 123),
414				},
415			},
416		},
417		{
418			name:        "malformed tags",
419			serviceName: "test-service",
420			tags:        "key",
421			expectedProcess: Process{
422				ServiceName: "test-service",
423			},
424		},
425	}
426
427	for _, tc := range testCases {
428		t.Run(tc.name, func(t *testing.T) {
429			envStore, err := ottest.SetEnvVariables(map[string]string{
430				envServiceName: tc.serviceName,
431				envTags:        tc.tags,
432			})
433			require.NoError(t, err)
434
435			p := ProcessFromEnv()
436			assert.Equal(t, tc.expectedProcess, p)
437
438			require.NoError(t, envStore.Restore())
439		})
440	}
441}
442
443func TestWithProcessFromEnv(t *testing.T) {
444	testCases := []struct {
445		name            string
446		envServiceName  string
447		envTags         string
448		options         options
449		expectedOptions options
450	}{
451		{
452			name:           "overwriting",
453			envServiceName: "service-name",
454			envTags:        "key=value",
455			options: options{
456				Process: Process{
457					ServiceName: "old-name",
458					Tags: []attribute.KeyValue{
459						attribute.String("old-key", "old-value"),
460					},
461				},
462			},
463			expectedOptions: options{
464				Process: Process{
465					ServiceName: "service-name",
466					Tags: []attribute.KeyValue{
467						attribute.String("key", "value"),
468					},
469				},
470			},
471		},
472		{
473			name:           "no overwriting",
474			envServiceName: "",
475			envTags:        "",
476			options: options{
477				Process: Process{
478					ServiceName: "old-name",
479					Tags: []attribute.KeyValue{
480						attribute.String("old-key", "old-value"),
481					},
482				},
483			},
484			expectedOptions: options{
485				Process: Process{
486					ServiceName: "old-name",
487					Tags: []attribute.KeyValue{
488						attribute.String("old-key", "old-value"),
489					},
490				},
491			},
492		},
493	}
494
495	envStore := ottest.NewEnvStore()
496	envStore.Record(envServiceName)
497	envStore.Record(envTags)
498	defer func() {
499		require.NoError(t, envStore.Restore())
500	}()
501	for _, tc := range testCases {
502		t.Run(tc.name, func(t *testing.T) {
503			require.NoError(t, os.Setenv(envServiceName, tc.envServiceName))
504			require.NoError(t, os.Setenv(envTags, tc.envTags))
505
506			f := WithProcessFromEnv()
507			f(&tc.options)
508
509			assert.Equal(t, tc.expectedOptions, tc.options)
510		})
511	}
512}
513