flashgate 0.5.0__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.0 → flashgate-0.5.2}/PKG-INFO +10 -2
- {flashgate-0.5.0 → flashgate-0.5.2}/README.md +5 -0
- {flashgate-0.5.0 → flashgate-0.5.2}/flashgate/cli.py +1 -1
- {flashgate-0.5.0 → flashgate-0.5.2}/flashgate/mcp_server.py +91 -23
- {flashgate-0.5.0 → flashgate-0.5.2}/flashgate.egg-info/PKG-INFO +10 -2
- {flashgate-0.5.0 → flashgate-0.5.2}/flashgate.egg-info/requires.txt +1 -1
- {flashgate-0.5.0 → flashgate-0.5.2}/pyproject.toml +11 -2
- {flashgate-0.5.0 → flashgate-0.5.2}/tests/test_cli.py +19 -9
- {flashgate-0.5.0 → flashgate-0.5.2}/tests/test_mcp.py +30 -0
- {flashgate-0.5.0 → flashgate-0.5.2}/LICENSE +0 -0
- {flashgate-0.5.0 → flashgate-0.5.2}/flashgate/__init__.py +0 -0
- {flashgate-0.5.0 → flashgate-0.5.2}/flashgate/__main__.py +0 -0
- {flashgate-0.5.0 → flashgate-0.5.2}/flashgate/board.py +0 -0
- {flashgate-0.5.0 → flashgate-0.5.2}/flashgate/flasher.py +0 -0
- {flashgate-0.5.0 → flashgate-0.5.2}/flashgate/gatestate.py +0 -0
- {flashgate-0.5.0 → flashgate-0.5.2}/flashgate/probes.py +0 -0
- {flashgate-0.5.0 → flashgate-0.5.2}/flashgate/results.py +0 -0
- {flashgate-0.5.0 → flashgate-0.5.2}/flashgate/serialmon.py +0 -0
- {flashgate-0.5.0 → flashgate-0.5.2}/flashgate/sttools.py +0 -0
- {flashgate-0.5.0 → flashgate-0.5.2}/flashgate/swdsig.py +0 -0
- {flashgate-0.5.0 → flashgate-0.5.2}/flashgate.egg-info/SOURCES.txt +0 -0
- {flashgate-0.5.0 → flashgate-0.5.2}/flashgate.egg-info/dependency_links.txt +0 -0
- {flashgate-0.5.0 → flashgate-0.5.2}/flashgate.egg-info/entry_points.txt +0 -0
- {flashgate-0.5.0 → flashgate-0.5.2}/flashgate.egg-info/top_level.txt +0 -0
- {flashgate-0.5.0 → flashgate-0.5.2}/setup.cfg +0 -0
- {flashgate-0.5.0 → flashgate-0.5.2}/tests/test_board.py +0 -0
- {flashgate-0.5.0 → flashgate-0.5.2}/tests/test_gatestate.py +0 -0
- {flashgate-0.5.0 → flashgate-0.5.2}/tests/test_probes.py +0 -0
- {flashgate-0.5.0 → 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
|
|
@@ -400,7 +400,7 @@ def _verify_uart(board: Board, probe_names: list[str] | None) -> int:
|
|
|
400
400
|
return _run_probes(board, None if all_probes else probe_names, conn)
|
|
401
401
|
|
|
402
402
|
print(_green(f"[verify] PASS — the board itself confirms the firmware booted "
|
|
403
|
-
f"(git={
|
|
403
|
+
f"(git={info.get('git')})"))
|
|
404
404
|
return EXIT_OK
|
|
405
405
|
finally:
|
|
406
406
|
conn.close()
|
|
@@ -14,7 +14,8 @@ Tools: board_info, doctor, build, flash, verify, probe, console_send,
|
|
|
14
14
|
console_read.
|
|
15
15
|
|
|
16
16
|
Every tool returns a `flashgate.results.Result` envelope (schema_version,
|
|
17
|
-
status, stable code, summary, CLI exit_code, data, warnings,
|
|
17
|
+
status, stable code, summary, CLI exit_code, data, evidence, warnings,
|
|
18
|
+
policy) —
|
|
18
19
|
never a bare log the model has to parse. With mcp 2.x the envelope is
|
|
19
20
|
emitted as MCP structuredContent (registered via structured_output=True)
|
|
20
21
|
plus a JSON text block; on the mcp 1.x fallback the JSON text is still
|
|
@@ -138,8 +139,17 @@ def _safe(policy: dict):
|
|
|
138
139
|
@_register(annotations=_ann(readOnlyHint=True), structured=True)
|
|
139
140
|
@_safe(P_READ)
|
|
140
141
|
def board_info(board: str | None = None) -> results.Result:
|
|
141
|
-
"""
|
|
142
|
-
|
|
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)."""
|
|
143
153
|
try:
|
|
144
154
|
b = _board(board)
|
|
145
155
|
except BoardError as exc:
|
|
@@ -149,7 +159,7 @@ def board_info(board: str | None = None) -> results.Result:
|
|
|
149
159
|
warnings: list[str] = []
|
|
150
160
|
try:
|
|
151
161
|
probes = list(probe_mod.load_probes(b.yaml_path))
|
|
152
|
-
except (OSError, ValueError) as exc:
|
|
162
|
+
except (OSError, ValueError, probe_mod.ProbeError) as exc:
|
|
153
163
|
warnings.append(f"probes unloadable: {exc}")
|
|
154
164
|
r = results.ok(
|
|
155
165
|
f"{b.name} ({b.mcu}) — {len(probes)} probe(s) defined",
|
|
@@ -172,8 +182,14 @@ def board_info(board: str | None = None) -> results.Result:
|
|
|
172
182
|
@_register(annotations=_ann(readOnlyHint=True), structured=True)
|
|
173
183
|
@_safe(P_READ)
|
|
174
184
|
def doctor(board: str | None = None) -> results.Result:
|
|
175
|
-
"""
|
|
176
|
-
|
|
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."""
|
|
177
193
|
try:
|
|
178
194
|
rc, log = _capture(cli_mod.cmd_doctor, _board(board))
|
|
179
195
|
except BoardError as exc:
|
|
@@ -185,7 +201,14 @@ def doctor(board: str | None = None) -> results.Result:
|
|
|
185
201
|
@_register(annotations=_ann(idempotentHint=True), structured=True)
|
|
186
202
|
@_safe(P_BUILD)
|
|
187
203
|
def build(board: str | None = None) -> results.Result:
|
|
188
|
-
"""
|
|
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."""
|
|
189
212
|
try:
|
|
190
213
|
rc, log = _capture(cli_mod.cmd_build, _board(board))
|
|
191
214
|
except BoardError as exc:
|
|
@@ -197,8 +220,16 @@ def build(board: str | None = None) -> results.Result:
|
|
|
197
220
|
@_register(annotations=_ann(destructiveHint=True), structured=True)
|
|
198
221
|
@_safe(P_FLASH)
|
|
199
222
|
def flash(board: str | None = None) -> results.Result:
|
|
200
|
-
"""
|
|
201
|
-
|
|
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."""
|
|
202
233
|
try:
|
|
203
234
|
rc, log = _capture(cli_mod.cmd_flash, _board(board))
|
|
204
235
|
except BoardError as exc:
|
|
@@ -210,12 +241,27 @@ def flash(board: str | None = None) -> results.Result:
|
|
|
210
241
|
@_register(annotations=_ann(destructiveHint=True), structured=True)
|
|
211
242
|
@_safe(P_VERIFY)
|
|
212
243
|
def verify(board: str | None = None) -> results.Result:
|
|
213
|
-
"""The
|
|
214
|
-
|
|
215
|
-
the
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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."""
|
|
219
265
|
try:
|
|
220
266
|
rc, log = _capture(cli_mod.cmd_verify, _board(board), ["all"])
|
|
221
267
|
except BoardError as exc:
|
|
@@ -227,10 +273,17 @@ def verify(board: str | None = None) -> results.Result:
|
|
|
227
273
|
@_register(annotations=_ann(readOnlyHint=False), structured=True)
|
|
228
274
|
@_safe(P_PROBE)
|
|
229
275
|
def probe(names: list[str] | None = None, board: str | None = None) -> results.Result:
|
|
230
|
-
"""
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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."""
|
|
234
287
|
try:
|
|
235
288
|
b = _board(board)
|
|
236
289
|
except BoardError as exc:
|
|
@@ -269,8 +322,17 @@ def probe(names: list[str] | None = None, board: str | None = None) -> results.R
|
|
|
269
322
|
@_register(annotations=_ann(readOnlyHint=False, openWorldHint=True), structured=True)
|
|
270
323
|
@_safe(P_CONSOLE_SEND)
|
|
271
324
|
def console_send(line: str, wait_s: float = 1.0, board: str | None = None) -> results.Result:
|
|
272
|
-
"""
|
|
273
|
-
|
|
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."""
|
|
274
336
|
try:
|
|
275
337
|
b = _board(board)
|
|
276
338
|
except BoardError as exc:
|
|
@@ -306,8 +368,14 @@ def console_send(line: str, wait_s: float = 1.0, board: str | None = None) -> re
|
|
|
306
368
|
@_register(annotations=_ann(readOnlyHint=True), structured=True)
|
|
307
369
|
@_safe(P_READ)
|
|
308
370
|
def console_read(seconds: float = 2.0, board: str | None = None) -> results.Result:
|
|
309
|
-
"""
|
|
310
|
-
|
|
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."""
|
|
311
379
|
try:
|
|
312
380
|
b = _board(board)
|
|
313
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,12 +148,22 @@ class TestAutoRouting:
|
|
|
148
148
|
assert seen["mode"] == "uart"
|
|
149
149
|
|
|
150
150
|
|
|
151
|
-
class
|
|
152
|
-
def
|
|
153
|
-
#
|
|
154
|
-
#
|
|
155
|
-
#
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
151
|
+
class TestUartPassPath:
|
|
152
|
+
def test_plain_uart_verify_passes_without_probe_flags(self, tmp_path, monkeypatch, capsys):
|
|
153
|
+
# Regression from 0.4.2: the identity-check refactor left a NameError
|
|
154
|
+
# on the uart PASS print (git={got} referenced a moved local) — verify
|
|
155
|
+
# crashed AFTER everything had passed, exit 1 (misread as build fail).
|
|
156
|
+
board = make_board(tmp_path)
|
|
157
|
+
monkeypatch.setattr(cli, "_console_port",
|
|
158
|
+
lambda b: ("COM77", "pinned")) # host serial topology must not matter
|
|
159
|
+
monkeypatch.setattr(cli, "_build", lambda b: cli.EXIT_OK)
|
|
160
|
+
monkeypatch.setattr(cli, "cmd_flash", lambda b: cli.EXIT_OK)
|
|
161
|
+
monkeypatch.setattr(cli.serialmon, "open_flush",
|
|
162
|
+
lambda *a, **k: SimpleNamespace(close=lambda: None))
|
|
163
|
+
monkeypatch.setattr(cli.serialmon, "wait_on",
|
|
164
|
+
lambda *a, **k: SimpleNamespace(
|
|
165
|
+
matched=True, error_hit=None, transcript="",
|
|
166
|
+
groups={"board": "test-board", "git": "abc1234"}))
|
|
167
|
+
monkeypatch.setattr(Board, "head_sha", lambda self: "abc1234")
|
|
168
|
+
assert cli._verify_uart(board, None) == cli.EXIT_OK
|
|
169
|
+
assert "PASS" in capsys.readouterr().out
|
|
@@ -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
|