|
6 | 6 | from datetime import datetime, timedelta, timezone |
7 | 7 | from typing import assert_never |
8 | 8 |
|
| 9 | +import pytest |
| 10 | + |
9 | 11 | # This is not great, we are depending on an internal pytest API, but it is |
10 | 12 | # the most convenient way to provide a custom approx() comparison for datetime |
11 | 13 | # and timedelta. |
|
14 | 16 | # https://github.com/pytest-dev/pytest/issues/8395 |
15 | 17 | from _pytest.python_api import ApproxBase |
16 | 18 |
|
| 19 | +from frequenz.sdk.timeseries._resampling._wall_clock_timer import ClocksInfo, TickInfo |
| 20 | + |
17 | 21 |
|
18 | 22 | def to_seconds(value: datetime | timedelta | float) -> float: |
19 | 23 | """Convert a datetime, timedelta, or float to seconds.""" |
@@ -87,3 +91,88 @@ def __eq__(self, actual: object) -> bool: |
87 | 91 | return NotImplemented |
88 | 92 |
|
89 | 93 | 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