Skip to content

Commit 9875b8c

Browse files
Fixed docstrings, typos and added test cases (#1176)
* Fixed docstrings, typos and added test cases * rollback printing out exception message in log line
1 parent 01ca6fc commit 9875b8c

5 files changed

Lines changed: 121 additions & 5 deletions

File tree

lib/engine_wrapper.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ def create_engine(engine_config: Configuration, game: model.Game | None = None)
3939
Use in a with-block to automatically close the engine when exiting the game.
4040
4141
:param engine_config: The options for the engine.
42+
:param game: The game to create the engine from.
4243
:return: An engine. Either UCI, XBoard, or Homemade.
4344
"""
4445
cfg = engine_config.engine

lib/lichess_bot.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ def do_correspondence_ping(control_queue: CONTROL_QUEUE_TYPE, period: datetime.t
136136
"""
137137
Tell the engine to check the correspondence games.
138138
139+
:param control_queue: Queue to put events in.
139140
:param period: How many seconds to wait before sending a correspondence ping.
140141
"""
141142
while not stop.terminated:
@@ -167,7 +168,7 @@ def logging_configurer(level: int, filename: str | None, disable_auto_logs: bool
167168
168169
:param level: The logging level. Either `logging.INFO` or `logging.DEBUG`.
169170
:param filename: The filename to write the logs to. If it is `None` then the logs aren't written to a file.
170-
:param auto_log_filename: The filename for the automatic logger. If it is `None` then the logs aren't written to a file.
171+
:param disable_auto_logs: Whether to disable automatic logging.
171172
"""
172173
console_handler = RichHandler()
173174
console_formatter = logging.Formatter("%(message)s")
@@ -252,7 +253,7 @@ def start(li: lichess.Lichess, user_profile: UserProfileType, config: Configurat
252253
:param config: The config that the bot will use.
253254
:param logging_level: The logging level. Either `logging.INFO` or `logging.DEBUG`.
254255
:param log_filename: The filename to write the logs to. If it is `None` then the logs aren't written to a file.
255-
:param auto_log_filename: The filename for the automatic logger. If it is `None` then the logs aren't written to a file.
256+
:param disable_auto_logging: Whether to disable automatic logging.
256257
:param one_game: Whether the bot should play only one game. Only used in `test_bot/test_bot.py` to test lichess-bot.
257258
"""
258259
logger.info(f"You're now connected to {config.url} and awaiting challenges.")
@@ -337,6 +338,7 @@ def lichess_bot_main(li: lichess.Lichess,
337338
:param control_queue: The queue containing all the events.
338339
:param correspondence_queue: The queue containing the correspondence games.
339340
:param logging_queue: The logging queue. Used by `logging_listener_proc`.
341+
:param pgn_queue: The queue containing the PGN games.
340342
:param one_game: Whether the bot should play only one game. Only used in `test_bot/test_bot.py` to test lichess-bot.
341343
"""
342344
max_games = config.challenge.concurrency
@@ -661,6 +663,7 @@ def play_game(li: lichess.Lichess,
661663
:param challenge_queue: The queue containing the challenges.
662664
:param correspondence_queue: The queue containing the correspondence games.
663665
:param logging_queue: The logging queue. Used by `logging_listener_proc`.
666+
:param pgn_queue: The queue containing the PGN games.
664667
"""
665668
thread_logging_configurer(logging_queue)
666669
logger = logging.getLogger(__name__)
@@ -766,7 +769,7 @@ def play_game(li: lichess.Lichess,
766769

767770

768771
def read_takeback_record(game: model.Game) -> int:
769-
"""Read the number of move takeback requests accepeted in a game."""
772+
"""Read the number of move takeback requests accepted in a game."""
770773
try:
771774
with open(takeback_record_file_name(game.id)) as takeback_file:
772775
return int(takeback_file.read())
@@ -775,7 +778,7 @@ def read_takeback_record(game: model.Game) -> int:
775778

776779

777780
def record_takeback(game: model.Game, accepted_count: int) -> None:
778-
"""Record the number of move takeback requests accepeted in a game."""
781+
"""Record the number of move takeback requests accepted in a game."""
779782
with open(takeback_record_file_name(game.id), "w") as takeback_file:
780783
takeback_file.write(str(accepted_count))
781784

test_bot/test_config.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
"""Test functions for config module."""
2+
import logging
3+
4+
import pytest
5+
6+
from lib import config
7+
8+
9+
def test_config_assert__false() -> None:
10+
"""Test that config_assert raises an exception with the provided error message."""
11+
with pytest.raises(Exception, match="some error"):
12+
config.config_assert(False, "some error")
13+
14+
15+
def test_config_assert__true() -> None:
16+
"""Test that config_assert does not raise when assertion is True."""
17+
config.config_assert(True, "no error")
18+
19+
20+
def test_config_warn__true(caplog: pytest.LogCaptureFixture) -> None:
21+
"""Test that config_warn does not log a warning when assertion is True."""
22+
with caplog.at_level(logging.WARNING):
23+
config.config_warn(True, "this should not appear")
24+
assert len(caplog.records) == 0 # No warning should be logged
25+
26+
27+
def test_config_warn__false(caplog: pytest.LogCaptureFixture) -> None:
28+
"""Test that config_warn logs a warning when assertion is False."""
29+
with caplog.at_level(logging.WARNING):
30+
config.config_warn(False, "test warning message")
31+
assert "test warning message" in caplog.text
32+
assert len(caplog.records) == 1
33+
assert caplog.records[0].levelname == "WARNING"

test_bot/test_matchmaking.py

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
"""Test functions for matchmaking module."""
2-
from lib.matchmaking import game_category
2+
from unittest.mock import Mock
3+
from lib.matchmaking import game_category, Matchmaking
4+
from lib.config import Configuration
5+
from lib.lichess_types import UserProfileType
36

47

58
def test_game_category_standard_bullet() -> None:
@@ -165,3 +168,59 @@ def test_game_category_realistic_scenarios() -> None:
165168

166169
# 30+20 classical
167170
assert game_category("standard", 1800, 20, 0) == "classical"
171+
172+
173+
def test_get_random_config_value__returns_specific_value() -> None:
174+
"""Test that get_random_config_value returns the config value when it's not 'random'."""
175+
# Create mock objects
176+
mock_li = Mock()
177+
mock_config = Configuration({
178+
"challenge": {"variants": ["standard"]},
179+
"matchmaking": {
180+
"allow_matchmaking": False,
181+
"block_list": [],
182+
"online_block_list": [],
183+
"challenge_timeout": 30
184+
}
185+
})
186+
mock_user_profile: UserProfileType = {"username": "testbot", "perfs": {}}
187+
188+
# Create matchmaking instance
189+
matchmaking = Matchmaking(mock_li, mock_config, mock_user_profile)
190+
191+
# Create config with a specific value
192+
test_config = Configuration({"challenge_variant": "atomic"})
193+
194+
# Test that it returns the specific value, not a random choice
195+
choices = ["standard", "chess960", "atomic", "horde"]
196+
result = matchmaking.get_random_config_value(test_config, "challenge_variant", choices)
197+
198+
assert result == "atomic", f"Expected 'atomic' but got '{result}'"
199+
200+
201+
def test_get_random_config_value__returns_from_choices_when_random() -> None:
202+
"""Test that get_random_config_value returns a value from choices when config value is 'random'."""
203+
# Create mock objects
204+
mock_li = Mock()
205+
mock_config = Configuration({
206+
"challenge": {"variants": ["standard"]},
207+
"matchmaking": {
208+
"allow_matchmaking": False,
209+
"block_list": [],
210+
"online_block_list": [],
211+
"challenge_timeout": 30
212+
}
213+
})
214+
mock_user_profile: UserProfileType = {"username": "testbot", "perfs": {}}
215+
216+
# Create matchmaking instance
217+
matchmaking = Matchmaking(mock_li, mock_config, mock_user_profile)
218+
219+
# Create config with "random" value
220+
test_config = Configuration({"challenge_mode": "random"})
221+
222+
# Test that it returns one of the choices
223+
choices = ["casual", "rated"]
224+
result = matchmaking.get_random_config_value(test_config, "challenge_mode", choices)
225+
226+
assert result in choices, f"Expected result to be in {choices} but got '{result}'"

test_bot/test_model.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,12 @@ def test_game() -> None:
8080
assert game_model.id == "zzzzzzzz"
8181
assert game_model.mode == "casual"
8282
assert game_model.is_white is False
83+
assert game_model.my_color == "black"
84+
assert game_model.url() == "https://lichess.org/zzzzzzzz/black"
85+
assert game_model.short_url() == "https://lichess.org/zzzzzzzz"
86+
assert game_model.pgn_event() == "Casual Bullet game"
87+
assert game_model.time_control() == "90+1"
88+
assert game_model.is_abortable() is True
8389

8490

8591
def test_player() -> None:
@@ -88,3 +94,17 @@ def test_player() -> None:
8894
player_model = model.Player(player)
8995
assert player_model.is_bot is True
9096
assert str(player_model) == "BOT b (3000)"
97+
98+
99+
def test_is_chess_960() -> None:
100+
"""Test the is_chess_960 function."""
101+
items = [
102+
{"fen": "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", "type": "standard", "is_960": False},
103+
{"fen": "brnkrqnb/pppppppp/8/8/8/8/PPPPPPPP/BRNKRQNB w KQkq - 0 1", "type": "960", "is_960": True}, # pos1
104+
{"fen": "nrbbnkqr/pppppppp/8/8/8/8/PPPPPPPP/NRBBNKQR w KQkq - 0 1", "type": "960", "is_960": True}, # pos2
105+
]
106+
for item in items:
107+
fen = str(item["fen"])
108+
expected = item["is_960"]
109+
result = model.is_chess_960(fen)
110+
assert result == expected

0 commit comments

Comments
 (0)