Spaces:
Runtime error
Runtime error
| """Test lichess-bot.""" | |
| import yaml | |
| import chess | |
| import chess.engine | |
| import threading | |
| import os | |
| import sys | |
| import datetime | |
| import time | |
| import logging | |
| import tempfile | |
| from multiprocessing import Manager | |
| from queue import Queue | |
| import test_bot.lichess | |
| from lib import config | |
| from lib.timer import Timer, seconds | |
| from lib.engine_wrapper import test_suffix | |
| from lib.lichess_types import CONFIG_DICT_TYPE | |
| if "pytest" not in sys.modules: | |
| sys.exit(f"The script {os.path.basename(__file__)} should only be run by pytest.") | |
| from lib import lichess_bot | |
| from test_bot.test_games import scholars_mate | |
| logging_level = logging.DEBUG | |
| testing_log_file_name = None | |
| lichess_bot.logging_configurer(logging_level, testing_log_file_name, True) | |
| logger = logging.getLogger(__name__) | |
| def lichess_org_simulator(move_queue: Queue[chess.Move | None], | |
| board_queue: Queue[chess.Board], | |
| clock_queue: Queue[tuple[datetime.timedelta, datetime.timedelta, datetime.timedelta]], | |
| results: Queue[bool]) -> None: | |
| """ | |
| Run a mocked version of the lichess.org server to provide an opponent for a test. This opponent always plays white. | |
| :param opponent_path: The path to the executable of the opponent. Usually Stockfish. | |
| :param move_queue: An interprocess queue that supplies the moves chosen by the bot being tested. | |
| :param board_queue: An interprocess queue where this function sends the updated board after choosing a move. | |
| :param clock_queue: An interprocess queue where this function sends the updated game clock after choosing a move. | |
| :param results: An interprocess queue where this function sends the result of the game to the testing function. | |
| """ | |
| start_time = seconds(10) | |
| increment = seconds(0.1) | |
| board = chess.Board() | |
| wtime = start_time | |
| btime = start_time | |
| while not board.is_game_over(): | |
| move_timer = Timer() | |
| if board.turn == chess.WHITE: | |
| move_count = len(board.move_stack) | |
| engine_move = board.parse_uci(scholars_mate[move_count]) | |
| board.push(engine_move) | |
| board_queue.put(board) | |
| clock_queue.put((wtime, btime, increment)) | |
| if len(board.move_stack) > 1: | |
| wtime -= move_timer.time_since_reset() | |
| wtime += increment | |
| else: | |
| move_timer = Timer() | |
| while (bot_move := move_queue.get()) is None: | |
| board_queue.put(board) | |
| clock_queue.put((wtime, btime, increment)) | |
| move_queue.task_done() | |
| board.push(bot_move) | |
| move_queue.task_done() | |
| if len(board.move_stack) > 2: | |
| btime -= move_timer.time_since_reset() | |
| btime += increment | |
| board_queue.put(board) | |
| clock_queue.put((wtime, btime, increment)) | |
| outcome = board.outcome() | |
| results.put(outcome is not None and outcome.winner == chess.BLACK) | |
| def run_bot(raw_config: CONFIG_DICT_TYPE, logging_level: int) -> bool: | |
| """ | |
| Start lichess-bot test with a mocked version of the lichess.org site. | |
| :param raw_config: A dictionary of values to specify the engine to test. This engine will play as white. | |
| :param logging_level: The level of logging to use during the test. Usually logging.DEBUG. | |
| :param opponent_path: The path to the executable that will play the opponent. The opponent plays as black. | |
| """ | |
| config.insert_default_values(raw_config) | |
| CONFIG = config.Configuration(raw_config) | |
| logger.info(lichess_bot.intro()) | |
| manager = Manager() | |
| board_queue: Queue[chess.Board] = manager.Queue() | |
| clock_queue: Queue[tuple[datetime.timedelta, datetime.timedelta, datetime.timedelta]] = manager.Queue() | |
| move_queue: Queue[chess.Move | None] = manager.Queue() | |
| li = test_bot.lichess.Lichess(move_queue, board_queue, clock_queue) | |
| user_profile = li.get_profile() | |
| username = user_profile["username"] | |
| if user_profile.get("title") != "BOT": | |
| return False | |
| logger.info(f"Welcome {username}!") | |
| lichess_bot.disable_restart() | |
| results: Queue[bool] = manager.Queue() | |
| thr = threading.Thread(target=lichess_org_simulator, args=[move_queue, board_queue, clock_queue, results]) | |
| thr.start() | |
| lichess_bot.start(li, user_profile, CONFIG, logging_level, testing_log_file_name, True, one_game=True) | |
| result = results.get() | |
| results.task_done() | |
| results.join() | |
| board_queue.join() | |
| clock_queue.join() | |
| move_queue.join() | |
| thr.join() | |
| return result | |
| def test_uci() -> None: | |
| """Test lichess-bot with Stockfish (UCI).""" | |
| with open("./config.yml.default") as file: | |
| CONFIG = yaml.safe_load(file) | |
| with tempfile.TemporaryDirectory() as temp: | |
| CONFIG["token"] = "" | |
| CONFIG["engine"]["dir"] = "test_bot" | |
| CONFIG["engine"]["name"] = "uci_engine.py" | |
| CONFIG["engine"]["interpreter"] = sys.executable | |
| CONFIG["pgn_directory"] = os.path.join(temp, "uci_game_record") | |
| CONFIG["engine"]["uci_options"] = {} | |
| win = run_bot(CONFIG, logging_level) | |
| logger.info("Finished Testing UCI") | |
| assert win | |
| time.sleep(0.1) # Wait for file to be written. | |
| assert os.path.isfile(os.path.join(CONFIG["pgn_directory"], | |
| "bo vs b - zzzzzzzz.pgn")) | |
| def test_xboard() -> None: | |
| """Test lichess-bot with an XBoard engine.""" | |
| with open("./config.yml.default") as file: | |
| CONFIG = yaml.safe_load(file) | |
| with tempfile.TemporaryDirectory() as temp: | |
| CONFIG["token"] = "" | |
| CONFIG["engine"]["dir"] = "test_bot" | |
| CONFIG["engine"]["name"] = "xboard_engine.py" | |
| CONFIG["engine"]["protocol"] = "xboard" | |
| CONFIG["engine"]["interpreter"] = sys.executable | |
| CONFIG["pgn_directory"] = os.path.join(temp, "lc0_game_record") | |
| win = run_bot(CONFIG, logging_level) | |
| logger.info("Finished Testing XBoard") | |
| assert win | |
| time.sleep(0.1) # Wait for file to be written. | |
| assert os.path.isfile(os.path.join(CONFIG["pgn_directory"], | |
| "bo vs b - zzzzzzzz.pgn")) | |
| def test_homemade() -> None: | |
| """Test lichess-bot with a homemade engine.""" | |
| with open("./config.yml.default") as file: | |
| CONFIG = yaml.safe_load(file) | |
| with tempfile.TemporaryDirectory() as temp: | |
| CONFIG["token"] = "" | |
| CONFIG["engine"]["name"] = f"ScholarsMate{test_suffix}" | |
| CONFIG["engine"]["protocol"] = "homemade" | |
| CONFIG["pgn_directory"] = os.path.join(temp, "homemade_game_record") | |
| win = run_bot(CONFIG, logging_level) | |
| logger.info("Finished Testing Homemade") | |
| assert win | |
| time.sleep(0.1) # Wait for file to be written. | |
| assert os.path.isfile(os.path.join(CONFIG["pgn_directory"], | |
| "bo vs b - zzzzzzzz.pgn")) | |
| def test_buggy_engine() -> None: | |
| """Test lichess-bot with an engine that causes a timeout error within python-chess.""" | |
| with open("./config.yml.default") as file: | |
| CONFIG = yaml.safe_load(file) | |
| with tempfile.TemporaryDirectory() as temp: | |
| CONFIG["token"] = "" | |
| CONFIG["engine"]["dir"] = "test_bot" | |
| CONFIG["engine"]["name"] = "buggy_engine.py" | |
| CONFIG["engine"]["interpreter"] = sys.executable | |
| CONFIG["engine"]["uci_options"] = {"go_commands": {"movetime": 100}} | |
| CONFIG["pgn_directory"] = os.path.join(temp, "bug_game_record") | |
| win = run_bot(CONFIG, logging_level) | |
| logger.info("Finished Testing Buggy Engine") | |
| assert win | |
| time.sleep(0.1) # Wait for file to be written. | |
| assert os.path.isfile(os.path.join(CONFIG["pgn_directory"], | |
| "bo vs b - zzzzzzzz.pgn")) | |