forked from JeanExtreme002/PyMemoryEditor
-
Notifications
You must be signed in to change notification settings - Fork 0
358 lines (348 loc) · 15.4 KB
/
Copy pathpython-package.yml
File metadata and controls
358 lines (348 loc) · 15.4 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
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
# This workflow will install Python dependencies, run tests and lint with a variety of Python versions
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
name: Python Package
on:
# `push` restricted to `main` so feature branches only run via `pull_request`.
# Otherwise every push to a branch with an open PR would trigger the workflow
# twice (once for `push`, once for `pull_request`) — doubling CI cost and
# latency for no benefit.
push:
branches: [main]
pull_request:
# Allow re-running the workflow without an empty push (handy after the
# workflow gets `disabled_inactivity` after 60 days idle).
workflow_dispatch:
schedule:
- cron: '0 0 */7 * *'
# Cancel an in-flight run when a newer commit lands on the same ref. Keeps the
# queue lean and stops stale runs from blocking the merge button.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install lint deps
run: |
python -m pip install --upgrade pip
pip install flake8
- name: Lint
run: |
flake8 PyMemoryEditor tests
type-check:
needs: lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install dev deps
# Include the `speed` extra (NumPy) so mypy type-checks the NumPy fast
# path against real stubs. Without it, `import numpy` resolves to `Any`
# and the `# type: ignore` in scan_numpy.py reads as unused.
run: |
python -m pip install --upgrade pip
pip install -e ".[dev,speed]"
- name: Run mypy
run: |
mypy PyMemoryEditor
build:
needs: lint
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
python-version: ['3.10', '3.11', '3.12', '3.13']
os:
- ubuntu-latest
- windows-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install Qt system libraries (Linux)
# PySide6 links against libEGL/libGL/libxkbcommon/libfontconfig and the
# XCB stack at import time, even when running under the `offscreen`
# platform plugin. The Ubuntu runner ships without them, so pytest-qt's
# `import QtGui` crashes with `libEGL.so.1: cannot open shared object`.
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
libegl1 \
libgl1 \
libxkbcommon0 \
libfontconfig1 \
libdbus-1-3 \
libxcb-cursor0 \
libxcb-icccm4 \
libxcb-image0 \
libxcb-keysyms1 \
libxcb-randr0 \
libxcb-render-util0 \
libxcb-shape0 \
libxcb-sync1 \
libxcb-xfixes0 \
libxcb-xinerama0 \
libxcb-xkb1 \
libxkbcommon-x11-0
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -e ".[dev]"
- name: Test with pytest
env:
QT_QPA_PLATFORM: offscreen
# `--ignore=tests/mcp_server`: the MCP server has its own job
# (`build-mcp`) so this matrix keeps measuring the library and the app,
# at the wall clock it always had. Its tests walk a live process's whole
# address space, which on macOS is ~390 GiB (see build-macos below) --
# running them in every cell of a 4x2 matrix plus macOS plus speed paid
# that cost eight times over, and it pushed build-macos past its
# 20-minute budget.
#
# --cov-fail-under is the real, in-repo coverage gate (deterministic and
# independent of any external service), and `--cov-config` is what keeps
# it meaningful now that the MCP suite lives in `build-mcp`.
#
# Without it this job measures PyMemoryEditor/mcp -- 918 statements it
# never runs -- as uncovered, which is a measurement of absence, not of
# quality. It also varies by platform in a way the gate cannot absorb:
# measured that way, Linux came out at 46% and macOS at 59%, so a floor
# picked from macOS alone failed all eight matrix cells. Scoped to the
# library, the numbers are 87% on macOS and 66% on Linux, and 60 is the
# same floor this job carried before the MCP server existed.
#
# (coverage.py only counts the host backend + shared code -- the two
# foreign backends aren't imported, which is most of that platform
# spread.) Ratchet up once the numbers settle on Codecov.
run: |
pytest tests --ignore=tests/mcp_server -v -s -x --cov=PyMemoryEditor --cov-config=.coveragerc-lib --cov-report=term --cov-report=xml --cov-fail-under=60
- name: Upload coverage to Codecov
# Upload from every matrix cell so Codecov merges each OS's backend
# coverage into one combined view (each platform exercises a different
# win32/linux/macos backend). Informational only — see codecov.yml — so a
# flaky upload never blocks the merge; the hard gate is --cov-fail-under
# above. Runs even when tests fail so partial coverage stays visible.
if: always()
uses: codecov/codecov-action@v5
with:
files: ./coverage.xml
flags: ${{ runner.os }}-py${{ matrix.python-version }}
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: false
# macOS gets a single cell instead of a row in the matrix above. Its only
# purpose is to measure the `macos` backend so the merged Codecov report
# covers all three platforms; the Python-version sweep stays on Linux and
# Windows, where it costs a fraction of the wall clock.
#
# Unlike the matrix, this job installs `.[dev,speed]` (NumPy). That is not a
# preference — it is what makes a macOS cell viable at all. A macOS process
# exposes ~390 GiB of sparse address space across ~145 regions, two orders of
# magnitude more than a Linux one, and the scan tests walk all of it. Measured
# on an M-series Mac, this exact command takes ~82s with NumPy and 681s
# without: an 8x gap that a 3-core runner only widens, which is what made
# macOS look like it hung here before (it was removed in 041bfc0).
# The pure-Python scan path loses nothing — all eight cells of the matrix
# above still exercise it.
build-macos:
needs: lint
runs-on: macos-latest
# Hard ceiling so a pathological scan can never sit at the 6h default.
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
# No Qt system-library step here: the PySide6 macOS wheel bundles its own
# Qt frameworks, unlike the Linux one.
- name: Install dependencies (with NumPy speed extra)
run: |
python -m pip install --upgrade pip
pip install -e ".[dev,speed]"
- name: Test with pytest
env:
QT_QPA_PLATFORM: offscreen
# Same conservative coverage gate as the main matrix (see that step's note).
run: |
pytest tests --ignore=tests/mcp_server -v -s -x --cov=PyMemoryEditor --cov-config=.coveragerc-lib --cov-report=term --cov-report=xml --cov-fail-under=60
- name: Upload coverage to Codecov
# Flag mirrors the matrix format (`runner.os` is "macOS" here) so Codecov
# groups this cell alongside the Linux and Windows uploads.
if: always()
uses: codecov/codecov-action@v5
with:
files: ./coverage.xml
flags: macOS-py3.12
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: false
# Isolated job for the optional NumPy-accelerated scan fast path (the
# `[speed]` extra). The matrix above deliberately runs WITHOUT NumPy so the
# pure-Python scan path stays covered; this single Linux job installs
# `.[speed]` on top of the dev deps to exercise the vectorized path.
build-speed:
needs: lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install Qt system libraries (Linux)
# PySide6 links against libEGL/libGL/libxkbcommon/libfontconfig and the
# XCB stack at import time, even when running under the `offscreen`
# platform plugin. The Ubuntu runner ships without them, so pytest-qt's
# `import QtGui` crashes with `libEGL.so.1: cannot open shared object`.
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
libegl1 \
libgl1 \
libxkbcommon0 \
libfontconfig1 \
libdbus-1-3 \
libxcb-cursor0 \
libxcb-icccm4 \
libxcb-image0 \
libxcb-keysyms1 \
libxcb-randr0 \
libxcb-render-util0 \
libxcb-shape0 \
libxcb-sync1 \
libxcb-xfixes0 \
libxcb-xinerama0 \
libxcb-xkb1 \
libxkbcommon-x11-0
- name: Install dependencies (with NumPy speed extra)
run: |
python -m pip install --upgrade pip
pip install -e ".[dev,speed]"
- name: Test with pytest (NumPy fast path)
env:
QT_QPA_PLATFORM: offscreen
# Same conservative coverage gate as the main matrix (see that step's note).
run: |
pytest tests --ignore=tests/mcp_server -v -s -x --cov=PyMemoryEditor --cov-config=.coveragerc-lib --cov-report=term --cov-report=xml --cov-fail-under=60
- name: Upload coverage to Codecov
if: always()
uses: codecov/codecov-action@v5
with:
files: ./coverage.xml
flags: speed-Linux-py3.12
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: false
# The MCP server gets its own job rather than a row in the matrix above.
#
# Its tests are the expensive ones: `test_live.py` and half of
# `test_fake_fidelity.py` attach to the running test process and scan its
# entire address space, which is exactly the workload the build-macos note
# below measures at ~390 GiB. Running them in all ten cells of the other
# jobs multiplied that by ten and timed macOS out; running them once per OS
# keeps the platform coverage that actually matters and costs a fraction.
#
# One Python version, three operating systems -- the opposite shape from the
# matrix above, and deliberately so. Nothing in this package is
# version-sensitive (it is plain 3.10+ typing over the library's own API),
# but plenty of it is platform-sensitive: the Windows-only read-only handle,
# macOS `task_for_pid`, and the per-platform process enumeration and denylist.
# The one Windows-only bug this suite has caught so far was a test reading a
# UTF-8 file with the locale's encoding, which only cp1252 could fail on.
#
# NumPy is installed for the same reason build-macos installs it: without the
# vectorized scan path, walking a macOS address space takes ~8x longer.
build-mcp:
needs: lint
runs-on: ${{ matrix.os }}
# Hard ceiling, same purpose as build-macos'. The suite itself takes
# ~15s of pytest on all three runners; this is generous on purpose.
#
# It was briefly 45 while this job took 20+ minutes everywhere. The cause
# was not scan cost: one test parametrized a 200 000-character value, so
# its pytest id was 400 KB, and the Actions log uploader spent ~150s per
# such line. Five lines, twenty minutes. Fixed at the test.
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
# `scan_heavy` -- the tests that scan a *live* process's whole address
# space -- runs on Linux only. The cost is wildly platform-dependent: a
# macOS process exposes ~390 GiB across ~145 regions and Windows is not
# much better, so on a runner each of those scans is minutes. Measured
# on the previous attempt: this job took 22m21s on macOS and hit its
# timeout at 30m on Windows, while the same suite is 18s locally.
#
# Nothing is lost by pinning them to Linux. What the other two OSes are
# here for is the platform-specific code -- the Windows-only read-only
# handle, macOS task_for_pid, per-platform enumeration and the
# truncated-name denylist -- none of which are scans. The library's own
# live-scan coverage on those platforms comes from `build` and
# `build-macos`.
#
# Set through PYTEST_ADDOPTS rather than the run: line because the
# Windows runner's default shell is pwsh, and `-m "not scan_heavy"`
# would need different quoting there than on bash.
include:
- os: ubuntu-latest
addopts: ''
- os: windows-latest
addopts: '-m "not scan_heavy"'
- os: macos-latest
addopts: '-m "not scan_heavy"'
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install dependencies (MCP SDK + NumPy speed extra)
run: |
python -m pip install --upgrade pip
pip install -e ".[dev,speed]"
- name: Test the MCP server
# Coverage is scoped to the package under test, via both --cov and
# .coveragerc-mcp. Measured against the whole package instead, this gate
# came out at 71% on macOS but 60.4% on Linux -- four tenths of a point
# above a floor of 60, on a number mostly about code this job does not
# target. Scoped, the MCP package is 91% covered by its own suite on
# macOS and 90.4% on Linux, so 80 leaves real headroom.
env:
PYTEST_ADDOPTS: ${{ matrix.addopts }}
# `-q --durations=25` rather than the `-v -s` the library jobs use. This
# job's timing is the open question -- 20 minutes on a runner against
# 12.5s in a container, and cutting the process-table walks 14x changed
# nothing -- and `-v -s` emitted 977 log lines in the first 8 seconds,
# so GitHub truncated the log long before the part that would say which
# tests were slow. Quiet output keeps the log small enough that the
# duration table at the end survives.
#
# -p no:pytest-qt: the dev extra brings pytest-qt, whose plugin imports
# QtGui at startup. The name is the entry point's, hyphen included --
# `-p no:pytestqt` silently does nothing, which is how this failed twice.
# It dies with `ImportError: libEGL.so.1` on a bare Linux runner; the
# library jobs apt-install a pile of X libraries for it. This job runs no
# Qt tests, so it disables the plugin instead of paying for them.
run: |
pytest tests/mcp_server -p no:pytest-qt -q --durations=25 -x --cov=PyMemoryEditor/mcp --cov-config=.coveragerc-mcp --cov-report=term --cov-report=xml --cov-fail-under=80
- name: Upload coverage to Codecov
# Its own flag, so the merged report shows MCP coverage alongside the
# library's instead of one overwriting the other.
if: always()
uses: codecov/codecov-action@v5
with:
files: ./coverage.xml
flags: mcp-${{ runner.os }}-py3.12
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: false