flashgate 0.5.1__tar.gz → 0.5.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {flashgate-0.5.1 → flashgate-0.5.2}/PKG-INFO +10 -2
- {flashgate-0.5.1 → flashgate-0.5.2}/README.md +5 -0
- {flashgate-0.5.1 → flashgate-0.5.2}/flashgate/mcp_server.py +89 -22
- {flashgate-0.5.1 → flashgate-0.5.2}/flashgate.egg-info/PKG-INFO +10 -2
- {flashgate-0.5.1 → flashgate-0.5.2}/flashgate.egg-info/requires.txt +1 -1
- {flashgate-0.5.1 → flashgate-0.5.2}/pyproject.toml +11 -2
- {flashgate-0.5.1 → flashgate-0.5.2}/tests/test_cli.py +2 -11
- {flashgate-0.5.1 → flashgate-0.5.2}/tests/test_mcp.py +30 -0
- {flashgate-0.5.1 → flashgate-0.5.2}/LICENSE +0 -0
- {flashgate-0.5.1 → flashgate-0.5.2}/flashgate/__init__.py +0 -0
- {flashgate-0.5.1 → flashgate-0.5.2}/flashgate/__main__.py +0 -0
- {flashgate-0.5.1 → flashgate-0.5.2}/flashgate/board.py +0 -0
- {flashgate-0.5.1 → flashgate-0.5.2}/flashgate/cli.py +0 -0
- {flashgate-0.5.1 → flashgate-0.5.2}/flashgate/flasher.py +0 -0
- {flashgate-0.5.1 → flashgate-0.5.2}/flashgate/gatestate.py +0 -0
- {flashgate-0.5.1 → flashgate-0.5.2}/flashgate/probes.py +0 -0
- {flashgate-0.5.1 → flashgate-0.5.2}/flashgate/results.py +0 -0
- {flashgate-0.5.1 → flashgate-0.5.2}/flashgate/serialmon.py +0 -0
- {flashgate-0.5.1 → flashgate-0.5.2}/flashgate/sttools.py +0 -0
- {flashgate-0.5.1 → flashgate-0.5.2}/flashgate/swdsig.py +0 -0
- {flashgate-0.5.1 → flashgate-0.5.2}/flashgate.egg-info/SOURCES.txt +0 -0
- {flashgate-0.5.1 → flashgate-0.5.2}/flashgate.egg-info/dependency_links.txt +0 -0
- {flashgate-0.5.1 → flashgate-0.5.2}/flashgate.egg-info/entry_points.txt +0 -0
- {flashgate-0.5.1 → flashgate-0.5.2}/flashgate.egg-info/top_level.txt +0 -0
- {flashgate-0.5.1 → flashgate-0.5.2}/setup.cfg +0 -0
- {flashgate-0.5.1 → flashgate-0.5.2}/tests/test_board.py +0 -0
- {flashgate-0.5.1 → flashgate-0.5.2}/tests/test_gatestate.py +0 -0
- {flashgate-0.5.1 → flashgate-0.5.2}/tests/test_probes.py +0 -0
- {flashgate-0.5.1 → flashgate-0.5.2}/tests/test_swdsig.py +0 -0
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: flashgate
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.2
|
|
4
4
|
Summary: Hardware-in-the-loop verification gate: your agent can't claim the firmware works until the board says so.
|
|
5
5
|
License: MIT
|
|
6
|
+
Project-URL: Homepage, https://github.com/Lion-1209/flashgate
|
|
7
|
+
Project-URL: Repository, https://github.com/Lion-1209/flashgate
|
|
8
|
+
Project-URL: Issues, https://github.com/Lion-1209/flashgate/issues
|
|
6
9
|
Requires-Python: >=3.11
|
|
7
10
|
Description-Content-Type: text/markdown
|
|
8
11
|
License-File: LICENSE
|
|
9
12
|
Requires-Dist: pyserial>=3.5
|
|
10
13
|
Requires-Dist: PyYAML>=6.0
|
|
11
14
|
Provides-Extra: mcp
|
|
12
|
-
Requires-Dist: mcp
|
|
15
|
+
Requires-Dist: mcp<3,>=1.2; extra == "mcp"
|
|
13
16
|
Provides-Extra: dev
|
|
14
17
|
Requires-Dist: pytest>=8; extra == "dev"
|
|
15
18
|
Dynamic: license-file
|
|
@@ -83,6 +86,11 @@ fresh clone verifies out of the box once wired up.
|
|
|
83
86
|
| 6 | environment error (no ST-Link / serial / tools) — including probes explicitly required via `--probe`/`--all-probes` but the console UART is unavailable: a check that cannot run never counts as a pass |
|
|
84
87
|
| 7 | functional probe failed |
|
|
85
88
|
|
|
89
|
+
## Contributing
|
|
90
|
+
|
|
91
|
+
Board profiles, probes, fixes and docs are welcome — see
|
|
92
|
+
[CONTRIBUTING.md](CONTRIBUTING.md) for the setup and contribution terms.
|
|
93
|
+
|
|
86
94
|
## The Stop hook
|
|
87
95
|
|
|
88
96
|
`hooks/flashgate_stop.py` is a Claude Code Stop hook (any harness
|
|
@@ -67,6 +67,11 @@ fresh clone verifies out of the box once wired up.
|
|
|
67
67
|
| 6 | environment error (no ST-Link / serial / tools) — including probes explicitly required via `--probe`/`--all-probes` but the console UART is unavailable: a check that cannot run never counts as a pass |
|
|
68
68
|
| 7 | functional probe failed |
|
|
69
69
|
|
|
70
|
+
## Contributing
|
|
71
|
+
|
|
72
|
+
Board profiles, probes, fixes and docs are welcome — see
|
|
73
|
+
[CONTRIBUTING.md](CONTRIBUTING.md) for the setup and contribution terms.
|
|
74
|
+
|
|
70
75
|
## The Stop hook
|
|
71
76
|
|
|
72
77
|
`hooks/flashgate_stop.py` is a Claude Code Stop hook (any harness
|
|
@@ -139,8 +139,17 @@ def _safe(policy: dict):
|
|
|
139
139
|
@_register(annotations=_ann(readOnlyHint=True), structured=True)
|
|
140
140
|
@_safe(P_READ)
|
|
141
141
|
def board_info(board: str | None = None) -> results.Result:
|
|
142
|
-
"""
|
|
143
|
-
|
|
142
|
+
"""Discover what hardware you are working with: the active board
|
|
143
|
+
profile.
|
|
144
|
+
|
|
145
|
+
Call this FIRST in any session — it tells you the board identity
|
|
146
|
+
(name, MCU), where the firmware builds (firmware_dir, artifact), how
|
|
147
|
+
it flashes (flash connection), and — most importantly — which
|
|
148
|
+
functional probes exist (the "probes" list holds the names the probe
|
|
149
|
+
tool accepts). Read-only; touches neither host nor board.
|
|
150
|
+
|
|
151
|
+
Result: data carries the full profile; warnings flag an unloadable
|
|
152
|
+
probes section (fix the board YAML before relying on probes)."""
|
|
144
153
|
try:
|
|
145
154
|
b = _board(board)
|
|
146
155
|
except BoardError as exc:
|
|
@@ -150,7 +159,7 @@ def board_info(board: str | None = None) -> results.Result:
|
|
|
150
159
|
warnings: list[str] = []
|
|
151
160
|
try:
|
|
152
161
|
probes = list(probe_mod.load_probes(b.yaml_path))
|
|
153
|
-
except (OSError, ValueError) as exc:
|
|
162
|
+
except (OSError, ValueError, probe_mod.ProbeError) as exc:
|
|
154
163
|
warnings.append(f"probes unloadable: {exc}")
|
|
155
164
|
r = results.ok(
|
|
156
165
|
f"{b.name} ({b.mcu}) — {len(probes)} probe(s) defined",
|
|
@@ -173,8 +182,14 @@ def board_info(board: str | None = None) -> results.Result:
|
|
|
173
182
|
@_register(annotations=_ann(readOnlyHint=True), structured=True)
|
|
174
183
|
@_safe(P_READ)
|
|
175
184
|
def doctor(board: str | None = None) -> results.Result:
|
|
176
|
-
"""
|
|
177
|
-
|
|
185
|
+
"""Diagnose the bench: is the ST-Link probe visible, is the console
|
|
186
|
+
serial port resolvable, is the cross toolchain installed?
|
|
187
|
+
|
|
188
|
+
Run this before anything else when a board operation fails — and
|
|
189
|
+
ALWAYS when a tool returns status "incomplete" or code
|
|
190
|
+
CAPABILITY_UNAVAILABLE; the report names the exact missing link.
|
|
191
|
+
Read-only. Findings are listed in data.log under "issues:"; an empty
|
|
192
|
+
problems list with exit_code 0 means every prerequisite is healthy."""
|
|
178
193
|
try:
|
|
179
194
|
rc, log = _capture(cli_mod.cmd_doctor, _board(board))
|
|
180
195
|
except BoardError as exc:
|
|
@@ -186,7 +201,14 @@ def doctor(board: str | None = None) -> results.Result:
|
|
|
186
201
|
@_register(annotations=_ann(idempotentHint=True), structured=True)
|
|
187
202
|
@_safe(P_BUILD)
|
|
188
203
|
def build(board: str | None = None) -> results.Result:
|
|
189
|
-
"""
|
|
204
|
+
"""Compile the board's firmware (incremental — recompiles only what
|
|
205
|
+
changed). No hardware is touched.
|
|
206
|
+
|
|
207
|
+
Mostly useful on its own for a fast failure loop; verify runs the
|
|
208
|
+
same build internally, so you don't need this before verify. Result:
|
|
209
|
+
succeeded + exit_code 0 = clean build; failed + BUILD_FAILED = the
|
|
210
|
+
compiler output in data.log names the offending file and line. The
|
|
211
|
+
binary lands at the profile's artifact path, ready for flash."""
|
|
190
212
|
try:
|
|
191
213
|
rc, log = _capture(cli_mod.cmd_build, _board(board))
|
|
192
214
|
except BoardError as exc:
|
|
@@ -198,8 +220,16 @@ def build(board: str | None = None) -> results.Result:
|
|
|
198
220
|
@_register(annotations=_ann(destructiveHint=True), structured=True)
|
|
199
221
|
@_safe(P_FLASH)
|
|
200
222
|
def flash(board: str | None = None) -> results.Result:
|
|
201
|
-
"""
|
|
202
|
-
|
|
223
|
+
"""Write the built firmware to the board's flash over ST-Link,
|
|
224
|
+
verify the write, then start the application. DESTRUCTIVE: whatever
|
|
225
|
+
runs on the MCU is replaced.
|
|
226
|
+
|
|
227
|
+
Flashes the artifact from the last build — call build first if
|
|
228
|
+
sources changed since. Tries up to 3 times on transient probe
|
|
229
|
+
errors. Any failure — including a missing ST-Link or CubeProgrammer —
|
|
230
|
+
surfaces as failed + FLASH_FAILED, exit_code 2; data.log carries the
|
|
231
|
+
programmer output. Run doctor to tell an environment problem (no
|
|
232
|
+
probe / no tools) apart from a write failure."""
|
|
203
233
|
try:
|
|
204
234
|
rc, log = _capture(cli_mod.cmd_flash, _board(board))
|
|
205
235
|
except BoardError as exc:
|
|
@@ -211,12 +241,27 @@ def flash(board: str | None = None) -> results.Result:
|
|
|
211
241
|
@_register(annotations=_ann(destructiveHint=True), structured=True)
|
|
212
242
|
@_safe(P_VERIFY)
|
|
213
243
|
def verify(board: str | None = None) -> results.Result:
|
|
214
|
-
"""The
|
|
215
|
-
|
|
216
|
-
the
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
244
|
+
"""The hardware gate — the authoritative answer to "does this
|
|
245
|
+
firmware actually work on the board?". status=succeeded means the
|
|
246
|
+
BOARD ITSELF booted the exact tree you are on (its reported git sha
|
|
247
|
+
matches the working tree) and every functional probe passed. Trust no
|
|
248
|
+
other signal; a clean build is not a pass.
|
|
249
|
+
|
|
250
|
+
What it does: rebuild the tree, flash over ST-Link, start the app,
|
|
251
|
+
then require boot evidence proving WHICH build is running (UART
|
|
252
|
+
banner; or, without a serial cable, an SWD RAM signature — wiped
|
|
253
|
+
first, best-effort, so a stale one cannot lie), then run every
|
|
254
|
+
defined probe, asserting on the board's answers — including live
|
|
255
|
+
register readbacks where the board provides them. DESTRUCTIVE:
|
|
256
|
+
rewrites the board's flash.
|
|
257
|
+
|
|
258
|
+
The failure code names the broken stage — BUILD_FAILED (compile),
|
|
259
|
+
FLASH_FAILED (write/start), BOOT_EVIDENCE_TIMEOUT (board silent),
|
|
260
|
+
BOOT_ERROR (fault string on serial), IDENTITY_MISMATCH (board runs a
|
|
261
|
+
different tree), CAPABILITY_UNAVAILABLE (a required check could not
|
|
262
|
+
run — most often probes needing the console UART, which is missing or
|
|
263
|
+
held by another program), PROBE_FAILED (a functional assertion did
|
|
264
|
+
not hold). Full transcript in data.log."""
|
|
220
265
|
try:
|
|
221
266
|
rc, log = _capture(cli_mod.cmd_verify, _board(board), ["all"])
|
|
222
267
|
except BoardError as exc:
|
|
@@ -228,10 +273,17 @@ def verify(board: str | None = None) -> results.Result:
|
|
|
228
273
|
@_register(annotations=_ann(readOnlyHint=False), structured=True)
|
|
229
274
|
@_safe(P_PROBE)
|
|
230
275
|
def probe(names: list[str] | None = None, board: str | None = None) -> results.Result:
|
|
231
|
-
"""
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
276
|
+
"""Exercise the firmware ALREADY RUNNING on the board with its
|
|
277
|
+
defined probes: each probe sends console commands and asserts on the
|
|
278
|
+
board's answers — including live register readbacks (e.g. the timer
|
|
279
|
+
value actually driving an LED). No rebuild, no reflash.
|
|
280
|
+
|
|
281
|
+
names picks specific probes (valid names are in board_info's
|
|
282
|
+
"probes" list); omit it or pass [] to run all. Probes need the
|
|
283
|
+
console UART: without it the result is incomplete +
|
|
284
|
+
CAPABILITY_UNAVAILABLE — never a pass. failed + PROBE_FAILED means an
|
|
285
|
+
assertion did not hold; data.log shows the exact step, the board's
|
|
286
|
+
last response, and which value was off."""
|
|
235
287
|
try:
|
|
236
288
|
b = _board(board)
|
|
237
289
|
except BoardError as exc:
|
|
@@ -270,8 +322,17 @@ def probe(names: list[str] | None = None, board: str | None = None) -> results.R
|
|
|
270
322
|
@_register(annotations=_ann(readOnlyHint=False, openWorldHint=True), structured=True)
|
|
271
323
|
@_safe(P_CONSOLE_SEND)
|
|
272
324
|
def console_send(line: str, wait_s: float = 1.0, board: str | None = None) -> results.Result:
|
|
273
|
-
"""
|
|
274
|
-
|
|
325
|
+
"""Talk to the firmware directly: send ONE command line on the
|
|
326
|
+
console UART and collect its answer.
|
|
327
|
+
|
|
328
|
+
Example: line "led0?" — the firmware replies with its LED state and
|
|
329
|
+
the live PWM register value (a hardware readback, not a cached
|
|
330
|
+
variable). The command set is board-specific — board_info and the
|
|
331
|
+
board's probe definitions show the known commands; unknown commands
|
|
332
|
+
typically answer "ERR unknown-cmd". The reply lands in
|
|
333
|
+
data.response within wait_s seconds; silence is reported as
|
|
334
|
+
"(no response)" in the summary + a warning — check wiring before
|
|
335
|
+
concluding the firmware is dead."""
|
|
275
336
|
try:
|
|
276
337
|
b = _board(board)
|
|
277
338
|
except BoardError as exc:
|
|
@@ -307,8 +368,14 @@ def console_send(line: str, wait_s: float = 1.0, board: str | None = None) -> re
|
|
|
307
368
|
@_register(annotations=_ann(readOnlyHint=True), structured=True)
|
|
308
369
|
@_safe(P_READ)
|
|
309
370
|
def console_read(seconds: float = 2.0, board: str | None = None) -> results.Result:
|
|
310
|
-
"""
|
|
311
|
-
|
|
371
|
+
"""Listen passively: capture everything the firmware prints on the
|
|
372
|
+
console for the given number of seconds, sending nothing.
|
|
373
|
+
|
|
374
|
+
Use it to watch boot banners, self-test prints, or fault dumps
|
|
375
|
+
(HardFault traces appear here). Output lands in data.text; a fully
|
|
376
|
+
silent window is reported as "(console silent)" in the summary —
|
|
377
|
+
which for a just-reset board usually means wiring or baud-rate
|
|
378
|
+
trouble rather than a healthy quiet firmware. Read-only."""
|
|
312
379
|
try:
|
|
313
380
|
b = _board(board)
|
|
314
381
|
except BoardError as exc:
|
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: flashgate
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.2
|
|
4
4
|
Summary: Hardware-in-the-loop verification gate: your agent can't claim the firmware works until the board says so.
|
|
5
5
|
License: MIT
|
|
6
|
+
Project-URL: Homepage, https://github.com/Lion-1209/flashgate
|
|
7
|
+
Project-URL: Repository, https://github.com/Lion-1209/flashgate
|
|
8
|
+
Project-URL: Issues, https://github.com/Lion-1209/flashgate/issues
|
|
6
9
|
Requires-Python: >=3.11
|
|
7
10
|
Description-Content-Type: text/markdown
|
|
8
11
|
License-File: LICENSE
|
|
9
12
|
Requires-Dist: pyserial>=3.5
|
|
10
13
|
Requires-Dist: PyYAML>=6.0
|
|
11
14
|
Provides-Extra: mcp
|
|
12
|
-
Requires-Dist: mcp
|
|
15
|
+
Requires-Dist: mcp<3,>=1.2; extra == "mcp"
|
|
13
16
|
Provides-Extra: dev
|
|
14
17
|
Requires-Dist: pytest>=8; extra == "dev"
|
|
15
18
|
Dynamic: license-file
|
|
@@ -83,6 +86,11 @@ fresh clone verifies out of the box once wired up.
|
|
|
83
86
|
| 6 | environment error (no ST-Link / serial / tools) — including probes explicitly required via `--probe`/`--all-probes` but the console UART is unavailable: a check that cannot run never counts as a pass |
|
|
84
87
|
| 7 | functional probe failed |
|
|
85
88
|
|
|
89
|
+
## Contributing
|
|
90
|
+
|
|
91
|
+
Board profiles, probes, fixes and docs are welcome — see
|
|
92
|
+
[CONTRIBUTING.md](CONTRIBUTING.md) for the setup and contribution terms.
|
|
93
|
+
|
|
86
94
|
## The Stop hook
|
|
87
95
|
|
|
88
96
|
`hooks/flashgate_stop.py` is a Claude Code Stop hook (any harness
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "flashgate"
|
|
7
|
-
version = "0.5.
|
|
7
|
+
version = "0.5.2"
|
|
8
8
|
description = "Hardware-in-the-loop verification gate: your agent can't claim the firmware works until the board says so."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -14,13 +14,22 @@ dependencies = [
|
|
|
14
14
|
"PyYAML>=6.0",
|
|
15
15
|
]
|
|
16
16
|
|
|
17
|
+
[project.urls]
|
|
18
|
+
Homepage = "https://github.com/Lion-1209/flashgate"
|
|
19
|
+
Repository = "https://github.com/Lion-1209/flashgate"
|
|
20
|
+
Issues = "https://github.com/Lion-1209/flashgate/issues"
|
|
21
|
+
|
|
17
22
|
[project.optional-dependencies]
|
|
18
|
-
mcp = ["mcp>=1.2"]
|
|
23
|
+
mcp = ["mcp>=1.2,<3"]
|
|
19
24
|
dev = ["pytest>=8"]
|
|
20
25
|
|
|
21
26
|
[project.scripts]
|
|
22
27
|
flashgate = "flashgate.cli:main"
|
|
23
28
|
flashgate-mcp = "flashgate.mcp_server:main"
|
|
24
29
|
|
|
30
|
+
[tool.pytest.ini_options]
|
|
31
|
+
# keep pytest from climbing into parent workspaces for config
|
|
32
|
+
testpaths = ["tests"]
|
|
33
|
+
|
|
25
34
|
[tool.setuptools.packages.find]
|
|
26
35
|
include = ["flashgate*"]
|
|
@@ -148,23 +148,14 @@ class TestAutoRouting:
|
|
|
148
148
|
assert seen["mode"] == "uart"
|
|
149
149
|
|
|
150
150
|
|
|
151
|
-
class TestEnvelopeCoupling:
|
|
152
|
-
def test_exit_env_maps_to_incomplete_never_succeeded(self):
|
|
153
|
-
# Belt-and-suspenders (mutation finding M5): the constant every
|
|
154
|
-
# fail-closed guard asserts must map to "incomplete" in the MCP
|
|
155
|
-
# envelope. If the mapping ever flips to "succeeded", a check that
|
|
156
|
-
# could not run would count as a pass.
|
|
157
|
-
from flashgate import results
|
|
158
|
-
assert results.from_exit(cli.EXIT_ENV, "x").status == "incomplete"
|
|
159
|
-
assert results.from_exit(cli.EXIT_PROBE_FAIL, "x").status == "failed"
|
|
160
|
-
|
|
161
|
-
|
|
162
151
|
class TestUartPassPath:
|
|
163
152
|
def test_plain_uart_verify_passes_without_probe_flags(self, tmp_path, monkeypatch, capsys):
|
|
164
153
|
# Regression from 0.4.2: the identity-check refactor left a NameError
|
|
165
154
|
# on the uart PASS print (git={got} referenced a moved local) — verify
|
|
166
155
|
# crashed AFTER everything had passed, exit 1 (misread as build fail).
|
|
167
156
|
board = make_board(tmp_path)
|
|
157
|
+
monkeypatch.setattr(cli, "_console_port",
|
|
158
|
+
lambda b: ("COM77", "pinned")) # host serial topology must not matter
|
|
168
159
|
monkeypatch.setattr(cli, "_build", lambda b: cli.EXIT_OK)
|
|
169
160
|
monkeypatch.setattr(cli, "cmd_flash", lambda b: cli.EXIT_OK)
|
|
170
161
|
monkeypatch.setattr(cli.serialmon, "open_flush",
|
|
@@ -136,6 +136,17 @@ class TestProbeTool:
|
|
|
136
136
|
assert "COMX" in sc["summary"]
|
|
137
137
|
|
|
138
138
|
|
|
139
|
+
class TestExitCoupling:
|
|
140
|
+
def test_exit_env_maps_to_incomplete_never_succeeded(self):
|
|
141
|
+
# Belt-and-suspenders (mutation finding M5): the CLI constants the
|
|
142
|
+
# fail-closed guards assert must map to "incomplete"/"failed" in the
|
|
143
|
+
# envelope. If the mapping ever flips to "succeeded", a check that
|
|
144
|
+
# could not run would count as a pass.
|
|
145
|
+
from flashgate import cli
|
|
146
|
+
assert results.from_exit(cli.EXIT_ENV, "x").status == "incomplete"
|
|
147
|
+
assert results.from_exit(cli.EXIT_PROBE_FAIL, "x").status == "failed"
|
|
148
|
+
|
|
149
|
+
|
|
139
150
|
class TestProbeEdgeCases:
|
|
140
151
|
"""Regressions from the adversarial review of 0.5.0."""
|
|
141
152
|
|
|
@@ -195,3 +206,22 @@ class TestCatchAllGuard:
|
|
|
195
206
|
assert sc["status"] == "failed"
|
|
196
207
|
assert sc["code"] == results.INTERNAL_ERROR
|
|
197
208
|
assert "kaboom" in sc["summary"]
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
class TestBoardInfoProbesWarning:
|
|
212
|
+
def test_no_step_probe_becomes_warning_not_internal_error(self, tmp_path):
|
|
213
|
+
# F2 regression: board_info claimed "warnings flag an unloadable
|
|
214
|
+
# probes section" but ProbeError (probe with no steps) escaped to
|
|
215
|
+
# INTERNAL_ERROR. The except tuple must match the probe tool's.
|
|
216
|
+
yaml = tmp_path / "board.yaml"
|
|
217
|
+
yaml.write_text(
|
|
218
|
+
"board: t\nmcu: m\ndescription: d\nfirmware:\n dir: fw\n"
|
|
219
|
+
" build: ninja -C build\n artifact: build/fw.bin\nserial:\n"
|
|
220
|
+
" baudrate: 9600\n banner: 'BOOT {git}'\n"
|
|
221
|
+
"probes:\n broken: {}\n", encoding="utf-8")
|
|
222
|
+
(tmp_path / "fw").mkdir()
|
|
223
|
+
res = asyncio.run(srv.mcp.call_tool("board_info", {"board": str(yaml)}))
|
|
224
|
+
sc = res.structured_content
|
|
225
|
+
assert sc["status"] == "succeeded" # profile itself is fine
|
|
226
|
+
assert sc["data"]["probes"] == []
|
|
227
|
+
assert any("probes unloadable" in w for w in sc["warnings"])
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|