diff --git a/AUTHORS b/AUTHORS index 74e1d46c6..59d692bb4 100644 --- a/AUTHORS +++ b/AUTHORS @@ -16,6 +16,7 @@ Adrian Petrescu (apetresc) Adrian (AdrianGHUB15) Ahmed Kerimov (wcdbmv) Ajith Chandy Jose (ajithcj) +AK-Khan02 Alain Savard (Rocky640) Alayan Feh (Alayan-stk-2) Alexander Kure diff --git a/tests/testing.py b/tests/testing.py index 9711a43ba..452e5cb05 100644 --- a/tests/testing.py +++ b/tests/testing.py @@ -6,12 +6,12 @@ import time import sys import traceback import fnmatch -from functools import wraps from contextlib import redirect_stdout import io import tarfile import pathlib -import concurrent.futures +import queue +import threading import tempfile import shutil import requests @@ -123,7 +123,8 @@ class OrderedClassMembers(type): class TimeoutException(Exception): - def __init__(self, message: str, timeout: int): + def __init__(self, message: str, timeout: float): + super().__init__(message) self.message = message self.timeout = timeout @@ -133,26 +134,6 @@ class UnexpectedOutputException(Exception): self.expected = expected -def timeout_decorator(timeout: float): - def decorator(func): - @wraps(func) - def wrapper(*args, **kwargs): - with concurrent.futures.ThreadPoolExecutor() as executor: - future = executor.submit(func, *args, **kwargs) - try: - result = future.result(timeout=timeout) - except concurrent.futures.TimeoutError: - raise TimeoutException( - f"Function {func.__name__} timed out after {timeout} seconds", - timeout, - ) - return result - - return wrapper - - return decorator - - class MiniTestFramework: def __init__(self): self.passed_test_suites = 0 @@ -305,6 +286,8 @@ class Stockfish: self.cli = cli self.prefix = prefix self.output = [] + self.output_queue = queue.Queue() + self.reader_thread = None self.start() @@ -336,6 +319,21 @@ class Stockfish: universal_newlines=True, bufsize=1, ) + self.reader_thread = threading.Thread( + target=self._read_process_output, daemon=True + ) + self.reader_thread.start() + + def _read_process_output(self): + try: + for line in self.process.stdout: + line = line.strip() + self.output.append(line) + self.output_queue.put(line) + except (OSError, ValueError): + pass + finally: + self.output_queue.put(None) def setoption(self, name: str, value: str): self.send_command(f"setoption name {name} value {value}") @@ -349,31 +347,26 @@ class Stockfish: self.process.stdin.write(command + "\n") self.process.stdin.flush() - @timeout_decorator(MAX_TIMEOUT) def equals(self, expected_output: str): for line in self.readline(): if line == expected_output: return - @timeout_decorator(MAX_TIMEOUT) def expect(self, expected_output: str): for line in self.readline(): if fnmatch.fnmatch(line, expected_output): return - @timeout_decorator(MAX_TIMEOUT) def contains(self, expected_output: str): for line in self.readline(): if expected_output in line: return - @timeout_decorator(MAX_TIMEOUT) def starts_with(self, expected_output: str): for line in self.readline(): if line.startswith(expected_output): return - @timeout_decorator(MAX_TIMEOUT) def check_output(self, callback): if not callback: raise ValueError("Callback function is required") @@ -382,7 +375,6 @@ class Stockfish: if callback(line) == True: return - @timeout_decorator(MAX_TIMEOUT) def expect_for_line_matching(self, line_match: str, expected: str): for line in self.readline(): if fnmatch.fnmatch(line, line_match): @@ -391,14 +383,33 @@ class Stockfish: else: raise UnexpectedOutputException(line, expected) - def readline(self): + def readline(self, timeout: float = MAX_TIMEOUT): if not self.process: raise RuntimeError("Stockfish process is not started") + deadline = time.monotonic() + timeout + while True: self._check_process_alive() - line = self.process.stdout.readline().strip() - self.output.append(line) + + remaining_time = deadline - time.monotonic() + if remaining_time <= 0: + raise TimeoutException( + f"No matching output received after {timeout} seconds", + timeout, + ) + + try: + line = self.output_queue.get(timeout=remaining_time) + except queue.Empty: + raise TimeoutException( + f"No matching output received after {timeout} seconds", + timeout, + ) + + if line is None: + self._check_process_alive() + raise RuntimeError("Stockfish process has terminated") yield line