chore: enable pyright for emu.py and scripts in test/
What changed, and why it matters
This commit is a routine developer tooling change. It turns on stricter Python type checking (Pyright) for the emulator script and some test helper scripts, and makes small code adjustments so those files pass the new checks. There is no change to the security-critical Trezor firmware itself, wallet operations, or cryptographic handling.
No security action required. Treat as normal code-quality/maintenance commit.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The commit enables Pyright static type checking for core/emu.py and selected scripts under tests/. Changes are limited to: adding files to pyrightconfig.json include lists, adding explicit type-assertions and ignore comments, replacing module-level imports with more specific imports, fixing a missing assertion in a test input flow, and adding null checks for optional fields. None of these touch firmware runtime, device drivers, or crypto code paths used by production devices.
Changed components
core/emu.pycore/pyrightconfig.jsonpyrightconfig.jsontests/bip32.pytests/conftest.pytests/definitions.pytests/input_flows.pytests/show_results.pytests/update_fixtures.pyInspect captured patch +34 / −25
diff --git a/core/emu.py b/core/emu.py
index 5e512600..5632a7c2 100755
--- a/core/emu.py
+++ b/core/emu.py
@@ -57,7 +57,9 @@ def watch_emulator(emulator: CoreEmulator) -> int:
assert inotify is not None
watch = inotify.adapters.InotifyTree(str(SRC_DIR))
try:
- for _, type_names, _, _ in watch.event_gen(yield_nones=False):
+ for ev in watch.event_gen(yield_nones=False):
+ assert ev is not None
+ type_names = ev[1]
if "IN_CLOSE_WRITE" in type_names:
emulator.restart()
except KeyboardInterrupt:
diff --git a/core/pyrightconfig.json b/core/pyrightconfig.json
index 49e022fa..1baab2ed 100644
--- a/core/pyrightconfig.json
+++ b/core/pyrightconfig.json
@@ -2,7 +2,8 @@
"include": [
"src",
"tools",
- "site_scons"
+ "site_scons",
+ "*.py"
],
"exclude": [
"src/apps/monero",
diff --git a/pyrightconfig.json b/pyrightconfig.json
index adba1e94..b44a810c 100644
--- a/pyrightconfig.json
+++ b/pyrightconfig.json
@@ -1,11 +1,17 @@
{
"include": [
"tools",
- "common"
+ "common",
+ "tests"
],
"exclude": [
"tools/pyright_tool.py",
- "tools/snippets"
+ "tools/snippets",
+ "tests/burn_tests",
+ "tests/click_tests",
+ "tests/device_tests",
+ "tests/fido_tests",
+ "tests/ui_tests"
],
"stubPath": "mocks/generated",
"typeCheckingMode": "basic",
diff --git a/tests/bip32.py b/tests/bip32.py
index 6fa08d6c..88bf7630 100644
--- a/tests/bip32.py
+++ b/tests/bip32.py
@@ -20,9 +20,10 @@ import struct
from copy import copy
from typing import Any, List, Tuple
-import ecdsa
from ecdsa.curves import SECP256k1
+from ecdsa.ecdsa import generator_secp256k1
from ecdsa.ellipticcurve import INFINITY, Point
+from ecdsa.numbertheory import square_root_mod_prime
from ecdsa.util import number_to_string, string_to_number
from trezorlib import messages, tools
@@ -47,14 +48,12 @@ def sec_to_public_pair(pubkey: bytes) -> Tuple[int, Any]:
curve = generator.curve()
p = curve.p()
alpha = (pow(x, 3, p) + curve.a() * x + curve.b()) % p
- beta = ecdsa.numbertheory.square_root_mod_prime(alpha, p)
+ beta = square_root_mod_prime(alpha, p)
if is_even == bool(beta & 1):
return (x, p - beta)
return (x, beta)
- return public_pair_for_x(
- ecdsa.ecdsa.generator_secp256k1, x, is_even=(sec0 == b"\2")
- )
+ return public_pair_for_x(generator_secp256k1, x, is_even=sec0 == b"\2")
def fingerprint(pubkey: bytes) -> int:
@@ -135,7 +134,7 @@ def deserialize(xpub: str) -> messages.HDNodeType:
fingerprint=struct.unpack(">I", data[5:9])[0],
child_num=struct.unpack(">I", data[9:13])[0],
chain_code=data[13:45],
- public_key=None,
+ public_key=b"",
)
key = data[45:-4]
diff --git a/tests/conftest.py b/tests/conftest.py
index b7128491..cac3901d 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -226,7 +226,7 @@ class ModelsFilter:
if isinstance(marker_list[0], models.TrezorModel):
# raw list of TrezorModels
- return set(marker_list) # type: ignore [incompatible with return type]
+ return set(marker_list) # type: ignore [is not assignable to return type]
if len(marker_list) == 1:
# @pytest.mark.models("t2t1,t2b1") -> ("t2t1,t2b1",) -> "t2t1,t2b1"
@@ -464,7 +464,7 @@ def pytest_sessionfinish(session: pytest.Session, exitstatus: pytest.ExitCode) -
if test_ui and _is_main_runner(session):
session.exitstatus = ui_tests.sessionfinish(
exitstatus,
- test_ui, # type: ignore
+ test_ui,
bool(session.config.getoption("ui_check_missing")),
bool(session.config.getoption("do_master_diff")),
)
@@ -480,7 +480,7 @@ def pytest_terminal_summary(
if ui_option:
ui_tests.terminal_summary(
terminalreporter.write_line,
- ui_option, # type: ignore
+ ui_option,
bool(config.getoption("ui_check_missing")),
exitstatus,
)
@@ -606,7 +606,7 @@ def pytest_runtest_makereport(item: pytest.Item, call) -> t.Generator:
# The device_handler fixture uses this as 'request.node.rep_call.passed' attribute,
# in order to raise error only if the test passed.
outcome = yield
- rep = outcome.get_result()
+ rep = outcome.get_result() # type: ignore [Cannot access attribute]
setattr(item, f"rep_{rep.when}", rep)
@@ -639,7 +639,7 @@ def device_handler(
# if test finished, make sure all background tasks are done
finalized_ok = device_handler.check_finalize()
- if test_res and not finalized_ok: # type: ignore [rep_call must exist]
+ if test_res and not finalized_ok:
raise RuntimeError("Test did not check result of background task")
diff --git a/tests/definitions.py b/tests/definitions.py
index a83350ab..2d0b7ff2 100644
--- a/tests/definitions.py
+++ b/tests/definitions.py
@@ -114,8 +114,8 @@ def encode_eth_token(
if isinstance(address, str):
if address.startswith("0x"):
address = address[2:]
- address = bytes.fromhex(address) # type: ignore (typechecker is lying)
- token = make_eth_token(symbol, decimals, address, chain_id, name) # type: ignore (typechecker is lying)
+ address = bytes.fromhex(address) # type: ignore [is not assignable to]
+ token = make_eth_token(symbol, decimals, address, chain_id, name) # type: ignore [cannot be assigned to]
payload = make_payload(
data_type=messages.DefinitionType.ETHEREUM_TOKEN, message=token
)
diff --git a/tests/input_flows.py b/tests/input_flows.py
index b0a624fb..bbf84ac5 100644
--- a/tests/input_flows.py
+++ b/tests/input_flows.py
@@ -125,7 +125,7 @@ class InputFlowNewWipeCodeCancel(InputFlowBase):
self.debug.synchronize_at("VerticalMenu")
self.debug.button_actions.navigate_to_menu_item(0)
- self.debug.read_layout().title == TR.wipe_code__cancel_setup
+ assert self.debug.read_layout().title() == TR.wipe_code__cancel_setup
self.debug.swipe_up()
self.debug.read_layout()
self.debug.synchronize_at("PromptScreen")
@@ -139,7 +139,7 @@ class InputFlowNewWipeCodeCancel(InputFlowBase):
self.debug.synchronize_at("VerticalMenu")
self.debug.button_actions.navigate_to_menu_item(0)
- self.debug.read_layout().title == TR.wipe_code__cancel_setup
+ assert TR.wipe_code__cancel_setup in self.debug.read_layout().text_content()
self.debug.press_no()
@@ -888,7 +888,7 @@ class InputFlowShowXpubQRCode(InputFlowBase):
self.debug.click(self.debug.screen_buttons.menu())
self.debug.press_no()
self.debug.press_no()
- for _ in range(br.pages - 1):
+ for _ in range((br.pages or 1) - 1):
self.debug.swipe_up()
self.debug.press_yes()
@@ -986,7 +986,7 @@ class InputFlowShowXpubQRCode(InputFlowBase):
# In case of page overflow, paginate to the last page
# The last page is the confirm page
- if br.pages > 1:
+ if br.pages and br.pages > 1:
for _ in range(br.pages - 1):
self.debug.click(self.debug.screen_buttons.ok())
diff --git a/tests/show_results.py b/tests/show_results.py
index ab68bd78..0e126220 100755
--- a/tests/show_results.py
+++ b/tests/show_results.py
@@ -67,8 +67,9 @@ class NoCacheRequestHandler(http.server.SimpleHTTPRequestHandler):
def do_POST(self) -> None:
if self.path == "/fixtures.json":
- length = int(self.headers.get("content-length"))
- field_data = self.rfile.read(length)
+ length = self.headers.get("content-length")
+ assert length
+ field_data = self.rfile.read(int(length))
data = json.loads(field_data)
test_name = data.get("test")
@@ -86,7 +87,7 @@ class NoCacheRequestHandler(http.server.SimpleHTTPRequestHandler):
def launch_http_server(port: int) -> None:
- http.server.test(HandlerClass=NoCacheRequestHandler, bind="localhost", port=port) # type: ignore [test is defined]
+ http.server.test(HandlerClass=NoCacheRequestHandler, bind="localhost", port=port) # type: ignore ["test" is not a known attribute]
@click.command()
diff --git a/tests/update_fixtures.py b/tests/update_fixtures.py
index c8750821..5549e649 100755
--- a/tests/update_fixtures.py
+++ b/tests/update_fixtures.py
@@ -98,7 +98,7 @@ def ci(
assert model == _model
group = next(iter(ui_res_dict[model].keys()))
current_model = current_fixtures.setdefault(model, {})
- current_group = current_model.setdefault(group, {}) # type: ignore
+ current_group = current_model.setdefault(group, {})
if remove_missing:
# get rid of tests that were not run in CI
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.