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 zipkinv2
16
17import (
18	"testing"
19	"time"
20
21	zipkinmodel "github.com/openzipkin/zipkin-go/model"
22	"github.com/stretchr/testify/assert"
23
24	"go.opentelemetry.io/collector/model/pdata"
25	"go.opentelemetry.io/collector/translator/conventions"
26	"go.opentelemetry.io/collector/translator/trace/internal/zipkin"
27)
28
29func TestZipkinSpansToInternalTraces(t *testing.T) {
30	tests := []struct {
31		name string
32		zs   []*zipkinmodel.SpanModel
33		td   pdata.Traces
34		err  error
35	}{
36		{
37			name: "empty",
38			zs:   make([]*zipkinmodel.SpanModel, 0),
39			td:   pdata.NewTraces(),
40			err:  nil,
41		},
42		{
43			name: "minimalSpan",
44			zs:   generateSpanNoEndpoints(),
45			td:   generateTraceSingleSpanNoResourceOrInstrLibrary(),
46			err:  nil,
47		},
48		{
49			name: "onlyLocalEndpointSpan",
50			zs:   generateSpanNoTags(),
51			td:   generateTraceSingleSpanMinmalResource(),
52			err:  nil,
53		},
54		{
55			name: "errorTag",
56			zs:   generateSpanErrorTags(),
57			td:   generateTraceSingleSpanErrorStatus(),
58			err:  nil,
59		},
60	}
61	for _, test := range tests {
62		t.Run(test.name, func(t *testing.T) {
63			td, err := ToTranslator{}.ToTraces(test.zs)
64			assert.EqualValues(t, test.err, err)
65			if test.name != "nilSpan" {
66				assert.Equal(t, len(test.zs), td.SpanCount())
67			}
68			assert.EqualValues(t, test.td, td)
69		})
70	}
71}
72
73func generateSpanNoEndpoints() []*zipkinmodel.SpanModel {
74	spans := make([]*zipkinmodel.SpanModel, 1)
75	spans[0] = &zipkinmodel.SpanModel{
76		SpanContext: zipkinmodel.SpanContext{
77			TraceID: convertTraceID(
78				pdata.NewTraceID([16]byte{0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF, 0x80})),
79			ID: convertSpanID(pdata.NewSpanID([8]byte{0xAF, 0xAE, 0xAD, 0xAC, 0xAB, 0xAA, 0xA9, 0xA8})),
80		},
81		Name:           "MinimalData",
82		Kind:           zipkinmodel.Client,
83		Timestamp:      time.Unix(1596911098, 294000000),
84		Duration:       1000000,
85		Shared:         false,
86		LocalEndpoint:  nil,
87		RemoteEndpoint: nil,
88		Annotations:    nil,
89		Tags:           nil,
90	}
91	return spans
92}
93
94func generateSpanNoTags() []*zipkinmodel.SpanModel {
95	spans := generateSpanNoEndpoints()
96	spans[0].LocalEndpoint = &zipkinmodel.Endpoint{ServiceName: "SoleAttr"}
97	return spans
98}
99
100func generateSpanErrorTags() []*zipkinmodel.SpanModel {
101	errorTags := make(map[string]string)
102	errorTags["error"] = "true"
103
104	spans := generateSpanNoEndpoints()
105	spans[0].Tags = errorTags
106	return spans
107}
108
109func generateTraceSingleSpanNoResourceOrInstrLibrary() pdata.Traces {
110	td := pdata.NewTraces()
111	span := td.ResourceSpans().AppendEmpty().InstrumentationLibrarySpans().AppendEmpty().Spans().AppendEmpty()
112	span.SetTraceID(
113		pdata.NewTraceID([16]byte{0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF, 0x80}))
114	span.SetSpanID(pdata.NewSpanID([8]byte{0xAF, 0xAE, 0xAD, 0xAC, 0xAB, 0xAA, 0xA9, 0xA8}))
115	span.SetName("MinimalData")
116	span.SetKind(pdata.SpanKindClient)
117	span.SetStartTimestamp(1596911098294000000)
118	span.SetEndTimestamp(1596911098295000000)
119	return td
120}
121
122func generateTraceSingleSpanMinmalResource() pdata.Traces {
123	td := generateTraceSingleSpanNoResourceOrInstrLibrary()
124	rs := td.ResourceSpans().At(0)
125	rsc := rs.Resource()
126	rsc.Attributes().UpsertString(conventions.AttributeServiceName, "SoleAttr")
127	return td
128}
129
130func generateTraceSingleSpanErrorStatus() pdata.Traces {
131	td := pdata.NewTraces()
132	span := td.ResourceSpans().AppendEmpty().InstrumentationLibrarySpans().AppendEmpty().Spans().AppendEmpty()
133	span.SetTraceID(
134		pdata.NewTraceID([16]byte{0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF, 0x80}))
135	span.SetSpanID(pdata.NewSpanID([8]byte{0xAF, 0xAE, 0xAD, 0xAC, 0xAB, 0xAA, 0xA9, 0xA8}))
136	span.SetName("MinimalData")
137	span.SetKind(pdata.SpanKindClient)
138	span.SetStartTimestamp(1596911098294000000)
139	span.SetEndTimestamp(1596911098295000000)
140	span.Status().SetCode(pdata.StatusCodeError)
141	return td
142}
143
144func TestV2SpanWithoutTimestampGetsTag(t *testing.T) {
145	duration := int64(2948533333)
146	spans := make([]*zipkinmodel.SpanModel, 1)
147	spans[0] = &zipkinmodel.SpanModel{
148		SpanContext: zipkinmodel.SpanContext{
149			TraceID: convertTraceID(
150				pdata.NewTraceID([16]byte{0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF, 0x80})),
151			ID: convertSpanID(pdata.NewSpanID([8]byte{0xAF, 0xAE, 0xAD, 0xAC, 0xAB, 0xAA, 0xA9, 0xA8})),
152		},
153		Name:           "NoTimestamps",
154		Kind:           zipkinmodel.Client,
155		Duration:       time.Duration(duration),
156		Shared:         false,
157		LocalEndpoint:  nil,
158		RemoteEndpoint: nil,
159		Annotations:    nil,
160		Tags:           nil,
161	}
162
163	gb, err := ToTranslator{}.ToTraces(spans)
164	if err != nil {
165		t.Errorf("Unexpected error: %v", err)
166		return
167	}
168
169	gs := gb.ResourceSpans().At(0).InstrumentationLibrarySpans().At(0).Spans().At(0)
170	assert.NotNil(t, gs.StartTimestamp)
171	assert.NotNil(t, gs.EndTimestamp)
172
173	// expect starttime to be set to zero (unix time)
174	unixTime := gs.StartTimestamp().AsTime().Unix()
175	assert.Equal(t, int64(0), unixTime)
176
177	// expect end time to be zero (unix time) plus the duration
178	assert.Equal(t, duration, gs.EndTimestamp().AsTime().UnixNano())
179
180	wasAbsent, mapContainedKey := gs.Attributes().Get(zipkin.StartTimeAbsent)
181	assert.True(t, mapContainedKey)
182	assert.True(t, wasAbsent.BoolVal())
183}
184