refactor(tests): only wipe and reconfigure when the device state has changed

pull/3565/head
matejcik 3 months ago
parent 5bd75adfdf
commit fa4321f112

@ -17,8 +17,9 @@
from __future__ import annotations
import os
import typing as t
from dataclasses import dataclass
from pathlib import Path
from typing import TYPE_CHECKING, Generator, Iterator
import pytest
import xdist
@ -27,6 +28,7 @@ from trezorlib import debuglink, log, models
from trezorlib.debuglink import TrezorClientDebugLink as Client
from trezorlib.device import apply_settings
from trezorlib.device import wipe as wipe_device
from trezorlib.messages import Capability
from trezorlib.transport import enumerate_devices, get_transport
# register rewrites before importing from local package
@ -37,7 +39,7 @@ from . import translations, ui_tests
from .device_handler import BackgroundDeviceHandler
from .emulators import EmulatorWrapper
if TYPE_CHECKING:
if t.TYPE_CHECKING:
from _pytest.config import Config
from _pytest.config.argparsing import Parser
from _pytest.terminal import TerminalReporter
@ -63,7 +65,7 @@ def _emulator_wrapper_main_args() -> list[str]:
@pytest.fixture
def core_emulator(request: pytest.FixtureRequest) -> Iterator[Emulator]:
def core_emulator(request: pytest.FixtureRequest) -> t.Iterator[Emulator]:
"""Fixture returning default core emulator with possibility of screen recording."""
with EmulatorWrapper("core", main_args=_emulator_wrapper_main_args()) as emu:
# Modifying emu.client to add screen recording (when --ui=test is used)
@ -72,7 +74,7 @@ def core_emulator(request: pytest.FixtureRequest) -> Iterator[Emulator]:
@pytest.fixture(scope="session")
def emulator(request: pytest.FixtureRequest) -> Generator["Emulator", None, None]:
def emulator(request: pytest.FixtureRequest) -> t.Generator["Emulator", None, None]:
"""Fixture for getting emulator connection in case tests should operate it on their own.
Is responsible for starting it at the start of the session and stopping
@ -123,8 +125,28 @@ def emulator(request: pytest.FixtureRequest) -> Generator["Emulator", None, None
yield emu
@dataclass
class SetupParams:
uninitialized: bool = False
mnemonic: str = " ".join(["all"] * 12)
pin: str | None = None
passphrase: bool | str = False
needs_backup: bool = False
no_backup: bool = False
experimental: bool = False
lang: str = "en"
class ClientConnection:
def __init__(self, client: Client):
self.client = client
self.setup_params = SetupParams()
self.language = client.features.language or "en-US"
self.storage_hash = b""
@pytest.fixture(scope="session")
def _raw_client(request: pytest.FixtureRequest) -> Client:
def client_connection(request: pytest.FixtureRequest) -> ClientConnection:
# In case tests run in parallel, each process has its own emulator/client.
# Requesting the emulator fixture only if relevant.
if request.session.config.getoption("control_emulators"):
@ -138,14 +160,7 @@ def _raw_client(request: pytest.FixtureRequest) -> Client:
else:
client = _find_client(request, interact)
# Setting the appropriate language
# Not doing it for T1
if client.model is not models.T1B1:
lang = request.session.config.getoption("lang") or "en"
assert isinstance(lang, str)
translations.set_language(client, lang)
return client
return ClientConnection(client)
def _client_from_path(
@ -173,8 +188,8 @@ def _find_client(request: pytest.FixtureRequest, interact: bool) -> Client:
@pytest.fixture(scope="function")
def client(
request: pytest.FixtureRequest, _raw_client: Client
) -> Generator[Client, None, None]:
request: pytest.FixtureRequest, client_connection: ClientConnection
) -> t.Generator[Client, None, None]:
"""Client fixture.
Every test function that requires a client instance will get it from here.
@ -198,29 +213,19 @@ def client(
@pytest.mark.experimental
"""
if (
request.node.get_closest_marker("skip_t2t1")
and _raw_client.model is models.T2T1
):
client = client_connection.client
if request.node.get_closest_marker("skip_t2t1") and client.model is models.T2T1:
pytest.skip("Test excluded on Trezor T")
if (
request.node.get_closest_marker("skip_t1b1")
and _raw_client.model is models.T1B1
):
if request.node.get_closest_marker("skip_t1b1") and client.model is models.T1B1:
pytest.skip("Test excluded on Trezor 1")
if (
request.node.get_closest_marker("skip_t2b1")
and _raw_client.model is models.T2B1
):
if request.node.get_closest_marker("skip_t2b1") and client.model is models.T2B1:
pytest.skip("Test excluded on Trezor T2B1")
if (
request.node.get_closest_marker("skip_t3t1")
and _raw_client.model is models.T3T1
):
if request.node.get_closest_marker("skip_t3t1") and client.model is models.T3T1:
pytest.skip("Test excluded on Trezor T3T1")
sd_marker = request.node.get_closest_marker("sd_card")
if sd_marker and not _raw_client.features.sd_card_present:
if sd_marker and not client.features.sd_card_present:
raise RuntimeError(
"This test requires SD card.\n"
"To skip all such tests, run:\n"
@ -229,74 +234,77 @@ def client(
test_ui = request.config.getoption("ui")
_raw_client.reset_debug_features()
_raw_client.open()
client.reset_debug_features()
client.open()
try:
_raw_client.init_device()
client.init_device()
except Exception:
request.session.shouldstop = "Failed to communicate with Trezor"
pytest.fail("Failed to communicate with Trezor")
# Resetting all the debug events to not be influenced by previous test
_raw_client.debug.reset_debug_events()
client.debug.reset_debug_events()
if test_ui:
# we need to reseed before the wipe
_raw_client.debug.reseed(0)
client.debug.reseed(0)
if sd_marker:
should_format = sd_marker.kwargs.get("formatted", True)
_raw_client.debug.erase_sd_card(format=should_format)
wipe_device(_raw_client)
# Load language again, as it got erased in wipe
if _raw_client.model is not models.T1B1:
lang = request.session.config.getoption("lang") or "en"
assert isinstance(lang, str)
if lang != "en":
translations.set_language(_raw_client, lang)
setup_params = dict(
uninitialized=False,
mnemonic=" ".join(["all"] * 12),
pin=None,
passphrase=False,
needs_backup=False,
no_backup=False,
)
client.debug.erase_sd_card(format=should_format)
setup_params = SetupParams()
setup_params.experimental = bool(request.node.get_closest_marker("experimental"))
setup_params.lang = t.cast(str, request.session.config.getoption("lang") or "en")
marker = request.node.get_closest_marker("setup_client")
if marker:
setup_params.update(marker.kwargs)
setup_params.__dict__.update(marker.kwargs)
use_passphrase = setup_params["passphrase"] is True or isinstance(
setup_params["passphrase"], str
)
storage_hash = client.debug.storage_hash()
if (
client_connection.storage_hash != storage_hash
or client_connection.setup_params != setup_params
):
# wipe and reconfigure
wipe_device(client)
if not setup_params["uninitialized"]:
debuglink.load_device(
_raw_client,
mnemonic=setup_params["mnemonic"], # type: ignore
pin=setup_params["pin"], # type: ignore
passphrase_protection=use_passphrase,
label="test",
needs_backup=setup_params["needs_backup"], # type: ignore
no_backup=setup_params["no_backup"], # type: ignore
use_passphrase = setup_params.passphrase is True or isinstance(
setup_params.passphrase, str
)
if request.node.get_closest_marker("experimental"):
apply_settings(_raw_client, experimental_features=True)
if (
setup_params.lang != "en"
and Capability.Translations in client.features.capabilities
):
translations.set_language(client, setup_params.lang)
if not setup_params.uninitialized:
debuglink.load_device(
client,
mnemonic=setup_params.mnemonic,
pin=setup_params.pin,
passphrase_protection=use_passphrase,
label="test",
needs_backup=setup_params.needs_backup,
no_backup=setup_params.no_backup,
)
if setup_params.experimental:
apply_settings(client, experimental_features=True)
if use_passphrase and isinstance(setup_params.passphrase, str):
client.use_passphrase(setup_params.passphrase)
if use_passphrase and isinstance(setup_params["passphrase"], str):
_raw_client.use_passphrase(setup_params["passphrase"])
client_connection.setup_params = setup_params
client_connection.storage_hash = client.debug.storage_hash()
_raw_client.clear_session()
client.clear_session()
with ui_tests.screen_recording(_raw_client, request):
yield _raw_client
with ui_tests.screen_recording(client, request):
yield client
_raw_client.close()
client.close()
def _is_main_runner(session_or_request: pytest.Session | pytest.FixtureRequest) -> bool:
@ -436,7 +444,7 @@ def pytest_runtest_setup(item: pytest.Item) -> None:
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_makereport(item: pytest.Item, call) -> Generator:
def pytest_runtest_makereport(item: pytest.Item, call) -> t.Generator:
# Make test results available in fixtures.
# See https://docs.pytest.org/en/latest/example/simple.html#making-test-result-information-available-in-fixtures
# The device_handler fixture uses this as 'request.node.rep_call.passed' attribute,
@ -447,7 +455,7 @@ def pytest_runtest_makereport(item: pytest.Item, call) -> Generator:
@pytest.fixture
def device_handler(client: Client, request: pytest.FixtureRequest) -> Generator:
def device_handler(client: Client, request: pytest.FixtureRequest) -> t.Generator:
device_handler = BackgroundDeviceHandler(client)
yield device_handler

Loading…
Cancel
Save