tests: download tropic emulator variants
What changed, and why it matters
This commit only changes test tooling. It extends a script that downloads Trezor firmware emulators for testing so it can also fetch a new 'tropic-enabled' variant for supported hardware models, and it fixes a shell quoting issue in a Nix helper command. There is no change to the actual device firmware, wallet logic, cryptography, or anything end users rely on for security.
No security action required. Treat as routine test-infrastructure maintenance.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The diff modifies tests/download_emulators.py to add a TROPIC_CAPABLE_MODELS set (currently {‘T3W1’}), a TROPIC_REMOTE_SUBPATH_SUFFIX (‘_tropic_on’), and logic to download an additional emulator artifact per version into a subfolder matching the S3 layout. It also refactors version parsing to handle non-semver strings and updates docs/tests/upgrade-tests.md and tests/download_emulators.sh (switching single quotes to double quotes so the NIX_BINTOOLS variable is expanded correctly). No runtime firmware, crypto, or privileged code is touched.
Changed components
tests/download_emulators.pytests/download_emulators.shdocs/tests/upgrade-tests.mdInspect captured patch +55 / −5
diff --git a/docs/tests/upgrade-tests.md b/docs/tests/upgrade-tests.md
index 84b3130f..93da4856 100644
--- a/docs/tests/upgrade-tests.md
+++ b/docs/tests/upgrade-tests.md
@@ -13,6 +13,8 @@ source .venv/bin/activate
tests/download_emulators.sh {model}
```
+For tropic-capable models, this also downloads tropic-enabled emulator variants into the same subfolder layout as on S3.
+
3. And run the tests using pytest:
```sh
diff --git a/tests/download_emulators.py b/tests/download_emulators.py
index 83a77238..76bb3658 100755
--- a/tests/download_emulators.py
+++ b/tests/download_emulators.py
@@ -22,6 +22,8 @@ OLDEST_AVAILABLE = {
}
EMULATORS_URL_PREFIX = "https://data.trezor.io/dev/firmware/releases/emulators-new"
+TROPIC_CAPABLE_MODELS = {"T3W1"}
+TROPIC_REMOTE_SUBPATH_SUFFIX = "_tropic_on"
TESTS_DIR = Path(__file__).resolve().parent
SAVE_DIR = TESTS_DIR / "emulators"
@@ -47,10 +49,17 @@ class Emulator:
model: str
url: str
save_path: Path | None = None
+ subpath: str | None
- def __init__(self, version: str, model: str) -> None:
+ def __init__(
+ self,
+ version: str,
+ model: str,
+ subpath: str | None = None,
+ ) -> None:
self.version = version
self.model = model
+ self.subpath = subpath
self.url = self._get_download_url()
def download(
@@ -84,9 +93,11 @@ class Emulator:
path.chmod(path.stat().st_mode | stat.S_IXUSR)
def check_download_availability(self) -> None:
-
- version_tuple = tuple(int(part) for part in self.version.split("."))
- if version_tuple < OLDEST_AVAILABLE[gen_from_model(self.model)]:
+ version_tuple = self._parse_version_tuple()
+ if (
+ version_tuple is not None
+ and version_tuple < OLDEST_AVAILABLE[gen_from_model(self.model)]
+ ):
# Is old known-to-be-unavailable version
raise KnownMissingArtifactError(self.model, self.version)
@@ -98,10 +109,29 @@ class Emulator:
def _get_filename(self) -> str:
return f"trezor-emu-{gen_from_model(self.model)}-{self.model}-v{self.version}"
+ def _parse_version_tuple(self) -> tuple[int, int, int] | None:
+ version = self.version.split("-", maxsplit=1)[0]
+ parts = version.split(".")
+ if len(parts) != 3:
+ return None
+
+ try:
+ major, minor, patch = (int(part) for part in parts)
+ return major, minor, patch
+ except ValueError:
+ return None
+
def _get_default_save_path(self) -> Path:
+ if self.subpath is not None:
+ return SAVE_DIR / self.model / self.subpath / self._get_filename()
return SAVE_DIR / self.model / self._get_filename()
def _get_download_url(self) -> str:
+ if self.subpath is not None:
+ return (
+ f"{EMULATORS_URL_PREFIX}/{self.model}/"
+ f"{self.subpath}/{self._get_filename()}"
+ )
return f"{EMULATORS_URL_PREFIX}/{self.model}/{self._get_filename()}"
@@ -113,6 +143,7 @@ def get_all_releases() -> EmulatorDict:
def get_emulators_for_model(model: str, firmwares: EmulatorDict) -> list[Emulator]:
emulators: list[Emulator] = []
+ is_tropic_capable = model in TROPIC_CAPABLE_MODELS
for version, models in firmwares.items():
if model in models:
try:
@@ -126,6 +157,23 @@ def get_emulators_for_model(model: str, firmwares: EmulatorDict) -> list[Emulato
click.echo(
f"Artifact for model {e.model}, version: {e.version} is unavailable!"
)
+
+ if is_tropic_capable:
+ try:
+ tropic_emu = Emulator(
+ version=version,
+ model=model,
+ subpath=f"{model}{TROPIC_REMOTE_SUBPATH_SUFFIX}",
+ )
+ tropic_emu.check_download_availability()
+ emulators.append(tropic_emu)
+ except KnownMissingArtifactError:
+ # Old artifacts that are known to be unavailable
+ pass
+ except MissingArtifactError as e:
+ click.echo(
+ f"Tropic artifact for model {e.model}, version: {e.version} is unavailable!"
+ )
return emulators
diff --git a/tests/download_emulators.sh b/tests/download_emulators.sh
index 1db5b2d8..68d66eb8 100755
--- a/tests/download_emulators.sh
+++ b/tests/download_emulators.sh
@@ -14,4 +14,4 @@ uv run python download_emulators.py "$MODEL"
cd ..
# are we in Nix(OS)?
-command -v nix-shell >/dev/null && nix-shell --run 'NIX_BINTOOLS=$NIX_BINTOOLS_FOR_TARGET autoPatchelf tests/emulators/$MODEL'
+command -v nix-shell >/dev/null && nix-shell --run "NIX_BINTOOLS=\$NIX_BINTOOLS_FOR_TARGET autoPatchelf tests/emulators/$MODEL"
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.