Skip to content

Commit fd57d3f

Browse files
committed
Add tests for WallClockTimerConfig
Signed-off-by: Leandro Lucarella <[email protected]>
1 parent 6423628 commit fd57d3f

File tree

1 file changed

+187
-0
lines changed

1 file changed

+187
-0
lines changed
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
# License: MIT
2+
# Copyright © 2025 Frequenz Energy-as-a-Service GmbH
3+
4+
"""Tests for the `WallClockTimerConfig` class."""
5+
6+
7+
import math
8+
import re
9+
from datetime import datetime, timedelta, timezone
10+
11+
import pytest
12+
from frequenz.core.datetime import UNIX_EPOCH
13+
14+
from frequenz.sdk.timeseries._resampling._wall_clock_timer import WallClockTimerConfig
15+
16+
17+
def test_from_interval_defaults() -> None:
18+
"""Test WallClockTimerConfig.from_interval() with only interval (all defaults)."""
19+
interval = timedelta(seconds=10)
20+
config = WallClockTimerConfig.from_interval(interval)
21+
assert config.align_to == UNIX_EPOCH
22+
assert config.async_drift_tolerance == pytest.approx(timedelta(seconds=1.0))
23+
assert config.wall_clock_drift_tolerance_factor == pytest.approx(0.1)
24+
assert config.wall_clock_jump_threshold == pytest.approx(timedelta(seconds=10.0))
25+
26+
27+
def test_from_interval_all_args() -> None:
28+
"""Test WallClockTimerConfig.from_interval() with all arguments provided."""
29+
interval = timedelta(seconds=5)
30+
align_to = datetime(2023, 1, 1, tzinfo=timezone.utc)
31+
async_factor = 0.2
32+
wall_factor = 0.3
33+
jump_factor = 0.4
34+
config = WallClockTimerConfig.from_interval(
35+
interval,
36+
align_to=align_to,
37+
async_drift_tolerance_factor=async_factor,
38+
wall_clock_drift_tolerance_factor=wall_factor,
39+
wall_clock_jump_threshold_factor=jump_factor,
40+
)
41+
assert config.align_to == align_to
42+
assert config.async_drift_tolerance == pytest.approx(timedelta(seconds=1.0))
43+
assert config.wall_clock_drift_tolerance_factor == pytest.approx(0.3)
44+
assert config.wall_clock_jump_threshold == pytest.approx(timedelta(seconds=2.0))
45+
46+
47+
@pytest.mark.parametrize(
48+
"interval", [timedelta(seconds=0), timedelta(seconds=-1)], ids=str
49+
)
50+
def test_from_interval_invalid(interval: timedelta) -> None:
51+
"""Test WallClockTimerConfig.from_interval() with invalid interval raises ValueError."""
52+
with pytest.raises(ValueError, match=r"^interval must be bigger than 0, not "):
53+
WallClockTimerConfig.from_interval(interval)
54+
55+
56+
def test_trivial_defaults() -> None:
57+
"""Test that WallClockTimerConfig can be constructed with all defaults."""
58+
config = WallClockTimerConfig()
59+
assert config.align_to == UNIX_EPOCH
60+
assert config.async_drift_tolerance is None
61+
assert config.wall_clock_drift_tolerance_factor is None
62+
assert config.wall_clock_jump_threshold is None
63+
64+
65+
def test_all_valid_arguments() -> None:
66+
"""Test that WallClockTimerConfig can be constructed with all valid arguments."""
67+
align_to = datetime(2024, 1, 1, tzinfo=timezone.utc)
68+
async_drift_tolerance = timedelta(seconds=5)
69+
wall_clock_drift_tolerance_factor = 0.5
70+
wall_clock_jump_threshold = timedelta(seconds=10)
71+
config = WallClockTimerConfig(
72+
align_to=align_to,
73+
async_drift_tolerance=async_drift_tolerance,
74+
wall_clock_drift_tolerance_factor=wall_clock_drift_tolerance_factor,
75+
wall_clock_jump_threshold=wall_clock_jump_threshold,
76+
)
77+
assert config.align_to == align_to
78+
assert config.async_drift_tolerance == async_drift_tolerance
79+
assert config.wall_clock_drift_tolerance_factor == wall_clock_drift_tolerance_factor
80+
assert config.wall_clock_jump_threshold == wall_clock_jump_threshold
81+
82+
83+
@pytest.mark.parametrize(
84+
"align_to",
85+
[None, datetime(2020, 1, 1, tzinfo=timezone.utc)],
86+
ids=str,
87+
)
88+
def test_valid_align_to(align_to: datetime | None) -> None:
89+
"""Test that align_to is accepted and set for valid input."""
90+
config = WallClockTimerConfig(align_to=align_to)
91+
assert config.align_to == align_to
92+
93+
94+
def test_align_to_timezone_unaware() -> None:
95+
"""Test checks on the resampling buffer."""
96+
with pytest.raises(
97+
ValueError, match=r"^align_to (.*) should be a timezone aware datetime$"
98+
):
99+
_ = WallClockTimerConfig(align_to=datetime(2020, 1, 1, tzinfo=None))
100+
101+
102+
_VALID_NUMBERS = [
103+
0.1,
104+
1.0,
105+
None,
106+
]
107+
_VALID_TIMEDELTAS = [
108+
*(timedelta(seconds=v) for v in _VALID_NUMBERS if v is not None),
109+
None,
110+
]
111+
112+
_INVALID_NUMBERS = [
113+
-0.0001,
114+
0.0,
115+
-1,
116+
0,
117+
float("inf"),
118+
float("-inf"),
119+
float("nan"),
120+
]
121+
122+
_INVALID_TIMEDELTAS = [
123+
*(timedelta(seconds=v) for v in _INVALID_NUMBERS if math.isfinite(v)),
124+
]
125+
126+
127+
@pytest.mark.parametrize("value", _VALID_TIMEDELTAS, ids=str)
128+
def test_valid_async_drift_tolerance(value: timedelta | None) -> None:
129+
"""Test that async_drift_tolerance accepts valid values."""
130+
config = WallClockTimerConfig(async_drift_tolerance=value)
131+
assert config.async_drift_tolerance == value
132+
133+
134+
@pytest.mark.parametrize("value", _INVALID_TIMEDELTAS, ids=str)
135+
def test_invalid_async_drift_tolerance(value: timedelta | None) -> None:
136+
"""Test that strictly positive fields reject invalid values (matrix)."""
137+
with pytest.raises(
138+
ValueError,
139+
match=rf"^async_drift_tolerance should be positive or None, not {re.escape(repr(value))}$",
140+
):
141+
_ = WallClockTimerConfig(async_drift_tolerance=value)
142+
143+
144+
@pytest.mark.parametrize("value", _VALID_TIMEDELTAS, ids=str)
145+
def test_valid_wall_clock_jump_threshold(
146+
value: timedelta | None,
147+
) -> None:
148+
"""Test that wall_clock_jump_threshold accepts valid values."""
149+
config = WallClockTimerConfig(wall_clock_jump_threshold=value)
150+
assert config.wall_clock_jump_threshold == value
151+
152+
153+
@pytest.mark.parametrize("value", _INVALID_TIMEDELTAS, ids=str)
154+
def test_invalid_wall_clock_jump_threshold(
155+
value: timedelta | None,
156+
) -> None:
157+
"""Test that strictly positive fields reject invalid values (matrix)."""
158+
with pytest.raises(
159+
ValueError,
160+
match=r"^wall_clock_jump_threshold should be positive or None, not "
161+
+ re.escape(repr(value))
162+
+ r"$",
163+
):
164+
_ = WallClockTimerConfig(wall_clock_jump_threshold=value)
165+
166+
167+
@pytest.mark.parametrize("value", [0.1, 1.0, 1, None])
168+
def test_valid_wall_clock_drift_tolerance_factor(
169+
value: float | None,
170+
) -> None:
171+
"""Test that strictly positive fields accept valid values."""
172+
config = WallClockTimerConfig(wall_clock_drift_tolerance_factor=value)
173+
assert config.wall_clock_drift_tolerance_factor == value
174+
175+
176+
@pytest.mark.parametrize("value", _INVALID_NUMBERS, ids=str)
177+
def test_invalid_wall_clock_drift_tolerance_factor(
178+
value: float | None,
179+
) -> None:
180+
"""Test that strictly positive fields reject invalid values (matrix)."""
181+
with pytest.raises(
182+
ValueError,
183+
match=r"^wall_clock_drift_tolerance_factor should be positive or None, not "
184+
+ re.escape(repr(value))
185+
+ r"$",
186+
):
187+
_ = WallClockTimerConfig(wall_clock_drift_tolerance_factor=value)

0 commit comments

Comments
 (0)