Skip to content

Commit ab460ea

Browse files
XinRanZhAWSADOT Patch workflow
andauthored
Implement Unit Test for Span Processing Util (#25)
*Description of changes:* Implement Unit Test for Span Processing Util By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice. --------- Co-authored-by: ADOT Patch workflow <[email protected]>
1 parent b9f8610 commit ab460ea

File tree

1 file changed

+358
-5
lines changed

1 file changed

+358
-5
lines changed
Lines changed: 358 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,365 @@
11
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
# SPDX-License-Identifier: Apache-2.0
33
from unittest import TestCase
4+
from unittest.mock import MagicMock
45

5-
from amazon.opentelemetry.distro._aws_span_processing_util import is_key_present
6-
from opentelemetry.sdk.trace import ReadableSpan
6+
from amazon.opentelemetry.distro._aws_attribute_keys import AWS_CONSUMER_PARENT_SPAN_KIND, AWS_LOCAL_OPERATION
7+
from amazon.opentelemetry.distro._aws_span_processing_util import (
8+
extract_api_path_value,
9+
get_egress_operation,
10+
get_ingress_operation,
11+
is_aws_sdk_span,
12+
is_consumer_process_span,
13+
is_key_present,
14+
is_local_root,
15+
should_generate_dependency_metric_attributes,
16+
should_generate_service_metric_attributes,
17+
should_use_internal_operation,
18+
)
19+
from opentelemetry.sdk.trace import Span, SpanContext
20+
from opentelemetry.sdk.util.instrumentation import InstrumentationScope
21+
from opentelemetry.semconv.trace import MessagingOperationValues, SpanAttributes
22+
from opentelemetry.trace import SpanKind
23+
from opentelemetry.util.types import Attributes
724

25+
_UNKNOWN_OPERATION: str = "UnknownOperation"
26+
_INTERNAL_OPERATION: str = "InternalOperation"
27+
_DEFAULT_PATH_VALUE: str = "/"
828

29+
30+
# pylint: disable=too-many-public-methods
931
class TestAwsSpanProcessingUtil(TestCase):
10-
def test_basic(self):
11-
span: ReadableSpan = ReadableSpan(name="test")
12-
self.assertFalse(is_key_present(span, "test"))
32+
def setUp(self):
33+
self.attributes_mock: Attributes = MagicMock()
34+
self.span_data_mock: Span = MagicMock()
35+
self.span_context_mock: SpanContext = MagicMock()
36+
self.span_data_mock.get_span_context.return_value = self.span_context_mock
37+
self.span_data_mock.attributes = self.attributes_mock
38+
self.attributes_mock.get = MagicMock(return_value=None)
39+
40+
def test_get_ingress_operation_valid_name(self):
41+
valid_name: str = "ValidName"
42+
self.span_data_mock.name = valid_name
43+
self.span_data_mock.kind = SpanKind.SERVER
44+
actual_operation: str = get_ingress_operation(self, self.span_data_mock)
45+
self.assertEqual(actual_operation, valid_name)
46+
47+
def test_get_ingress_operation_with_not_server(self):
48+
valid_name: str = "ValidName"
49+
self.span_data_mock.name = valid_name
50+
self.span_data_mock.kind = SpanKind.CLIENT
51+
actual_operation: str = get_ingress_operation(self, self.span_data_mock)
52+
self.assertEqual(actual_operation, _INTERNAL_OPERATION)
53+
54+
def test_get_ingress_operation_http_method_name_and_no_fallback(self):
55+
invalid_name: str = "GET"
56+
self.span_data_mock.name = invalid_name
57+
self.span_data_mock.kind = SpanKind.SERVER
58+
59+
def attributes_get_side_effect(key):
60+
if key == SpanAttributes.HTTP_METHOD:
61+
return invalid_name
62+
return None
63+
64+
self.attributes_mock.get.side_effect = attributes_get_side_effect
65+
actual_operation: str = get_ingress_operation(self, self.span_data_mock)
66+
self.assertEqual(actual_operation, _UNKNOWN_OPERATION)
67+
68+
def test_get_ingress_operation_null_name_and_no_fallback(self):
69+
invalid_name: str = None
70+
self.span_data_mock.name = invalid_name
71+
self.span_data_mock.kind = SpanKind.SERVER
72+
actual_operation: str = get_ingress_operation(self, self.span_data_mock)
73+
self.assertEqual(actual_operation, _UNKNOWN_OPERATION)
74+
75+
def test_get_ingress_operation_unknown_name_and_no_fallback(self):
76+
invalid_name: str = _UNKNOWN_OPERATION
77+
self.span_data_mock.name = invalid_name
78+
self.span_data_mock.kind = SpanKind.SERVER
79+
actual_operation: str = get_ingress_operation(self, self.span_data_mock)
80+
self.assertEqual(actual_operation, _UNKNOWN_OPERATION)
81+
82+
def test_get_ingress_operation_invalid_name_and_valid_target(self):
83+
invalid_name = None
84+
valid_target: str = "/"
85+
self.span_data_mock.name = invalid_name
86+
self.span_data_mock.kind = SpanKind.SERVER
87+
88+
def attributes_get_side_effect(key):
89+
if key == SpanAttributes.HTTP_TARGET:
90+
return valid_target
91+
return None
92+
93+
self.attributes_mock.get.side_effect = attributes_get_side_effect
94+
actual_operation = get_ingress_operation(self, self.span_data_mock)
95+
self.assertEqual(actual_operation, valid_target)
96+
97+
def test_get_ingress_operation_invalid_name_and_valid_target_and_method(self):
98+
invalid_name = None
99+
valid_target: str = "/"
100+
valid_method: str = "GET"
101+
self.span_data_mock.name = invalid_name
102+
self.span_data_mock.kind = SpanKind.SERVER
103+
104+
def attributes_get_side_effect(key):
105+
if key == SpanAttributes.HTTP_TARGET:
106+
return valid_target
107+
if key == SpanAttributes.HTTP_METHOD:
108+
return valid_method
109+
return None
110+
111+
self.attributes_mock.get.side_effect = attributes_get_side_effect
112+
actual_operation = get_ingress_operation(self, self.span_data_mock)
113+
expected_operation = f"{valid_method} {valid_target}"
114+
self.assertEqual(actual_operation, expected_operation)
115+
116+
def test_get_egress_operation_use_internal_operation(self):
117+
invalid_name = None
118+
self.span_data_mock.name = invalid_name
119+
self.span_data_mock.kind = SpanKind.CONSUMER
120+
121+
actual_operation = get_egress_operation(self.span_data_mock)
122+
self.assertEqual(actual_operation, _INTERNAL_OPERATION)
123+
124+
def test_get_egress_operation_get_local_operation(self):
125+
operation: str = "TestOperation"
126+
127+
def attributes_get_side_effect(key):
128+
if key == AWS_LOCAL_OPERATION:
129+
return operation
130+
return None
131+
132+
self.attributes_mock.get.side_effect = attributes_get_side_effect
133+
self.span_data_mock.kind = SpanKind.SERVER
134+
135+
actual_operation = get_egress_operation(self.span_data_mock)
136+
self.assertEqual(actual_operation, operation)
137+
138+
def test_is_key_present_key_present(self):
139+
def attributes_get_side_effect(key):
140+
if key == SpanAttributes.HTTP_TARGET:
141+
return "target"
142+
return None
143+
144+
self.attributes_mock.get.side_effect = attributes_get_side_effect
145+
self.assertTrue(is_key_present(self.span_data_mock, SpanAttributes.HTTP_TARGET))
146+
147+
def test_is_key_present_key_absent(self):
148+
self.attributes_mock.get.return_value = None
149+
self.assertFalse(is_key_present(self.span_data_mock, "HTTP_TARGET"))
150+
151+
def test_is_aws_span_true(self):
152+
def attributes_get_side_effect(key):
153+
if key == SpanAttributes.RPC_SYSTEM:
154+
return "aws-api"
155+
return None
156+
157+
self.attributes_mock.get.side_effect = attributes_get_side_effect
158+
self.assertTrue(is_aws_sdk_span(self.span_data_mock))
159+
160+
def test_is_aws_span_false(self):
161+
self.attributes_mock.get.return_value = None
162+
self.assertFalse(is_aws_sdk_span(self.span_data_mock))
163+
164+
def test_should_use_internal_operation_false(self):
165+
self.span_data_mock.kind = SpanKind.SERVER
166+
self.assertFalse(should_use_internal_operation(self.span_data_mock))
167+
168+
parent_span_context: SpanContext = MagicMock()
169+
parent_span_context.is_remote = False
170+
parent_span_context.is_valid = True
171+
172+
self.span_data_mock.kind = SpanKind.CONSUMER
173+
self.span_data_mock.parent = parent_span_context
174+
175+
self.assertFalse(should_use_internal_operation(self.span_data_mock))
176+
177+
def test_extract_api_path_value_empty_target(self):
178+
invalid_target = ""
179+
path_value = extract_api_path_value(invalid_target)
180+
self.assertEqual(path_value, _DEFAULT_PATH_VALUE)
181+
182+
def test_extract_api_path_value_null_target(self):
183+
invalid_target = None
184+
path_value = extract_api_path_value(invalid_target)
185+
self.assertEqual(path_value, _DEFAULT_PATH_VALUE)
186+
187+
def test_extract_api_path_value_no_slash(self):
188+
invalid_target = "users"
189+
path_value = extract_api_path_value(invalid_target)
190+
self.assertEqual(path_value, _DEFAULT_PATH_VALUE)
191+
192+
def test_extract_api_path_value_only_slash(self):
193+
invalid_target = "/"
194+
path_value = extract_api_path_value(invalid_target)
195+
self.assertEqual(path_value, _DEFAULT_PATH_VALUE)
196+
197+
def test_extract_api_path_value_only_slash_at_end(self):
198+
invalid_target = "users/"
199+
path_value = extract_api_path_value(invalid_target)
200+
self.assertEqual(path_value, _DEFAULT_PATH_VALUE)
201+
202+
def test_extract_api_path_valid_path(self):
203+
valid_target = "/users/1/pet?query#fragment"
204+
path_value = extract_api_path_value(valid_target)
205+
self.assertEqual(path_value, "/users")
206+
207+
def test_should_generate_service_metric_attributes(self):
208+
parent_span_context: SpanContext = MagicMock()
209+
parent_span_context.is_remote = False
210+
parent_span_context.is_valid = True
211+
self.span_data_mock.parent = parent_span_context
212+
213+
self.span_data_mock.kind = SpanKind.SERVER
214+
self.assertTrue(should_generate_service_metric_attributes(self.span_data_mock))
215+
216+
self.span_data_mock.kind = SpanKind.CONSUMER
217+
self.assertFalse(should_generate_service_metric_attributes(self.span_data_mock))
218+
219+
self.span_data_mock.kind = SpanKind.INTERNAL
220+
self.assertFalse(should_generate_service_metric_attributes(self.span_data_mock))
221+
222+
self.span_data_mock.kind = SpanKind.PRODUCER
223+
self.assertFalse(should_generate_service_metric_attributes(self.span_data_mock))
224+
225+
self.span_data_mock.kind = SpanKind.CLIENT
226+
self.assertFalse(should_generate_service_metric_attributes(self.span_data_mock))
227+
228+
# It's a local root, so should return true
229+
parent_span_context.is_remote = True
230+
self.span_data_mock.kind = SpanKind.PRODUCER
231+
self.span_data_mock.parent_span_context = parent_span_context
232+
self.assertTrue(should_generate_service_metric_attributes(self.span_data_mock))
233+
234+
def test_should_generate_dependency_metric_attributes(self):
235+
self.span_data_mock.kind = SpanKind.SERVER
236+
self.assertFalse(should_generate_dependency_metric_attributes(self.span_data_mock))
237+
238+
self.span_data_mock.kind = SpanKind.INTERNAL
239+
self.assertFalse(should_generate_dependency_metric_attributes(self.span_data_mock))
240+
241+
self.span_data_mock.kind = SpanKind.CONSUMER
242+
self.assertTrue(should_generate_dependency_metric_attributes(self.span_data_mock))
243+
244+
self.span_data_mock.kind = SpanKind.PRODUCER
245+
self.assertTrue(should_generate_dependency_metric_attributes(self.span_data_mock))
246+
247+
self.span_data_mock.kind = SpanKind.CLIENT
248+
self.assertTrue(should_generate_dependency_metric_attributes(self.span_data_mock))
249+
250+
parent_span_context_mock = MagicMock()
251+
parent_span_context_mock.is_valid = True
252+
parent_span_context_mock.is_remote = False
253+
self.span_data_mock.kind = SpanKind.CONSUMER
254+
self.span_data_mock.parent = parent_span_context_mock
255+
256+
def attributes_get_side_effect(key):
257+
if key == SpanAttributes.MESSAGING_OPERATION:
258+
return MessagingOperationValues.PROCESS
259+
if key == AWS_CONSUMER_PARENT_SPAN_KIND:
260+
return SpanKind.CONSUMER
261+
return None
262+
263+
self.attributes_mock.get.side_effect = attributes_get_side_effect
264+
265+
self.assertFalse(should_generate_dependency_metric_attributes(self.span_data_mock))
266+
267+
parent_span_context_mock.is_valid = False
268+
self.assertTrue(should_generate_dependency_metric_attributes(self.span_data_mock))
269+
270+
def test_is_local_root(self):
271+
# Parent Context is empty
272+
self.assertTrue(is_local_root(self.span_data_mock))
273+
274+
parent_span_context = MagicMock()
275+
self.span_data_mock.parent = parent_span_context
276+
277+
parent_span_context.is_remote = False
278+
parent_span_context.is_valid = True
279+
self.assertFalse(is_local_root(self.span_data_mock))
280+
281+
parent_span_context.is_remote = True
282+
parent_span_context.is_valid = True
283+
self.assertTrue(is_local_root(self.span_data_mock))
284+
285+
parent_span_context.is_remote = False
286+
parent_span_context.is_valid = False
287+
self.assertTrue(is_local_root(self.span_data_mock))
288+
289+
parent_span_context.is_remote = True
290+
parent_span_context.is_valid = False
291+
self.assertTrue(is_local_root(self.span_data_mock))
292+
293+
def test_is_consumer_process_span_false(self):
294+
self.assertFalse(is_consumer_process_span(self.span_data_mock))
295+
296+
def test_is_consumer_process_span_true(self):
297+
def attributes_get_side_effect(key):
298+
if key == SpanAttributes.MESSAGING_OPERATION:
299+
return MessagingOperationValues.PROCESS
300+
return None
301+
302+
self.attributes_mock.get.side_effect = attributes_get_side_effect
303+
self.span_data_mock.kind = SpanKind.CONSUMER
304+
305+
self.assertTrue(is_consumer_process_span(self.span_data_mock))
306+
307+
# check that AWS SDK v1 SQS ReceiveMessage consumer spans metrics are suppressed
308+
def test_no_metric_attributes_for_sqs_consumer_span_aws_sdk_v1(self):
309+
instrumentation_scope_mock: InstrumentationScope = MagicMock()
310+
instrumentation_scope_mock.name = "io.opentelemetry.aws-sdk-1.11"
311+
self.span_data_mock.instrumentation_scope = instrumentation_scope_mock
312+
self.span_data_mock.kind = SpanKind.CONSUMER
313+
self.span_data_mock.name = "SQS.ReceiveMessage"
314+
self.assertFalse(should_generate_service_metric_attributes(self.span_data_mock))
315+
self.assertFalse(should_generate_dependency_metric_attributes(self.span_data_mock))
316+
317+
# check that AWS SDK v1 SQS ReceiveMessage consumer spans metrics are suppressed
318+
def test_no_metric_attributes_for_sqs_consumer_span_aws_sdk_v2(self):
319+
instrumentation_scope_mock: InstrumentationScope = MagicMock()
320+
instrumentation_scope_mock.name = "io.opentelemetry.aws-sdk-2.2"
321+
self.span_data_mock.instrumentation_scope = instrumentation_scope_mock
322+
self.span_data_mock.kind = SpanKind.CONSUMER
323+
self.span_data_mock.name = "SQS.ReceiveMessage"
324+
self.assertFalse(should_generate_service_metric_attributes(self.span_data_mock))
325+
self.assertFalse(should_generate_dependency_metric_attributes(self.span_data_mock))
326+
327+
# check that SQS ReceiveMessage consumer spans metrics are still generated for other instrumentation
328+
def test_metric_attributes_generated_for_other_instrumentation_sqs_consumer_span(self):
329+
instrumentation_scope_info_mock = MagicMock()
330+
instrumentation_scope_info_mock.name = "my-instrumentation"
331+
self.span_data_mock.instrumentation_scope = instrumentation_scope_info_mock
332+
self.span_data_mock.kind = SpanKind.CONSUMER
333+
self.span_data_mock.name = "Sqs.ReceiveMessage"
334+
335+
self.assertTrue(should_generate_service_metric_attributes(self.span_data_mock))
336+
self.assertTrue(should_generate_dependency_metric_attributes(self.span_data_mock))
337+
338+
# check that SQS ReceiveMessage consumer span metrics are suppressed if messaging operation
339+
# is process and not receive
340+
def test_no_metric_attributes_for_aws_sdk_sqs_consumer_process_span(self):
341+
instrumentation_scope_info_mock = MagicMock()
342+
instrumentation_scope_info_mock.name = "io.opentelemetry.aws-sdk-2.2"
343+
self.span_data_mock.instrumentation_scope = instrumentation_scope_info_mock
344+
self.span_data_mock.kind = SpanKind.CONSUMER
345+
self.span_data_mock.name = "Sqs.ReceiveMessage"
346+
347+
def attributes_get_side_effect_process(key):
348+
if key == SpanAttributes.MESSAGING_OPERATION:
349+
return MessagingOperationValues.PROCESS
350+
return None
351+
352+
self.attributes_mock.get.side_effect = attributes_get_side_effect_process
353+
self.span_data_mock.attributes = self.attributes_mock
354+
355+
self.assertFalse(should_generate_service_metric_attributes(self.span_data_mock))
356+
self.assertFalse(should_generate_dependency_metric_attributes(self.span_data_mock))
357+
358+
def attributes_get_side_effect_receive(key):
359+
if key == SpanAttributes.MESSAGING_OPERATION:
360+
return MessagingOperationValues.RECEIVE
361+
return None
362+
363+
self.attributes_mock.get.side_effect = attributes_get_side_effect_receive
364+
self.assertTrue(should_generate_service_metric_attributes(self.span_data_mock))
365+
self.assertTrue(should_generate_dependency_metric_attributes(self.span_data_mock))

0 commit comments

Comments
 (0)