From 1d2c0fe2d01779369afa44501f93615688d98bdb Mon Sep 17 00:00:00 2001 From: Adrien Barbaresi Date: Fri, 10 Jan 2025 16:57:43 +0100 Subject: [PATCH 1/4] timer: use import submodules only and add tests --- lib/timer.py | 58 +++++++++++++------------- test_bot/test_timer.py | 95 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 125 insertions(+), 28 deletions(-) create mode 100644 test_bot/test_timer.py diff --git a/lib/timer.py b/lib/timer.py index 1f220052e..c16a8c739 100644 --- a/lib/timer.py +++ b/lib/timer.py @@ -1,60 +1,62 @@ """A timer for use in lichess-bot.""" -import time -import datetime + +from datetime import datetime, timedelta +from time import perf_counter from typing import Optional -def msec(time_in_msec: float) -> datetime.timedelta: +def msec(time_in_msec: float) -> timedelta: """Create a timedelta duration in milliseconds.""" - return datetime.timedelta(milliseconds=time_in_msec) + return timedelta(milliseconds=time_in_msec) -def to_msec(duration: datetime.timedelta) -> float: +def to_msec(duration: timedelta) -> float: """Return a bare number representing the length of the duration in milliseconds.""" return duration / msec(1) -def msec_str(duration: datetime.timedelta) -> str: +def msec_str(duration: timedelta) -> str: """Return a string with the duration value in whole number milliseconds.""" return str(round(to_msec(duration))) -def seconds(time_in_sec: float) -> datetime.timedelta: +def seconds(time_in_sec: float) -> timedelta: """Create a timedelta duration in seconds.""" - return datetime.timedelta(seconds=time_in_sec) + return timedelta(seconds=time_in_sec) -def to_seconds(duration: datetime.timedelta) -> float: +def to_seconds(duration: timedelta) -> float: """Return a bare number representing the length of the duration in seconds.""" return duration.total_seconds() -def sec_str(duration: datetime.timedelta) -> str: +def sec_str(duration: timedelta) -> str: """Return a string with the duration value in whole number seconds.""" return str(round(to_seconds(duration))) -def minutes(time_in_minutes: float) -> datetime.timedelta: +def minutes(time_in_minutes: float) -> timedelta: """Create a timedelta duration in minutes.""" - return datetime.timedelta(minutes=time_in_minutes) + return timedelta(minutes=time_in_minutes) -def hours(time_in_hours: float) -> datetime.timedelta: +def hours(time_in_hours: float) -> timedelta: """Create a timedelta duration in hours.""" - return datetime.timedelta(hours=time_in_hours) + return timedelta(hours=time_in_hours) -def days(time_in_days: float) -> datetime.timedelta: +def days(time_in_days: float) -> timedelta: """Create a timedelta duration in days.""" - return datetime.timedelta(days=time_in_days) + return timedelta(days=time_in_days) -def years(time_in_years: float) -> datetime.timedelta: +def years(time_in_years: float) -> timedelta: """Create a timedelta duration in median years--i.e., 365 days.""" return days(365) * time_in_years class Timer: + __slots__ = ["duration", "starting_time"] """ A timer for use in lichess-bot. An instance of timer can be used both as a countdown timer and a stopwatch. @@ -68,8 +70,8 @@ class Timer: the timer was created or since it was last reset. """ - def __init__(self, duration: datetime.timedelta = seconds(0), - backdated_timestamp: Optional[datetime.datetime] = None) -> None: + def __init__(self, duration: timedelta = seconds(0), + backdated_timestamp: Optional[datetime] = None) -> None: """ Start the timer. @@ -77,10 +79,10 @@ def __init__(self, duration: datetime.timedelta = seconds(0), :param backdated_timestamp: When the timer should have started. Used to keep the timers between sessions. """ self.duration = duration - self.reset() - if backdated_timestamp is not None: - time_already_used = datetime.datetime.now() - backdated_timestamp - self.starting_time -= to_seconds(time_already_used) + self.starting_time = perf_counter() + + if backdated_timestamp: + self.starting_time -= to_seconds(datetime.now() - backdated_timestamp) def is_expired(self) -> bool: """Check if a timer is expired.""" @@ -88,16 +90,16 @@ def is_expired(self) -> bool: def reset(self) -> None: """Reset the timer.""" - self.starting_time = time.perf_counter() + self.starting_time = perf_counter() - def time_since_reset(self) -> datetime.timedelta: + def time_since_reset(self) -> timedelta: """How much time has passed.""" - return seconds(time.perf_counter() - self.starting_time) + return seconds(perf_counter() - self.starting_time) - def time_until_expiration(self) -> datetime.timedelta: + def time_until_expiration(self) -> timedelta: """How much time is left until it expires.""" return max(seconds(0), self.duration - self.time_since_reset()) def starting_timestamp(self, timestamp_format: str) -> str: """When the timer started.""" - return (datetime.datetime.now() - self.time_since_reset()).strftime(timestamp_format) + return (datetime.now() - self.time_since_reset()).strftime(timestamp_format) diff --git a/test_bot/test_timer.py b/test_bot/test_timer.py new file mode 100644 index 000000000..b405044d4 --- /dev/null +++ b/test_bot/test_timer.py @@ -0,0 +1,95 @@ +from datetime import datetime, timedelta + +from lib import timer + + +def test_time_conversion(): + assert timer.msec(1000) == timedelta(milliseconds=1000) + assert timer.to_msec(timedelta(milliseconds=1000)) == 1000.0 + + assert timer.msec_str(timedelta(milliseconds=1000)) == "1000" + + assert timer.seconds(1) == timedelta(seconds=1) + assert timer.to_seconds(timedelta(seconds=1)) == 1.0 + + assert timer.sec_str(timedelta(seconds=1)) == "1" + + assert timer.minutes(1) == timedelta(minutes=1) + assert timer.hours(1) == timedelta(hours=1) + assert timer.days(1) == timedelta(days=1) + assert timer.years(1) == timedelta(days=365) + + assert timer.to_msec(timer.seconds(1)) == 1000.0 + assert timer.to_seconds(timer.minutes(1)) == 60.0 + assert timer.to_seconds(timer.hours(1)) == 3600.0 + assert timer.to_seconds(timer.days(1)) == 86400.0 + assert timer.to_seconds(timer.years(1)) == 31536000.0 + + +def test_init_default(): + t = timer.Timer() + assert t.duration == timedelta(0) + assert t.starting_time is not None + +def test_init_custom_duration(): + duration = timedelta(seconds=10) + t = timer.Timer(duration) + assert t.duration == duration + assert t.starting_time is not None + +def test_init_backdated_timestamp(): + backdated_timestamp = datetime.now() - timedelta(seconds=10) + t = timer.Timer(backdated_timestamp=backdated_timestamp) + assert t.starting_time is not None + assert t.time_since_reset() >= timedelta(seconds=10) + +def test_is_expired_not_expired(): + t = timer.Timer(timedelta(seconds=10)) + assert not t.is_expired() + +def test_is_expired_expired(): + t = timer.Timer(timedelta(seconds=0)) + assert t.is_expired() + +def test_is_expired_expired_after_reset(): + t = timer.Timer(timedelta(seconds=10)) + t.reset() + t.starting_time -= 10 + assert t.is_expired() + +def test_reset(): + t = timer.Timer(timedelta(seconds=10)) + t.reset() + assert t.starting_time is not None + assert timer.sec_str(t.time_since_reset()) == timer.sec_str(timedelta(0)) + +def test_time_since_reset(): + t = timer.Timer(timedelta(seconds=10)) + t.starting_time -= 5 + assert timer.sec_str(t.time_since_reset()) == timer.sec_str(timedelta(seconds=5)) + +def test_time_until_expiration(): + t = timer.Timer(timedelta(seconds=10)) + t.starting_time -= 5 + assert timer.sec_str(t.time_until_expiration()) == timer.sec_str(timedelta(seconds=5)) + +def test_time_until_expiration_expired(): + t = timer.Timer(timedelta(seconds=10)) + t.starting_time -= 15 # Simulate time passing + assert t.time_until_expiration() == timedelta(0) + +def test_starting_timestamp(): + t = timer.Timer(timedelta(seconds=10)) + timestamp_format = "%Y-%m-%d %H:%M:%S" + expected_timestamp = (datetime.now() - t.time_since_reset()).strftime(timestamp_format) + assert t.starting_timestamp(timestamp_format) == expected_timestamp + +def test_time_until_expiration_max_zero(): + t = timer.Timer(timedelta(seconds=10)) + t.starting_time -= 15 + assert t.time_until_expiration() == timedelta(0) + +def test_time_until_expiration_max_positive(): + t = timer.Timer(timedelta(seconds=10)) + t.starting_time -= 5 + assert timer.sec_str(t.time_until_expiration()) == timer.sec_str(timedelta(seconds=5)) From 0af98c3837f700adf2bfe4f3b2bb5530a0511dd4 Mon Sep 17 00:00:00 2001 From: Adrien Barbaresi Date: Fri, 10 Jan 2025 17:10:41 +0100 Subject: [PATCH 2/4] address ruff errors --- test_bot/test_timer.py | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/test_bot/test_timer.py b/test_bot/test_timer.py index b405044d4..447d0c12a 100644 --- a/test_bot/test_timer.py +++ b/test_bot/test_timer.py @@ -1,9 +1,12 @@ +"""Test functions dedicated to time measurement and conversion.""" + from datetime import datetime, timedelta from lib import timer -def test_time_conversion(): +def test_time_conversion() -> None: + """Test conversion of time units.""" assert timer.msec(1000) == timedelta(milliseconds=1000) assert timer.to_msec(timedelta(milliseconds=1000)) == 1000.0 @@ -26,70 +29,67 @@ def test_time_conversion(): assert timer.to_seconds(timer.years(1)) == 31536000.0 -def test_init_default(): +def test_init() -> None: + """Test Timer class init.""" t = timer.Timer() assert t.duration == timedelta(0) assert t.starting_time is not None -def test_init_custom_duration(): duration = timedelta(seconds=10) t = timer.Timer(duration) assert t.duration == duration assert t.starting_time is not None -def test_init_backdated_timestamp(): backdated_timestamp = datetime.now() - timedelta(seconds=10) t = timer.Timer(backdated_timestamp=backdated_timestamp) assert t.starting_time is not None assert t.time_since_reset() >= timedelta(seconds=10) -def test_is_expired_not_expired(): +def test_is_expired() -> None: + """Test timer expiration.""" t = timer.Timer(timedelta(seconds=10)) assert not t.is_expired() -def test_is_expired_expired(): t = timer.Timer(timedelta(seconds=0)) assert t.is_expired() -def test_is_expired_expired_after_reset(): t = timer.Timer(timedelta(seconds=10)) t.reset() t.starting_time -= 10 assert t.is_expired() -def test_reset(): +def test_reset() -> None: + """Test timer reset.""" t = timer.Timer(timedelta(seconds=10)) t.reset() assert t.starting_time is not None assert timer.sec_str(t.time_since_reset()) == timer.sec_str(timedelta(0)) -def test_time_since_reset(): +def test_time() -> None: + """Test time measurement, expiration, and time until expiration.""" t = timer.Timer(timedelta(seconds=10)) t.starting_time -= 5 assert timer.sec_str(t.time_since_reset()) == timer.sec_str(timedelta(seconds=5)) -def test_time_until_expiration(): t = timer.Timer(timedelta(seconds=10)) t.starting_time -= 5 assert timer.sec_str(t.time_until_expiration()) == timer.sec_str(timedelta(seconds=5)) -def test_time_until_expiration_expired(): t = timer.Timer(timedelta(seconds=10)) t.starting_time -= 15 # Simulate time passing assert t.time_until_expiration() == timedelta(0) -def test_starting_timestamp(): - t = timer.Timer(timedelta(seconds=10)) - timestamp_format = "%Y-%m-%d %H:%M:%S" - expected_timestamp = (datetime.now() - t.time_since_reset()).strftime(timestamp_format) - assert t.starting_timestamp(timestamp_format) == expected_timestamp - -def test_time_until_expiration_max_zero(): t = timer.Timer(timedelta(seconds=10)) t.starting_time -= 15 assert t.time_until_expiration() == timedelta(0) -def test_time_until_expiration_max_positive(): t = timer.Timer(timedelta(seconds=10)) t.starting_time -= 5 assert timer.sec_str(t.time_until_expiration()) == timer.sec_str(timedelta(seconds=5)) + +def test_starting_timestamp() -> None: + """Test timestamp conversion and integration.""" + t = timer.Timer(timedelta(seconds=10)) + timestamp_format = "%Y-%m-%d %H:%M:%S" + expected_timestamp = (datetime.now() - t.time_since_reset()).strftime(timestamp_format) + assert t.starting_timestamp(timestamp_format) == expected_timestamp From 6f423a718abacb48f36e0b42efad5a64d8c46617 Mon Sep 17 00:00:00 2001 From: Adrien Barbaresi Date: Sat, 11 Jan 2025 11:26:34 +0100 Subject: [PATCH 3/4] fix ruff error --- lib/timer.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/timer.py b/lib/timer.py index c16a8c739..62f4080f1 100644 --- a/lib/timer.py +++ b/lib/timer.py @@ -56,7 +56,6 @@ def years(time_in_years: float) -> timedelta: class Timer: - __slots__ = ["duration", "starting_time"] """ A timer for use in lichess-bot. An instance of timer can be used both as a countdown timer and a stopwatch. @@ -70,6 +69,8 @@ class Timer: the timer was created or since it was last reset. """ + __slots__ = ["duration", "starting_time"] + def __init__(self, duration: timedelta = seconds(0), backdated_timestamp: Optional[datetime] = None) -> None: """ From 047681b7a02fc8e8a68abfcd8b68751a949da99d Mon Sep 17 00:00:00 2001 From: Adrien Barbaresi Date: Sun, 12 Jan 2025 12:16:22 +0100 Subject: [PATCH 4/4] add recognizable numbers in tests --- test_bot/test_timer.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/test_bot/test_timer.py b/test_bot/test_timer.py index 447d0c12a..576180ebf 100644 --- a/test_bot/test_timer.py +++ b/test_bot/test_timer.py @@ -8,12 +8,12 @@ def test_time_conversion() -> None: """Test conversion of time units.""" assert timer.msec(1000) == timedelta(milliseconds=1000) - assert timer.to_msec(timedelta(milliseconds=1000)) == 1000.0 + assert timer.to_msec(timedelta(milliseconds=1000)) == 1000 assert timer.msec_str(timedelta(milliseconds=1000)) == "1000" assert timer.seconds(1) == timedelta(seconds=1) - assert timer.to_seconds(timedelta(seconds=1)) == 1.0 + assert timer.to_seconds(timedelta(seconds=1)) == 1 assert timer.sec_str(timedelta(seconds=1)) == "1" @@ -22,11 +22,11 @@ def test_time_conversion() -> None: assert timer.days(1) == timedelta(days=1) assert timer.years(1) == timedelta(days=365) - assert timer.to_msec(timer.seconds(1)) == 1000.0 - assert timer.to_seconds(timer.minutes(1)) == 60.0 - assert timer.to_seconds(timer.hours(1)) == 3600.0 - assert timer.to_seconds(timer.days(1)) == 86400.0 - assert timer.to_seconds(timer.years(1)) == 31536000.0 + assert timer.to_msec(timer.seconds(1)) == 1000 + assert timer.to_seconds(timer.minutes(1)) == 60 + assert timer.to_seconds(timer.hours(1)) == 60*60 + assert timer.to_seconds(timer.days(1)) == 24*60*60 + assert timer.to_seconds(timer.years(1)) == 365*24*60*60 def test_init() -> None: