1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-07-14 02:28:19 +00:00

WIP - reorganize tests

This commit is contained in:
matejcik 2024-01-29 13:43:28 +01:00
parent 82cf2d9bc7
commit ba7afc3e28
6 changed files with 296 additions and 316 deletions

View File

@ -17,13 +17,14 @@
import json import json
import re import re
import time import time
import typing as t
from pathlib import Path from pathlib import Path
from typing import TYPE_CHECKING, Generator, Optional from typing import TYPE_CHECKING, Generator, Optional
from unittest import mock from unittest import mock
import pytest import pytest
from trezorlib import btc, messages, tools from trezorlib import btc, cosi, messages, tools
from trezorlib.messages import ButtonRequestType from trezorlib.messages import ButtonRequestType
if TYPE_CHECKING: if TYPE_CHECKING:
@ -33,6 +34,7 @@ if TYPE_CHECKING:
from trezorlib.debuglink import TrezorClientDebugLink as Client from trezorlib.debuglink import TrezorClientDebugLink as Client
from trezorlib.messages import ButtonRequest from trezorlib.messages import ButtonRequest
PRIVATE_KEYS_DEV = [byte * 32 for byte in (b"\xdd", b"\xde", b"\xdf")]
BRGeneratorType = Generator[None, messages.ButtonRequest, None] BRGeneratorType = Generator[None, messages.ButtonRequest, None]
@ -291,3 +293,19 @@ def compact_size(n: int) -> bytes:
return bytes([254]) + n.to_bytes(4, "little") return bytes([254]) + n.to_bytes(4, "little")
else: else:
return bytes([255]) + n.to_bytes(8, "little") return bytes([255]) + n.to_bytes(8, "little")
def sign_with_privkeys(digest: bytes, privkeys: t.Sequence[bytes]) -> bytes:
"""Locally produce a CoSi signature."""
pubkeys = [cosi.pubkey_from_privkey(sk) for sk in privkeys]
nonces = [cosi.get_nonce(sk, digest, i) for i, sk in enumerate(privkeys)]
global_pk = cosi.combine_keys(pubkeys)
global_R = cosi.combine_keys(R for _, R in nonces)
sigs = [
cosi.sign_with_privkey(digest, sk, global_pk, r, global_R)
for sk, (r, _) in zip(privkeys, nonces)
]
return cosi.combine_sig(global_R, sigs)

View File

@ -23,13 +23,18 @@ from typing import TYPE_CHECKING, Generator, Iterator
import pytest import pytest
import xdist import xdist
from trezorlib import debuglink, log, translations from trezorlib import debuglink, log
from trezorlib._internal import translations
from trezorlib.debuglink import TrezorClientDebugLink as Client from trezorlib.debuglink import TrezorClientDebugLink as Client
from trezorlib.device import apply_settings, change_language from trezorlib.device import apply_settings
from trezorlib.device import wipe as wipe_device from trezorlib.device import wipe as wipe_device
from trezorlib.transport import enumerate_devices, get_transport from trezorlib.transport import enumerate_devices, get_transport
from . import ui_tests # register rewrites before importing from local package
# so that we see details of failed asserts from this module
pytest.register_assert_rewrite("tests.common")
from . import translations, ui_tests
from .device_handler import BackgroundDeviceHandler from .device_handler import BackgroundDeviceHandler
from .emulators import EmulatorWrapper from .emulators import EmulatorWrapper
@ -43,14 +48,6 @@ if TYPE_CHECKING:
HERE = Path(__file__).resolve().parent HERE = Path(__file__).resolve().parent
CORE = HERE.parent / "core" CORE = HERE.parent / "core"
TRANSLATIONS = CORE / "embed" / "rust" / "src" / "ui" / "translations"
CS_JSON = TRANSLATIONS / "cs.json"
FR_JSON = TRANSLATIONS / "fr.json"
# So that we see details of failed asserts from this module
pytest.register_assert_rewrite("tests.common")
def _emulator_wrapper_main_args() -> list[str]: def _emulator_wrapper_main_args() -> list[str]:
@ -146,26 +143,8 @@ def _raw_client(request: pytest.FixtureRequest) -> Client:
# Not doing it for T1 # Not doing it for T1
if client.features.model != "1": if client.features.model != "1":
lang = request.session.config.getoption("lang") or "en" lang = request.session.config.getoption("lang") or "en"
_set_language(client, lang) # type: ignore assert isinstance(lang, str)
translations.set_language(client, lang)
return client
def _set_language(client: Client, lang: str) -> Client:
model = client.features.model or ""
if lang == "en":
with client:
change_language(client, language_data=b"")
elif lang == "cs":
change_language(
client, language_data=translations.blob_from_file(CS_JSON, model)
)
elif lang == "fr":
change_language(
client, language_data=translations.blob_from_file(FR_JSON, model)
)
else:
raise RuntimeError(f"Unknown language: {lang}")
return client return client

View File

@ -4,25 +4,9 @@ import io
import typing as t import typing as t
from hashlib import sha256 from hashlib import sha256
from trezorlib import cosi, definitions, messages, protobuf from trezorlib import definitions, messages, protobuf
PRIVATE_KEYS_DEV = [byte * 32 for byte in (b"\xdd", b"\xde", b"\xdf")] from ...common import sign_with_privkeys, PRIVATE_KEYS_DEV
def sign_with_privkeys(digest: bytes, privkeys: t.Sequence[bytes]) -> bytes:
"""Locally produce a CoSi signature."""
pubkeys = [cosi.pubkey_from_privkey(sk) for sk in privkeys]
nonces = [cosi.get_nonce(sk, digest, i) for i, sk in enumerate(privkeys)]
global_pk = cosi.combine_keys(pubkeys)
global_R = cosi.combine_keys(R for _, R in nonces)
sigs = [
cosi.sign_with_privkey(digest, sk, global_pk, r, global_R)
for sk, (r, _) in zip(privkeys, nonces)
]
return cosi.combine_sig(global_R, sigs)
def make_network( def make_network(

View File

@ -14,85 +14,41 @@
# You should have received a copy of the License along with this library. # You should have received a copy of the License along with this library.
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>. # If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
import json
from contextlib import contextmanager
from copy import deepcopy from copy import deepcopy
from pathlib import Path from typing import Iterator
from typing import Any, Dict, Generator
import pytest import pytest
from trezorlib import debuglink, device, exceptions, messages, translations from trezorlib import debuglink, device, exceptions, messages, models
from trezorlib._internal import translations
from trezorlib.debuglink import TrezorClientDebugLink as Client from trezorlib.debuglink import TrezorClientDebugLink as Client
from ..translations import (
LANGUAGES,
get_lang_json,
set_language,
build_and_sign_blob,
)
pytestmark = pytest.mark.skip_t1 pytestmark = pytest.mark.skip_t1
HERE = Path(__file__).parent.resolve()
CORE = HERE.parent.parent / "core"
TRANSLATIONS = CORE / "embed" / "rust" / "src" / "ui" / "translations"
FONTS = TRANSLATIONS / "fonts"
ORDER_FILE = TRANSLATIONS / "order.json"
EN_JSON = TRANSLATIONS / "en.json" MAX_DATA_LENGTH = {models.T2T1: 48 * 1024, models.T2B1: 32 * 1024}
CS_JSON = TRANSLATIONS / "cs.json"
FR_JSON = TRANSLATIONS / "fr.json"
MAX_DATA_LENGTH = {"T": 48 * 1024, "Safe 3": 32 * 1024}
def _read_confirm_word(file: Path) -> str: def get_confirm(lang: str) -> str:
content = json.loads(file.read_text()) content = get_lang_json(lang)
return content["translations"]["words"]["confirm"] return content["translations"]["words__confirm"]
ENGLISH_CONFIRM = _read_confirm_word(EN_JSON) @pytest.fixture
CZECH_CONFIRM = _read_confirm_word(CS_JSON) def client(client: Client) -> Iterator[Client]:
FRENCH_CONFIRM = _read_confirm_word(FR_JSON)
@contextmanager
def _set_english_return_back(client: Client) -> Generator[Client, None, None]:
lang_before = client.features.language or "" lang_before = client.features.language or ""
try: try:
_set_default_english(client) set_language(client, "en")
yield client yield client
finally: finally:
if lang_before.startswith("en"): set_language(client, lang_before[:2])
_set_default_english(client)
elif lang_before == "cs":
_set_full_czech(client)
elif lang_before == "fr":
_set_full_french(client)
else:
raise RuntimeError(f"Unknown language: {lang_before}")
def _set_full_czech(client: Client):
device.change_language(
client,
language_data=translations.blob_from_file(CS_JSON, client.features.model or ""),
)
def _set_full_french(client: Client):
device.change_language(
client,
language_data=translations.blob_from_file(FR_JSON, client.features.model or ""),
)
def _set_default_english(client: Client):
with client:
device.change_language(client, language_data=b"")
def _get_data_from_dict(data: Dict[str, Any], client: Client) -> bytes:
return translations.blob_from_dict(
data,
font_dir=FONTS,
order_json_file=ORDER_FILE,
model=client.features.model or "",
)
def _check_ping_screen_texts(client: Client, title: str, right_button: str) -> None: def _check_ping_screen_texts(client: Client, title: str, right_button: str) -> None:
@ -115,210 +71,186 @@ def _check_ping_screen_texts(client: Client, title: str, right_button: str) -> N
def test_change_language_errors(client: Client): def test_change_language_errors(client: Client):
with _set_english_return_back(client) as client: assert client.features.language == "enUS"
assert client.features.language == "en-US"
# Translations too short # Translations too short
# Sending less data than the header length # Sending less data than the header length
with pytest.raises( with pytest.raises(
exceptions.TrezorFailure, match="Translations too short" exceptions.TrezorFailure, match="Translations too short"
), client: ), client:
bad_data = (translations.HEADER_LEN - 1) * b"a" bad_data = (translations.HEADER_LEN - 1) * b"a"
device.change_language(client, language_data=bad_data) device.change_language(client, language_data=bad_data)
assert client.features.language == "en-US" assert client.features.language == "enUS"
# Translations too long # Translations too long
# Sending more than allowed by the flash capacity # Sending more than allowed by the flash capacity
max_length = MAX_DATA_LENGTH[client.features.model] max_length = MAX_DATA_LENGTH[client.model]
with pytest.raises( with pytest.raises(exceptions.TrezorFailure, match="Translations too long"), client:
exceptions.TrezorFailure, match="Translations too long" bad_data = (max_length + 1) * b"a"
), client: device.change_language(client, language_data=bad_data)
bad_data = (max_length + 1) * b"a" assert client.features.language == "enUS"
device.change_language(client, language_data=bad_data)
assert client.features.language == "en-US"
# Invalid header data length # Invalid header data length
# Sending more data than advertised in the header # Sending more data than advertised in the header
with pytest.raises( with pytest.raises(
exceptions.TrezorFailure, match="Invalid header data length" exceptions.TrezorFailure, match="Invalid header data length"
), client: ), client:
good_data = translations.blob_from_file( good_data = build_and_sign_blob("cs", client.model)
CS_JSON, client.features.model or "" bad_data = good_data + b"abcd"
) device.change_language(client, language_data=bad_data)
bad_data = good_data + b"abcd" assert client.features.language == "enUS"
device.change_language(client, language_data=bad_data)
assert client.features.language == "en-US"
# Invalid header magic # Invalid header magic
# Does not match the expected magic # Does not match the expected magic
with pytest.raises( with pytest.raises(exceptions.TrezorFailure, match="Invalid header magic"), client:
exceptions.TrezorFailure, match="Invalid header magic" good_data = build_and_sign_blob("cs", client.model)
), client: bad_data = 4 * b"a" + good_data[4:]
good_data = translations.blob_from_file( device.change_language(client, language_data=bad_data)
CS_JSON, client.features.model or "" assert client.features.language == "enUS"
)
bad_data = 4 * b"a" + good_data[4:]
device.change_language(client, language_data=bad_data)
assert client.features.language == "en-US"
# Invalid header data # Invalid header data
# Putting non-zero bytes where zero is expected # Putting non-zero bytes where zero is expected
with pytest.raises( with pytest.raises(exceptions.TrezorFailure, match="Invalid header data"), client:
exceptions.TrezorFailure, match="Invalid header data" good_data = build_and_sign_blob("cs", client.model)
), client: pre_sig_pos = translations.HEADER_LEN - translations.SIG_LEN
good_data = translations.blob_from_file( bad_data = good_data[: pre_sig_pos - 4] + 4 * b"a" + good_data[pre_sig_pos:]
CS_JSON, client.features.model or "" device.change_language(
) client,
pre_sig_pos = translations.HEADER_LEN - translations.SIG_LEN language_data=bad_data,
bad_data = good_data[: pre_sig_pos - 4] + 4 * b"a" + good_data[pre_sig_pos:] )
device.change_language( assert client.features.language == "enUS"
client,
language_data=bad_data,
)
assert client.features.language == "en-US"
# Invalid data hash # Invalid data hash
# Changing the data after their hash has been calculated # Changing the data after their hash has been calculated
with pytest.raises(exceptions.TrezorFailure, match="Invalid data hash"), client: with pytest.raises(exceptions.TrezorFailure, match="Invalid data hash"), client:
good_data = translations.blob_from_file( good_data = build_and_sign_blob("cs", client.model)
CS_JSON, client.features.model or "" bad_data = good_data[:-8] + 8 * b"a"
) device.change_language(
bad_data = good_data[:-8] + 8 * b"a" client,
device.change_language( language_data=bad_data,
client, )
language_data=bad_data, assert client.features.language == "enUS"
)
assert client.features.language == "en-US"
# Invalid translations version # Invalid translations version
# Change the version to one not matching the current device # Change the version to one not matching the current device
with pytest.raises( with pytest.raises(
exceptions.TrezorFailure, match="Invalid translations version" exceptions.TrezorFailure, match="Invalid translations version"
), client: ), client:
with open(CS_JSON, "r") as f: data = get_lang_json("cs")
data = json.load(f) data["header"]["version"] = "3.5.4"
data["header"]["version"] = "3.5.4" device.change_language(
device.change_language( client,
client, language_data=build_and_sign_blob(data, client.model),
language_data=_get_data_from_dict(data, client), )
) assert client.features.language == "enUS"
assert client.features.language == "en-US"
# Invalid header version # Invalid header version
# Version is not a valid semver with integers # Version is not a valid semver with integers
with pytest.raises( with pytest.raises(
exceptions.TrezorFailure, match="Invalid header version" exceptions.TrezorFailure, match="Invalid header version"
), client: ), client:
with open(CS_JSON, "r") as f: data = get_lang_json("cs")
data = json.load(f) data["header"]["version"] = "ABC.XYZ.DEF"
data["header"]["version"] = "ABC.XYZ.DEF" device.change_language(
device.change_language( client,
client, language_data=build_and_sign_blob(data, client.model),
language_data=_get_data_from_dict(data, client), )
) assert client.features.language == "enUS"
assert client.features.language == "en-US"
# Invalid translations signature # Invalid translations signature
# Modifying the signature part of the header # Modifying the signature part of the header
with pytest.raises( with pytest.raises(
exceptions.TrezorFailure, match="Invalid translations signature" exceptions.TrezorFailure, match="Invalid translations signature"
), client: ), client:
good_data = translations.blob_from_file( good_data = translations.blob_from_file(
CS_JSON, client.features.model or "" get_lang_json("cs"), client.features.model or ""
) )
bad_data = ( bad_data = (
good_data[: translations.HEADER_LEN - 8] good_data[: translations.HEADER_LEN - 8]
+ 8 * b"a" + 8 * b"a"
+ good_data[translations.HEADER_LEN :] + good_data[translations.HEADER_LEN :]
) )
device.change_language( device.change_language(
client, client,
language_data=bad_data, language_data=bad_data,
) )
assert client.features.language == "en-US" assert client.features.language == "enUS"
_check_ping_screen_texts(client, ENGLISH_CONFIRM, ENGLISH_CONFIRM) _check_ping_screen_texts(client, get_confirm("en"), get_confirm("en"))
def test_full_language_change(client: Client): @pytest.mark.parametrize("lang", LANGUAGES)
with _set_english_return_back(client) as client: def test_full_language_change(client: Client, lang: str):
assert client.features.language == "en-US" assert client.features.language == "enUS"
# Setting cs language # Setting selected language
_set_full_czech(client) set_language(client, lang)
assert client.features.language == "cs" assert client.features.language[:2] == lang
_check_ping_screen_texts(client, CZECH_CONFIRM, CZECH_CONFIRM) _check_ping_screen_texts(client, get_confirm(lang), get_confirm(lang))
# Setting fr language # Setting the default language via empty data
_set_full_french(client) set_language(client, "en")
assert client.features.language == "fr" assert client.features.language == "enUS"
_check_ping_screen_texts(client, FRENCH_CONFIRM, FRENCH_CONFIRM) _check_ping_screen_texts(client, get_confirm("en"), get_confirm("en"))
# Setting the default language via empty data
_set_default_english(client)
assert client.features.language == "en-US"
_check_ping_screen_texts(client, ENGLISH_CONFIRM, ENGLISH_CONFIRM)
def test_language_stays_after_wipe(client: Client): def test_language_stays_after_wipe(client: Client):
with _set_english_return_back(client) as client: assert client.features.language == "enUS"
assert client.features.language == "en-US"
_check_ping_screen_texts(client, ENGLISH_CONFIRM, ENGLISH_CONFIRM) _check_ping_screen_texts(client, get_confirm("en"), get_confirm("en"))
# Setting cs language # Setting cs language
_set_full_czech(client) set_language(client, "cs")
assert client.features.language == "cs" assert client.features.language == "csCZ"
_check_ping_screen_texts(client, CZECH_CONFIRM, CZECH_CONFIRM) _check_ping_screen_texts(client, get_confirm("cs"), get_confirm("cs"))
# Wipe device # Wipe device
device.wipe(client) device.wipe(client)
assert client.features.language == "cs" assert client.features.language == "csCZ"
# Load it again # Load it again
debuglink.load_device( debuglink.load_device(
client, client,
mnemonic=" ".join(["all"] * 12), mnemonic=" ".join(["all"] * 12),
pin=None, pin=None,
passphrase_protection=False, passphrase_protection=False,
label="test", label="test",
) )
assert client.features.language == "cs" assert client.features.language == "csCZ"
_check_ping_screen_texts(client, CZECH_CONFIRM, CZECH_CONFIRM) _check_ping_screen_texts(client, get_confirm("cs"), get_confirm("cs"))
def test_translations_renders_on_screen(client: Client): def test_translations_renders_on_screen(client: Client):
with open(CS_JSON, "r") as f: czech_data = get_lang_json("cs")
czech_data = json.load(f)
# Setting some values of words__confirm key and checking that in ping screen title # Setting some values of words__confirm key and checking that in ping screen title
with _set_english_return_back(client) as client: assert client.features.language == "enUS"
assert client.features.language == "en-US"
# Normal english # Normal english
_check_ping_screen_texts(client, ENGLISH_CONFIRM, ENGLISH_CONFIRM) _check_ping_screen_texts(client, get_confirm("en"), get_confirm("en"))
# Normal czech # Normal czech
_set_full_czech(client) set_language(client, "cs")
assert client.features.language == "cs" assert client.features.language == "csCZ"
_check_ping_screen_texts(client, CZECH_CONFIRM, CZECH_CONFIRM) _check_ping_screen_texts(client, get_confirm("cs"), get_confirm("cs"))
# Modified czech - changed value # Modified czech - changed value
czech_data_copy = deepcopy(czech_data) czech_data_copy = deepcopy(czech_data)
new_czech_confirm = "ABCD" new_czech_confirm = "ABCD"
czech_data_copy["translations"]["words"]["confirm"] = new_czech_confirm czech_data_copy["translations"]["words__confirm"] = new_czech_confirm
device.change_language( device.change_language(
client, client,
language_data=_get_data_from_dict(czech_data_copy, client), language_data=build_and_sign_blob(czech_data_copy, client.model),
) )
_check_ping_screen_texts(client, new_czech_confirm, CZECH_CONFIRM) _check_ping_screen_texts(client, new_czech_confirm, get_confirm("cs"))
# Modified czech - key deleted completely, english is shown # Modified czech - key deleted completely, english is shown
czech_data_copy = deepcopy(czech_data) czech_data_copy = deepcopy(czech_data)
del czech_data_copy["translations"]["words"]["confirm"] del czech_data_copy["translations"]["words__confirm"]
device.change_language( device.change_language(
client, language_data=_get_data_from_dict(czech_data_copy, client) client,
) language_data=build_and_sign_blob(czech_data_copy, client.model),
_check_ping_screen_texts(client, ENGLISH_CONFIRM, CZECH_CONFIRM) )
_check_ping_screen_texts(client, get_confirm("en"), get_confirm("cs"))

View File

@ -1,32 +1,84 @@
import json import json
import typing as t
from hashlib import sha256
from pathlib import Path from pathlib import Path
from typing import Any, Iterable
from trezorlib import device, models
from trezorlib._internal import translations
from trezorlib.debuglink import TrezorClientDebugLink as Client
from . import common
HERE = Path(__file__).resolve().parent HERE = Path(__file__).resolve().parent
ROOT = HERE.parent ROOT = HERE.parent
TRANSLATIONS = ROOT / "core" / "embed" / "rust" / "src" / "ui" / "translations" TRANSLATIONS = ROOT / "core" / "translations"
FONTS_DIR = TRANSLATIONS / "fonts"
ORDER_FILE = TRANSLATIONS / "order.json"
LANGUAGES = ["cs", "en", "fr"] LANGUAGES = [file.stem for file in TRANSLATIONS.glob("??.json")]
def _get_all_language_data() -> list[dict[str, dict[str, str]]]: def build_and_sign_blob(
lang_or_def: translations.JsonDef | Path | str, model: models.TrezorModel
) -> bytes:
order = translations.order_from_json(json.loads(ORDER_FILE.read_text()))
if isinstance(lang_or_def, str):
lang_or_def = get_lang_json(lang_or_def)
if isinstance(lang_or_def, Path):
lang_or_def = t.cast(translations.JsonDef, json.loads(lang_or_def.read_text()))
# generate raw blob
blob = translations.blob_from_defs(lang_or_def, order, model, FONTS_DIR)
# build 0-item Merkle proof
digest = sha256(b"\x00" + blob.header_bytes).digest()
signature = common.sign_with_privkeys(digest, common.PRIVATE_KEYS_DEV)
blob.proof = translations.Proof(
merkle_proof=[],
sigmask=0b111,
signature=signature,
)
return blob.build()
def set_language(client: Client, lang: str):
if lang.startswith("en"):
language_data = b""
else:
language_data = build_and_sign_blob(lang, client.model)
with client:
device.change_language(client, language_data)
def get_lang_json(lang: str) -> translations.JsonDef:
assert lang in LANGUAGES
return json.loads((TRANSLATIONS / f"{lang}.json").read_text())
def _get_all_language_data() -> list[dict[str, str]]:
return [_get_language_data(language) for language in LANGUAGES] return [_get_language_data(language) for language in LANGUAGES]
def _get_language_data(language: str) -> dict[str, dict[str, str]]: def _get_language_data(lang: str) -> dict[str, str]:
file = TRANSLATIONS / f"{language}.json" defs = get_lang_json(lang)
return json.loads(file.read_text())["translations"] # TODO: remove this before merge
translations = {
k: v.replace(" (TODO)", "") for k, v in defs["translations"].items()
}
return translations
all_language_data = _get_all_language_data() all_language_data = _get_all_language_data()
def _resolve_path_to_texts(path: str, template: Iterable[Any] = ()) -> list[str]: def _resolve_path_to_texts(
path: str, template: t.Iterable[t.Any] = (), lower: bool = True
) -> list[str]:
texts: list[str] = [] texts: list[str] = []
lookups = path.split(".") lookups = path.split(".")
for language_data in all_language_data: for language_data in all_language_data:
data: dict[str, Any] | str = language_data data: dict[str, t.Any] | str = language_data
for lookup in lookups: for lookup in lookups:
assert isinstance(data, dict), f"{lookup} is not a dict" assert isinstance(data, dict), f"{lookup} is not a dict"
data = data[lookup] data = data[lookup]
@ -34,27 +86,39 @@ def _resolve_path_to_texts(path: str, template: Iterable[Any] = ()) -> list[str]
if template: if template:
data = data.format(*template) data = data.format(*template)
texts.append(data) texts.append(data)
if lower:
texts = [t.lower() for t in texts]
return texts return texts
def assert_equals(text: str, path: str, template: Iterable[Any] = ()) -> None: def assert_equals(text: str, path: str, template: t.Iterable[t.Any] = ()) -> None:
# TODO: we can directly pass in the current device language # TODO: we can directly pass in the current device language
texts = _resolve_path_to_texts(path, template) texts = _resolve_path_to_texts(path, template)
assert text in texts, f"{text} not found in {texts}" assert text.lower() in texts, f"{text} not found in {texts}"
def assert_in(text: str, path: str, template: Iterable[Any] = ()) -> None: def assert_equals_multiple(
text: str, paths: list[str], template: t.Iterable[t.Any] = ()
) -> None:
texts: list[str] = []
for path in paths:
texts += _resolve_path_to_texts(path, template)
assert text.lower() in texts, f"{text} not found in {texts}"
def assert_in(text: str, path: str, template: t.Iterable[t.Any] = ()) -> None:
texts = _resolve_path_to_texts(path, template) texts = _resolve_path_to_texts(path, template)
for t in texts: for t in texts:
if t in text: if t in text.lower():
return return
assert False, f"{text} not found in {texts}" assert False, f"{text} not found in {texts}"
def assert_startswith(text: str, path: str, template: Iterable[Any] = ()) -> None: def assert_startswith(text: str, path: str, template: t.Iterable[t.Any] = ()) -> None:
texts = _resolve_path_to_texts(path, template) texts = _resolve_path_to_texts(path, template)
for t in texts: for t in texts:
if text.startswith(t): if text.lower().startswith(t):
return return
assert False, f"{text} not found in {texts}" assert False, f"{text} not found in {texts}"
@ -64,10 +128,13 @@ def assert_template(text: str, template_path: str) -> None:
for t in templates: for t in templates:
# Checking at least the first part # Checking at least the first part
first_part = t.split("{")[0] first_part = t.split("{")[0]
if text.startswith(first_part): if text.lower().startswith(first_part):
return return
assert False, f"{text} not found in {templates}" assert False, f"{text} not found in {templates}"
def translate(path: str, template: Iterable[Any] = ()) -> list[str]: def translate(
return _resolve_path_to_texts(path, template) path: str, template: t.Iterable[t.Any] = (), lower: bool = False
) -> list[str]:
# Do not converting to lowercase, we want the exact value
return _resolve_path_to_texts(path, template, lower=lower)

View File

@ -17,6 +17,7 @@ import pytest
from PIL import Image from PIL import Image
from typing_extensions import Self from typing_extensions import Self
from trezorlib import models
from trezorlib.debuglink import TrezorClientDebugLink as Client from trezorlib.debuglink import TrezorClientDebugLink as Client
UI_TESTS_DIR = Path(__file__).resolve().parent UI_TESTS_DIR = Path(__file__).resolve().parent
@ -36,10 +37,7 @@ FixturesType = t.NewType("FixturesType", "dict[str, dict[str, dict[str, str]]]")
FIXTURES: FixturesType = FixturesType({}) FIXTURES: FixturesType = FixturesType({})
ENGLISH_LANGUAGE_TREZOR = "en-US"
ENGLISH_LANGUAGE = "en" ENGLISH_LANGUAGE = "en"
FOREIGN_LANGUAGES = ["cs", "fr"]
SUPPORTED_LANGUAGES = FOREIGN_LANGUAGES + [ENGLISH_LANGUAGE]
def get_current_fixtures() -> FixturesType: def get_current_fixtures() -> FixturesType:
@ -236,17 +234,18 @@ class TestCase:
@classmethod @classmethod
def build(cls, client: Client, request: pytest.FixtureRequest) -> Self: def build(cls, client: Client, request: pytest.FixtureRequest) -> Self:
model = client.features.model
# FIXME # FIXME
if model == "Safe 3": if client.model is models.T2B1:
model = "R" model_name = "R"
else:
model_name = client.model.name
name, group = _get_test_name_and_group(request.node.nodeid) name, group = _get_test_name_and_group(request.node.nodeid)
language = client.features.language or "" if client.features.language:
if language == ENGLISH_LANGUAGE_TREZOR: language = client.features.language[:2]
else:
language = ENGLISH_LANGUAGE language = ENGLISH_LANGUAGE
assert language in SUPPORTED_LANGUAGES
return cls( return cls(
model=f"T{model}", model=f"T{model_name}",
name=name, name=name,
group=group, group=group,
language=language, language=language,
@ -254,8 +253,9 @@ class TestCase:
@staticmethod @staticmethod
def get_language_from_fixture_name(fixture_name: str) -> str: def get_language_from_fixture_name(fixture_name: str) -> str:
# FIXME
lang = fixture_name.split("_")[1] lang = fixture_name.split("_")[1]
if lang in FOREIGN_LANGUAGES: if len(lang) == 2:
return lang return lang
# English (currently) is implicit there # English (currently) is implicit there
return ENGLISH_LANGUAGE return ENGLISH_LANGUAGE