Skip to content

Commit 6e5b145

Browse files
committed
improve test coverage
1 parent 0ee0948 commit 6e5b145

File tree

2 files changed

+265
-7
lines changed

2 files changed

+265
-7
lines changed

aws-opentelemetry-distro/tests/amazon/opentelemetry/distro/exporter/aws/metrics/test_console_emf_exporter.py

Lines changed: 264 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,26 @@
11
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
# SPDX-License-Identifier: Apache-2.0
33

4+
import json
5+
import logging
46
import unittest
5-
from unittest.mock import patch
67
from io import StringIO
8+
from unittest.mock import MagicMock, patch
9+
10+
from opentelemetry.sdk.metrics import Counter, MeterProvider
11+
from opentelemetry.sdk.metrics.export import AggregationTemporality, MetricsData
12+
from opentelemetry.sdk.resources import Resource
713

814
from amazon.opentelemetry.distro.exporter.aws.metrics.console_emf_exporter import ConsoleEmfExporter
915

1016

1117
class TestConsoleEmfExporter(unittest.TestCase):
1218
"""Test ConsoleEmfExporter class."""
1319

20+
def setUp(self):
21+
"""Set up test fixtures."""
22+
self.exporter = ConsoleEmfExporter()
23+
1424
def test_namespace_initialization(self):
1525
"""Test exporter initialization with different namespace scenarios."""
1626
# Test default namespace
@@ -25,10 +35,37 @@ def test_namespace_initialization(self):
2535
exporter = ConsoleEmfExporter(namespace=None)
2636
self.assertEqual(exporter.namespace, "default")
2737

28-
def test_send_log_event(self):
29-
"""Test that log events are properly sent to console output."""
30-
exporter = ConsoleEmfExporter()
38+
# Test empty string namespace (should remain empty)
39+
exporter = ConsoleEmfExporter(namespace="")
40+
self.assertEqual(exporter.namespace, "")
41+
42+
def test_initialization_with_parameters(self):
43+
"""Test exporter initialization with optional parameters."""
44+
# Test with preferred_temporality
45+
preferred_temporality = {Counter: AggregationTemporality.CUMULATIVE}
46+
exporter = ConsoleEmfExporter(
47+
namespace="TestNamespace",
48+
preferred_temporality=preferred_temporality
49+
)
50+
self.assertEqual(exporter.namespace, "TestNamespace")
51+
self.assertEqual(exporter._preferred_temporality[Counter], AggregationTemporality.CUMULATIVE)
3152

53+
# Test with preferred_aggregation
54+
preferred_aggregation = {Counter: "TestAggregation"}
55+
exporter = ConsoleEmfExporter(
56+
preferred_aggregation=preferred_aggregation
57+
)
58+
self.assertEqual(exporter._preferred_aggregation[Counter], "TestAggregation")
59+
60+
# Test with additional kwargs
61+
exporter = ConsoleEmfExporter(
62+
namespace="TestNamespace",
63+
extra_param="ignored" # Should be ignored
64+
)
65+
self.assertEqual(exporter.namespace, "TestNamespace")
66+
67+
def test_export_log_event_success(self):
68+
"""Test that log events are properly sent to console output."""
3269
# Create a simple log event with EMF-formatted message
3370
test_message = (
3471
'{"_aws":{"Timestamp":1640995200000,"CloudWatchMetrics":[{"Namespace":"TestNamespace",'
@@ -39,12 +76,233 @@ def test_send_log_event(self):
3976

4077
# Capture stdout to verify the output
4178
with patch("sys.stdout", new_callable=StringIO) as mock_stdout:
42-
exporter._export(log_event)
79+
self.exporter._export(log_event)
4380

44-
# Verify the message was printed to stdout
81+
# Verify the message was printed to stdout with flush
4582
captured_output = mock_stdout.getvalue().strip()
4683
self.assertEqual(captured_output, test_message)
4784

85+
def test_export_log_event_empty_message(self):
86+
"""Test handling of log events with empty messages."""
87+
log_event = {"message": "", "timestamp": 1640995200000}
88+
89+
with patch("sys.stdout", new_callable=StringIO) as mock_stdout:
90+
with patch("amazon.opentelemetry.distro.exporter.aws.metrics.console_emf_exporter.logger") as mock_logger:
91+
self.exporter._export(log_event)
92+
93+
# Should not print anything for empty message
94+
captured_output = mock_stdout.getvalue().strip()
95+
self.assertEqual(captured_output, "")
96+
97+
# Should log a warning
98+
mock_logger.warning.assert_called_once()
99+
100+
def test_export_log_event_missing_message(self):
101+
"""Test handling of log events without message key."""
102+
log_event = {"timestamp": 1640995200000}
103+
104+
with patch("sys.stdout", new_callable=StringIO) as mock_stdout:
105+
with patch("amazon.opentelemetry.distro.exporter.aws.metrics.console_emf_exporter.logger") as mock_logger:
106+
self.exporter._export(log_event)
107+
108+
# Should not print anything when message is missing
109+
captured_output = mock_stdout.getvalue().strip()
110+
self.assertEqual(captured_output, "")
111+
112+
# Should log a warning
113+
mock_logger.warning.assert_called_once()
114+
115+
def test_export_log_event_with_none_message(self):
116+
"""Test handling of log events with None message."""
117+
log_event = {"message": None, "timestamp": 1640995200000}
118+
119+
with patch("sys.stdout", new_callable=StringIO) as mock_stdout:
120+
with patch("amazon.opentelemetry.distro.exporter.aws.metrics.console_emf_exporter.logger") as mock_logger:
121+
self.exporter._export(log_event)
122+
123+
# Should not print anything when message is None
124+
captured_output = mock_stdout.getvalue().strip()
125+
self.assertEqual(captured_output, "")
126+
127+
# Should log a warning
128+
mock_logger.warning.assert_called_once()
129+
130+
def test_export_log_event_print_exception(self):
131+
"""Test error handling when print() raises an exception."""
132+
log_event = {"message": "test message", "timestamp": 1640995200000}
133+
134+
with patch("builtins.print", side_effect=Exception("Print failed")):
135+
with patch("amazon.opentelemetry.distro.exporter.aws.metrics.console_emf_exporter.logger") as mock_logger:
136+
self.exporter._export(log_event)
137+
138+
# Should log the error
139+
mock_logger.error.assert_called_once()
140+
args = mock_logger.error.call_args[0]
141+
self.assertIn("Failed to write EMF log to console", args[0])
142+
self.assertEqual(args[1], log_event)
143+
self.assertIn("Print failed", str(args[2]))
144+
145+
def test_export_log_event_various_message_types(self):
146+
"""Test _export with various message types."""
147+
# Test with JSON string
148+
json_message = '{"key": "value"}'
149+
log_event = {"message": json_message, "timestamp": 1640995200000}
150+
151+
with patch("sys.stdout", new_callable=StringIO) as mock_stdout:
152+
self.exporter._export(log_event)
153+
154+
captured_output = mock_stdout.getvalue().strip()
155+
self.assertEqual(captured_output, json_message)
156+
157+
# Test with plain string
158+
plain_message = "Simple log message"
159+
log_event = {"message": plain_message, "timestamp": 1640995200000}
160+
161+
with patch("sys.stdout", new_callable=StringIO) as mock_stdout:
162+
self.exporter._export(log_event)
163+
164+
captured_output = mock_stdout.getvalue().strip()
165+
self.assertEqual(captured_output, plain_message)
166+
167+
def test_force_flush(self):
168+
"""Test force_flush method."""
169+
with patch("amazon.opentelemetry.distro.exporter.aws.metrics.console_emf_exporter.logger") as mock_logger:
170+
# Test with default timeout
171+
result = self.exporter.force_flush()
172+
self.assertTrue(result)
173+
mock_logger.debug.assert_called_once()
174+
175+
# Reset mock for next call
176+
mock_logger.reset_mock()
177+
178+
# Test with custom timeout
179+
result = self.exporter.force_flush(timeout_millis=5000)
180+
self.assertTrue(result)
181+
mock_logger.debug.assert_called_once()
182+
183+
def test_shutdown(self):
184+
"""Test shutdown method."""
185+
with patch("amazon.opentelemetry.distro.exporter.aws.metrics.console_emf_exporter.logger") as mock_logger:
186+
# Test with no timeout
187+
result = self.exporter.shutdown()
188+
self.assertTrue(result)
189+
mock_logger.debug.assert_called_once_with(
190+
"ConsoleEmfExporter shutdown called with timeout_millis=%s", None
191+
)
192+
193+
# Reset mock for next call
194+
mock_logger.reset_mock()
195+
196+
# Test with timeout
197+
result = self.exporter.shutdown(timeout_millis=3000)
198+
self.assertTrue(result)
199+
mock_logger.debug.assert_called_once_with(
200+
"ConsoleEmfExporter shutdown called with timeout_millis=%s", 3000
201+
)
202+
203+
# Reset mock for next call
204+
mock_logger.reset_mock()
205+
206+
# Test with additional kwargs
207+
result = self.exporter.shutdown(timeout_millis=3000, extra_arg="ignored")
208+
self.assertTrue(result)
209+
mock_logger.debug.assert_called_once()
210+
211+
def test_integration_with_metrics_data(self):
212+
"""Test the full export flow with actual MetricsData."""
213+
# Create a mock MetricsData
214+
mock_metrics_data = MagicMock(spec=MetricsData)
215+
mock_resource_metrics = MagicMock()
216+
mock_scope_metrics = MagicMock()
217+
mock_metric = MagicMock()
218+
219+
# Set up the mock hierarchy
220+
mock_metrics_data.resource_metrics = [mock_resource_metrics]
221+
mock_resource_metrics.scope_metrics = [mock_scope_metrics]
222+
mock_scope_metrics.metrics = [mock_metric]
223+
224+
# Mock the metric to have no data_points to avoid complex setup
225+
mock_metric.data = None
226+
227+
with patch("sys.stdout", new_callable=StringIO):
228+
result = self.exporter.export(mock_metrics_data)
229+
230+
# Should succeed even with no actual metrics
231+
from opentelemetry.sdk.metrics.export import MetricExportResult
232+
self.assertEqual(result, MetricExportResult.SUCCESS)
233+
234+
def test_integration_export_success(self):
235+
"""Test export method returns success."""
236+
# Create empty MetricsData
237+
mock_metrics_data = MagicMock(spec=MetricsData)
238+
mock_metrics_data.resource_metrics = []
239+
240+
from opentelemetry.sdk.metrics.export import MetricExportResult
241+
result = self.exporter.export(mock_metrics_data)
242+
self.assertEqual(result, MetricExportResult.SUCCESS)
243+
244+
def test_integration_export_with_timeout(self):
245+
"""Test export method with timeout parameter."""
246+
mock_metrics_data = MagicMock(spec=MetricsData)
247+
mock_metrics_data.resource_metrics = []
248+
249+
from opentelemetry.sdk.metrics.export import MetricExportResult
250+
result = self.exporter.export(mock_metrics_data, timeout_millis=5000)
251+
self.assertEqual(result, MetricExportResult.SUCCESS)
252+
253+
def test_export_failure_handling(self):
254+
"""Test export method handles exceptions and returns failure."""
255+
# Create a mock that raises an exception
256+
mock_metrics_data = MagicMock(spec=MetricsData)
257+
mock_metrics_data.resource_metrics = [MagicMock()]
258+
259+
# Make the resource_metrics access raise an exception
260+
type(mock_metrics_data).resource_metrics = property(
261+
lambda self: (_ for _ in ()).throw(Exception("Test exception"))
262+
)
263+
264+
# Patch the logger in the base_emf_exporter since that's where the error logging happens
265+
with patch("amazon.opentelemetry.distro.exporter.aws.metrics.base_emf_exporter.logger") as mock_logger:
266+
from opentelemetry.sdk.metrics.export import MetricExportResult
267+
result = self.exporter.export(mock_metrics_data)
268+
269+
self.assertEqual(result, MetricExportResult.FAILURE)
270+
mock_logger.error.assert_called_once()
271+
self.assertIn("Failed to export metrics", mock_logger.error.call_args[0][0])
272+
273+
def test_flush_output_verification(self):
274+
"""Test that print is called with flush=True."""
275+
log_event = {"message": "test message", "timestamp": 1640995200000}
276+
277+
with patch("builtins.print") as mock_print:
278+
self.exporter._export(log_event)
279+
280+
# Verify print was called with flush=True
281+
mock_print.assert_called_once_with("test message", flush=True)
282+
283+
def test_logger_levels(self):
284+
"""Test that appropriate log levels are used."""
285+
# Test debug logging in force_flush
286+
with patch("amazon.opentelemetry.distro.exporter.aws.metrics.console_emf_exporter.logger") as mock_logger:
287+
self.exporter.force_flush()
288+
mock_logger.debug.assert_called_once()
289+
290+
# Test debug logging in shutdown
291+
with patch("amazon.opentelemetry.distro.exporter.aws.metrics.console_emf_exporter.logger") as mock_logger:
292+
self.exporter.shutdown()
293+
mock_logger.debug.assert_called_once()
294+
295+
# Test warning logging for empty message
296+
with patch("amazon.opentelemetry.distro.exporter.aws.metrics.console_emf_exporter.logger") as mock_logger:
297+
self.exporter._export({"message": "", "timestamp": 123})
298+
mock_logger.warning.assert_called_once()
299+
300+
# Test error logging for exception
301+
with patch("builtins.print", side_effect=Exception("Test")):
302+
with patch("amazon.opentelemetry.distro.exporter.aws.metrics.console_emf_exporter.logger") as mock_logger:
303+
self.exporter._export({"message": "test", "timestamp": 123})
304+
mock_logger.error.assert_called_once()
305+
48306

49307
if __name__ == "__main__":
50308
unittest.main()

aws-opentelemetry-distro/tests/amazon/opentelemetry/distro/test_aws_opentelementry_configurator.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,12 @@
3838
_customize_span_processors,
3939
_export_unsampled_span_for_agent_observability,
4040
_export_unsampled_span_for_lambda,
41+
_fetch_logs_header,
4142
_init_logging,
4243
_is_application_signals_enabled,
4344
_is_application_signals_runtime_enabled,
4445
_is_defer_to_workers_enabled,
4546
_is_wsgi_master_process,
46-
_fetch_logs_header,
4747
)
4848
from amazon.opentelemetry.distro.aws_opentelemetry_distro import AwsOpenTelemetryDistro
4949
from amazon.opentelemetry.distro.aws_span_metrics_processor import AwsSpanMetricsProcessor

0 commit comments

Comments
 (0)