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