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:
parent
82cf2d9bc7
commit
ba7afc3e28
@ -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)
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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(
|
||||||
|
@ -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"))
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user