-
Notifications
You must be signed in to change notification settings - Fork 583
181 lines (174 loc) · 10 KB
/
Copy pathpython-app.yml
File metadata and controls
181 lines (174 loc) · 10 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# This workflow will install Python dependencies, run tests and lint with a single version of Python
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
name: Python application
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Pypy 3.9
uses: actions/setup-python@v4
with:
python-version: 'pypy3.10'
- name: Install uv
uses: astral-sh/setup-uv@v5
- name: Install dependencies
run: uv pip install --system -r pyproject.toml --group dev
- name: Install polyglot adapter (XBoard compatibility test)
run: sudo apt-get install -y -q polyglot || true
- name: Lint with flake8
run: |
# stop the build if there are Python syntax errors or undefined names
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics --exclude=compressed.py
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics --exclude=compressed.py
- name: Test
run: |
bash tools/quick_tests.sh "python ./sunfish.py"
- name: UCI smoke test (sunfish)
run: |
printf 'uci\nisready\nposition startpos moves e2e4\ngo movetime 500\nquit\n' \
| python ./sunfish.py | tee /dev/stderr | grep -q bestmove
# Sudden-death time control, no increments (regression test for #117)
printf 'uci\nposition startpos moves e2e4\ngo wtime 60000 btime 60000\nquit\n' \
| timeout 60 python ./sunfish.py | tee /dev/stderr | grep -q bestmove
# "stop" right after "go infinite" must yield a bestmove (regression test for #117)
printf 'uci\nposition startpos\ngo infinite\nstop\nquit\n' \
| timeout 60 python ./sunfish.py | tee /dev/stderr | grep -q bestmove
# The polyglot shebang must make the script directly executable (issue #102)
printf 'uci\nquit\n' | ./sunfish.py | tee /dev/stderr | grep -q uciok
- name: UCI smoke test (packed/tiny interface)
# The packed 4K build strips the minifier-hide block and uses the tiny
# built-in UCI loop at the bottom of sunfish.py. Test that loop directly.
run: |
sed '/# minifier-hide start/,/# minifier-hide end/d' sunfish.py > tiny_sunfish.py
printf 'uci\nisready\nposition startpos moves e2e4\ngo wtime 60000 btime 60000\nquit\n' \
| timeout 60 python tiny_sunfish.py | tee /dev/stderr | grep -q bestmove
# Clock-only: this loop is the simplified TCEC-4k UCI and does not
# read movetime. 4s/no-increment lands on its floors (soft 100ms,
# wall 200ms), so this is a fixed, fast smoke.
printf 'uci\nposition startpos\ngo wtime 4000 winc 0\nquit\n' \
| timeout 60 python tiny_sunfish.py | tee /dev/stderr | grep -q bestmove
rm tiny_sunfish.py
- name: Pack test (tools/build/pack.sh end-to-end)
# The packed build is a release artifact; run the real packer and
# smoke the self-extracting executable it produces. See build/README.md.
run: |
pip install python-minifier==3.2.0
bash tools/build/pack.sh sunfish.py sunfish.packed
{ printf 'uci\nisready\nposition startpos\ngo wtime 4000 winc 0\n'; sleep 3; printf 'quit\n'; } \
| timeout 60 ./sunfish.packed | tee /dev/stderr | grep -q bestmove
rm sunfish.packed
- name: compressed.py is current (root artifact matches sunfish.py)
# compressed.py is the committed engine core (Position/Searcher,
# numbfish-style) the README links to -- a READING artifact, not a
# runnable script (no imports/tables by design), regenerated by
# tools/hooks/pre-commit via tools/build/compress.py (stdlib-only,
# deterministic). Text: direct byte compare, plus a parse check.
run: |
python3 tools/build/compress.py sunfish.py > fresh.py
cmp fresh.py compressed.py \
|| { echo "compressed.py is stale: run 'python3 tools/build/compress.py sunfish.py > compressed.py'"; \
echo "(or enable the hook once: git config core.hooksPath tools/hooks)"; exit 1; }
rm fresh.py
python3 -c "import ast; ast.parse(open('compressed.py').read())"
n=$(wc -l < compressed.py | tr -d ' ')
grep -qF "just [$n lines of code](compressed.py)" README.md \
|| { echo "README line-count claim is stale: compressed.py has $n lines"; exit 1; }
- name: 4k entry is fresh and under budget
# nnue_4k/pst_entry.py is GENERATED. That is only worth something if it
# stays true: the committed source once drifted 38 lines behind its
# generator with nobody noticing, because the drift was inside
# minifier-hide and the packed bytes were unchanged. Checks the source
# against the generator AND the artifact against the 4096-byte ceiling.
run: |
pip install python-minifier==3.2.0
bash tools/build/check_entry.sh
- name: Minify test (tools/build/clean.sh)
# Assert the minified engine actually runs.
run: |
bash tools/build/clean.sh sunfish.py > clean_sunfish.py
{ printf 'uci\nisready\nposition startpos\ngo wtime 4000 winc 0\n'; sleep 3; printf 'quit\n'; } \
| timeout 60 python clean_sunfish.py | tee /dev/stderr | grep -q bestmove
rm clean_sunfish.py
- name: Tournament manager match (fastchess)
# Play quick self-play matches under a real tournament manager, like the
# ones TCEC and major GUIs use: one TC with increment, one sudden death.
# (cutechess-cli publishes no Linux binaries, so we use fastchess, its
# command-line-compatible successor used by Stockfish testing.)
run: |
curl -sL https://github.com/Disservin/fastchess/releases/download/v1.8.2-alpha/fastchess-linux-x86-64.tar \
| tar x fastchess-linux-x86-64/fastchess
./fastchess-linux-x86-64/fastchess \
-engine cmd=./sunfish.py name=sunfish-a -engine cmd=./sunfish.py name=sunfish-b \
-each proto=uci tc=8+0.1 -rounds 1 -games 2 | tee cc.log
./fastchess-linux-x86-64/fastchess \
-engine cmd=./sunfish.py name=sunfish-a -engine cmd=./sunfish.py name=sunfish-b \
-each proto=uci tc=8 -rounds 1 -games 2 | tee -a cc.log
# Time losses are tolerated at these speeds; crashes and rule bugs are not
! grep -Ei "disconnect|stall|illegal|invalid" cc.log
- name: UCI smoke test (sunfish_nnue)
# The NNUE version lives on the nnue branch until it works well.
if: hashFiles('sunfish_nnue.py') != ''
continue-on-error: true
run: |
pip install "numpy<2"
printf 'uci\nisready\nposition startpos moves e2e4\ngo movetime 500\nquit\n' \
| python ./sunfish_nnue.py nnue/models/tanh.pickle | tee /dev/stderr | grep -q bestmove
- name: Pytest suite
run: |
# exit code 5 = nothing collected (all gated tests skipped): fine
if [ -d tests ]; then python -m pytest tests -q || [ $? -eq 5 ]; else echo "no tests/ directory on this branch"; fi
- name: Packed NNUE suite (nnue_4k)
run: |
if [ -d nnue_4k/tests ]; then python -m pytest nnue_4k/tests -q || [ $? -eq 5 ]; fi
- name: Packed NNUE verify battery
run: |
if [ -d nnue_4k ]; then python nnue_4k/packed/verify.py nnue_4k/sunfish_nnue.py nnue_4k/packed/net128.sfnn 60 10; fi
bot-integration:
# The lichess-bot bridge needs Python >= 3.11, so this runs as its own
# job on CPython (the engine's polyglot shebang falls back to python3).
# Real lichess-bot process (pinned commit + the bundle's production
# patch) against tests/mock_lichess.py with pondering on and realistic
# opponent pacing; catches UCI protocol races, time-forfeit regressions,
# the concurrency-overflow starvation, event-stream deafness, and the
# mid-game-restart resume (~6 min). The static bundle checks (greeting
# lengths, pins, patch presence) run first -- they fail in seconds.
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: '3.12'
- name: Install dependencies
run: pip install chess pytest pyyaml
- name: Lichess bundle static checks
run: python -m pytest tests/test_lichess_config.py -q
- name: Lichess bot integration test (mock server)
run: BOT_CI=1 python -m pytest tests/test_bot_integration.py -q
timeout-minutes: 15
windows-smoke:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: UCI smoke test on Windows
run: |
python -c "import subprocess,sys; p=subprocess.run([sys.executable,'-u','sunfish.py'],input=b'uci\nisready\nposition startpos\ngo movetime 300\nquit\n',capture_output=True,timeout=120); print(p.stdout.decode()); print('STDERR:', p.stderr.decode(), file=sys.stderr); sys.exit(0 if ('uciok' in p.stdout.decode() and 'bestmove' in p.stdout.decode()) else 1)"
- name: Installed entry point on Windows
# The bare 'sunfish' entry is the play interface; the engine is
# sunfish-uci. Also checks that the wheel ships sunfish_ui/, the real UCI
# interface: sunfish.py imports it unconditionally, and from this
# Black-to-move FEN the packed loop would answer from rank 1 or 2
# (issue #156). Run from a scratch directory so the checkout cannot
# satisfy the import.
run: |
pip install .
python -c "import subprocess,sys,tempfile,re; p=subprocess.run(['sunfish-uci'],input=b'uci\nisready\nposition fen rnbqkbnr/ppp1p1pp/8/3pPp2/8/8/PPPP1PPP/RNBQKBNR b KQkq - 1 4\ngo movetime 300\nquit\n',capture_output=True,timeout=120,shell=True,cwd=tempfile.mkdtemp()); out=p.stdout.decode(); print(out); print('STDERR:', p.stderr.decode(), file=sys.stderr); bm=re.findall(r'^bestmove (\S+)', out, re.M); sys.exit(0 if ('uciok' in out and bm and not re.match(r'[a-h][12]', bm[-1])) else 1)"