From 320854eebcff5d0f11e692c09d92744576bc03de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BD=D1=82=D0=BE=D0=BD=20=D0=A2=D1=80=D0=BE=D1=88?= =?UTF-8?q?=D0=B8=D0=BD?= Date: Tue, 6 Oct 2026 21:22:22 +0300 Subject: [PATCH] v3.22.4: the proactive-drain pending exemption The runner's unexpected-pending-command guard fired for any non-expect step, so a proactive-drain after a step that answered 91XX failed with "unexpected proactive command pending (SW 91XX) - add an expect step or a status action" - contradicting the drain's own doc ("consume whatever the card announces") and the v3.22.2 RAM recipe ("an expect or a proactive-drain"). The guard now exempts a proactive-drain step: its STATUS poll re-announces the unfetched command, fetches and answers it, and the drain's final-SW and pending handling is unchanged (a still-pending card is handed to the next step, an idle card passes). No pending-length plumbing: STATUS-first keeps the drain's attempts/log semantics and tolerates a card that dropped the command. The guard note now names the drain instead of the misleading "status action" (a status action in that position also trips the guard). Docs: the drain section in test-scripts.md, api.md's action list and the help EN/RU drain bullet. Tests: 839 frontend + 819 Python; sw cache simple-v408. --- docs/api.md | 4 +++- docs/test-scripts.md | 7 ++++++- frontend/help-ru.html | 2 +- frontend/help.html | 2 +- frontend/index.html | 2 +- frontend/sw.js | 2 +- pyproject.toml | 2 +- pysim_simple_server/server.py | 12 +++++++++--- tests/test_testscript.py | 23 +++++++++++++++++++++++ 9 files changed, 46 insertions(+), 10 deletions(-) diff --git a/docs/api.md b/docs/api.md index 832734b..b8ff33b 100644 --- a/docs/api.md +++ b/docs/api.md @@ -743,7 +743,9 @@ mask `91??`, i.e. poll until the card announces a command) or `proactive-drain` optional `require` `{command, qualifier?}` asserting at least one drained command; the final SW is the step's `check.sw`, so a still-pending card fails and hands the command to the next step - an empty drain passes unless -`require` was given). +`require` was given). It is exempt from the runner's +unexpected-pending-command guard, so it also consumes a command a previous +step left pending (`91XX`). `check` is `{"sw": ..., "data": ...}` (exact or `{"mode": "mask", "value": "91??"}`, `?` = per-nibble wildcard) plus `"por"` for SCP80 - diff --git a/docs/test-scripts.md b/docs/test-scripts.md index 00b6ef4..d5490c6 100644 --- a/docs/test-scripts.md +++ b/docs/test-scripts.md @@ -319,7 +319,12 @@ IMS registration, the form's choice otherwise) - a hand-written step without Consume whatever the card announces and leave it idle - at the start, in the middle or at the end of a script (a leftover command from a previous run must -not derail the next step). Each round polls STATUS; a `91XX` answer fetches +not derail the next step). The drain is also the tool for a command **the +previous step left pending** (`91XX`): the runner's *unexpected proactive +command pending* guard does not apply to it - its STATUS poll re-announces +the unfetched command - so a `scp80`/`menu-select` step that answers `91XX` +can be followed directly by a drain. Each round polls STATUS; a `91XX` +answer fetches the command and answers it (the first command with `first`, the rest with `respond` - both `{"result": …}` like an expectation's response). The loop ends when STATUS answers `9000` (idle), a non-`91XX` SW appears or `attempts` diff --git a/frontend/help-ru.html b/frontend/help-ru.html index 1e27703..e57b781 100644 --- a/frontend/help-ru.html +++ b/frontend/help-ru.html @@ -564,7 +564,7 @@

Тест-скрипт — упорядоченный список действий и ожиданий проактивных команд, выполняемый на подключённой карте на стороне сервера. Пока запуск активен, карта принадлежит скрипту: остальные операции с картой отклоняются (HTTP 409), фоновый опрос STATUS приостанавливается; отчёт о запуске хранится в памяти (в том числе после перезагрузки страницы) до нажатия Очистить отчёт. Скрипты хранятся в хранилище на сервере (~/.pysim-simple-server/test_scripts.json, --test-scripts), поэтому все браузеры работают с одним набором; изменения записываются в хранилище по мере правки (кнопки «Сохранить» нет — черновик без валидного шага остаётся локальным); экспорт/импорт JSON остаётся для резервных копий и переноса скриптов между стендами (старый экспорт из localStorage импортируется как есть).

У вкладки четыре представления: список наборов (по умолчанию; Добавить набор / Редактировать / Запустить / Экспорт / Удалить), редактор набора (скрипты набора в порядке выполнения — необязательный setup первым и teardown последним, с пометками ролей; у каждого скрипта Запустить/Редактировать/Переместить/Скопировать/Удалить, политика при ошибке остановить (по умолчанию) / продолжить и Требуется ADM), редактор скрипта (список шагов; кнопка Старт выполняет только этот скрипт и показывает результат под шагами — он не сохраняется) и отчёт набора (после запуска набора: статус каждого скрипта, таблица шагов с ожидаемым/фактическим, примечание об ошибке и выдержка из журнала запуска; сводка с успешно/предупреждения/ошибки/пропущено, время выполнения, счётчики SCP80 до → после по каждому keyset, пресет и предупреждение о смене сессии карты; экспорт в JSON или Markdown). Запуск набора выполняет его скрипты по порядку в одной сессии карты, без сбросов и без продолжения с места остановки; при ошибке решает, остановится ли набор на этом скрипте или запустится следующий, teardown выполняется всегда при остановке или ошибке (пока карта в ридере), чтобы следующий тест начался с чистого состояния, а сброс карты останавливает набор и отмечается в отчёте. Требуется ADM (у набора или скрипта) проверяет ключ ADM из подобранного пресета один раз перед запуском — ошибка отклоняет запуск (проверьте вручную через значок ADM; автоматических повторов нет). Скрипты хранятся в хранилище наборов (~/.pysim-simple-server/test_suites.json, --test-suites); каждый скрипт принадлежит ровно одному набору, и скрипт можно переместить или скопировать в другой набор (экспорт набора — это пакет с набором и его скриптами; старый экспорт только скриптов импортируется в автоматически созданный набор «Imported scripts»).