From cff32556a2b0a89aaadc51694be198ea80bf46f7 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 2 Mar 2026 00:21:44 -1000 Subject: [PATCH] Add static_asserts and type hints to tests Add compile-time static_asserts for micros_to_millis correctness. Add parameter type hints and return annotations to test functions. --- esphome/core/helpers.h | 4 ++++ tests/unit_tests/test_micros_to_millis.py | 28 +++++++++++------------ 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 72ef66283b..cbb893bf5a 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -636,6 +636,10 @@ template inline constexpr ESPHOME_ALWAYS_INLINE Ret // static_cast(hi) widens to 64-bit when ReturnT=uint64_t, preserving upper bits of hi*q return static_cast(hi) * q + (adj < lo ? (adj + r) / d + q : adj / d); } +static_assert(micros_to_millis(0) == 0); +static_assert(micros_to_millis(999) == 0); +static_assert(micros_to_millis(1000) == 1); +static_assert(micros_to_millis(2592000000000ULL) == 2592000000U); // 30 days /// Return a random 32-bit unsigned integer. uint32_t random_uint32(); diff --git a/tests/unit_tests/test_micros_to_millis.py b/tests/unit_tests/test_micros_to_millis.py index 6659959b87..7e0da9048b 100644 --- a/tests/unit_tests/test_micros_to_millis.py +++ b/tests/unit_tests/test_micros_to_millis.py @@ -76,19 +76,19 @@ UPTIME_VALUES = [ @pytest.mark.parametrize("us", BOUNDARY_VALUES, ids=lambda v: f"us={v}") -def test_32bit_boundary_values(us): +def test_32bit_boundary_values(us: int) -> None: assert micros_to_millis(us) == reference_32(us) @pytest.mark.parametrize("hi", HI_VALUES, ids=lambda v: f"hi={v}") @pytest.mark.parametrize("lo_offset", LO_VALUES, ids=lambda v: f"lo={v}") -def test_32bit_hi_lo_combinations(hi, lo_offset): +def test_32bit_hi_lo_combinations(hi: int, lo_offset: int) -> None: us = (hi << 32) | lo_offset assert micros_to_millis(us) == reference_32(us) @pytest.mark.parametrize("hi", [1, 50, 100, 500, 1000, 5000], ids=lambda v: f"hi={v}") -def test_32bit_carry_boundary(hi): +def test_32bit_carry_boundary(hi: int) -> None: """Test around the adj overflow boundary (hi * R + lo > UINT32_MAX).""" base = hi << 35 hi_r = hi * R @@ -102,11 +102,11 @@ def test_32bit_carry_boundary(hi): @pytest.mark.parametrize( "us", UPTIME_VALUES, ids=["30_days", "1_year", "near_safe_limit"] ) -def test_32bit_realistic_uptimes(us): +def test_32bit_realistic_uptimes(us: int) -> None: assert micros_to_millis(us) == reference_32(us) -def test_32bit_shift_boundary_mod8(): +def test_32bit_shift_boundary_mod8() -> None: """Values where us % 8 varies — exercises the >>3 shift edge.""" for base in [0, 1000, 8000, UINT32_MAX, 603 << 32]: for offset in range(8): @@ -118,19 +118,19 @@ def test_32bit_shift_boundary_mod8(): @pytest.mark.parametrize("us", BOUNDARY_VALUES, ids=lambda v: f"us={v}") -def test_64bit_boundary_values(us): +def test_64bit_boundary_values(us: int) -> None: assert micros_to_millis_64(us) == reference_64(us) @pytest.mark.parametrize("hi", HI_VALUES, ids=lambda v: f"hi={v}") @pytest.mark.parametrize("lo_offset", LO_VALUES, ids=lambda v: f"lo={v}") -def test_64bit_hi_lo_combinations(hi, lo_offset): +def test_64bit_hi_lo_combinations(hi: int, lo_offset: int) -> None: us = (hi << 32) | lo_offset assert micros_to_millis_64(us) == reference_64(us) @pytest.mark.parametrize("hi", [1, 50, 100, 500, 1000, 5000], ids=lambda v: f"hi={v}") -def test_64bit_carry_boundary(hi): +def test_64bit_carry_boundary(hi: int) -> None: """Test around the adj overflow boundary for 64-bit result.""" base = hi << 35 hi_r = hi * R @@ -144,11 +144,11 @@ def test_64bit_carry_boundary(hi): @pytest.mark.parametrize( "us", UPTIME_VALUES, ids=["30_days", "1_year", "near_safe_limit"] ) -def test_64bit_realistic_uptimes(us): +def test_64bit_realistic_uptimes(us: int) -> None: assert micros_to_millis_64(us) == reference_64(us) -def test_64bit_shift_boundary_mod8(): +def test_64bit_shift_boundary_mod8() -> None: """Values where us % 8 varies — exercises the >>3 shift edge.""" for base in [0, 1000, 8000, UINT32_MAX, 603 << 32]: for offset in range(8): @@ -156,7 +156,7 @@ def test_64bit_shift_boundary_mod8(): assert micros_to_millis_64(us) == reference_64(us) -def test_64bit_preserves_upper_bits(): +def test_64bit_preserves_upper_bits() -> None: """Verify 64-bit variant does not truncate large results.""" # 30-day uptime: result > UINT32_MAX us = 2_592_000_000_000 @@ -172,14 +172,14 @@ def test_64bit_preserves_upper_bits(): # --- Shared tests --- -def test_constants_match(): +def test_constants_match() -> None: """Verify the Euclidean decomposition constants are correct.""" assert Q == 34359738 assert R == 46 assert Q * D + R == (1 << 32) -def test_constexpr_values(): +def test_constexpr_values() -> None: """Values suitable for static_assert in C++ if made constexpr.""" assert micros_to_millis(0) == 0 assert micros_to_millis(999) == 0 @@ -190,7 +190,7 @@ def test_constexpr_values(): assert micros_to_millis_64(2_592_000_000_000) == 2_592_000_000 -def test_32bit_and_64bit_agree_when_result_fits(): +def test_32bit_and_64bit_agree_when_result_fits() -> None: """Both variants agree when result fits in 32 bits.""" for us in [0, 1000, 999_999, UINT32_MAX]: assert micros_to_millis(us) == micros_to_millis_64(us)