Skip to content

Commit 9fd6e78

Browse files
committed
Add a utility to compare TickInfo objects approximately
Like pytest.approx(), but compares `TickInfo` objects. It uses an absolute 1ms tolerance by default. Signed-off-by: Leandro Lucarella <[email protected]>
1 parent 01f74c0 commit 9fd6e78

File tree

1 file changed

+89
-0
lines changed
  • tests/timeseries/_resampling/wall_clock_timer

1 file changed

+89
-0
lines changed

tests/timeseries/_resampling/wall_clock_timer/util.py

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
from datetime import datetime, timedelta, timezone
77
from typing import assert_never
88

9+
import pytest
10+
911
# This is not great, we are depending on an internal pytest API, but it is
1012
# the most convenient way to provide a custom approx() comparison for datetime
1113
# and timedelta.
@@ -14,6 +16,8 @@
1416
# https://github.com/pytest-dev/pytest/issues/8395
1517
from _pytest.python_api import ApproxBase
1618

19+
from frequenz.sdk.timeseries._resampling._wall_clock_timer import ClocksInfo, TickInfo
20+
1721

1822
def to_seconds(value: datetime | timedelta | float) -> float:
1923
"""Convert a datetime, timedelta, or float to seconds."""
@@ -87,3 +91,88 @@ def __eq__(self, actual: object) -> bool:
8791
return NotImplemented
8892

8993
return abs(diff) <= self.abs
94+
95+
96+
assert set(ClocksInfo.__dataclass_fields__.keys()) == {
97+
"monotonic_requested_sleep",
98+
"monotonic_time",
99+
"wall_clock_time",
100+
"monotonic_elapsed",
101+
"wall_clock_elapsed",
102+
"wall_clock_factor",
103+
}, "ClocksInfo fields have changed, please update the approx_tick_info function."
104+
105+
assert set(TickInfo.__dataclass_fields__.keys()) == {
106+
"expected_tick_time",
107+
"sleep_infos",
108+
}
109+
110+
111+
def approx_tick_info(
112+
expected: TickInfo,
113+
*,
114+
abs: timedelta = timedelta(milliseconds=1), # pylint: disable=redefined-builtin
115+
) -> TickInfo:
116+
"""Create a copy of a `TickInfo` object with approximate comparisons.
117+
118+
Fields are replaced by approximate comparison objects (`approx_time` or
119+
`pytest.approx`).
120+
121+
This version bypasses `__post_init__` to avoid validation `TypeError`s.
122+
123+
Args:
124+
expected: The expected `TickInfo` object to compare against.
125+
abs: The absolute tolerance as a `timedelta` for all time-based
126+
comparisons. Defaults to 1ms.
127+
128+
Returns:
129+
A new `TickInfo` instance ready for approximate comparison.
130+
"""
131+
abs_s = abs.total_seconds()
132+
approx_sleeps = []
133+
for s_info in expected.sleep_infos:
134+
# HACK: Create a blank instance to bypass __init__ and __post_init__.
135+
# This prevents the TypeError from the validation logic.
136+
approx_s = object.__new__(ClocksInfo)
137+
138+
# Use object.__setattr__ to assign fields to the frozen instance.
139+
object.__setattr__(
140+
approx_s,
141+
"monotonic_requested_sleep",
142+
approx_time(s_info.monotonic_requested_sleep, abs=abs),
143+
)
144+
# Use the standard pytest.approx for float values
145+
object.__setattr__(
146+
approx_s, "monotonic_time", pytest.approx(s_info.monotonic_time, abs=abs_s)
147+
)
148+
object.__setattr__(
149+
approx_s, "wall_clock_time", approx_time(s_info.wall_clock_time, abs=abs)
150+
)
151+
object.__setattr__(
152+
approx_s,
153+
"monotonic_elapsed",
154+
approx_time(s_info.monotonic_elapsed, abs=abs),
155+
)
156+
object.__setattr__(
157+
approx_s,
158+
"wall_clock_elapsed",
159+
approx_time(s_info.wall_clock_elapsed, abs=abs),
160+
)
161+
if s_info.wall_clock_factor is not None:
162+
object.__setattr__(
163+
approx_s,
164+
"wall_clock_factor",
165+
pytest.approx(s_info.wall_clock_factor, abs=abs_s),
166+
)
167+
approx_sleeps.append(approx_s)
168+
169+
# Do the same for the top-level frozen TickInfo object
170+
approx_tick_info = object.__new__(TickInfo)
171+
object.__setattr__(
172+
approx_tick_info,
173+
"expected_tick_time",
174+
approx_time(expected.expected_tick_time, abs=abs),
175+
)
176+
object.__setattr__(approx_tick_info, "sleep_infos", approx_sleeps)
177+
178+
return approx_tick_info

0 commit comments

Comments
 (0)