From 2181ef6bbfc703a23016f2f0c91828f6e9ac08c5 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: Fri, 23 Oct 2026 23:44:42 +0300 Subject: [PATCH] v3.37.0: load testing for test scripts and suites POST /api/test/run takes an optional iterations count (the PWA's Iterations field): the run repeats, successful iterations are discarded (steps/log cleared, only load.done and the timing grow), the first error stops the loop and keeps that iteration's report, a stop ends it after the current iteration, and a card session change between iterations is an error. The SCP80 counter state is shared across iterations, so packets continue from what the previous run consumed. The status gains a load block (iteration/done/failed_iteration/ elapsed_ms/avg_ms; disabled for a normal run). The suite runner was split into a per-iteration body (_test_suite_execute_once) + the single-run finaliser, and two load workers own the loops. Review fixes: a raised runner error finalises as error (no stuck running state), a suite-internal session change is an error, a stopped member ends the suite loop, the final status keeps a warning from any iteration, avg_ms covers the attempted iterations, and each suite iteration's counters_before is read fresh. 1041 Python + 1069 frontend; simple-v475. --- CHANGELOG.md | 25 +++ docs/api.md | 13 +- docs/test-scripts.md | 13 ++ frontend/help-ru.html | 1 + frontend/help.html | 1 + frontend/index.html | 130 ++++++++--- frontend/sw.js | 2 +- frontend/tests/test_load.test.js | 166 +++++++++++++++ frontend/tests/testscripts.test.js | 3 + pyproject.toml | 2 +- pysim_simple_server/server.py | 331 ++++++++++++++++++++++++++--- tests/test_poll.py | 6 +- tests/test_test_load.py | 308 +++++++++++++++++++++++++++ 13 files changed, 937 insertions(+), 64 deletions(-) create mode 100644 frontend/tests/test_load.test.js create mode 100644 tests/test_test_load.py diff --git a/CHANGELOG.md b/CHANGELOG.md index e41ee30..aaf3849 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,31 @@ every change carries a descriptive commit message. ## [Unreleased] +## [3.37.0] — 2026-10-23 + +### Added + +- **Load testing** for test scripts and suites. `POST /api/test/run` takes an optional + `iterations` count (the PWA's **Iterations** field next to *Start* / *Run suite*): the + run repeats N times, successful iterations are discarded (only the iteration counter and + the timing are kept; the per-step log is not rendered or retained), and the first + iteration that ends in an **error** stops the loop and keeps that iteration's full + report. A manual stop ends it after the current iteration, and a card session change + between iterations is an error (a swapped card is never run against another card's + script). The SCP80 counter state is shared across iterations, so packets continue from + what the previous run consumed. The run status gains a `load` block (`iteration`, + `done`, `failed_iteration`, `elapsed_ms`, `avg_ms`); the count is a run parameter, not + part of the stored script or suite. + +### Fixed + +- The load-test loop closes properly on a raised runner error (it used to leave the run + `running` forever - the PWA polled a dead run and *Clear report* stayed refused), on a + card session change inside a suite iteration, and on a member that was stopped; a + warning in any iteration is reflected in the final status, `avg_ms` covers the attempted + iterations (the failing one included), and each suite iteration's `counters_before` is + read fresh from the preset store. + ## [3.36.0] — 2026-10-23 ### Added diff --git a/docs/api.md b/docs/api.md index 8e1624a..17003c9 100644 --- a/docs/api.md +++ b/docs/api.md @@ -1006,11 +1006,22 @@ on every stop** as long as the card is in the reader; a card reset (session change) stops the suite, skips the remaining members and is flagged in the report. +`iterations` (optional, 1-1,000,000, default 1) repeats the script or the +whole suite: successful iterations are discarded (only the counter and the +timing are kept; the per-step log is not retained), the first iteration that +ends in an **error** stops the loop and keeps that iteration's report, a +manual stop ends it after the current iteration, and a card session change +between iterations is an error. The status gains `load`: +`{enabled, iterations, iteration, done, failed_iteration, started, finished, +elapsed_ms, avg_ms}`. + The suite run's snapshot adds `suite`: the member records (`status`, `steps`, `note`, `log` - the semantic run-log excerpt), the `summary` (ok/warning/ error/skipped counts, `wall_ms`, `stopped`, `session_changed`, the SCP80 `counters_before`/`counters_after` per keyset), the `start_reset` flag and -the `reset` result (ICCID + ms) and the run's `adm` state. +the `reset` result (ICCID + ms) and the run's `adm` state. A successful load +test leaves `suite` `null` (no iteration is tracked); a failing one keeps the +failing iteration's suite state. The report is exported from the PWA as JSON or Markdown; the run state lives in the server until `POST /api/test/clear` (no history). diff --git a/docs/test-scripts.md b/docs/test-scripts.md index 61e4b8c..79fd4e1 100644 --- a/docs/test-scripts.md +++ b/docs/test-scripts.md @@ -229,6 +229,19 @@ TEST-RUN step 4: MENU-SELECT ENVELOPE=80c2000009d30702020181900101 item=1 ('One' (`APDU-TRACE(…): → SW: … RESP: …`) for every APDU, including the file-manager reads and pySim's auto-handler traffic. +## Load testing + +A run may be repeated: `POST /api/test/run {..., "iterations": N}` (the +PWA's **Iterations** field next to Start / Run suite) executes the script or +the whole suite N times. Successful iterations are discarded - only the +iteration counter and the timing are kept, so memory use stays flat. The +first iteration that ends in an error stops the loop and keeps its full +report (steps, failed checks, log excerpt; for a suite, the member report); +a manual stop ends it after the current iteration, and a card session change +between iterations is an error. The SCP80 counter state is shared across +iterations, so packets continue from what the previous iteration consumed. +The iteration count is a run parameter - the stored script/suite is unchanged. + ## Document shape ```json diff --git a/frontend/help-ru.html b/frontend/help-ru.html index 7792c5b..626ec00 100644 --- a/frontend/help-ru.html +++ b/frontend/help-ru.html @@ -573,6 +573,7 @@

8.9 Тест-скрипт

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

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

+

Прогон можно повторить: поле «Итерации» рядом с Start / Run suite выполняет скрипт или весь набор указанное число раз — это нагрузочный тест. Успешные итерации не сохраняются: в статусе видны только счётчик (итерация k / N) и время, журнал шагов остаётся пустым, а память не растёт при любом числе итераций. Первая итерация, завершившаяся ошибкой, останавливает цикл и сохраняет свой полный отчёт (шаги, неудачные проверки, фрагмент журнала, отчёт по скриптам набора); Остановить завершает работу после текущей итерации, а смена карты между итерациями считается ошибкой (следующая карта никогда не запускается со скриптом другой карты). Число итераций — параметр прогона, сохранённый скрипт/набор не меняется; счётчики SCP80 продолжаются с того значения, которое израсходовала предыдущая итерация.