Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
official-stockfish
GitHub Repository: official-stockfish/Stockfish
Path: blob/master/tests/instrumented.py
634 views
1
import argparse
2
import re
3
import sys
4
import subprocess
5
import pathlib
6
import os
7
8
from testing import (
9
EPD,
10
TSAN,
11
Stockfish as Engine,
12
MiniTestFramework,
13
OrderedClassMembers,
14
Valgrind,
15
Syzygy,
16
)
17
18
PATH = pathlib.Path(__file__).parent.resolve()
19
CWD = os.getcwd()
20
21
22
def get_prefix():
23
if args.valgrind:
24
return Valgrind.get_valgrind_command()
25
if args.valgrind_thread:
26
return Valgrind.get_valgrind_thread_command()
27
28
return []
29
30
31
def get_threads():
32
if args.valgrind_thread or args.sanitizer_thread:
33
return 2
34
return 1
35
36
37
def get_path():
38
return os.path.abspath(os.path.join(CWD, args.stockfish_path))
39
40
41
def postfix_check(output):
42
if args.sanitizer_undefined:
43
for idx, line in enumerate(output):
44
if "runtime error:" in line:
45
# print next possible 50 lines
46
for i in range(50):
47
debug_idx = idx + i
48
if debug_idx < len(output):
49
print(output[debug_idx])
50
return False
51
52
if args.sanitizer_thread:
53
for idx, line in enumerate(output):
54
if "WARNING: ThreadSanitizer:" in line:
55
# print next possible 50 lines
56
for i in range(50):
57
debug_idx = idx + i
58
if debug_idx < len(output):
59
print(output[debug_idx])
60
return False
61
62
return True
63
64
65
def Stockfish(*args, **kwargs):
66
return Engine(get_prefix(), get_path(), *args, **kwargs)
67
68
69
class TestCLI(metaclass=OrderedClassMembers):
70
def beforeAll(self):
71
pass
72
73
def afterAll(self):
74
pass
75
76
def beforeEach(self):
77
self.stockfish = None
78
79
def afterEach(self):
80
assert postfix_check(self.stockfish.get_output()) == True
81
self.stockfish.clear_output()
82
83
def test_eval(self):
84
self.stockfish = Stockfish("eval".split(" "), True)
85
assert self.stockfish.process.returncode == 0
86
87
def test_go_nodes_1000(self):
88
self.stockfish = Stockfish("go nodes 1000".split(" "), True)
89
assert self.stockfish.process.returncode == 0
90
91
def test_go_depth_10(self):
92
self.stockfish = Stockfish("go depth 10".split(" "), True)
93
assert self.stockfish.process.returncode == 0
94
95
def test_go_perft_4(self):
96
self.stockfish = Stockfish("go perft 4".split(" "), True)
97
assert self.stockfish.process.returncode == 0
98
99
def test_go_movetime_1000(self):
100
self.stockfish = Stockfish("go movetime 1000".split(" "), True)
101
assert self.stockfish.process.returncode == 0
102
103
def test_go_wtime_8000_btime_8000_winc_500_binc_500(self):
104
self.stockfish = Stockfish(
105
"go wtime 8000 btime 8000 winc 500 binc 500".split(" "),
106
True,
107
)
108
assert self.stockfish.process.returncode == 0
109
110
def test_go_wtime_1000_btime_1000_winc_0_binc_0(self):
111
self.stockfish = Stockfish(
112
"go wtime 1000 btime 1000 winc 0 binc 0".split(" "),
113
True,
114
)
115
assert self.stockfish.process.returncode == 0
116
117
def test_go_wtime_1000_btime_1000_winc_0_binc_0_movestogo_5(self):
118
self.stockfish = Stockfish(
119
"go wtime 1000 btime 1000 winc 0 binc 0 movestogo 5".split(" "),
120
True,
121
)
122
assert self.stockfish.process.returncode == 0
123
124
def test_go_movetime_200(self):
125
self.stockfish = Stockfish("go movetime 200".split(" "), True)
126
assert self.stockfish.process.returncode == 0
127
128
def test_go_nodes_20000_searchmoves_e2e4_d2d4(self):
129
self.stockfish = Stockfish(
130
"go nodes 20000 searchmoves e2e4 d2d4".split(" "), True
131
)
132
assert self.stockfish.process.returncode == 0
133
134
def test_bench_128_threads_8_default_depth(self):
135
self.stockfish = Stockfish(
136
f"bench 128 {get_threads()} 8 default depth".split(" "),
137
True,
138
)
139
assert self.stockfish.process.returncode == 0
140
141
def test_bench_128_threads_3_bench_tmp_epd_depth(self):
142
self.stockfish = Stockfish(
143
f"bench 128 {get_threads()} 3 {os.path.join(PATH, 'bench_tmp.epd')} depth".split(
144
" "
145
),
146
True,
147
)
148
assert self.stockfish.process.returncode == 0
149
150
def test_d(self):
151
self.stockfish = Stockfish("d".split(" "), True)
152
assert self.stockfish.process.returncode == 0
153
154
def test_compiler(self):
155
self.stockfish = Stockfish("compiler".split(" "), True)
156
assert self.stockfish.process.returncode == 0
157
158
def test_license(self):
159
self.stockfish = Stockfish("license".split(" "), True)
160
assert self.stockfish.process.returncode == 0
161
162
def test_uci(self):
163
self.stockfish = Stockfish("uci".split(" "), True)
164
assert self.stockfish.process.returncode == 0
165
166
def test_export_net_verify_nnue(self):
167
current_path = os.path.abspath(os.getcwd())
168
self.stockfish = Stockfish(
169
f"export_net {os.path.join(current_path, 'verify.nnue')}".split(" "), True
170
)
171
assert self.stockfish.process.returncode == 0
172
173
# verify the generated net equals the base net
174
175
def test_network_equals_base(self):
176
self.stockfish = Stockfish(
177
["uci"],
178
True,
179
)
180
181
output = self.stockfish.process.stdout
182
183
# find line
184
for line in output.split("\n"):
185
if "option name EvalFile type string default" in line:
186
network = line.split(" ")[-1]
187
break
188
189
# find network file in src dir
190
network = os.path.join(PATH.parent.resolve(), "src", network)
191
192
if not os.path.exists(network):
193
print(
194
f"Network file {network} not found, please download the network file over the make command."
195
)
196
assert False
197
198
diff = subprocess.run(["diff", network, f"verify.nnue"])
199
200
assert diff.returncode == 0
201
202
203
class TestInteractive(metaclass=OrderedClassMembers):
204
def beforeAll(self):
205
self.stockfish = Stockfish()
206
207
def afterAll(self):
208
self.stockfish.quit()
209
assert self.stockfish.close() == 0
210
211
def afterEach(self):
212
assert postfix_check(self.stockfish.get_output()) == True
213
self.stockfish.clear_output()
214
215
def test_startup_output(self):
216
self.stockfish.starts_with("Stockfish")
217
218
def test_uci_command(self):
219
self.stockfish.send_command("uci")
220
self.stockfish.equals("uciok")
221
222
def test_set_threads_option(self):
223
self.stockfish.send_command(f"setoption name Threads value {get_threads()}")
224
225
def test_ucinewgame_and_startpos_nodes_1000(self):
226
self.stockfish.send_command("ucinewgame")
227
self.stockfish.send_command("position startpos")
228
self.stockfish.send_command("go nodes 1000")
229
self.stockfish.starts_with("bestmove")
230
231
def test_ucinewgame_and_startpos_moves(self):
232
self.stockfish.send_command("ucinewgame")
233
self.stockfish.send_command("position startpos moves e2e4 e7e6")
234
self.stockfish.send_command("go nodes 1000")
235
self.stockfish.starts_with("bestmove")
236
237
def test_fen_position_1(self):
238
self.stockfish.send_command("ucinewgame")
239
self.stockfish.send_command("position fen 5rk1/1K4p1/8/8/3B4/8/8/8 b - - 0 1")
240
self.stockfish.send_command("go nodes 1000")
241
self.stockfish.starts_with("bestmove")
242
243
def test_fen_position_2_flip(self):
244
self.stockfish.send_command("ucinewgame")
245
self.stockfish.send_command("position fen 5rk1/1K4p1/8/8/3B4/8/8/8 b - - 0 1")
246
self.stockfish.send_command("flip")
247
self.stockfish.send_command("go nodes 1000")
248
self.stockfish.starts_with("bestmove")
249
250
def test_depth_5_with_callback(self):
251
self.stockfish.send_command("ucinewgame")
252
self.stockfish.send_command("position startpos")
253
self.stockfish.send_command("go depth 5")
254
255
def callback(output):
256
regex = r"info depth \d+ seldepth \d+ multipv \d+ score cp -?\d+ nodes \d+ nps \d+ hashfull \d+ tbhits \d+ time \d+ pv"
257
if output.startswith("info depth") and not re.match(regex, output):
258
assert False
259
if output.startswith("bestmove"):
260
return True
261
return False
262
263
self.stockfish.check_output(callback)
264
265
def test_ucinewgame_and_go_depth_9(self):
266
self.stockfish.send_command("ucinewgame")
267
self.stockfish.send_command("setoption name UCI_ShowWDL value true")
268
self.stockfish.send_command("position startpos")
269
self.stockfish.send_command("go depth 9")
270
271
depth = 1
272
273
def callback(output):
274
nonlocal depth
275
276
regex = rf"info depth {depth} seldepth \d+ multipv \d+ score cp -?\d+ wdl \d+ \d+ \d+ nodes \d+ nps \d+ hashfull \d+ tbhits \d+ time \d+ pv"
277
278
if output.startswith("info depth"):
279
if not re.match(regex, output):
280
assert False
281
depth += 1
282
283
if output.startswith("bestmove"):
284
assert depth == 10
285
return True
286
287
return False
288
289
self.stockfish.check_output(callback)
290
291
def test_clear_hash(self):
292
self.stockfish.send_command("setoption name Clear Hash")
293
294
def test_fen_position_mate_1(self):
295
self.stockfish.send_command("ucinewgame")
296
self.stockfish.send_command(
297
"position fen 5K2/8/2qk4/2nPp3/3r4/6B1/B7/3R4 w - e6"
298
)
299
self.stockfish.send_command("go depth 18")
300
301
self.stockfish.expect("* score mate 1 * pv d5e6")
302
self.stockfish.equals("bestmove d5e6")
303
304
def test_fen_position_mate_minus_1(self):
305
self.stockfish.send_command("ucinewgame")
306
self.stockfish.send_command(
307
"position fen 2brrb2/8/p7/Q7/1p1kpPp1/1P1pN1K1/3P4/8 b - -"
308
)
309
self.stockfish.send_command("go depth 18")
310
self.stockfish.expect("* score mate -1 *")
311
self.stockfish.starts_with("bestmove")
312
313
def test_fen_position_fixed_node(self):
314
self.stockfish.send_command("ucinewgame")
315
self.stockfish.send_command(
316
"position fen 5K2/8/2P1P1Pk/6pP/3p2P1/1P6/3P4/8 w - - 0 1"
317
)
318
self.stockfish.send_command("go nodes 500000")
319
self.stockfish.starts_with("bestmove")
320
321
def test_fen_position_with_mate_go_depth(self):
322
self.stockfish.send_command("ucinewgame")
323
self.stockfish.send_command(
324
"position fen 8/5R2/2K1P3/4k3/8/b1PPpp1B/5p2/8 w - -"
325
)
326
self.stockfish.send_command("go depth 18 searchmoves c6d7")
327
self.stockfish.expect("* score mate 2 * pv c6d7 * f7f5")
328
329
self.stockfish.starts_with("bestmove")
330
331
def test_fen_position_with_mate_go_mate(self):
332
self.stockfish.send_command("ucinewgame")
333
self.stockfish.send_command(
334
"position fen 8/5R2/2K1P3/4k3/8/b1PPpp1B/5p2/8 w - -"
335
)
336
self.stockfish.send_command("go mate 2 searchmoves c6d7")
337
self.stockfish.expect("* score mate 2 * pv c6d7 *")
338
339
self.stockfish.starts_with("bestmove")
340
341
def test_fen_position_with_mate_go_nodes(self):
342
self.stockfish.send_command("ucinewgame")
343
self.stockfish.send_command(
344
"position fen 8/5R2/2K1P3/4k3/8/b1PPpp1B/5p2/8 w - -"
345
)
346
self.stockfish.send_command("go nodes 500000 searchmoves c6d7")
347
self.stockfish.expect("* score mate 2 * pv c6d7 * f7f5")
348
349
self.stockfish.starts_with("bestmove")
350
351
def test_fen_position_depth_27(self):
352
self.stockfish.send_command("ucinewgame")
353
self.stockfish.send_command(
354
"position fen r1b2r1k/pp1p2pp/2p5/2B1q3/8/8/P1PN2PP/R4RK1 w - - 0 18"
355
)
356
self.stockfish.send_command("go")
357
self.stockfish.contains("score mate 1")
358
359
self.stockfish.starts_with("bestmove")
360
361
def test_fen_position_with_mate_go_depth_and_promotion(self):
362
self.stockfish.send_command("ucinewgame")
363
self.stockfish.send_command(
364
"position fen 8/5R2/2K1P3/4k3/8/b1PPpp1B/5p2/8 w - - moves c6d7 f2f1q"
365
)
366
self.stockfish.send_command("go depth 18")
367
self.stockfish.expect("* score mate 1 * pv f7f5")
368
self.stockfish.starts_with("bestmove f7f5")
369
370
def test_fen_position_with_mate_go_depth_and_searchmoves(self):
371
self.stockfish.send_command("ucinewgame")
372
self.stockfish.send_command(
373
"position fen 8/5R2/2K1P3/4k3/8/b1PPpp1B/5p2/8 w - -"
374
)
375
self.stockfish.send_command("go depth 18 searchmoves c6d7")
376
self.stockfish.expect("* score mate 2 * pv c6d7 * f7f5")
377
378
self.stockfish.starts_with("bestmove c6d7")
379
380
def test_fen_position_with_moves_with_mate_go_depth_and_searchmoves(self):
381
self.stockfish.send_command("ucinewgame")
382
self.stockfish.send_command(
383
"position fen 8/5R2/2K1P3/4k3/8/b1PPpp1B/5p2/8 w - - moves c6d7"
384
)
385
self.stockfish.send_command("go depth 18 searchmoves e3e2")
386
self.stockfish.expect("* score mate -1 * pv e3e2 f7f5")
387
self.stockfish.starts_with("bestmove e3e2")
388
389
def test_verify_nnue_network(self):
390
current_path = os.path.abspath(os.getcwd())
391
Stockfish(
392
f"export_net {os.path.join(current_path, 'verify.nnue')}".split(" "), True
393
)
394
395
self.stockfish.send_command("setoption name EvalFile value verify.nnue")
396
self.stockfish.send_command("position startpos")
397
self.stockfish.send_command("go depth 5")
398
self.stockfish.starts_with("bestmove")
399
400
def test_multipv_setting(self):
401
self.stockfish.send_command("setoption name MultiPV value 4")
402
self.stockfish.send_command("position startpos")
403
self.stockfish.send_command("go depth 5")
404
self.stockfish.starts_with("bestmove")
405
406
def test_fen_position_with_skill_level(self):
407
self.stockfish.send_command("setoption name Skill Level value 10")
408
self.stockfish.send_command("position startpos")
409
self.stockfish.send_command("go depth 5")
410
self.stockfish.starts_with("bestmove")
411
412
self.stockfish.send_command("setoption name Skill Level value 20")
413
414
415
class TestSyzygy(metaclass=OrderedClassMembers):
416
def beforeAll(self):
417
self.stockfish = Stockfish()
418
419
def afterAll(self):
420
self.stockfish.quit()
421
assert self.stockfish.close() == 0
422
423
def afterEach(self):
424
assert postfix_check(self.stockfish.get_output()) == True
425
self.stockfish.clear_output()
426
427
def test_syzygy_setup(self):
428
self.stockfish.starts_with("Stockfish")
429
self.stockfish.send_command("uci")
430
self.stockfish.send_command(
431
f"setoption name SyzygyPath value {os.path.join(PATH, 'syzygy')}"
432
)
433
self.stockfish.expect(
434
"info string Found 35 WDL and 35 DTZ tablebase files (up to 4-man)."
435
)
436
437
def test_syzygy_bench(self):
438
self.stockfish.send_command("bench 128 1 8 default depth")
439
self.stockfish.expect("Nodes searched :*")
440
441
def test_syzygy_position(self):
442
self.stockfish.send_command("ucinewgame")
443
self.stockfish.send_command("position fen 4k3/PP6/8/8/8/8/8/4K3 w - - 0 1")
444
self.stockfish.send_command("go depth 5")
445
446
def check_output(output):
447
if "score cp 20000" in output or "score mate" in output:
448
return True
449
450
self.stockfish.check_output(check_output)
451
self.stockfish.expect("bestmove *")
452
453
def test_syzygy_position_2(self):
454
self.stockfish.send_command("ucinewgame")
455
self.stockfish.send_command("position fen 8/1P6/2B5/8/4K3/8/6k1/8 w - - 0 1")
456
self.stockfish.send_command("go depth 5")
457
458
def check_output(output):
459
if "score cp 20000" in output or "score mate" in output:
460
return True
461
462
self.stockfish.check_output(check_output)
463
self.stockfish.expect("bestmove *")
464
465
def test_syzygy_position_3(self):
466
self.stockfish.send_command("ucinewgame")
467
self.stockfish.send_command("position fen 8/1P6/2B5/8/4K3/8/6k1/8 b - - 0 1")
468
self.stockfish.send_command("go depth 5")
469
470
def check_output(output):
471
if "score cp -20000" in output or "score mate -" in output:
472
return True
473
474
self.stockfish.check_output(check_output)
475
self.stockfish.expect("bestmove *")
476
477
478
def parse_args():
479
parser = argparse.ArgumentParser(description="Run Stockfish with testing options")
480
parser.add_argument("--valgrind", action="store_true", help="Run valgrind testing")
481
parser.add_argument(
482
"--valgrind-thread", action="store_true", help="Run valgrind-thread testing"
483
)
484
parser.add_argument(
485
"--sanitizer-undefined",
486
action="store_true",
487
help="Run sanitizer-undefined testing",
488
)
489
parser.add_argument(
490
"--sanitizer-thread", action="store_true", help="Run sanitizer-thread testing"
491
)
492
493
parser.add_argument(
494
"--none", action="store_true", help="Run without any testing options"
495
)
496
parser.add_argument("stockfish_path", type=str, help="Path to Stockfish binary")
497
498
return parser.parse_args()
499
500
501
if __name__ == "__main__":
502
args = parse_args()
503
504
EPD.create_bench_epd()
505
TSAN.set_tsan_option()
506
Syzygy.download_syzygy()
507
508
framework = MiniTestFramework()
509
510
# Each test suite will be run inside a temporary directory
511
framework.run([TestCLI, TestInteractive, TestSyzygy])
512
513
EPD.delete_bench_epd()
514
TSAN.unset_tsan_option()
515
516
if framework.has_failed():
517
sys.exit(1)
518
519
sys.exit(0)
520
521