chore(core): move backup flow adapters to `tests.input_flows`
What changed, and why it matters
This commit is a routine test-code cleanup. It moves helper functions used only in automated device tests from one test file into a shared test helper file so they can be reused. No firmware behavior, cryptography, or user-facing functionality is changed.
No security action needed. This is a test-only refactor.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The change refactors two Python test helpers, _normal and _try_to_cancel, from tests/device_tests/test_msg_backup_device.py into tests/input_flows.py, renaming them to normal and try_to_cancel. It also converts try_to_cancel into a factory that accepts an optional skip_cancel set and adds a cancels counter with an assertion that at least one cancel was attempted. All existing backup tests are updated to import the helpers from the new location. The actual device-under-test logic remains identical.
Changed components
tests/device_tests/test_msg_backup_device.pytests/input_flows.pyInspect captured patch +58 / −49
diff --git a/tests/device_tests/test_msg_backup_device.py b/tests/device_tests/test_msg_backup_device.py
index 7e5af8dd..6f89c630 100644
--- a/tests/device_tests/test_msg_backup_device.py
+++ b/tests/device_tests/test_msg_backup_device.py
@@ -15,12 +15,10 @@
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
import itertools
-import typing as t
import pytest
import shamir_mnemonic as shamir
-from tests.common import BRGeneratorType
from trezorlib import device, messages, models
from trezorlib.debuglink import DebugSession as Session
from trezorlib.debuglink import LayoutType
@@ -35,55 +33,21 @@ from ..common import (
MNEMONIC_SLIP39_CUSTOM_1of1,
)
from ..input_flows import (
+ FlowAdapter,
InputFlowBip39Backup,
InputFlowSlip39AdvancedBackup,
InputFlowSlip39BasicBackup,
InputFlowSlip39CustomBackup,
+ normal,
+ try_to_cancel,
)
-BACKUP_IN_PROGRESS = messages.Failure(
- code=messages.FailureType.InProgress,
- message="Backup in progress",
-)
-
-
-def _normal(
- _session: Session, flow: t.Callable[[], BRGeneratorType]
-) -> BRGeneratorType:
- return flow()
-
-
-def _try_to_cancel(
- session: Session, flow: t.Callable[[], BRGeneratorType]
-) -> BRGeneratorType:
- gen = flow()
- next(gen)
- while True:
- br = yield
- # Entering session's context will send an explicit THP ACK after `BACKUP_IN_PROGRESS` is received.
- with session.client._interact(force_flush=True):
- # Try to cancel the backup flow on Core
- with pytest.raises(TrezorFailure) as exc_info:
- session.call(messages.Cancel(), expect=messages.Failure)
- # Following #6483, backup is not cancellable
- assert exc_info.value.failure == BACKUP_IN_PROGRESS
- try:
- gen.send(br)
- except StopIteration:
- return
-
-
-if t.TYPE_CHECKING:
- FlowAdapter = t.Callable[
- [Session, t.Callable[[], BRGeneratorType]], BRGeneratorType
- ]
+FLOW_ADAPTERS = [normal, try_to_cancel()]
@pytest.mark.models("core") # TODO we want this for t1 too
@pytest.mark.setup_client(needs_backup=True, mnemonic=MNEMONIC12)
-@pytest.mark.parametrize(
- "adapt_flow", [_try_to_cancel, _normal], ids=lambda f: f.__name__
-)
+@pytest.mark.parametrize("adapt_flow", FLOW_ADAPTERS, ids=lambda f: f.__name__)
def test_backup_bip39(session: Session, adapt_flow: "FlowAdapter"):
assert session.features.backup_availability == messages.BackupAvailability.Required
@@ -103,7 +67,7 @@ def test_backup_bip39(session: Session, adapt_flow: "FlowAdapter"):
assert session.features.backup_type is messages.BackupType.Bip39
-SLIP39_BASIC_PARAMS = list(itertools.product([True, False], [_try_to_cancel, _normal]))
+SLIP39_BASIC_PARAMS = list(itertools.product([True, False], FLOW_ADAPTERS))
SLIP39_BASIC_IDS = [
f"{['no_click_info', 'click_info'][click_info]}_{adapt_flow.__name__}"
for click_info, adapt_flow in SLIP39_BASIC_PARAMS
@@ -146,9 +110,7 @@ def test_backup_slip39_basic(
@pytest.mark.models("core")
@pytest.mark.setup_client(needs_backup=True, mnemonic=MNEMONIC_SLIP39_SINGLE_EXT_20)
-@pytest.mark.parametrize(
- "adapt_flow", [_try_to_cancel, _normal], ids=lambda f: f.__name__
-)
+@pytest.mark.parametrize("adapt_flow", FLOW_ADAPTERS, ids=lambda f: f.__name__)
def test_backup_slip39_single(session: Session, adapt_flow: "FlowAdapter"):
assert session.features.backup_availability == messages.BackupAvailability.Required
@@ -175,9 +137,7 @@ def test_backup_slip39_single(session: Session, adapt_flow: "FlowAdapter"):
)
-SLIP39_ADVANCED_PARAMS = list(
- itertools.product([True, False], [_try_to_cancel, _normal])
-)
+SLIP39_ADVANCED_PARAMS = list(itertools.product([True, False], FLOW_ADAPTERS))
SLIP39_ADVANCED_IDS = [
f"{['no_click_info', 'click_info'][click_info]}_{adapt_flow.__name__}"
for click_info, adapt_flow in SLIP39_ADVANCED_PARAMS
@@ -223,7 +183,7 @@ def test_backup_slip39_advanced(
SLIP39_CUSTOM_PARAMS = [
(threshold, count, adapt_flow)
for threshold, count in ((1, 1), (2, 2), (3, 5))
- for adapt_flow in (_try_to_cancel, _normal)
+ for adapt_flow in FLOW_ADAPTERS
]
SLIP39_CUSTOM_IDS = [
f"{threshold}_of_{count}_{adapt_flow.__name__}"
diff --git a/tests/input_flows.py b/tests/input_flows.py
index 9757eb89..36cee74f 100644
--- a/tests/input_flows.py
+++ b/tests/input_flows.py
@@ -14,11 +14,14 @@ from __future__ import annotations
import time
from typing import Callable, Generator, Sequence
+import pytest
+
from trezorlib import messages
from trezorlib.client import Session
from trezorlib.debuglink import DebugLink, DebugSession, LayoutContent, LayoutType
from trezorlib.debuglink import TrezorTestContext as Client
from trezorlib.debuglink import multipage_content
+from trezorlib.exceptions import TrezorFailure
from . import translations as TR
from .common import (
@@ -35,6 +38,8 @@ from .input_flows_helpers import BackupFlow, EthereumFlow, PinFlow, RecoveryFlow
B = messages.ButtonRequestType
+FlowAdapter = Callable[[Session, Callable[[], BRGeneratorType]], BRGeneratorType]
+
class InputFlowBase:
def __init__(self, client: Client | DebugSession):
@@ -3181,3 +3186,47 @@ class InputFlowCancelBrightness(InputFlowBase):
def input_flow_delizia(self):
yield
self.debug.click(self.debug.screen_buttons.menu())
+
+
+# InputFlow adaptors
+
+
+def normal(_session: Session, flow: Callable[[], BRGeneratorType]) -> BRGeneratorType:
+ return flow()
+
+
+def try_to_cancel(skip_cancel: set[str] | None = None) -> FlowAdapter:
+ BACKUP_IN_PROGRESS = messages.Failure(
+ code=messages.FailureType.InProgress,
+ message="Backup in progress",
+ )
+
+ if skip_cancel is None:
+ skip_cancel = set()
+
+ def _try_to_cancel(
+ session: Session, flow: Callable[[], BRGeneratorType]
+ ) -> BRGeneratorType:
+ gen = flow()
+ next(gen)
+ cancels = 0
+ while True:
+ br = yield
+
+ # Don't cancel if the button request appears in `skip_cancel`
+ if br.name not in skip_cancel:
+ # Entering session's context will send an explicit THP ACK after `BACKUP_IN_PROGRESS` is received.
+ with session.client._interact(force_flush=True):
+ # Try to cancel the backup flow on Core
+ with pytest.raises(TrezorFailure) as exc_info:
+ session.call(messages.Cancel(), expect=messages.Failure)
+ # Following #6483, backup is not cancellable
+ assert exc_info.value.failure == BACKUP_IN_PROGRESS
+ cancels += 1
+ try:
+ gen.send(br)
+ except StopIteration:
+ assert cancels > 0
+ return
+
+ return _try_to_cancel
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.