Spaces:
Runtime error
Runtime error
File size: 7,833 Bytes
3014f14 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 | """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"))
|