mirror of
https://github.com/kuhyx/utils.git
synced 2026-11-10 20:27:15 +01:00
pylint scored the suite at 8.58/10, and 277 of the ~300 findings were missing docstrings -- every one of them in crdt_sync/tests/, none in library code. ruff exempts tests from the docstring rules, so the two linters disagreed and the score sat well below the bar. Rather than silence pylint to match ruff, every test class, test function and stub method now says what it pins. Stub arguments that exist only to match a real signature are underscore-prefixed, which is the documented way to say "deliberately unused" and clears the unused-argument findings too. No behaviour change: 259 tests, still 100% branch coverage. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VCucBhUkHBBHHbsY7bSdGV
122 lines
4.5 KiB
Python
122 lines
4.5 KiB
Python
"""Tests for the Hybrid Logical Clock."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import TYPE_CHECKING
|
|
|
|
import pytest
|
|
|
|
from crdt_sync import Hlc
|
|
|
|
if TYPE_CHECKING:
|
|
from collections.abc import Callable
|
|
|
|
|
|
class TestOrdering:
|
|
"""Ordering."""
|
|
|
|
def test_greater_wall_time_wins(self, make_hlc: Callable[..., Hlc]) -> None:
|
|
"""Greater wall time wins."""
|
|
assert make_hlc(200) > make_hlc(100)
|
|
|
|
def test_equal_wall_time_greater_counter_wins(
|
|
self, make_hlc: Callable[..., Hlc]
|
|
) -> None:
|
|
"""Equal wall time greater counter wins."""
|
|
assert make_hlc(100, counter=2) > make_hlc(100, counter=1)
|
|
|
|
def test_equal_wall_time_and_counter_breaks_tie_on_node_id(
|
|
self, make_hlc: Callable[..., Hlc]
|
|
) -> None:
|
|
"""Equal wall time and counter breaks tie on node id."""
|
|
assert make_hlc(100, counter=1, node_id="b") > make_hlc(
|
|
100, counter=1, node_id="a"
|
|
)
|
|
|
|
def test_identical_clocks_compare_equal(self, make_hlc: Callable[..., Hlc]) -> None:
|
|
"""Identical clocks compare equal."""
|
|
assert make_hlc(100, counter=1, node_id="a") == make_hlc(
|
|
100, counter=1, node_id="a"
|
|
)
|
|
|
|
|
|
class TestNewTick:
|
|
"""New tick."""
|
|
|
|
def test_first_tick_has_counter_zero(self) -> None:
|
|
"""First tick has counter zero."""
|
|
tick = Hlc.new_tick("node-a", wall_time_ms=1000)
|
|
assert tick == Hlc(wall_time_ms=1000, counter=0, node_id="node-a")
|
|
|
|
def test_advancing_wall_clock_resets_counter(
|
|
self, make_hlc: Callable[..., Hlc]
|
|
) -> None:
|
|
"""Advancing wall clock resets counter."""
|
|
previous = make_hlc(1000, counter=5)
|
|
tick = Hlc.new_tick("node-a", previous=previous, wall_time_ms=2000)
|
|
assert tick == Hlc(wall_time_ms=2000, counter=0, node_id="node-a")
|
|
|
|
def test_stalled_wall_clock_increments_counter(
|
|
self, make_hlc: Callable[..., Hlc]
|
|
) -> None:
|
|
"""Stalled wall clock increments counter."""
|
|
previous = make_hlc(1000, counter=5)
|
|
tick = Hlc.new_tick("node-a", previous=previous, wall_time_ms=1000)
|
|
assert tick == Hlc(wall_time_ms=1000, counter=6, node_id="node-a")
|
|
|
|
def test_regressed_wall_clock_still_advances_monotonically(
|
|
self, make_hlc: Callable[..., Hlc]
|
|
) -> None:
|
|
"""Regressed wall clock still advances monotonically."""
|
|
previous = make_hlc(1000, counter=5)
|
|
tick = Hlc.new_tick("node-a", previous=previous, wall_time_ms=500)
|
|
assert tick == Hlc(wall_time_ms=1000, counter=6, node_id="node-a")
|
|
|
|
def test_defaults_to_the_real_clock_when_unset(self) -> None:
|
|
"""Defaults to the real clock when unset."""
|
|
tick = Hlc.new_tick("node-a")
|
|
assert tick.node_id == "node-a"
|
|
assert tick.wall_time_ms > 0
|
|
|
|
|
|
class TestStringRoundTrip:
|
|
"""String round trip."""
|
|
|
|
def test_round_trips_through_to_str_and_from_str(
|
|
self, make_hlc: Callable[..., Hlc]
|
|
) -> None:
|
|
"""Round trips through to str and from str."""
|
|
original = make_hlc(1_751_000_123_456, counter=7, node_id="phone")
|
|
assert Hlc.from_str(original.to_str()) == original
|
|
|
|
def test_to_str_is_lexicographically_sortable_by_wall_time(
|
|
self, make_hlc: Callable[..., Hlc]
|
|
) -> None:
|
|
"""To str is lexicographically sortable by wall time."""
|
|
earlier = make_hlc(1000)
|
|
later = make_hlc(2000)
|
|
assert earlier.to_str() < later.to_str()
|
|
|
|
def test_to_str_is_lexicographically_sortable_by_counter(
|
|
self, make_hlc: Callable[..., Hlc]
|
|
) -> None:
|
|
"""To str is lexicographically sortable by counter."""
|
|
lower = make_hlc(1000, counter=1)
|
|
higher = make_hlc(1000, counter=200)
|
|
assert lower.to_str() < higher.to_str()
|
|
|
|
def test_from_str_rejects_a_missing_z_separator(self) -> None:
|
|
"""From str rejects a missing z separator."""
|
|
with pytest.raises(ValueError, match="not a valid Hlc string"):
|
|
Hlc.from_str("not-a-valid-clock-string-at-all")
|
|
|
|
def test_from_str_rejects_a_wrong_length_iso_prefix(self) -> None:
|
|
"""From str rejects a wrong length iso prefix."""
|
|
with pytest.raises(ValueError, match="not a valid Hlc string"):
|
|
Hlc.from_str("2026-07-05T12:00:00Z-0000-node-a")
|
|
|
|
def test_from_str_rejects_a_missing_node_id_separator(self) -> None:
|
|
"""From str rejects a missing node id separator."""
|
|
with pytest.raises(ValueError, match="not a valid Hlc string"):
|
|
Hlc.from_str("2026-07-05T12:00:00.000Z-0000")
|