v3.22.7: a raised card error honours the step's on_fail

The runner's step loop caught an exception and hard-coded status "error" +
stop, ignoring on_fail - so a file-read of an absent optional file
(re-verified live on 3.22.6: MF/A153/49AC, note "6a82: Wrong parameters -
File not found", on_fail "warning" still stopped the run) could not express
the "read an optional file" pattern (49AC is documented optional: its
absence means no menu password).

The handler now treats a raised error as a failed "Error" check at the
step's level (the message as actual/detail and the note): on_fail "warning"
warns and the run continues, the default still stops; the drain-if-pending
behaviour is unchanged.  Every raised exception is treated the same - the
two "file absent" raise paths (pySim's SwMatchError for model files and
probe_file's RuntimeError with the decoded SW for custom FIDs) cannot be
told apart by type, and load-time validation (normalise_script) stays
strict.

Docs: the guide's on_fail paragraph and run-errors list (the 6A82 bullet),
api.md and help EN/RU.  Tests: a raising file-read with on_fail "warning"
warns and the next step runs; without on_fail the run still stops.
843 frontend + 821 Python; sw cache simple-v411.
This commit is contained in:
2026-10-07 00:53:20 +03:00
parent 0fa1dee4b5
commit 00675ca09c
9 changed files with 76 additions and 14 deletions
+15 -6
View File
@@ -36,7 +36,7 @@ from osmocom.tlv import BER_TLV_IE
from osmocom.utils import rpad
VERSION = '3.22.6'
VERSION = '3.22.7'
MAX_ENVELOPE_SEGMENTS = 5 # max SMS segments for outgoing C-APDU in ENVELOPE
@@ -7124,16 +7124,25 @@ def _test_run_script(server, script, preset, shared=None):
# Error terminates the script; Warning and OK continue.
break
except Exception as e:
_test_entry_update(entry, {
'status': 'error', 'note': str(e),
'ms': int((time.time() - entry['started']) * 1000)})
sys.stderr.write('TEST-RUN step %d failed: %s\n' % (index + 1, e))
# A raised card/transport error is a step failure like any other:
# it honours the step's on_fail (v3.22.7). An absent optional
# file (6A82), a refused SELECT or a preset/keyset error then
# warns and the run continues, with the error as a failed check;
# the default (error) still stops the run.
level = step.get('on_fail') or 'error'
check = _test_check_result('Error', False, 'the step to run',
str(e), level, detail=str(e))
_test_finish_entry(entry, {'status': level, 'checks': [check],
'note': str(e)})
sys.stderr.write('TEST-RUN step %d failed (%s): %s\n'
% (index + 1, level, e))
if pending is not None:
with _CARD_LOCK:
drained = _test_drain_pending(scc, pending)
_test_entry_update(entry, {k: v for k, v in drained.items() if k != 'note'})
pending = None
break
if level == 'error':
break
if pending is not None:
with _CARD_LOCK:
drained = _test_drain_pending(scc, pending)