From c22e6ef380bfcb2b48b60ea939909d8b51670ef4 Mon Sep 17 00:00:00 2001 From: Mathias BROUSSET Date: Tue, 31 Mar 2026 09:21:51 +0200 Subject: [PATCH 1/2] remove support for python 3.10 --- .github/workflows/build_and_tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build_and_tests.yml b/.github/workflows/build_and_tests.yml index 29935a7a..f9a5141b 100644 --- a/.github/workflows/build_and_tests.yml +++ b/.github/workflows/build_and_tests.yml @@ -64,7 +64,7 @@ jobs: strategy: fail-fast: false matrix: - python_version: ['3.9', '3.10', '3.11', '3.12', '3.13'] + python_version: ['3.10', '3.11', '3.12', '3.13'] device: ${{ fromJson(needs.prepare_matrix.outputs.devices) }} steps: From d4909e6af81e4b694ed4f7a51cf6dc5c3c2d4645 Mon Sep 17 00:00:00 2001 From: Mathias BROUSSET Date: Thu, 19 Mar 2026 15:24:53 +0100 Subject: [PATCH 2/2] add fixture to retrieve stack consumption --- CHANGELOG.md | 6 +++ src/ragger/conftest/base_conftest.py | 58 +++++++++++++++++++++++++++- 2 files changed, 63 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 89dbc111..43de1bda 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.44.0] - 2026-03-31 + +### Added + +- Added --get-stack-consumption option + ## [1.43.0] - 2026-03-02 ### Added diff --git a/src/ragger/conftest/base_conftest.py b/src/ragger/conftest/base_conftest.py index 62d5eb47..93f7c708 100644 --- a/src/ragger/conftest/base_conftest.py +++ b/src/ragger/conftest/base_conftest.py @@ -15,7 +15,8 @@ from ragger.navigator import Navigator, NanoNavigator, TouchNavigator, NavigateWithScenario from ragger.utils import find_project_root_dir, find_library_application, find_application from ragger.utils.misc import get_current_app_name_and_version, exit_current_app, open_app_from_dashboard -from ragger.error import MissingElfError +from ragger.error import ExceptionRAPDU, MissingElfError, StatusWords +from ragger.utils.structs import RAPDU from . import configuration as conf @@ -53,6 +54,10 @@ def pytest_addoption(parser): action="store_true", default=False, help="Skip tests instead of failing when application binaries are missing") + parser.addoption("--get-stack-consumption", + action="store_true", + default=False, + help="Send APDUs to measure stack consumption. Based on CLA=0xB0, INS=0x57.") # Always allow "default" even if application conftest does not define it allowed_setups = conf.OPTIONAL.ALLOWED_SETUPS if "default" not in allowed_setups: @@ -113,6 +118,57 @@ def ignore_missing_binaries(pytestconfig): return pytestconfig.getoption("ignore_missing_binaries") +@pytest.fixture(scope="session") +def get_stack_consumption(pytestconfig): + return pytestconfig.getoption("get_stack_consumption") + + +# Storage for stack consumption results across tests (populated by stack_consumption_hooks) +_stack_consumption_results: dict = {} + + +@pytest.fixture(autouse=True) +def stack_consumption_hooks(request, get_stack_consumption: bool): + if get_stack_consumption: + backend = request.getfixturevalue("backend") + _backend_name = request.getfixturevalue("backend_name") + if _backend_name.lower() == "speculos": + try: + backend.exchange(cla=0xB0, ins=0x57, p1=0x00, p2=0x01, data=b"") + except ExceptionRAPDU as e: + msg = ( + "Stack consumption not supported: app not built with DEBUG_OS_STACK_CONSUMPTION=1" + if e.status == StatusWords.SWO_INVALID_CLA else + f"Unexpected SW: {e.status:04X}") + pytest.fail(msg) + yield + if get_stack_consumption: + backend = request.getfixturevalue("backend") + _backend_name = request.getfixturevalue("backend_name") + if _backend_name.lower() == "speculos": + try: + rapdu_retrieve: RAPDU = backend.exchange(cla=0xB0, + ins=0x57, + p1=0x01, + p2=0x01, + data=b"") + consumption = int.from_bytes(rapdu_retrieve.data, byteorder='big') + print(f"\n[stack consumption] {consumption} bytes.") + _stack_consumption_results[request.node.nodeid] = consumption + except ExceptionRAPDU as e: + pytest.fail(f"Stack consumption retrieve failed with SW: {e.status:04X}") + + +def pytest_terminal_summary(terminalreporter, exitstatus, config): + if not _stack_consumption_results: + return + terminalreporter.write_sep("=", "stack consumption summary") + for test_name, consumption in sorted(_stack_consumption_results.items()): + terminalreporter.write_line(f" {consumption:>8} bytes {test_name}") + worst_test, worst_value = max(_stack_consumption_results.items(), key=lambda x: x[1]) + terminalreporter.write_sep("-", f"worst case: {worst_value} bytes ({worst_test})") + + @pytest.fixture(scope="session") def pki_prod(pytestconfig): return pytestconfig.getoption("pki_prod")