Skip to content

Commit 5680779

Browse files
test(sdk): add tests for force_flush meaningful bool return (#5020)
1 parent 9e0c780 commit 5680779

File tree

2 files changed

+60
-10
lines changed

2 files changed

+60
-10
lines changed

opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/export/__init__.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
from sys import stdout
2424
from threading import Event, Lock, RLock, Thread
2525
from time import perf_counter, time_ns
26-
from typing import IO, Callable, Iterable, Optional
26+
from typing import IO, Callable, Iterable
2727

2828
from typing_extensions import final
2929

@@ -336,7 +336,7 @@ def __init__(
336336
)
337337

338338
@final
339-
def collect(self, timeout_millis: float = 10_000) -> Optional[bool]:
339+
def collect(self, timeout_millis: float = 10_000) -> bool | None:
340340
"""Collects the metrics from the internal SDK state and
341341
invokes the `_receive_metrics` with the collection.
342342
@@ -387,7 +387,7 @@ def _receive_metrics(
387387
metrics_data: MetricsData,
388388
timeout_millis: float = 10_000,
389389
**kwargs,
390-
) -> bool:
390+
) -> bool | None:
391391
"""Called by `MetricReader.collect` when it receives a batch of metrics.
392392
393393
Subclasses must return ``True`` on success and ``False`` on failure.
@@ -445,7 +445,7 @@ def __init__(
445445

446446
def get_metrics_data(
447447
self,
448-
) -> Optional[MetricsData]:
448+
) -> MetricsData | None:
449449
"""Reads and returns current metrics from the SDK"""
450450
with self._lock:
451451
self.collect()
@@ -480,8 +480,8 @@ class PeriodicExportingMetricReader(MetricReader):
480480
def __init__(
481481
self,
482482
exporter: MetricExporter,
483-
export_interval_millis: Optional[float] = None,
484-
export_timeout_millis: Optional[float] = None,
483+
export_interval_millis: float | None = None,
484+
export_timeout_millis: float | None = None,
485485
) -> None:
486486
# PeriodicExportingMetricReader defers to exporter for configuration
487487
super().__init__(
@@ -608,6 +608,6 @@ def _shutdown():
608608
self._exporter.shutdown(timeout=(deadline_ns - time_ns()) / 10**6)
609609

610610
def force_flush(self, timeout_millis: float = 10_000) -> bool:
611-
collect_ok = super().force_flush(timeout_millis=timeout_millis)
612-
exporter_ok = self._exporter.force_flush(timeout_millis=timeout_millis)
613-
return collect_ok and exporter_ok
611+
if not super().force_flush(timeout_millis=timeout_millis):
612+
return False
613+
return self._exporter.force_flush(timeout_millis=timeout_millis)

opentelemetry-sdk/tests/metrics/test_periodic_exporting_metric_reader.py

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -359,4 +359,54 @@ def test_metric_reader_metrics(self):
359359
assert isinstance(name, str)
360360
self.assertTrue(name.startswith("periodic_metric_reader/"))
361361

362-
mp.shutdown()
362+
mp.shutdown()
363+
364+
def test_force_flush_returns_true_on_success(self):
365+
exporter = FakeMetricsExporter()
366+
pmr = self._create_periodic_reader(metrics, exporter)
367+
result = pmr.force_flush(timeout_millis=5_000)
368+
self.assertTrue(result)
369+
pmr.shutdown()
370+
371+
def test_force_flush_returns_false_on_export_failure(self):
372+
exporter = FakeMetricsExporter()
373+
exporter.export = Mock(return_value=MetricExportResult.FAILURE)
374+
pmr = self._create_periodic_reader(metrics, exporter)
375+
result = pmr.force_flush(timeout_millis=5_000)
376+
self.assertFalse(result)
377+
pmr.shutdown()
378+
379+
def test_force_flush_skips_exporter_flush_when_collect_fails(self):
380+
exporter = FakeMetricsExporter()
381+
exporter.force_flush = Mock(return_value=True)
382+
pmr = PeriodicExportingMetricReader(
383+
exporter, export_interval_millis=math.inf
384+
)
385+
# No collect callback registered → collect returns None → force_flush
386+
# on base treats None as not-False (success), so wire up a failing one
387+
exporter.export = Mock(return_value=MetricExportResult.FAILURE)
388+
389+
def _collect_failure(reader, timeout_millis):
390+
return metrics
391+
392+
pmr._set_collect_callback(_collect_failure)
393+
exporter.export = Mock(return_value=MetricExportResult.FAILURE)
394+
result = pmr.force_flush(timeout_millis=5_000)
395+
self.assertFalse(result)
396+
exporter.force_flush.assert_not_called()
397+
pmr.shutdown()
398+
399+
def test_detach_called_on_export_failure(self):
400+
"""detach(token) must run in finally even when export returns FAILURE."""
401+
from unittest.mock import patch
402+
403+
exporter = FakeMetricsExporter()
404+
exporter.export = Mock(return_value=MetricExportResult.FAILURE)
405+
pmr = self._create_periodic_reader(metrics, exporter)
406+
407+
with patch(
408+
"opentelemetry.sdk.metrics._internal.export.detach"
409+
) as mock_detach:
410+
pmr.force_flush(timeout_millis=5_000)
411+
self.assertTrue(mock_detach.called)
412+
pmr.shutdown()

0 commit comments

Comments
 (0)