Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/build_and_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
58 changes: 57 additions & 1 deletion src/ragger/conftest/base_conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -53,6 +54,10 @@
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:
Expand Down Expand Up @@ -113,6 +118,57 @@
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):

Check failure on line 131 in src/ragger/conftest/base_conftest.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this function to reduce its Cognitive Complexity from 16 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=LedgerHQ_ragger&issues=AZ1I1lPB5heiKBF9hW09&open=AZ1I1lPB5heiKBF9hW09&pullRequest=268
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):

Check warning on line 162 in src/ragger/conftest/base_conftest.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove the unused function parameter "exitstatus".

See more on https://sonarcloud.io/project/issues?id=LedgerHQ_ragger&issues=AZ1IfaHmZJpE-0qMQffl&open=AZ1IfaHmZJpE-0qMQffl&pullRequest=268

Check warning on line 162 in src/ragger/conftest/base_conftest.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove the unused function parameter "config".

See more on https://sonarcloud.io/project/issues?id=LedgerHQ_ragger&issues=AZ1IfaHmZJpE-0qMQffm&open=AZ1IfaHmZJpE-0qMQffm&pullRequest=268
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")
Expand Down
Loading