mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-07-12 17:48:09 +00:00
WIP - reorganize tests
This commit is contained in:
parent
82cf2d9bc7
commit
ba7afc3e28
@ -17,13 +17,14 @@
|
||||
import json
|
||||
import re
|
||||
import time
|
||||
import typing as t
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Generator, Optional
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
|
||||
from trezorlib import btc, messages, tools
|
||||
from trezorlib import btc, cosi, messages, tools
|
||||
from trezorlib.messages import ButtonRequestType
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@ -33,6 +34,7 @@ if TYPE_CHECKING:
|
||||
from trezorlib.debuglink import TrezorClientDebugLink as Client
|
||||
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]
|
||||
|
||||
@ -291,3 +293,19 @@ def compact_size(n: int) -> bytes:
|
||||
return bytes([254]) + n.to_bytes(4, "little")
|
||||
else:
|
||||
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)
|
||||
|
@ -23,13 +23,18 @@ from typing import TYPE_CHECKING, Generator, Iterator
|
||||
import pytest
|
||||
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.device import apply_settings, change_language
|
||||
from trezorlib.device import apply_settings
|
||||
from trezorlib.device import wipe as wipe_device
|
||||
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 .emulators import EmulatorWrapper
|
||||
|
||||
@ -43,14 +48,6 @@ if TYPE_CHECKING:
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
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]:
|
||||
@ -146,26 +143,8 @@ def _raw_client(request: pytest.FixtureRequest) -> Client:
|
||||
# Not doing it for T1
|
||||
if client.features.model != "1":
|
||||
lang = request.session.config.getoption("lang") or "en"
|
||||
_set_language(client, lang) # type: ignore
|
||||
|
||||
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}")
|
||||
assert isinstance(lang, str)
|
||||
translations.set_language(client, lang)
|
||||
|
||||
return client
|
||||
|
||||
|
@ -4,25 +4,9 @@ import io
|
||||
import typing as t
|
||||
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")]
|
||||
|
||||
|
||||
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)
|
||||
from ...common import sign_with_privkeys, PRIVATE_KEYS_DEV
|
||||
|
||||
|
||||
def make_network(
|
||||
|
@ -14,85 +14,41 @@
|
||||
# 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>.
|
||||
|
||||
import json
|
||||
from contextlib import contextmanager
|
||||
from copy import deepcopy
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, Generator
|
||||
from typing import Iterator
|
||||
|
||||
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 ..translations import (
|
||||
LANGUAGES,
|
||||
get_lang_json,
|
||||
set_language,
|
||||
build_and_sign_blob,
|
||||
)
|
||||
|
||||
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"
|
||||
CS_JSON = TRANSLATIONS / "cs.json"
|
||||
FR_JSON = TRANSLATIONS / "fr.json"
|
||||
|
||||
MAX_DATA_LENGTH = {"T": 48 * 1024, "Safe 3": 32 * 1024}
|
||||
MAX_DATA_LENGTH = {models.T2T1: 48 * 1024, models.T2B1: 32 * 1024}
|
||||
|
||||
|
||||
def _read_confirm_word(file: Path) -> str:
|
||||
content = json.loads(file.read_text())
|
||||
return content["translations"]["words"]["confirm"]
|
||||
def get_confirm(lang: str) -> str:
|
||||
content = get_lang_json(lang)
|
||||
return content["translations"]["words__confirm"]
|
||||
|
||||
|
||||
ENGLISH_CONFIRM = _read_confirm_word(EN_JSON)
|
||||
CZECH_CONFIRM = _read_confirm_word(CS_JSON)
|
||||
FRENCH_CONFIRM = _read_confirm_word(FR_JSON)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def _set_english_return_back(client: Client) -> Generator[Client, None, None]:
|
||||
@pytest.fixture
|
||||
def client(client: Client) -> Iterator[Client]:
|
||||
lang_before = client.features.language or ""
|
||||
try:
|
||||
_set_default_english(client)
|
||||
set_language(client, "en")
|
||||
yield client
|
||||
finally:
|
||||
if lang_before.startswith("en"):
|
||||
_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 "",
|
||||
)
|
||||
set_language(client, lang_before[:2])
|
||||
|
||||
|
||||
def _check_ping_screen_texts(client: Client, title: str, right_button: str) -> None:
|
||||
@ -115,8 +71,7 @@ def _check_ping_screen_texts(client: Client, title: str, right_button: str) -> N
|
||||
|
||||
|
||||
def test_change_language_errors(client: Client):
|
||||
with _set_english_return_back(client) as client:
|
||||
assert client.features.language == "en-US"
|
||||
assert client.features.language == "enUS"
|
||||
|
||||
# Translations too short
|
||||
# Sending less data than the header length
|
||||
@ -125,98 +80,82 @@ def test_change_language_errors(client: Client):
|
||||
), client:
|
||||
bad_data = (translations.HEADER_LEN - 1) * b"a"
|
||||
device.change_language(client, language_data=bad_data)
|
||||
assert client.features.language == "en-US"
|
||||
assert client.features.language == "enUS"
|
||||
|
||||
# Translations too long
|
||||
# Sending more than allowed by the flash capacity
|
||||
max_length = MAX_DATA_LENGTH[client.features.model]
|
||||
with pytest.raises(
|
||||
exceptions.TrezorFailure, match="Translations too long"
|
||||
), client:
|
||||
max_length = MAX_DATA_LENGTH[client.model]
|
||||
with pytest.raises(exceptions.TrezorFailure, match="Translations too long"), client:
|
||||
bad_data = (max_length + 1) * b"a"
|
||||
device.change_language(client, language_data=bad_data)
|
||||
assert client.features.language == "en-US"
|
||||
assert client.features.language == "enUS"
|
||||
|
||||
# Invalid header data length
|
||||
# Sending more data than advertised in the header
|
||||
with pytest.raises(
|
||||
exceptions.TrezorFailure, match="Invalid header data length"
|
||||
), client:
|
||||
good_data = translations.blob_from_file(
|
||||
CS_JSON, client.features.model or ""
|
||||
)
|
||||
good_data = build_and_sign_blob("cs", client.model)
|
||||
bad_data = good_data + b"abcd"
|
||||
device.change_language(client, language_data=bad_data)
|
||||
assert client.features.language == "en-US"
|
||||
assert client.features.language == "enUS"
|
||||
|
||||
# Invalid header magic
|
||||
# Does not match the expected magic
|
||||
with pytest.raises(
|
||||
exceptions.TrezorFailure, match="Invalid header magic"
|
||||
), client:
|
||||
good_data = translations.blob_from_file(
|
||||
CS_JSON, client.features.model or ""
|
||||
)
|
||||
with pytest.raises(exceptions.TrezorFailure, match="Invalid header magic"), client:
|
||||
good_data = build_and_sign_blob("cs", client.model)
|
||||
bad_data = 4 * b"a" + good_data[4:]
|
||||
device.change_language(client, language_data=bad_data)
|
||||
assert client.features.language == "en-US"
|
||||
assert client.features.language == "enUS"
|
||||
|
||||
# Invalid header data
|
||||
# Putting non-zero bytes where zero is expected
|
||||
with pytest.raises(
|
||||
exceptions.TrezorFailure, match="Invalid header data"
|
||||
), client:
|
||||
good_data = translations.blob_from_file(
|
||||
CS_JSON, client.features.model or ""
|
||||
)
|
||||
with pytest.raises(exceptions.TrezorFailure, match="Invalid header data"), client:
|
||||
good_data = build_and_sign_blob("cs", client.model)
|
||||
pre_sig_pos = translations.HEADER_LEN - translations.SIG_LEN
|
||||
bad_data = good_data[: pre_sig_pos - 4] + 4 * b"a" + good_data[pre_sig_pos:]
|
||||
device.change_language(
|
||||
client,
|
||||
language_data=bad_data,
|
||||
)
|
||||
assert client.features.language == "en-US"
|
||||
assert client.features.language == "enUS"
|
||||
|
||||
# Invalid data hash
|
||||
# Changing the data after their hash has been calculated
|
||||
with pytest.raises(exceptions.TrezorFailure, match="Invalid data hash"), client:
|
||||
good_data = translations.blob_from_file(
|
||||
CS_JSON, client.features.model or ""
|
||||
)
|
||||
good_data = build_and_sign_blob("cs", client.model)
|
||||
bad_data = good_data[:-8] + 8 * b"a"
|
||||
device.change_language(
|
||||
client,
|
||||
language_data=bad_data,
|
||||
)
|
||||
assert client.features.language == "en-US"
|
||||
assert client.features.language == "enUS"
|
||||
|
||||
# Invalid translations version
|
||||
# Change the version to one not matching the current device
|
||||
with pytest.raises(
|
||||
exceptions.TrezorFailure, match="Invalid translations version"
|
||||
), client:
|
||||
with open(CS_JSON, "r") as f:
|
||||
data = json.load(f)
|
||||
data = get_lang_json("cs")
|
||||
data["header"]["version"] = "3.5.4"
|
||||
device.change_language(
|
||||
client,
|
||||
language_data=_get_data_from_dict(data, client),
|
||||
language_data=build_and_sign_blob(data, client.model),
|
||||
)
|
||||
assert client.features.language == "en-US"
|
||||
assert client.features.language == "enUS"
|
||||
|
||||
# Invalid header version
|
||||
# Version is not a valid semver with integers
|
||||
with pytest.raises(
|
||||
exceptions.TrezorFailure, match="Invalid header version"
|
||||
), client:
|
||||
with open(CS_JSON, "r") as f:
|
||||
data = json.load(f)
|
||||
data = get_lang_json("cs")
|
||||
data["header"]["version"] = "ABC.XYZ.DEF"
|
||||
device.change_language(
|
||||
client,
|
||||
language_data=_get_data_from_dict(data, client),
|
||||
language_data=build_and_sign_blob(data, client.model),
|
||||
)
|
||||
assert client.features.language == "en-US"
|
||||
assert client.features.language == "enUS"
|
||||
|
||||
# Invalid translations signature
|
||||
# Modifying the signature part of the header
|
||||
@ -224,7 +163,7 @@ def test_change_language_errors(client: Client):
|
||||
exceptions.TrezorFailure, match="Invalid translations signature"
|
||||
), client:
|
||||
good_data = translations.blob_from_file(
|
||||
CS_JSON, client.features.model or ""
|
||||
get_lang_json("cs"), client.features.model or ""
|
||||
)
|
||||
bad_data = (
|
||||
good_data[: translations.HEADER_LEN - 8]
|
||||
@ -235,46 +174,40 @@ def test_change_language_errors(client: Client):
|
||||
client,
|
||||
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):
|
||||
with _set_english_return_back(client) as client:
|
||||
assert client.features.language == "en-US"
|
||||
@pytest.mark.parametrize("lang", LANGUAGES)
|
||||
def test_full_language_change(client: Client, lang: str):
|
||||
assert client.features.language == "enUS"
|
||||
|
||||
# Setting cs language
|
||||
_set_full_czech(client)
|
||||
assert client.features.language == "cs"
|
||||
_check_ping_screen_texts(client, CZECH_CONFIRM, CZECH_CONFIRM)
|
||||
|
||||
# Setting fr language
|
||||
_set_full_french(client)
|
||||
assert client.features.language == "fr"
|
||||
_check_ping_screen_texts(client, FRENCH_CONFIRM, FRENCH_CONFIRM)
|
||||
# Setting selected language
|
||||
set_language(client, lang)
|
||||
assert client.features.language[:2] == lang
|
||||
_check_ping_screen_texts(client, get_confirm(lang), get_confirm(lang))
|
||||
|
||||
# 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)
|
||||
set_language(client, "en")
|
||||
assert client.features.language == "enUS"
|
||||
_check_ping_screen_texts(client, get_confirm("en"), get_confirm("en"))
|
||||
|
||||
|
||||
def test_language_stays_after_wipe(client: Client):
|
||||
with _set_english_return_back(client) as client:
|
||||
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"))
|
||||
|
||||
# Setting cs language
|
||||
_set_full_czech(client)
|
||||
assert client.features.language == "cs"
|
||||
set_language(client, "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
|
||||
device.wipe(client)
|
||||
assert client.features.language == "cs"
|
||||
assert client.features.language == "csCZ"
|
||||
|
||||
# Load it again
|
||||
debuglink.load_device(
|
||||
@ -284,41 +217,40 @@ def test_language_stays_after_wipe(client: Client):
|
||||
passphrase_protection=False,
|
||||
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):
|
||||
with open(CS_JSON, "r") as f:
|
||||
czech_data = json.load(f)
|
||||
czech_data = get_lang_json("cs")
|
||||
|
||||
# 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 == "en-US"
|
||||
assert client.features.language == "enUS"
|
||||
|
||||
# Normal english
|
||||
_check_ping_screen_texts(client, ENGLISH_CONFIRM, ENGLISH_CONFIRM)
|
||||
_check_ping_screen_texts(client, get_confirm("en"), get_confirm("en"))
|
||||
|
||||
# Normal czech
|
||||
_set_full_czech(client)
|
||||
assert client.features.language == "cs"
|
||||
_check_ping_screen_texts(client, CZECH_CONFIRM, CZECH_CONFIRM)
|
||||
set_language(client, "cs")
|
||||
assert client.features.language == "csCZ"
|
||||
_check_ping_screen_texts(client, get_confirm("cs"), get_confirm("cs"))
|
||||
|
||||
# Modified czech - changed value
|
||||
czech_data_copy = deepcopy(czech_data)
|
||||
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(
|
||||
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
|
||||
czech_data_copy = deepcopy(czech_data)
|
||||
del czech_data_copy["translations"]["words"]["confirm"]
|
||||
del czech_data_copy["translations"]["words__confirm"]
|
||||
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"))
|
||||
|
@ -1,32 +1,84 @@
|
||||
import json
|
||||
import typing as t
|
||||
from hashlib import sha256
|
||||
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
|
||||
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]
|
||||
|
||||
|
||||
def _get_language_data(language: str) -> dict[str, dict[str, str]]:
|
||||
file = TRANSLATIONS / f"{language}.json"
|
||||
return json.loads(file.read_text())["translations"]
|
||||
def _get_language_data(lang: str) -> dict[str, str]:
|
||||
defs = get_lang_json(lang)
|
||||
# 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()
|
||||
|
||||
|
||||
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] = []
|
||||
lookups = path.split(".")
|
||||
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:
|
||||
assert isinstance(data, dict), f"{lookup} is not a dict"
|
||||
data = data[lookup]
|
||||
@ -34,27 +86,39 @@ def _resolve_path_to_texts(path: str, template: Iterable[Any] = ()) -> list[str]
|
||||
if template:
|
||||
data = data.format(*template)
|
||||
texts.append(data)
|
||||
|
||||
if lower:
|
||||
texts = [t.lower() for t in 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
|
||||
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)
|
||||
for t in texts:
|
||||
if t in text:
|
||||
if t in text.lower():
|
||||
return
|
||||
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)
|
||||
for t in texts:
|
||||
if text.startswith(t):
|
||||
if text.lower().startswith(t):
|
||||
return
|
||||
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:
|
||||
# Checking at least the first part
|
||||
first_part = t.split("{")[0]
|
||||
if text.startswith(first_part):
|
||||
if text.lower().startswith(first_part):
|
||||
return
|
||||
assert False, f"{text} not found in {templates}"
|
||||
|
||||
|
||||
def translate(path: str, template: Iterable[Any] = ()) -> list[str]:
|
||||
return _resolve_path_to_texts(path, template)
|
||||
def translate(
|
||||
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)
|
||||
|
@ -17,6 +17,7 @@ import pytest
|
||||
from PIL import Image
|
||||
from typing_extensions import Self
|
||||
|
||||
from trezorlib import models
|
||||
from trezorlib.debuglink import TrezorClientDebugLink as Client
|
||||
|
||||
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({})
|
||||
|
||||
ENGLISH_LANGUAGE_TREZOR = "en-US"
|
||||
ENGLISH_LANGUAGE = "en"
|
||||
FOREIGN_LANGUAGES = ["cs", "fr"]
|
||||
SUPPORTED_LANGUAGES = FOREIGN_LANGUAGES + [ENGLISH_LANGUAGE]
|
||||
|
||||
|
||||
def get_current_fixtures() -> FixturesType:
|
||||
@ -236,17 +234,18 @@ class TestCase:
|
||||
|
||||
@classmethod
|
||||
def build(cls, client: Client, request: pytest.FixtureRequest) -> Self:
|
||||
model = client.features.model
|
||||
# FIXME
|
||||
if model == "Safe 3":
|
||||
model = "R"
|
||||
if client.model is models.T2B1:
|
||||
model_name = "R"
|
||||
else:
|
||||
model_name = client.model.name
|
||||
name, group = _get_test_name_and_group(request.node.nodeid)
|
||||
language = client.features.language or ""
|
||||
if language == ENGLISH_LANGUAGE_TREZOR:
|
||||
if client.features.language:
|
||||
language = client.features.language[:2]
|
||||
else:
|
||||
language = ENGLISH_LANGUAGE
|
||||
assert language in SUPPORTED_LANGUAGES
|
||||
return cls(
|
||||
model=f"T{model}",
|
||||
model=f"T{model_name}",
|
||||
name=name,
|
||||
group=group,
|
||||
language=language,
|
||||
@ -254,8 +253,9 @@ class TestCase:
|
||||
|
||||
@staticmethod
|
||||
def get_language_from_fixture_name(fixture_name: str) -> str:
|
||||
# FIXME
|
||||
lang = fixture_name.split("_")[1]
|
||||
if lang in FOREIGN_LANGUAGES:
|
||||
if len(lang) == 2:
|
||||
return lang
|
||||
# English (currently) is implicit there
|
||||
return ENGLISH_LANGUAGE
|
||||
|
Loading…
Reference in New Issue
Block a user