Skip to content

Commit 0ee0948

Browse files
committed
improve test coverage
1 parent c3c2aae commit 0ee0948

File tree

1 file changed

+292
-0
lines changed

1 file changed

+292
-0
lines changed
Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,292 @@
1+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
import time
5+
import unittest
6+
from unittest.mock import Mock
7+
8+
from amazon.opentelemetry.distro.exporter.aws.metrics.base_emf_exporter import BaseEmfExporter, MetricRecord
9+
from opentelemetry.sdk.metrics.export import MetricExportResult
10+
from opentelemetry.sdk.resources import Resource
11+
12+
13+
class ConcreteEmfExporter(BaseEmfExporter):
14+
"""Concrete implementation of BaseEmfExporter for testing."""
15+
16+
def __init__(self, *args, **kwargs):
17+
super().__init__(*args, **kwargs)
18+
self.exported_logs = []
19+
20+
def _export(self, log_event):
21+
"""Implementation that stores exported logs for testing."""
22+
self.exported_logs.append(log_event)
23+
24+
def force_flush(self, timeout_millis=None):
25+
"""Force flush implementation for testing."""
26+
return True
27+
28+
def shutdown(self, timeout_millis=None):
29+
"""Shutdown implementation for testing."""
30+
return True
31+
32+
33+
class TestMetricRecord(unittest.TestCase):
34+
"""Test MetricRecord class."""
35+
36+
def test_metric_record_initialization(self):
37+
"""Test MetricRecord initialization with basic values."""
38+
record = MetricRecord("test_metric", "Count", "Test description")
39+
40+
self.assertEqual(record.name, "test_metric")
41+
self.assertEqual(record.unit, "Count")
42+
self.assertEqual(record.description, "Test description")
43+
self.assertIsNone(record.timestamp)
44+
self.assertEqual(record.attributes, {})
45+
self.assertIsNone(record.value)
46+
self.assertIsNone(record.sum_data)
47+
self.assertIsNone(record.histogram_data)
48+
self.assertIsNone(record.exp_histogram_data)
49+
50+
51+
class TestBaseEmfExporter(unittest.TestCase):
52+
"""Test BaseEmfExporter class."""
53+
54+
def setUp(self):
55+
"""Set up test fixtures."""
56+
self.exporter = ConcreteEmfExporter(namespace="TestNamespace")
57+
58+
def test_initialization(self):
59+
"""Test exporter initialization."""
60+
exporter = ConcreteEmfExporter()
61+
self.assertEqual(exporter.namespace, "default")
62+
63+
exporter_custom = ConcreteEmfExporter(namespace="CustomNamespace")
64+
self.assertEqual(exporter_custom.namespace, "CustomNamespace")
65+
66+
def test_get_metric_name(self):
67+
"""Test metric name extraction."""
68+
# Test with valid name
69+
record = Mock()
70+
record.name = "test_metric"
71+
result = self.exporter._get_metric_name(record)
72+
self.assertEqual(result, "test_metric")
73+
74+
# Test with empty name
75+
record.name = ""
76+
result = self.exporter._get_metric_name(record)
77+
self.assertIsNone(result)
78+
79+
# Test with None name
80+
record.name = None
81+
result = self.exporter._get_metric_name(record)
82+
self.assertIsNone(result)
83+
84+
def test_get_unit(self):
85+
"""Test unit mapping functionality."""
86+
# Test EMF supported units (should return as-is)
87+
record = MetricRecord("test", "Count", "desc")
88+
self.assertEqual(self.exporter._get_unit(record), "Count")
89+
90+
record = MetricRecord("test", "Percent", "desc")
91+
self.assertEqual(self.exporter._get_unit(record), "Percent")
92+
93+
# Test OTel unit mapping
94+
record = MetricRecord("test", "ms", "desc")
95+
self.assertEqual(self.exporter._get_unit(record), "Milliseconds")
96+
97+
record = MetricRecord("test", "s", "desc")
98+
self.assertEqual(self.exporter._get_unit(record), "Seconds")
99+
100+
record = MetricRecord("test", "By", "desc")
101+
self.assertEqual(self.exporter._get_unit(record), "Bytes")
102+
103+
# Test units that map to empty string
104+
record = MetricRecord("test", "1", "desc")
105+
self.assertEqual(self.exporter._get_unit(record), "")
106+
107+
# Test unknown unit
108+
record = MetricRecord("test", "unknown", "desc")
109+
self.assertIsNone(self.exporter._get_unit(record))
110+
111+
# Test None unit
112+
record = MetricRecord("test", None, "desc")
113+
self.assertIsNone(self.exporter._get_unit(record))
114+
115+
def test_get_dimension_names(self):
116+
"""Test dimension names extraction."""
117+
attributes = {"service": "test", "env": "prod"}
118+
result = self.exporter._get_dimension_names(attributes)
119+
self.assertEqual(set(result), {"service", "env"})
120+
121+
# Test empty attributes
122+
result = self.exporter._get_dimension_names({})
123+
self.assertEqual(result, [])
124+
125+
def test_get_attributes_key(self):
126+
"""Test attributes key generation."""
127+
attrs1 = {"b": "2", "a": "1"}
128+
attrs2 = {"a": "1", "b": "2"}
129+
130+
key1 = self.exporter._get_attributes_key(attrs1)
131+
key2 = self.exporter._get_attributes_key(attrs2)
132+
133+
# Keys should be consistent regardless of order
134+
self.assertEqual(key1, key2)
135+
self.assertIsInstance(key1, str)
136+
137+
def test_normalize_timestamp(self):
138+
"""Test timestamp normalization."""
139+
timestamp_ns = 1609459200000000000 # nanoseconds
140+
expected_ms = 1609459200000 # milliseconds
141+
142+
result = self.exporter._normalize_timestamp(timestamp_ns)
143+
self.assertEqual(result, expected_ms)
144+
145+
def test_create_metric_record(self):
146+
"""Test metric record creation."""
147+
record = self.exporter._create_metric_record("test_metric", "Count", "Description")
148+
149+
self.assertIsInstance(record, MetricRecord)
150+
self.assertEqual(record.name, "test_metric")
151+
self.assertEqual(record.unit, "Count")
152+
self.assertEqual(record.description, "Description")
153+
154+
def test_convert_gauge_and_sum(self):
155+
"""Test gauge and sum conversion."""
156+
metric = Mock()
157+
metric.name = "test_gauge"
158+
metric.unit = "Count"
159+
metric.description = "Test gauge"
160+
161+
data_point = Mock()
162+
data_point.value = 42.0
163+
data_point.attributes = {"key": "value"}
164+
data_point.time_unix_nano = 1609459200000000000
165+
166+
record = self.exporter._convert_gauge_and_sum(metric, data_point)
167+
168+
self.assertEqual(record.name, "test_gauge")
169+
self.assertEqual(record.value, 42.0)
170+
self.assertEqual(record.attributes, {"key": "value"})
171+
self.assertEqual(record.timestamp, 1609459200000)
172+
173+
def test_convert_histogram(self):
174+
"""Test histogram conversion."""
175+
metric = Mock()
176+
metric.name = "test_histogram"
177+
metric.unit = "ms"
178+
metric.description = "Test histogram"
179+
180+
data_point = Mock()
181+
data_point.count = 5
182+
data_point.sum = 25.0
183+
data_point.min = 1.0
184+
data_point.max = 10.0
185+
data_point.attributes = {"service": "test"}
186+
data_point.time_unix_nano = 1609459200000000000
187+
188+
record = self.exporter._convert_histogram(metric, data_point)
189+
190+
self.assertEqual(record.name, "test_histogram")
191+
expected_data = {"Count": 5, "Sum": 25.0, "Min": 1.0, "Max": 10.0}
192+
self.assertEqual(record.histogram_data, expected_data)
193+
self.assertEqual(record.attributes, {"service": "test"})
194+
195+
def test_convert_exp_histogram(self):
196+
"""Test exponential histogram conversion."""
197+
metric = Mock()
198+
metric.name = "test_exp_histogram"
199+
metric.unit = "s"
200+
metric.description = "Test exponential histogram"
201+
202+
data_point = Mock()
203+
data_point.count = 10
204+
data_point.sum = 50.0
205+
data_point.min = 1.0
206+
data_point.max = 20.0
207+
data_point.scale = 1
208+
data_point.zero_count = 0
209+
data_point.attributes = {"env": "test"}
210+
data_point.time_unix_nano = 1609459200000000000
211+
212+
# Mock buckets
213+
data_point.positive = Mock()
214+
data_point.positive.offset = 0
215+
data_point.positive.bucket_counts = [1, 2, 1]
216+
217+
data_point.negative = Mock()
218+
data_point.negative.offset = 0
219+
data_point.negative.bucket_counts = []
220+
221+
record = self.exporter._convert_exp_histogram(metric, data_point)
222+
223+
self.assertEqual(record.name, "test_exp_histogram")
224+
self.assertIsNotNone(record.exp_histogram_data)
225+
self.assertIn("Values", record.exp_histogram_data)
226+
self.assertIn("Counts", record.exp_histogram_data)
227+
self.assertEqual(record.exp_histogram_data["Count"], 10)
228+
self.assertEqual(record.exp_histogram_data["Sum"], 50.0)
229+
230+
def test_group_by_attributes_and_timestamp(self):
231+
"""Test grouping by attributes and timestamp."""
232+
record = Mock()
233+
record.attributes = {"env": "test"}
234+
record.timestamp = 1234567890
235+
236+
result = self.exporter._group_by_attributes_and_timestamp(record)
237+
238+
self.assertIsInstance(result, tuple)
239+
self.assertEqual(len(result), 2)
240+
self.assertEqual(result[1], 1234567890)
241+
242+
def test_create_emf_log(self):
243+
"""Test EMF log creation."""
244+
# Create a simple metric record
245+
record = self.exporter._create_metric_record("test_metric", "Count", "Test")
246+
record.value = 50.0
247+
record.timestamp = 1234567890
248+
record.attributes = {"env": "test"}
249+
250+
records = [record]
251+
resource = Resource.create({"service.name": "test-service"})
252+
253+
result = self.exporter._create_emf_log(records, resource, 1234567890)
254+
255+
# Check basic EMF structure
256+
self.assertIn("_aws", result)
257+
self.assertIn("CloudWatchMetrics", result["_aws"])
258+
self.assertEqual(result["_aws"]["Timestamp"], 1234567890)
259+
self.assertEqual(result["Version"], "1")
260+
261+
# Check metric value
262+
self.assertEqual(result["test_metric"], 50.0)
263+
264+
# Check resource attributes
265+
self.assertEqual(result["otel.resource.service.name"], "test-service")
266+
267+
# Check CloudWatch metrics
268+
cw_metrics = result["_aws"]["CloudWatchMetrics"][0]
269+
self.assertEqual(cw_metrics["Namespace"], "TestNamespace")
270+
self.assertEqual(cw_metrics["Metrics"][0]["Name"], "test_metric")
271+
272+
def test_export_empty_metrics(self):
273+
"""Test export with empty metrics data."""
274+
metrics_data = Mock()
275+
metrics_data.resource_metrics = []
276+
277+
result = self.exporter.export(metrics_data)
278+
self.assertEqual(result, MetricExportResult.SUCCESS)
279+
280+
def test_export_failure_handling(self):
281+
"""Test export failure handling."""
282+
metrics_data = Mock()
283+
# Make iteration fail
284+
metrics_data.resource_metrics = Mock()
285+
metrics_data.resource_metrics.__iter__ = Mock(side_effect=Exception("Test exception"))
286+
287+
result = self.exporter.export(metrics_data)
288+
self.assertEqual(result, MetricExportResult.FAILURE)
289+
290+
291+
if __name__ == "__main__":
292+
unittest.main()

0 commit comments

Comments
 (0)