mirror of
https://github.com/official-stockfish/Stockfish.git
synced 2026-07-22 20:57:10 +00:00
Improve CLI error handling a bit
Reduce the number of *easy* ways to hit UB or an uncaught exception. This builds on Sopel's work on FEN validation. Obviously SF is not and will never be a security boundary, but at least we can reduce the number of reports about uncaught `std::stoi` out of range, etc. I also added a gentle fallback to `NumaConfig` specifically because that's something that (as a dev) I frequently set by hand in the CLI, but happy to revert if that's too annoying of a change. closes https://github.com/official-stockfish/Stockfish/pull/6844 No functional change
This commit is contained in:
committed by
Joost VandeVondele
parent
48a9118251
commit
cd75d31127
+120
-1
@@ -563,6 +563,115 @@ class TestEnPassantSanitization(metaclass=OrderedClassMembers):
|
||||
self.stockfish.check_output(check_output)
|
||||
self.stockfish.expect("bestmove d8d7*")
|
||||
|
||||
class TestInvalidFEN(metaclass=OrderedClassMembers):
|
||||
def beforeEach(self):
|
||||
self.stockfish = None
|
||||
|
||||
def afterEach(self):
|
||||
assert postfix_check(self.stockfish.get_output()) == True
|
||||
self.stockfish.clear_output()
|
||||
|
||||
def _expect_critical(self, fen):
|
||||
self.stockfish = Stockfish(f"position fen {fen}".split(" "), True)
|
||||
assert self.stockfish.process.returncode != 0
|
||||
assert "CRITICAL ERROR" in self.stockfish.process.stdout
|
||||
|
||||
def test_no_kings(self):
|
||||
self._expect_critical("8/8/8/8/8/8/8/8 w - - 0 1")
|
||||
|
||||
def test_invalid_piece(self):
|
||||
self._expect_critical("rnbqkbnr/pppXpppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1")
|
||||
|
||||
def test_invalid_side_to_move(self):
|
||||
self._expect_critical("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR x KQkq - 0 1")
|
||||
|
||||
def test_pawns_on_back_rank(self):
|
||||
self._expect_critical("pppppppp/8/8/8/8/8/8/4K2k w - - 0 1")
|
||||
|
||||
def test_invalid_skip_count(self):
|
||||
self._expect_critical("9/8/8/8/8/8/8/8 w - - 0 1")
|
||||
|
||||
|
||||
class TestInvalidOptions(metaclass=OrderedClassMembers):
|
||||
def beforeAll(self):
|
||||
self.stockfish = Stockfish()
|
||||
|
||||
def afterAll(self):
|
||||
self.stockfish.quit()
|
||||
assert self.stockfish.close() == 0
|
||||
|
||||
def afterEach(self):
|
||||
assert postfix_check(self.stockfish.get_output()) == True
|
||||
self.stockfish.clear_output()
|
||||
|
||||
# Ignore bogus spin values
|
||||
def test_spin_non_numeric(self):
|
||||
self.stockfish.send_command("setoption name Threads value abc")
|
||||
self.stockfish.send_command("isready")
|
||||
self.stockfish.equals("readyok")
|
||||
|
||||
def test_spin_out_of_range(self):
|
||||
self.stockfish.send_command("setoption name Threads value 999999999999")
|
||||
self.stockfish.send_command("isready")
|
||||
self.stockfish.equals("readyok")
|
||||
|
||||
def test_spin_negative(self):
|
||||
self.stockfish.send_command("setoption name Threads value -5")
|
||||
self.stockfish.send_command("isready")
|
||||
self.stockfish.equals("readyok")
|
||||
|
||||
# Warn on bogus NUMA configs
|
||||
def test_numa_garbage(self):
|
||||
self.stockfish.send_command("setoption name NumaPolicy value zzz")
|
||||
self.stockfish.expect("*NumaPolicy: invalid value 'zzz', keeping previous config.*")
|
||||
|
||||
def test_numa_malformed_range(self):
|
||||
self.stockfish.send_command("setoption name NumaPolicy value 0-")
|
||||
self.stockfish.expect("*NumaPolicy: invalid value '0-', keeping previous config.*")
|
||||
|
||||
def test_numa_overflow(self):
|
||||
self.stockfish.send_command(
|
||||
"setoption name NumaPolicy value 99999999999999999999999"
|
||||
)
|
||||
self.stockfish.expect("*NumaPolicy: invalid value*keeping previous config.*")
|
||||
self.stockfish.send_command("isready")
|
||||
self.stockfish.equals("readyok")
|
||||
|
||||
|
||||
class TestBenchFile(metaclass=OrderedClassMembers):
|
||||
def beforeEach(self):
|
||||
self.stockfish = None
|
||||
|
||||
def afterEach(self):
|
||||
assert postfix_check(self.stockfish.get_output()) == True
|
||||
self.stockfish.clear_output()
|
||||
|
||||
def _bench(self, name, content):
|
||||
with open(name, "w") as f:
|
||||
f.write(content)
|
||||
self.stockfish = Stockfish(f"bench 16 1 4 {name} depth".split(" "), True)
|
||||
|
||||
def test_valid_file(self):
|
||||
self._bench("good.epd", "4k3/8/4K3/8/8/8/8/8 w - - 0 1\n")
|
||||
assert self.stockfish.process.returncode == 0
|
||||
assert "Nodes searched" in self.stockfish.process.stderr
|
||||
|
||||
def test_empty_file(self):
|
||||
self._bench("empty.epd", "")
|
||||
assert self.stockfish.process.returncode == 0
|
||||
|
||||
def test_malformed_fen(self):
|
||||
self._bench("bad.epd", "not a valid fen\n")
|
||||
assert self.stockfish.process.returncode != 0
|
||||
assert "CRITICAL ERROR" in self.stockfish.process.stdout
|
||||
|
||||
def test_missing_file(self):
|
||||
self.stockfish = Stockfish(
|
||||
"bench 16 1 4 does_not_exist.epd depth".split(" "), True
|
||||
)
|
||||
assert self.stockfish.process.returncode != 0
|
||||
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser(description="Run Stockfish with testing options")
|
||||
parser.add_argument("--valgrind", action="store_true", help="Run valgrind testing")
|
||||
@@ -595,7 +704,17 @@ if __name__ == "__main__":
|
||||
framework = MiniTestFramework()
|
||||
|
||||
# Each test suite will be run inside a temporary directory
|
||||
framework.run([TestCLI, TestInteractive, TestSyzygy, TestEnPassantSanitization])
|
||||
framework.run(
|
||||
[
|
||||
TestCLI,
|
||||
TestInteractive,
|
||||
TestSyzygy,
|
||||
TestEnPassantSanitization,
|
||||
TestInvalidFEN,
|
||||
TestInvalidOptions,
|
||||
TestBenchFile,
|
||||
]
|
||||
)
|
||||
|
||||
EPD.delete_bench_epd()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user