1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-12-18 04:18:10 +00:00

chore(tests): improve common code

This commit is contained in:
grdddj 2023-05-04 14:20:00 +02:00 committed by Martin Milata
parent f809a37f7d
commit 1f3bb6d0af
2 changed files with 94 additions and 23 deletions

View File

@ -1,6 +1,8 @@
import time
from typing import Iterator, Tuple
Coords = Tuple[int, int]
DISPLAY_WIDTH = 240
DISPLAY_HEIGHT = 240
@ -22,7 +24,10 @@ OK = (RIGHT, BOTTOM)
CANCEL = (LEFT, BOTTOM)
INFO = (MID, BOTTOM)
CORNER_BUTTON = (215, 25)
CONFIRM_WORD = (MID, TOP)
TOP_ROW = (MID, TOP)
RESET_MINUS = (LEFT, grid(DISPLAY_HEIGHT, 5, 1))
RESET_PLUS = (RIGHT, grid(DISPLAY_HEIGHT, 5, 1))
@ -37,48 +42,89 @@ RESET_WORD_CHECK = [
BUTTON_LETTERS_BIP39 = ("abc", "def", "ghi", "jkl", "mno", "pqr", "stu", "vwx", "yz")
BUTTON_LETTERS_SLIP39 = ("ab", "cd", "ef", "ghij", "klm", "nopq", "rs", "tuv", "wxyz")
# fmt: off
PASSPHRASE_LOWERCASE = (" ", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz", "*#")
PASSPHRASE_UPPERCASE = (" ", "ABC", "DEF", "GHI", "JKL", "MNO", "PQRS", "TUV", "WXYZ", "*#")
PASSPHRASE_DIGITS = ("1", "2", "3", "4", "5", "6", "7", "8", "9", "0")
PASSPHRASE_SPECIAL = ("_<>", ".:@", "/|\\", "!()", "+%&", "-[]", "?{}", ",'`", ";\"~", "$^=")
# fmt: on
def grid35(x: int, y: int) -> Tuple[int, int]:
def get_passphrase_choices(char: str) -> tuple[str, ...]:
if char in " *#":
return PASSPHRASE_LOWERCASE
if char.islower():
return PASSPHRASE_LOWERCASE
elif char.isupper():
return PASSPHRASE_UPPERCASE
elif char.isdigit():
return PASSPHRASE_DIGITS
else:
return PASSPHRASE_SPECIAL
def passphrase(char: str) -> Tuple[Coords, int]:
choices = get_passphrase_choices(char)
idx = next(i for i, letters in enumerate(choices) if char in letters)
click_amount = choices[idx].index(char) + 1
return pin_passphrase_index(idx), click_amount
def pin_passphrase_index(idx: int) -> Coords:
if idx == 9:
idx = 10 # last digit is in the middle
return pin_passphrase_grid(idx)
def pin_passphrase_grid(idx: int) -> Coords:
grid_x = idx % 3
grid_y = idx // 3 + 1 # first line is empty
return grid35(grid_x, grid_y)
def grid35(x: int, y: int) -> Coords:
return grid(DISPLAY_WIDTH, 3, x), grid(DISPLAY_HEIGHT, 5, y)
def grid34(x: int, y: int) -> Tuple[int, int]:
def grid34(x: int, y: int) -> Coords:
return grid(DISPLAY_WIDTH, 3, x), grid(DISPLAY_HEIGHT, 4, y)
def _grid34_from_index(idx: int) -> Tuple[int, int]:
def _grid34_from_index(idx: int) -> Coords:
grid_x = idx % 3
grid_y = idx // 3 + 1 # first line is empty
return grid34(grid_x, grid_y)
def type_word(word: str, is_slip39: bool = False) -> Iterator[Tuple[int, int]]:
def type_word(word: str, is_slip39: bool = False) -> Iterator[Coords]:
if is_slip39:
yield from type_word_slip39(word)
yield from _type_word_slip39(word)
else:
yield from type_word_bip39(word)
yield from _type_word_bip39(word)
def type_word_slip39(word: str) -> Iterator[Tuple[int, int]]:
def _type_word_slip39(word: str) -> Iterator[Coords]:
for l in word:
idx = next(i for i, letters in enumerate(BUTTON_LETTERS_SLIP39) if l in letters)
yield _grid34_from_index(idx)
def type_word_bip39(word: str) -> Iterator[Tuple[int, int]]:
coords_prev: Tuple[int, int] | None = None
def _type_word_bip39(word: str) -> Iterator[Coords]:
coords_prev: Coords | None = None
for letter in word:
coords, amount = letter_coords_and_amount(letter)
time.sleep(0.1) # not being so quick to miss something
coords, amount = _letter_coords_and_amount(letter)
# If the button is the same as for the previous letter,
# waiting a second before pressing it again.
if coords == coords_prev:
time.sleep(1)
time.sleep(1.1)
coords_prev = coords
for _ in range(amount):
yield coords
def letter_coords_and_amount(letter: str) -> Tuple[Tuple[int, int], int]:
def _letter_coords_and_amount(letter: str) -> Tuple[Coords, int]:
idx = next(i for i, letters in enumerate(BUTTON_LETTERS_BIP39) if letter in letters)
click_amount = BUTTON_LETTERS_BIP39[idx].index(letter) + 1
return _grid34_from_index(idx), click_amount

View File

@ -15,8 +15,10 @@
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
import json
import time
from pathlib import Path
from typing import TYPE_CHECKING, Generator, List, Optional
from typing import TYPE_CHECKING, Generator, Optional
from unittest import mock
import pytest
@ -59,6 +61,10 @@ COMMON_FIXTURES_DIR = (
Path(__file__).resolve().parent.parent / "common" / "tests" / "fixtures"
)
# So that all the random things are consistent
MOCK_OS_URANDOM = mock.Mock(return_value=EXTERNAL_ENTROPY)
WITH_MOCK_URANDOM = mock.patch("os.urandom", MOCK_OS_URANDOM)
def parametrize_using_common_fixtures(*paths: str) -> "MarkDecorator":
fixtures = []
@ -124,7 +130,7 @@ def generate_entropy(
def recovery_enter_shares(
debug: "DebugLink",
shares: List[str],
shares: list[str],
groups: bool = False,
click_info: bool = False,
) -> Generator[None, "ButtonRequest", None]:
@ -179,7 +185,7 @@ def recovery_enter_shares(
def click_through(
debug: "DebugLink", screens: int, code: ButtonRequestType = None
debug: "DebugLink", screens: int, code: Optional[ButtonRequestType] = None
) -> Generator[None, "ButtonRequest", None]:
"""Click through N dialog screens.
@ -214,20 +220,25 @@ def read_and_confirm_mnemonic(
mnemonic = yield from read_and_confirm_mnemonic(client.debug)
"""
mnemonic = []
mnemonic: list[str] = []
br = yield
assert br.pages is not None
for _ in range(br.pages - 1):
mnemonic.extend(debug.read_reset_word().split())
debug.swipe_up(wait=True)
# last page is confirmation
mnemonic.extend(debug.read_reset_word().split())
debug.wait_layout()
for i in range(br.pages):
words = debug.wait_layout().seed_words()
mnemonic.extend(words)
# Not swiping on the last page
if i < br.pages - 1:
debug.swipe_up()
debug.press_yes()
# check share
for _ in range(3):
index = debug.read_reset_word_pos()
for i in range(3):
word_pos = int(debug.wait_layout().text_content().split()[2])
index = word_pos - 1
if choose_wrong:
debug.input(mnemonic[(index + 1) % len(mnemonic)])
return None
@ -237,6 +248,20 @@ def read_and_confirm_mnemonic(
return " ".join(mnemonic)
def click_info_button(debug: "DebugLink"):
"""Click Shamir backup info button and return back."""
debug.press_info()
yield # Info screen with text
debug.press_yes()
def check_pin_backoff_time(attempts: int, start: float) -> None:
"""Helper to assert the exponentially growing delay after incorrect PIN attempts"""
expected = (2**attempts) - 1
got = round(time.time() - start, 2)
assert got >= expected
def get_test_address(client: "Client") -> str:
"""Fetch a testnet address on a fixed path. Useful to make a pin/passphrase
protected call, or to identify the root secret (seed+passphrase)"""