Added test suite

This commit is contained in:
Mattia Giambirtone 2024-04-13 16:28:48 +02:00
parent 6153112c21
commit 4d4b12a603
5 changed files with 192 additions and 11 deletions

4
.gitignore vendored
View File

@ -3,4 +3,6 @@ nimcache/
nimblecache/ nimblecache/
htmldocs/ htmldocs/
nim.cfg nim.cfg
bin bin
# Python
__pycache__

View File

@ -2258,7 +2258,7 @@ proc main: int =
echo &"Unknown command '{cmd[0]}'. Type 'help' for more information." echo &"Unknown command '{cmd[0]}'. Type 'help' for more information."
except IOError: except IOError:
echo "" echo ""
return -1 return 0
except EOFError: except EOFError:
echo "" echo ""
return 0 return 0

View File

@ -1,7 +1,5 @@
import re import re
import os
import sys import sys
import time
import subprocess import subprocess
from shutil import which from shutil import which
from pathlib import Path from pathlib import Path
@ -10,17 +8,19 @@ from argparse import ArgumentParser, Namespace
def main(args: Namespace) -> int: def main(args: Namespace) -> int:
if args.silent:
print = lambda *_: ...
print("Nimfish move validator v0.0.1 by nocturn9x") print("Nimfish move validator v0.0.1 by nocturn9x")
try: try:
STOCKFISH = (args.stockfish or Path(which("stockfish"))).resolve(strict=True) STOCKFISH = (args.stockfish or Path(which("stockfish"))).resolve(strict=True)
except Exception as e: except Exception as e:
print(f"Could not locate stockfish executable -> {type(e).__name__}: {e}") print(f"Could not locate stockfish executable -> {type(e).__name__}: {e}")
return -1 return 2
try: try:
NIMFISH = (args.nimfish or (Path.cwd() / "bin" / "nimfish")).resolve(strict=True) NIMFISH = (args.nimfish or (Path.cwd() / "bin" / "nimfish")).resolve(strict=True)
except Exception as e: except Exception as e:
print(f"Could not locate nimfish executable -> {type(e).__name__}: {e}") print(f"Could not locate nimfish executable -> {type(e).__name__}: {e}")
return -1 return 2
print(f"Starting Stockfish engine at {STOCKFISH.as_posix()!r}") print(f"Starting Stockfish engine at {STOCKFISH.as_posix()!r}")
stockfish_process = subprocess.Popen(STOCKFISH, stockfish_process = subprocess.Popen(STOCKFISH,
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
@ -49,8 +49,14 @@ def main(args: Namespace) -> int:
print(f"Engines started, beginning search to depth {args.ply}") print(f"Engines started, beginning search to depth {args.ply}")
nimfish_process.stdin.write(f"go perft {args.ply} {'bulk' if args.bulk else ''}\n") nimfish_process.stdin.write(f"go perft {args.ply} {'bulk' if args.bulk else ''}\n")
stockfish_process.stdin.write(f"go perft {args.ply}\n") stockfish_process.stdin.write(f"go perft {args.ply}\n")
stockfish_output = stockfish_process.communicate()[0] stockfish_output, stockfish_error = stockfish_process.communicate()
nimfish_output = nimfish_process.communicate()[0] nimfish_output, nimfish_error = nimfish_process.communicate()
if nimfish_process.returncode != 0:
print(f"Nimfish crashed, stderr output below:\n{nimfish_error}")
if stockfish_process.returncode != 0:
print(f"Stockfish crashed, stderr below:\n{stockfish_error}")
if not all([stockfish_process.returncode == 0, nimfish_process.returncode == 0]):
return 3
positions = { positions = {
"all": {}, "all": {},
"stockfish": {}, "stockfish": {},
@ -142,18 +148,20 @@ def main(args: Namespace) -> int:
for move in mistakes: for move in mistakes:
missed = positions["stockfish"][move] - positions["nimfish"][move] missed = positions["stockfish"][move] - positions["nimfish"][move]
print(f" - {move}: expected {positions['stockfish'][move]}, got {positions['nimfish'][move]} ({'-' if missed > 0 else '+'}{abs(missed)})") print(f" - {move}: expected {positions['stockfish'][move]}, got {positions['nimfish'][move]} ({'-' if missed > 0 else '+'}{abs(missed)})")
return 1
else: else:
print("No discrepancies detected") print("No discrepancies detected")
return 0
if __name__ == "__main__": if __name__ == "__main__":
parser = ArgumentParser(description="Automatically compare perft results between our engine and Stockfish") parser = ArgumentParser(description="Automatically compare perft results between Nimfish and Stockfish")
parser.add_argument("--fen", "-f", type=str, default="", help="The FEN string of the position to start from (empty string means the initial one). Defaults to ''") parser.add_argument("--fen", "-f", type=str, default="", help="The FEN string of the position to start from (empty string means the initial one). Defaults to ''")
parser.add_argument("--ply", "-d", type=int, required=True, help="The depth to stop at, expressed in plys (half-moves)") parser.add_argument("--ply", "-d", type=int, required=True, help="The depth to stop at, expressed in plys (half-moves)")
parser.add_argument("--bulk", action="store_true", help="Enable bulk-counting for Nimfish (faster, less debuggable)", default=False) parser.add_argument("--bulk", action="store_true", help="Enable bulk-counting for Nimfish (much faster)", default=False)
parser.add_argument("--stockfish", type=Path, help="Path to the stockfish executable. Defaults to '' (detected automatically)", default=None) parser.add_argument("--stockfish", type=Path, help="Path to the stockfish executable. Defaults to '' (detected automatically)", default=None)
parser.add_argument("--nimfish", type=Path, help="Path to the nimfish executable. Defaults to '' (detected automatically)", default=None) parser.add_argument("--nimfish", type=Path, help="Path to the nimfish executable. Defaults to '' (detected automatically)", default=None)
parser.add_argument("--auto-mode", action="store_true", help="Automatically attempt to detect which moves Nimfish got wrong") parser.add_argument("--silent", action="store_true", help="Disable all output (a return code of 0 means the test was successful)", default=False)
sys.exit(main(parser.parse_args())) sys.exit(main(parser.parse_args()))

View File

@ -0,0 +1,128 @@
rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1
r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 1
4k3/8/8/8/8/8/8/4K2R w K - 0 1
4k3/8/8/8/8/8/8/R3K3 w Q - 0 1
4k2r/8/8/8/8/8/8/4K3 w k - 0 1
r3k3/8/8/8/8/8/8/4K3 w q - 0 1
4k3/8/8/8/8/8/8/R3K2R w KQ - 0 1
r3k2r/8/8/8/8/8/8/4K3 w kq - 0 1
8/8/8/8/8/8/6k1/4K2R w K - 0 1
8/8/8/8/8/8/1k6/R3K3 w Q - 0 1
4k2r/6K1/8/8/8/8/8/8 w k - 0 1
r3k3/1K6/8/8/8/8/8/8 w q - 0 1
r3k2r/8/8/8/8/8/8/R3K2R w KQkq - 0 1
r3k2r/8/8/8/8/8/8/1R2K2R w Kkq - 0 1
r3k2r/8/8/8/8/8/8/2R1K2R w Kkq - 0 1
r3k2r/8/8/8/8/8/8/R3K1R1 w Qkq - 0 1
1r2k2r/8/8/8/8/8/8/R3K2R w KQk - 0 1
2r1k2r/8/8/8/8/8/8/R3K2R w KQk - 0 1
r3k1r1/8/8/8/8/8/8/R3K2R w KQq - 0 1
4k3/8/8/8/8/8/8/4K2R b K - 0 1
4k3/8/8/8/8/8/8/R3K3 b Q - 0 1
4k2r/8/8/8/8/8/8/4K3 b k - 0 1
r3k3/8/8/8/8/8/8/4K3 b q - 0 1
4k3/8/8/8/8/8/8/R3K2R b KQ - 0 1
r3k2r/8/8/8/8/8/8/4K3 b kq - 0 1
8/8/8/8/8/8/6k1/4K2R b K - 0 1
8/8/8/8/8/8/1k6/R3K3 b Q - 0 1
4k2r/6K1/8/8/8/8/8/8 b k - 0 1
r3k3/1K6/8/8/8/8/8/8 b q - 0 1
r3k2r/8/8/8/8/8/8/R3K2R b KQkq - 0 1
r3k2r/8/8/8/8/8/8/1R2K2R b Kkq - 0 1
r3k2r/8/8/8/8/8/8/2R1K2R b Kkq - 0 1
r3k2r/8/8/8/8/8/8/R3K1R1 b Qkq - 0 1
1r2k2r/8/8/8/8/8/8/R3K2R b KQk - 0 1
2r1k2r/8/8/8/8/8/8/R3K2R b KQk - 0 1
r3k1r1/8/8/8/8/8/8/R3K2R b KQq - 0 1
8/1n4N1/2k5/8/8/5K2/1N4n1/8 w - - 0 1
8/1k6/8/5N2/8/4n3/8/2K5 w - - 0 1
8/8/4k3/3Nn3/3nN3/4K3/8/8 w - - 0 1
K7/8/2n5/1n6/8/8/8/k6N w - - 0 1
k7/8/2N5/1N6/8/8/8/K6n w - - 0 1
8/1n4N1/2k5/8/8/5K2/1N4n1/8 b - - 0 1
8/1k6/8/5N2/8/4n3/8/2K5 b - - 0 1
8/8/3K4/3Nn3/3nN3/4k3/8/8 b - - 0 1
K7/8/2n5/1n6/8/8/8/k6N b - - 0 1
k7/8/2N5/1N6/8/8/8/K6n b - - 0 1
B6b/8/8/8/2K5/4k3/8/b6B w - - 0 1
8/8/1B6/7b/7k/8/2B1b3/7K w - - 0 1
k7/B7/1B6/1B6/8/8/8/K6b w - - 0 1
K7/b7/1b6/1b6/8/8/8/k6B w - - 0 1
B6b/8/8/8/2K5/5k2/8/b6B b - - 0 1
8/8/1B6/7b/7k/8/2B1b3/7K b - - 0 1
k7/B7/1B6/1B6/8/8/8/K6b b - - 0 1
K7/b7/1b6/1b6/8/8/8/k6B b - - 0 1
7k/RR6/8/8/8/8/rr6/7K w - - 0 1
R6r/8/8/2K5/5k2/8/8/r6R w - - 0 1
7k/RR6/8/8/8/8/rr6/7K b - - 0 1
R6r/8/8/2K5/5k2/8/8/r6R b - - 0 1
6kq/8/8/8/8/8/8/7K w - - 0 1
6KQ/8/8/8/8/8/8/7k b - - 0 1
K7/8/8/3Q4/4q3/8/8/7k w - - 0 1
6qk/8/8/8/8/8/8/7K b - - 0 1
6KQ/8/8/8/8/8/8/7k b - - 0 1
K7/8/8/3Q4/4q3/8/8/7k b - - 0 1
8/8/8/8/8/K7/P7/k7 w - - 0 1
8/8/8/8/8/7K/7P/7k w - - 0 1
K7/p7/k7/8/8/8/8/8 w - - 0 1
7K/7p/7k/8/8/8/8/8 w - - 0 1
8/2k1p3/3pP3/3P2K1/8/8/8/8 w - - 0 1
8/8/8/8/8/K7/P7/k7 b - - 0 1
8/8/8/8/8/7K/7P/7k b - - 0 1
K7/p7/k7/8/8/8/8/8 b - - 0 1
7K/7p/7k/8/8/8/8/8 b - - 0 1
8/2k1p3/3pP3/3P2K1/8/8/8/8 b - - 0 1
8/8/8/8/8/4k3/4P3/4K3 w - - 0 1
4k3/4p3/4K3/8/8/8/8/8 b - - 0 1
8/8/7k/7p/7P/7K/8/8 w - - 0 1
8/8/k7/p7/P7/K7/8/8 w - - 0 1
8/8/3k4/3p4/3P4/3K4/8/8 w - - 0 1
8/3k4/3p4/8/3P4/3K4/8/8 w - - 0 1
8/8/3k4/3p4/8/3P4/3K4/8 w - - 0 1
k7/8/3p4/8/3P4/8/8/7K w - - 0 1
8/8/7k/7p/7P/7K/8/8 b - - 0 1
8/8/k7/p7/P7/K7/8/8 b - - 0 1
8/8/3k4/3p4/3P4/3K4/8/8 b - - 0 1
8/3k4/3p4/8/3P4/3K4/8/8 b - - 0 1
8/8/3k4/3p4/8/3P4/3K4/8 b - - 0 1
k7/8/3p4/8/3P4/8/8/7K b - - 0 1
7k/3p4/8/8/3P4/8/8/K7 w - - 0 1
7k/8/8/3p4/8/8/3P4/K7 w - - 0 1
k7/8/8/7p/6P1/8/8/K7 w - - 0 1
k7/8/7p/8/8/6P1/8/K7 w - - 0 1
k7/8/8/6p1/7P/8/8/K7 w - - 0 1
k7/8/6p1/8/8/7P/8/K7 w - - 0 1
k7/8/8/3p4/4p3/8/8/7K w - - 0 1
k7/8/3p4/8/8/4P3/8/7K w - - 0 1
7k/3p4/8/8/3P4/8/8/K7 b - - 0 1
7k/8/8/3p4/8/8/3P4/K7 b - - 0 1
k7/8/8/7p/6P1/8/8/K7 b - - 0 1
k7/8/7p/8/8/6P1/8/K7 b - - 0 1
k7/8/8/6p1/7P/8/8/K7 b - - 0 1
k7/8/6p1/8/8/7P/8/K7 b - - 0 1
k7/8/8/3p4/4p3/8/8/7K b - - 0 1
k7/8/3p4/8/8/4P3/8/7K b - - 0 1
7k/8/8/p7/1P6/8/8/7K w - - 0 1
7k/8/p7/8/8/1P6/8/7K w - - 0 1
7k/8/8/1p6/P7/8/8/7K w - - 0 1
7k/8/1p6/8/8/P7/8/7K w - - 0 1
k7/7p/8/8/8/8/6P1/K7 w - - 0 1
k7/6p1/8/8/8/8/7P/K7 w - - 0 1
3k4/3pp3/8/8/8/8/3PP3/3K4 w - - 0 1
7k/8/8/p7/1P6/8/8/7K b - - 0 1
7k/8/p7/8/8/1P6/8/7K b - - 0 1
7k/8/8/1p6/P7/8/8/7K b - - 0 1
7k/8/1p6/8/8/P7/8/7K b - - 0 1
k7/7p/8/8/8/8/6P1/K7 b - - 0 1
k7/6p1/8/8/8/8/7P/K7 b - - 0 1
3k4/3pp3/8/8/8/8/3PP3/3K4 b - - 0 1
8/Pk6/8/8/8/8/6Kp/8 w - - 0 1
n1n5/1Pk5/8/8/8/8/5Kp1/5N1N w - - 0 1
8/PPPk4/8/8/8/8/4Kppp/8 w - - 0 1
n1n5/PPPk4/8/8/8/8/4Kppp/5N1N w - - 0 1
8/Pk6/8/8/8/8/6Kp/8 b - - 0 1
n1n5/1Pk5/8/8/8/8/5Kp1/5N1N b - - 0 1
8/PPPk4/8/8/8/8/4Kppp/8 b - - 0 1
n1n5/PPPk4/8/8/8/8/4Kppp/5N1N b - - 0 1
8/2p5/3p4/KP5r/1R3p1k/8/4P1P1/8 w - - 0 1
rnbqkb1r/ppppp1pp/7n/4Pp2/8/8/PPPP1PPP/RNBQKBNR w KQkq f6 0 3

43
src/Chess/tests/suite.py Normal file
View File

@ -0,0 +1,43 @@
import sys
import timeit
from pathlib import Path
from argparse import Namespace, ArgumentParser
from compare_positions import main as test
def main(args: Namespace) -> int:
print("[S] Starting test suite")
successful = []
failed = []
positions = args.positions.read_text().splitlines()
start = timeit.default_timer()
longest_fen = max(sorted([len(fen) for fen in positions]))
for i, fen in enumerate(positions):
fen = fen.strip(" ")
fen += " " * (longest_fen - len(fen))
sys.stdout.write(f"\r[S] Testing {fen} ({i + 1}/{len(positions)})\033[K")
args.fen = fen
args.silent = not args.no_silent
if test(args) == 0:
successful.append(fen)
else:
failed.append(fen)
stop = timeit.default_timer()
print(f"\r[S] Ran {len(positions)} tests at depth {args.ply} in {stop - start:.2f} seconds ({len(successful)} successful, {len(failed)} failed)\033[K")
if failed and args.show_failures:
print("[S] The following FENs failed to pass the test:", end="")
print("\n\t".join(failed))
if __name__ == "__main__":
parser = ArgumentParser(description="Run a set of tests using compare_positions.py")
parser.add_argument("--ply", "-d", type=int, required=True, help="The depth to stop at, expressed in plys (half-moves)")
parser.add_argument("--bulk", action="store_true", help="Enable bulk-counting for Nimfish (much faster)", default=False)
parser.add_argument("--stockfish", type=Path, help="Path to the stockfish executable. Defaults to '' (detected automatically)", default=None)
parser.add_argument("--nimfish", type=Path, help="Path to the nimfish executable. Defaults to '' (detected automatically)", default=None)
parser.add_argument("--positions", type=Path, help="Location of the file containing FENs to test, one per line. Defaults to 'tests/positions.txt'",
default=Path("tests/positions.txt"))
parser.add_argument("--no-silent", action="store_true", help="Do not suppress output from compare_positions.py (defaults)", default=False)
parser.add_argument("--show-failures", action="store_true", help="Show which FENs failed to pass the test", default=False)
sys.exit(main(parser.parse_args()))