import typing as typ
import pytest
from hypothesis import given
from hypothesis import strategies as st
from nextest_budgets import largest_test_allowance
from timeout_budgets import seconds
_EXACT_UNITS: typ.Final[dict[str, int]] = {
"s": 1,
"sec": 1,
"seconds": 1,
"m": 60,
"min": 60,
"minutes": 60,
"h": 3600,
"hr": 3600,
"hours": 3600,
"d": 86400,
"day": 86400,
"w": 604800,
"wk": 604800,
"M": 2630016,
"y": 31557600,
"yr": 31557600,
}
_COMPONENTS: typ.Final[st.SearchStrategy[tuple[int, str]]] = st.tuples(
st.integers(min_value=0, max_value=999),
st.sampled_from(sorted(_EXACT_UNITS)),
)
def _profile(*lines: str) -> str:
body = "\n".join(lines)
return f"[profile.default]\n{body}\n"
@given(
components=st.lists(_COMPONENTS, min_size=1, max_size=6),
separators=st.lists(st.sampled_from(["", " ", " "]), min_size=6, max_size=6),
)
def test_a_multi_component_duration_reads_as_the_sum_of_its_components(
components: list[tuple[int, str]],
separators: list[str],
) -> None:
rendered = "".join(
f"{value}{unit}{separators[index % len(separators)]}"
for index, (value, unit) in enumerate(components)
)
expected = sum(value * _EXACT_UNITS[unit] for value, unit in components)
assert seconds(rendered) == pytest.approx(float(expected)), (
f"{rendered!r} must read as {expected} seconds"
)
@given(
period=st.integers(min_value=1, max_value=86_400),
multiplier=st.integers(min_value=1, max_value=99),
)
def test_a_per_test_budget_is_the_period_times_the_terminate_after(
period: int,
multiplier: int,
) -> None:
config = _profile(
f'slow-timeout = {{ period = "{period}s", terminate-after = {multiplier} }}'
)
assert largest_test_allowance(config) == pytest.approx(
float(period * multiplier)
), f"a {period}s period terminating after {multiplier} bounds a test at their product"