mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-11-22 07:28:10 +00:00
tests: introduce UI tests for core with diffs (#784)
tests: introduce UI tests for core with diffs
This commit is contained in:
commit
7c41b40dff
1
Pipfile
1
Pipfile
@ -18,6 +18,7 @@ flaky = ">=3.6.1" # https://github.com/box/flaky/issues/156
|
||||
pytest-ordering = "*"
|
||||
pytest-random-order = "*"
|
||||
tox = "*"
|
||||
dominate = "*"
|
||||
|
||||
## test requirements
|
||||
shamir-mnemonic = "*"
|
||||
|
10
Pipfile.lock
generated
10
Pipfile.lock
generated
@ -1,7 +1,7 @@
|
||||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "ef2e6e714004592166f0d16d22887ef55ddb3d61b49ac882e7ead764b638d7f9"
|
||||
"sha256": "e8d9a82935300b8716e549422ded189b2ce4408bc31b8d5c91bbe9979bf15a0a"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {},
|
||||
@ -196,6 +196,14 @@
|
||||
"index": "pypi",
|
||||
"version": "==2.2.4"
|
||||
},
|
||||
"dominate": {
|
||||
"hashes": [
|
||||
"sha256:6e833aea505f0236a9fc692326bac575f8bd38ae0f3a1bdc73d20ca606ac75d5",
|
||||
"sha256:a92474b4312bd8b4c1789792f3ec8c571cd8afa8e7502a2b1c64dd48cd67e59c"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.4.0"
|
||||
},
|
||||
"ecdsa": {
|
||||
"hashes": [
|
||||
"sha256:867ec9cf6df0b03addc8ef66b56359643cb5d0c1dc329df76ba7ecfe256c8061",
|
||||
|
@ -194,3 +194,18 @@ upgrade tests legacy deploy:
|
||||
- branches # run for tags only
|
||||
tags:
|
||||
- deploy
|
||||
|
||||
# UI tests
|
||||
|
||||
ui tests core fixtures deploy:
|
||||
stage: deploy
|
||||
variables:
|
||||
DEPLOY_PATH: "${DEPLOY_BASE_DIR}/ui_tests/"
|
||||
before_script: [] # no pipenv
|
||||
dependencies:
|
||||
- core unix device ui test
|
||||
script:
|
||||
- echo "Deploying to $DEPLOY_PATH"
|
||||
- rsync --delete -va ci/ui_test_records/* "$DEPLOY_PATH"
|
||||
tags:
|
||||
- deploy
|
||||
|
21
ci/prepare_ui_artifacts.py
Normal file
21
ci/prepare_ui_artifacts.py
Normal file
@ -0,0 +1,21 @@
|
||||
import hashlib
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def _hash_files(path):
|
||||
files = path.iterdir()
|
||||
hasher = hashlib.sha256()
|
||||
for file in sorted(files):
|
||||
hasher.update(file.read_bytes())
|
||||
|
||||
return hasher.digest().hex()
|
||||
|
||||
|
||||
fixture_root = Path().cwd() / "../tests/ui_tests/fixtures/"
|
||||
|
||||
for recorded_dir in fixture_root.glob("*/recorded"):
|
||||
expected_hash = (recorded_dir.parent / "hash.txt").read_text()
|
||||
actual_hash = _hash_files(recorded_dir)
|
||||
assert expected_hash == actual_hash
|
||||
shutil.make_archive("ui_test_records/" + actual_hash, "zip", recorded_dir)
|
23
ci/test.yml
23
ci/test.yml
@ -38,6 +38,29 @@ core unix unit test:
|
||||
- cd core
|
||||
- pipenv run make test
|
||||
|
||||
core unix device ui test:
|
||||
stage: test
|
||||
<<: *only_changes_core
|
||||
dependencies:
|
||||
- core unix frozen regular build
|
||||
script:
|
||||
- cd core
|
||||
- pipenv run make test_emu_ui
|
||||
- cp /var/tmp/trezor.log ${CI_PROJECT_DIR}
|
||||
- cd ../ci
|
||||
- pipenv run python prepare_ui_artifacts.py
|
||||
artifacts:
|
||||
name: core-unix-device-ui-test
|
||||
paths:
|
||||
- trezor.log
|
||||
- ci/ui_test_records/
|
||||
- tests/ui_tests/reports/
|
||||
- tests/junit.xml
|
||||
when: always
|
||||
expire_in: 1 week
|
||||
reports:
|
||||
junit: tests/junit.xml
|
||||
|
||||
core unix device test:
|
||||
stage: test
|
||||
<<: *only_changes_core
|
||||
|
2
ci/ui_test_records/.gitignore
vendored
Normal file
2
ci/ui_test_records/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
*
|
||||
!.gitignore
|
@ -39,6 +39,24 @@ message DebugLinkLayout {
|
||||
repeated string lines = 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Request: Re-seed RNG with given value
|
||||
* @start
|
||||
* @next Success
|
||||
*/
|
||||
message DebugLinkReseedRandom {
|
||||
optional uint32 value = 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Request: Start or stop recording screen changes into given target directory
|
||||
* @start
|
||||
* @next Success
|
||||
*/
|
||||
message DebugLinkRecordScreen {
|
||||
optional string target_directory = 1; // empty or missing to stop recording
|
||||
}
|
||||
|
||||
/**
|
||||
* Request: Computer asks for device state
|
||||
* @start
|
||||
|
@ -107,6 +107,8 @@ enum MessageType {
|
||||
MessageType_DebugLinkMemoryWrite = 112 [(wire_debug_in) = true];
|
||||
MessageType_DebugLinkFlashErase = 113 [(wire_debug_in) = true];
|
||||
MessageType_DebugLinkLayout = 9001 [(wire_debug_out) = true];
|
||||
MessageType_DebugLinkReseedRandom = 9002 [(wire_debug_in) = true];
|
||||
MessageType_DebugLinkRecordScreen = 9003 [(wire_debug_in) = true];
|
||||
|
||||
// Ethereum
|
||||
MessageType_EthereumGetPublicKey = 450 [(wire_in) = true];
|
||||
|
@ -79,6 +79,12 @@ test_emu_fido2: ## run fido2 device tests
|
||||
test_emu_click: ## run click tests
|
||||
cd tests ; ./run_tests_click_emu.sh $(TESTOPTS)
|
||||
|
||||
test_emu_ui: ## run ui integration tests
|
||||
cd tests ; ./run_tests_device_emu.sh --ui=test -m "not skip_ui" $(TESTOPTS)
|
||||
|
||||
test_emu_ui_record: ## record and hash screens for ui integration tests
|
||||
cd tests ; ./run_tests_device_emu.sh --ui=record -m "not skip_ui" $(TESTOPTS)
|
||||
|
||||
pylint: ## run pylint on application sources and tests
|
||||
pylint -E $(shell find src tests -name *.py)
|
||||
|
||||
|
@ -84,6 +84,19 @@ STATIC mp_obj_t mod_trezorcrypto_random_shuffle(mp_obj_t data) {
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorcrypto_random_shuffle_obj,
|
||||
mod_trezorcrypto_random_shuffle);
|
||||
|
||||
#ifdef TREZOR_EMULATOR
|
||||
/// def reseed(value: int) -> None:
|
||||
/// """
|
||||
/// Re-seed the RNG with given value.
|
||||
/// """
|
||||
STATIC mp_obj_t mod_trezorcrypto_random_reseed(mp_obj_t data) {
|
||||
random_reseed(trezor_obj_get_uint(data));
|
||||
return mp_const_none;
|
||||
}
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorcrypto_random_reseed_obj,
|
||||
mod_trezorcrypto_random_reseed);
|
||||
#endif
|
||||
|
||||
STATIC const mp_rom_map_elem_t mod_trezorcrypto_random_globals_table[] = {
|
||||
{MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_random)},
|
||||
{MP_ROM_QSTR(MP_QSTR_uniform),
|
||||
@ -92,6 +105,10 @@ STATIC const mp_rom_map_elem_t mod_trezorcrypto_random_globals_table[] = {
|
||||
MP_ROM_PTR(&mod_trezorcrypto_random_bytes_obj)},
|
||||
{MP_ROM_QSTR(MP_QSTR_shuffle),
|
||||
MP_ROM_PTR(&mod_trezorcrypto_random_shuffle_obj)},
|
||||
#ifdef TREZOR_EMULATOR
|
||||
{MP_ROM_QSTR(MP_QSTR_reseed),
|
||||
MP_ROM_PTR(&mod_trezorcrypto_random_reseed_obj)},
|
||||
#endif
|
||||
};
|
||||
STATIC MP_DEFINE_CONST_DICT(mod_trezorcrypto_random_globals,
|
||||
mod_trezorcrypto_random_globals_table);
|
||||
|
@ -499,3 +499,5 @@ void display_refresh(void) {
|
||||
}
|
||||
|
||||
const char *display_save(const char *prefix) { return NULL; }
|
||||
|
||||
void display_clear_save(void) {}
|
||||
|
@ -54,6 +54,8 @@ static SDL_Renderer *RENDERER;
|
||||
static SDL_Surface *BUFFER;
|
||||
static SDL_Texture *TEXTURE, *BACKGROUND;
|
||||
|
||||
static SDL_Surface *PREV_SAVED;
|
||||
|
||||
int sdl_display_res_x = DISPLAY_RESX, sdl_display_res_y = DISPLAY_RESY;
|
||||
int sdl_touch_offset_x, sdl_touch_offset_y;
|
||||
|
||||
@ -219,7 +221,6 @@ const char *display_save(const char *prefix) {
|
||||
}
|
||||
static int count;
|
||||
static char filename[256];
|
||||
static SDL_Surface *prev;
|
||||
// take a cropped view of the screen contents
|
||||
const SDL_Rect rect = {0, 0, DISPLAY_RESX, DISPLAY_RESY};
|
||||
SDL_Surface *crop = SDL_CreateRGBSurface(
|
||||
@ -228,16 +229,21 @@ const char *display_save(const char *prefix) {
|
||||
BUFFER->format->Amask);
|
||||
SDL_BlitSurface(BUFFER, &rect, crop, NULL);
|
||||
// compare with previous screen, skip if equal
|
||||
if (prev != NULL) {
|
||||
if (memcmp(prev->pixels, crop->pixels, crop->pitch * crop->h) == 0) {
|
||||
if (PREV_SAVED != NULL) {
|
||||
if (memcmp(PREV_SAVED->pixels, crop->pixels, crop->pitch * crop->h) == 0) {
|
||||
SDL_FreeSurface(crop);
|
||||
return filename;
|
||||
}
|
||||
SDL_FreeSurface(prev);
|
||||
SDL_FreeSurface(PREV_SAVED);
|
||||
}
|
||||
// save to png
|
||||
snprintf(filename, sizeof(filename), "%s%08d.png", prefix, count++);
|
||||
IMG_SavePNG(crop, filename);
|
||||
prev = crop;
|
||||
PREV_SAVED = crop;
|
||||
return filename;
|
||||
}
|
||||
|
||||
void display_clear_save(void) {
|
||||
SDL_FreeSurface(PREV_SAVED);
|
||||
PREV_SAVED = NULL;
|
||||
}
|
||||
|
@ -69,6 +69,7 @@
|
||||
void display_init(void);
|
||||
void display_refresh(void);
|
||||
const char *display_save(const char *prefix);
|
||||
void display_clear_save(void);
|
||||
|
||||
// provided by common
|
||||
|
||||
|
@ -535,6 +535,17 @@ STATIC mp_obj_t mod_trezorui_Display_save(mp_obj_t self, mp_obj_t prefix) {
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_trezorui_Display_save_obj,
|
||||
mod_trezorui_Display_save);
|
||||
|
||||
/// def clear_save(self) -> None:
|
||||
/// """
|
||||
/// Clears buffers in display saving.
|
||||
/// """
|
||||
STATIC mp_obj_t mod_trezorui_Display_clear_save(mp_obj_t self) {
|
||||
display_clear_save();
|
||||
return mp_const_none;
|
||||
}
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorui_Display_clear_save_obj,
|
||||
mod_trezorui_Display_clear_save);
|
||||
|
||||
STATIC const mp_rom_map_elem_t mod_trezorui_Display_locals_dict_table[] = {
|
||||
{MP_ROM_QSTR(MP_QSTR_clear), MP_ROM_PTR(&mod_trezorui_Display_clear_obj)},
|
||||
{MP_ROM_QSTR(MP_QSTR_refresh),
|
||||
@ -561,6 +572,8 @@ STATIC const mp_rom_map_elem_t mod_trezorui_Display_locals_dict_table[] = {
|
||||
MP_ROM_PTR(&mod_trezorui_Display_backlight_obj)},
|
||||
{MP_ROM_QSTR(MP_QSTR_offset), MP_ROM_PTR(&mod_trezorui_Display_offset_obj)},
|
||||
{MP_ROM_QSTR(MP_QSTR_save), MP_ROM_PTR(&mod_trezorui_Display_save_obj)},
|
||||
{MP_ROM_QSTR(MP_QSTR_clear_save),
|
||||
MP_ROM_PTR(&mod_trezorui_Display_clear_save_obj)},
|
||||
{MP_ROM_QSTR(MP_QSTR_WIDTH), MP_ROM_INT(DISPLAY_RESX)},
|
||||
{MP_ROM_QSTR(MP_QSTR_HEIGHT), MP_ROM_INT(DISPLAY_RESY)},
|
||||
{MP_ROM_QSTR(MP_QSTR_FONT_SIZE), MP_ROM_INT(FONT_SIZE)},
|
||||
|
@ -21,3 +21,10 @@ def shuffle(data: list) -> None:
|
||||
"""
|
||||
Shuffles items of given list (in-place).
|
||||
"""
|
||||
|
||||
|
||||
# extmod/modtrezorcrypto/modtrezorcrypto-random.h
|
||||
def reseed(value: int) -> None:
|
||||
"""
|
||||
Re-seed the RNG with given value.
|
||||
"""
|
||||
|
@ -187,3 +187,8 @@ class Display:
|
||||
"""
|
||||
Saves current display contents to PNG file with given prefix.
|
||||
"""
|
||||
|
||||
def clear_save(self) -> None:
|
||||
"""
|
||||
Clears buffers in display saving.
|
||||
"""
|
||||
|
@ -1,5 +1,5 @@
|
||||
import storage.device
|
||||
from trezor import ui, workflow
|
||||
from trezor import ui, utils, workflow
|
||||
from trezor.crypto import bip39, slip39
|
||||
from trezor.messages import BackupType
|
||||
|
||||
@ -34,7 +34,7 @@ def get_seed(passphrase: str = "", progress_bar: bool = True) -> bytes:
|
||||
raise ValueError("Mnemonic not set")
|
||||
|
||||
render_func = None
|
||||
if progress_bar:
|
||||
if progress_bar and not utils.DISABLE_ANIMATION:
|
||||
_start_progress()
|
||||
render_func = _render_progress
|
||||
|
||||
@ -62,11 +62,11 @@ def _start_progress() -> None:
|
||||
ui.backlight_fade(ui.BACKLIGHT_DIM)
|
||||
ui.display.clear()
|
||||
ui.header("Please wait")
|
||||
ui.display.refresh()
|
||||
ui.refresh()
|
||||
ui.backlight_fade(ui.BACKLIGHT_NORMAL)
|
||||
|
||||
|
||||
def _render_progress(progress: int, total: int) -> None:
|
||||
p = 1000 * progress // total
|
||||
ui.display.loader(p, False, 18, ui.WHITE, ui.BG)
|
||||
ui.display.refresh()
|
||||
ui.refresh()
|
||||
|
@ -4,16 +4,23 @@ if not __debug__:
|
||||
halt("debug mode inactive")
|
||||
|
||||
if __debug__:
|
||||
from trezor import config, io, log, loop, ui, utils, wire
|
||||
from trezor import io, ui, wire
|
||||
from trezor.messages import MessageType, DebugSwipeDirection
|
||||
from trezor.messages.DebugLinkLayout import DebugLinkLayout
|
||||
from trezor import config, crypto, log, loop, utils
|
||||
from trezor.messages.Success import Success
|
||||
|
||||
if False:
|
||||
from typing import List, Optional
|
||||
from trezor.messages.DebugLinkDecision import DebugLinkDecision
|
||||
from trezor.messages.DebugLinkGetState import DebugLinkGetState
|
||||
from trezor.messages.DebugLinkRecordScreen import DebugLinkRecordScreen
|
||||
from trezor.messages.DebugLinkReseedRandom import DebugLinkReseedRandom
|
||||
from trezor.messages.DebugLinkState import DebugLinkState
|
||||
|
||||
save_screen = False
|
||||
save_screen_directory = "."
|
||||
|
||||
reset_internal_entropy = None # type: Optional[bytes]
|
||||
reset_current_words = loop.chan()
|
||||
reset_word_index = loop.chan()
|
||||
@ -30,6 +37,12 @@ if __debug__:
|
||||
layout_change_chan = loop.chan()
|
||||
current_content = None # type: Optional[List[str]]
|
||||
|
||||
def screenshot() -> bool:
|
||||
if utils.SAVE_SCREEN or save_screen:
|
||||
ui.display.save(save_screen_directory + "/refresh-")
|
||||
return True
|
||||
return False
|
||||
|
||||
def notify_layout_change(layout: ui.Layout) -> None:
|
||||
global current_content
|
||||
current_content = layout.read_content()
|
||||
@ -104,12 +117,35 @@ if __debug__:
|
||||
m.reset_word = " ".join(await reset_current_words.take())
|
||||
return m
|
||||
|
||||
async def dispatch_DebugLinkRecordScreen(
|
||||
ctx: wire.Context, msg: DebugLinkRecordScreen
|
||||
) -> Success:
|
||||
global save_screen_directory
|
||||
global save_screen
|
||||
|
||||
if msg.target_directory:
|
||||
save_screen_directory = msg.target_directory
|
||||
save_screen = True
|
||||
else:
|
||||
save_screen = False
|
||||
ui.display.clear_save() # clear C buffers
|
||||
|
||||
return Success()
|
||||
|
||||
async def dispatch_DebugLinkReseedRandom(
|
||||
ctx: wire.Context, msg: DebugLinkReseedRandom
|
||||
) -> Success:
|
||||
if msg.value is not None:
|
||||
crypto.random.reseed(msg.value)
|
||||
return Success()
|
||||
|
||||
def boot() -> None:
|
||||
# wipe storage when debug build is used on real hardware
|
||||
if not utils.EMULATOR:
|
||||
config.wipe()
|
||||
|
||||
wire.add(MessageType.LoadDevice, __name__, "load_device")
|
||||
wire.register(MessageType.DebugLinkDecision, dispatch_DebugLinkDecision)
|
||||
wire.register(MessageType.DebugLinkGetState, dispatch_DebugLinkGetState)
|
||||
|
||||
wire.add(MessageType.LoadDevice, __name__, "load_device")
|
||||
wire.register(MessageType.DebugLinkReseedRandom, dispatch_DebugLinkReseedRandom)
|
||||
wire.register(MessageType.DebugLinkRecordScreen, dispatch_DebugLinkRecordScreen)
|
||||
|
@ -1,4 +1,4 @@
|
||||
from trezor import ui
|
||||
from trezor import ui, utils
|
||||
|
||||
_progress = 0
|
||||
_steps = 0
|
||||
@ -24,5 +24,7 @@ def report_init():
|
||||
|
||||
|
||||
def report():
|
||||
if utils.DISABLE_ANIMATION:
|
||||
return
|
||||
p = 1000 * _progress // _steps
|
||||
ui.display.loader(p, False, 18, ui.WHITE, ui.BG)
|
||||
|
26
core/src/trezor/messages/DebugLinkRecordScreen.py
Normal file
26
core/src/trezor/messages/DebugLinkRecordScreen.py
Normal file
@ -0,0 +1,26 @@
|
||||
# Automatically generated by pb2py
|
||||
# fmt: off
|
||||
import protobuf as p
|
||||
|
||||
if __debug__:
|
||||
try:
|
||||
from typing import Dict, List # noqa: F401
|
||||
from typing_extensions import Literal # noqa: F401
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
class DebugLinkRecordScreen(p.MessageType):
|
||||
MESSAGE_WIRE_TYPE = 9003
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
target_directory: str = None,
|
||||
) -> None:
|
||||
self.target_directory = target_directory
|
||||
|
||||
@classmethod
|
||||
def get_fields(cls) -> Dict:
|
||||
return {
|
||||
1: ('target_directory', p.UnicodeType, 0),
|
||||
}
|
26
core/src/trezor/messages/DebugLinkReseedRandom.py
Normal file
26
core/src/trezor/messages/DebugLinkReseedRandom.py
Normal file
@ -0,0 +1,26 @@
|
||||
# Automatically generated by pb2py
|
||||
# fmt: off
|
||||
import protobuf as p
|
||||
|
||||
if __debug__:
|
||||
try:
|
||||
from typing import Dict, List # noqa: F401
|
||||
from typing_extensions import Literal # noqa: F401
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
class DebugLinkReseedRandom(p.MessageType):
|
||||
MESSAGE_WIRE_TYPE = 9002
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
value: int = None,
|
||||
) -> None:
|
||||
self.value = value
|
||||
|
||||
@classmethod
|
||||
def get_fields(cls) -> Dict:
|
||||
return {
|
||||
1: ('value', p.UVarintType, 0),
|
||||
}
|
@ -74,6 +74,8 @@ DebugLinkMemory = 111 # type: Literal[111]
|
||||
DebugLinkMemoryWrite = 112 # type: Literal[112]
|
||||
DebugLinkFlashErase = 113 # type: Literal[113]
|
||||
DebugLinkLayout = 9001 # type: Literal[9001]
|
||||
DebugLinkReseedRandom = 9002 # type: Literal[9002]
|
||||
DebugLinkRecordScreen = 9003 # type: Literal[9003]
|
||||
if not utils.BITCOIN_ONLY:
|
||||
EthereumGetPublicKey = 450 # type: Literal[450]
|
||||
EthereumPublicKey = 451 # type: Literal[451]
|
||||
|
@ -1,4 +1,4 @@
|
||||
from trezor import ui
|
||||
from trezor import ui, utils
|
||||
|
||||
if False:
|
||||
from typing import Any, Optional
|
||||
@ -28,6 +28,8 @@ def show_pin_timeout(seconds: int, progress: int, message: str) -> bool:
|
||||
ui.display.text_center(
|
||||
ui.WIDTH // 2, 37, message, ui.BOLD, ui.FG, ui.BG, ui.WIDTH
|
||||
)
|
||||
|
||||
if not utils.DISABLE_ANIMATION:
|
||||
ui.display.loader(progress, False, 0, ui.FG, ui.BG)
|
||||
|
||||
if seconds != _previous_seconds:
|
||||
@ -42,6 +44,6 @@ def show_pin_timeout(seconds: int, progress: int, message: str) -> bool:
|
||||
)
|
||||
_previous_seconds = seconds
|
||||
|
||||
ui.display.refresh()
|
||||
ui.refresh()
|
||||
_previous_progress = progress
|
||||
return False
|
||||
|
@ -39,18 +39,21 @@ _alert_in_progress = False
|
||||
|
||||
# in debug mode, display an indicator in top right corner
|
||||
if __debug__:
|
||||
from apps.debug import screenshot
|
||||
|
||||
def debug_display_refresh() -> None:
|
||||
def refresh() -> None:
|
||||
if not screenshot():
|
||||
display.bar(Display.WIDTH - 8, 0, 8, 8, 0xF800)
|
||||
display.refresh()
|
||||
if utils.SAVE_SCREEN:
|
||||
display.save("refresh")
|
||||
|
||||
loop.after_step_hook = debug_display_refresh
|
||||
|
||||
else:
|
||||
refresh = display.refresh
|
||||
|
||||
|
||||
# in both debug and production, emulator needs to draw the screen explicitly
|
||||
elif utils.EMULATOR:
|
||||
loop.after_step_hook = display.refresh
|
||||
if utils.EMULATOR:
|
||||
loop.after_step_hook = refresh
|
||||
|
||||
|
||||
def lerpi(a: int, b: int, t: float) -> int:
|
||||
@ -120,7 +123,7 @@ async def click() -> Pos:
|
||||
|
||||
def backlight_fade(val: int, delay: int = 14000, step: int = 15) -> None:
|
||||
if __debug__:
|
||||
if utils.DISABLE_FADE:
|
||||
if utils.DISABLE_ANIMATION:
|
||||
display.backlight(val)
|
||||
return
|
||||
current = display.backlight()
|
||||
@ -346,7 +349,7 @@ class Layout(Component):
|
||||
# Display is usually refreshed after every loop step, but here we are
|
||||
# rendering everything synchronously, so refresh it manually and turn
|
||||
# the brightness on again.
|
||||
display.refresh()
|
||||
refresh()
|
||||
backlight_fade(style.BACKLIGHT_NORMAL)
|
||||
sleep = loop.sleep(_RENDER_DELAY_US)
|
||||
while True:
|
||||
|
@ -1,6 +1,6 @@
|
||||
from micropython import const
|
||||
|
||||
from trezor import loop, res, ui
|
||||
from trezor import loop, res, ui, utils
|
||||
from trezor.ui.button import Button, ButtonCancel, ButtonConfirm, ButtonDefault
|
||||
from trezor.ui.loader import Loader, LoaderDefault
|
||||
|
||||
@ -150,12 +150,18 @@ class ConfirmPageable(Confirm):
|
||||
t = ui.pulse(PULSE_PERIOD)
|
||||
c = ui.blend(ui.GREY, ui.DARK_GREY, t)
|
||||
icon = res.load(ui.ICON_SWIPE_RIGHT)
|
||||
if utils.DISABLE_ANIMATION:
|
||||
ui.display.icon(18, 68, icon, ui.GREY, ui.BG)
|
||||
else:
|
||||
ui.display.icon(18, 68, icon, c, ui.BG)
|
||||
|
||||
if not self.pageable.is_last():
|
||||
t = ui.pulse(PULSE_PERIOD, PULSE_PERIOD // 2)
|
||||
c = ui.blend(ui.GREY, ui.DARK_GREY, t)
|
||||
icon = res.load(ui.ICON_SWIPE_LEFT)
|
||||
if utils.DISABLE_ANIMATION:
|
||||
ui.display.icon(205, 68, icon, ui.GREY, ui.BG)
|
||||
else:
|
||||
ui.display.icon(205, 68, icon, c, ui.BG)
|
||||
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import utime
|
||||
from micropython import const
|
||||
|
||||
from trezor import res, ui
|
||||
from trezor import res, ui, utils
|
||||
from trezor.ui import display
|
||||
|
||||
if False:
|
||||
@ -74,13 +74,13 @@ class Loader(ui.Component):
|
||||
else:
|
||||
s = self.active_style
|
||||
|
||||
Y = const(-24)
|
||||
_Y = const(-24)
|
||||
|
||||
if s.icon is None:
|
||||
display.loader(r, False, Y, s.fg_color, s.bg_color)
|
||||
display.loader(r, False, _Y, s.fg_color, s.bg_color)
|
||||
else:
|
||||
display.loader(
|
||||
r, False, Y, s.fg_color, s.bg_color, res.load(s.icon), s.icon_fg_color
|
||||
r, False, _Y, s.fg_color, s.bg_color, res.load(s.icon), s.icon_fg_color
|
||||
)
|
||||
if (r == 0) and (self.stop_ms is not None):
|
||||
self.start_ms = None
|
||||
@ -107,5 +107,8 @@ class LoadingAnimation(ui.Layout):
|
||||
self.loader.start()
|
||||
self.loader.dispatch(event, x, y)
|
||||
|
||||
if utils.DISABLE_ANIMATION:
|
||||
self.on_finish()
|
||||
|
||||
def on_finish(self) -> None:
|
||||
raise ui.Result(None)
|
||||
|
@ -1,4 +1,4 @@
|
||||
from trezor import loop, ui
|
||||
from trezor import loop, ui, utils
|
||||
|
||||
if False:
|
||||
from typing import Tuple
|
||||
@ -7,6 +7,9 @@ if False:
|
||||
class Popup(ui.Layout):
|
||||
def __init__(self, content: ui.Component, time_ms: int = 0) -> None:
|
||||
self.content = content
|
||||
if utils.DISABLE_ANIMATION:
|
||||
self.time_ms = 0
|
||||
else:
|
||||
self.time_ms = time_ms
|
||||
|
||||
def dispatch(self, event: int, x: int, y: int) -> None:
|
||||
|
@ -1,6 +1,6 @@
|
||||
from micropython import const
|
||||
|
||||
from trezor import loop, res, ui
|
||||
from trezor import loop, res, ui, utils
|
||||
from trezor.ui.button import Button, ButtonCancel, ButtonConfirm, ButtonDefault
|
||||
from trezor.ui.confirm import CANCELLED, CONFIRMED
|
||||
from trezor.ui.swipe import SWIPE_DOWN, SWIPE_UP, SWIPE_VERTICAL, Swipe
|
||||
@ -32,11 +32,14 @@ def render_scrollbar(pages: int, page: int) -> None:
|
||||
|
||||
|
||||
def render_swipe_icon() -> None:
|
||||
if utils.DISABLE_ANIMATION:
|
||||
c = ui.GREY
|
||||
else:
|
||||
PULSE_PERIOD = const(1200000)
|
||||
|
||||
icon = res.load(ui.ICON_SWIPE)
|
||||
t = ui.pulse(PULSE_PERIOD)
|
||||
c = ui.blend(ui.GREY, ui.DARK_GREY, t)
|
||||
|
||||
icon = res.load(ui.ICON_SWIPE)
|
||||
ui.display.icon(70, 205, icon, c, ui.BG)
|
||||
|
||||
|
||||
|
@ -14,17 +14,18 @@ from trezorutils import ( # type: ignore[attr-defined] # noqa: F401
|
||||
set_mode_unprivileged,
|
||||
)
|
||||
|
||||
DISABLE_ANIMATION = 0
|
||||
|
||||
if __debug__:
|
||||
if EMULATOR:
|
||||
import uos
|
||||
|
||||
TEST = int(uos.getenv("TREZOR_TEST") or "0")
|
||||
DISABLE_FADE = int(uos.getenv("TREZOR_DISABLE_FADE") or "0")
|
||||
DISABLE_ANIMATION = int(uos.getenv("TREZOR_DISABLE_ANIMATION") or "0")
|
||||
SAVE_SCREEN = int(uos.getenv("TREZOR_SAVE_SCREEN") or "0")
|
||||
LOG_MEMORY = int(uos.getenv("TREZOR_LOG_MEMORY") or "0")
|
||||
else:
|
||||
TEST = 0
|
||||
DISABLE_FADE = 0
|
||||
SAVE_SCREEN = 0
|
||||
LOG_MEMORY = 0
|
||||
|
||||
|
@ -6,7 +6,7 @@ CORE_DIR="$(SHELL_SESSION_FILE='' && cd "$( dirname "${BASH_SOURCE[0]}" )/.." >/
|
||||
MICROPYTHON="${MICROPYTHON:-$CORE_DIR/build/unix/micropython}"
|
||||
TREZOR_SRC="${CORE_DIR}/src"
|
||||
|
||||
DISABLE_FADE=1
|
||||
DISABLE_ANIMATION=1
|
||||
PYOPT="${PYOPT:-0}"
|
||||
upy_pid=""
|
||||
|
||||
@ -22,7 +22,7 @@ if [[ $RUN_TEST_EMU > 0 ]]; then
|
||||
echo "Starting emulator: $MICROPYTHON $ARGS ${MAIN}"
|
||||
|
||||
TREZOR_TEST=1 \
|
||||
TREZOR_DISABLE_FADE=$DISABLE_FADE \
|
||||
TREZOR_DISABLE_ANIMATION=$DISABLE_ANIMATION \
|
||||
$MICROPYTHON $ARGS "${MAIN}" &> "${TREZOR_LOGFILE}" &
|
||||
upy_pid=$!
|
||||
cd -
|
||||
|
@ -6,7 +6,6 @@ CORE_DIR="$(SHELL_SESSION_FILE='' && cd "$( dirname "${BASH_SOURCE[0]}" )/.." >/
|
||||
MICROPYTHON="${MICROPYTHON:-$CORE_DIR/build/unix/micropython}"
|
||||
TREZOR_SRC="${CORE_DIR}/src"
|
||||
|
||||
DISABLE_FADE=1
|
||||
PYOPT="${PYOPT:-0}"
|
||||
upy_pid=""
|
||||
|
||||
@ -22,7 +21,7 @@ if [[ $RUN_TEST_EMU > 0 ]]; then
|
||||
echo "Starting emulator: $MICROPYTHON $ARGS ${MAIN}"
|
||||
|
||||
TREZOR_TEST=1 \
|
||||
TREZOR_DISABLE_FADE=$DISABLE_FADE \
|
||||
TREZOR_DISABLE_ANIMATION=1 \
|
||||
$MICROPYTHON $ARGS "${MAIN}" &> "${TREZOR_LOGFILE}" &
|
||||
upy_pid=$!
|
||||
cd -
|
||||
|
@ -6,7 +6,7 @@ CORE_DIR="$(SHELL_SESSION_FILE='' && cd "$( dirname "${BASH_SOURCE[0]}" )/.." >/
|
||||
MICROPYTHON="${MICROPYTHON:-$CORE_DIR/build/unix/micropython}"
|
||||
TREZOR_SRC="${CORE_DIR}/src"
|
||||
|
||||
DISABLE_FADE=1
|
||||
DISABLE_ANIMATION=1
|
||||
PYOPT="${PYOPT:-0}"
|
||||
upy_pid=""
|
||||
|
||||
@ -22,7 +22,7 @@ if [[ $RUN_TEST_EMU > 0 ]]; then
|
||||
echo "Starting emulator: $MICROPYTHON $ARGS ${MAIN}"
|
||||
|
||||
TREZOR_TEST=1 \
|
||||
TREZOR_DISABLE_FADE=$DISABLE_FADE \
|
||||
TREZOR_DISABLE_ANIMATION=$DISABLE_ANIMATION \
|
||||
$MICROPYTHON $ARGS "${MAIN}" &> "${TREZOR_LOGFILE}" &
|
||||
upy_pid=$!
|
||||
cd -
|
||||
|
@ -8,7 +8,7 @@ CORE_DIR="$(SHELL_SESSION_FILE='' && cd "$( dirname "${BASH_SOURCE[0]}" )/.." >/
|
||||
MICROPYTHON="${MICROPYTHON:-$CORE_DIR/build/unix/micropython}"
|
||||
TREZOR_SRC="${CORE_DIR}/src"
|
||||
|
||||
DISABLE_FADE=1
|
||||
DISABLE_ANIMATION=1
|
||||
PYOPT="${PYOPT:-0}"
|
||||
upy_pid=""
|
||||
|
||||
@ -24,7 +24,7 @@ if [[ $RUN_TEST_EMU > 0 ]]; then
|
||||
echo "Starting emulator: $MICROPYTHON $ARGS ${MAIN}"
|
||||
|
||||
TREZOR_TEST=1 \
|
||||
TREZOR_DISABLE_FADE=$DISABLE_FADE \
|
||||
TREZOR_DISABLE_ANIMATION=$DISABLE_ANIMATION \
|
||||
$MICROPYTHON $ARGS "${MAIN}" &> "${TREZOR_LOGFILE}" &
|
||||
upy_pid=$!
|
||||
cd -
|
||||
|
@ -6,7 +6,7 @@ CORE_DIR="$(SHELL_SESSION_FILE='' && cd "$( dirname "${BASH_SOURCE[0]}" )/.." >/
|
||||
MICROPYTHON="${MICROPYTHON:-$CORE_DIR/build/unix/micropython}"
|
||||
TREZOR_SRC="${CORE_DIR}/src"
|
||||
|
||||
DISABLE_FADE=1
|
||||
DISABLE_ANIMATION=1
|
||||
PYOPT="${PYOPT:-0}"
|
||||
upy_pid=""
|
||||
|
||||
@ -22,7 +22,7 @@ if [[ $RUN_TEST_EMU > 0 ]]; then
|
||||
echo "Starting emulator: $MICROPYTHON $ARGS ${MAIN}"
|
||||
|
||||
TREZOR_TEST=1 \
|
||||
TREZOR_DISABLE_FADE=$DISABLE_FADE \
|
||||
TREZOR_DISABLE_ANIMATION=$DISABLE_ANIMATION \
|
||||
$MICROPYTHON $ARGS "${MAIN}" &> "${TREZOR_LOGFILE}" &
|
||||
upy_pid=$!
|
||||
cd -
|
||||
|
@ -82,9 +82,9 @@ If ``` TREZOR_SAVE_SCREEN=1 ``` is set, the emulator makes print screen on every
|
||||
|
||||
If ```TREZOR_LOG_MEMORY=1``` is set, the emulator prints memory usage information after each workflow task is finished.
|
||||
|
||||
#### Disable fade
|
||||
#### Disable animations
|
||||
|
||||
```TREZOR_DISABLE_FADE=1``` disables fading, which speeds up the UI workflows (useful for tests).
|
||||
```TREZOR_DISABLE_ANIMATION=1``` disables fading and other animations, which speeds up the UI workflows significantly (useful for tests). This is also requirement for UI integration tests.
|
||||
|
||||
#### Tests
|
||||
|
||||
|
@ -2,7 +2,7 @@ ifneq ($(V),1)
|
||||
Q := @
|
||||
endif
|
||||
|
||||
SKIPPED_MESSAGES := Binance Cardano DebugMonero Eos Monero Ontology Ripple SdProtect Tezos WebAuthn
|
||||
SKIPPED_MESSAGES := Binance Cardano DebugMonero Eos Monero Ontology Ripple SdProtect Tezos WebAuthn DebugLinkRecordScreen DebugLinkReseedRandom
|
||||
|
||||
ifeq ($(BITCOIN_ONLY), 1)
|
||||
SKIPPED_MESSAGES += Ethereum Lisk NEM Stellar
|
||||
|
@ -15,7 +15,6 @@ DebugLinkMemory.memory max_size:1024
|
||||
DebugLinkMemoryWrite.memory max_size:1024
|
||||
|
||||
# unused fields
|
||||
DebugLinkState.layout_lines max_count:0
|
||||
DebugLinkState.layout_lines max_size:1
|
||||
DebugLinkLayout.lines max_size:1
|
||||
DebugLinkLayout.lines max_count:0
|
||||
DebugLinkState.layout_lines max_count:10 max_size:30
|
||||
DebugLinkLayout.lines max_count:10 max_size:30
|
||||
DebugLinkRecordScreen.target_directory max_size:16
|
||||
|
@ -138,6 +138,15 @@ class DebugLink:
|
||||
def stop(self):
|
||||
self._call(proto.DebugLinkStop(), nowait=True)
|
||||
|
||||
def reseed(self, value):
|
||||
self._call(proto.DebugLinkReseedRandom(value=value))
|
||||
|
||||
def start_recording(self, directory):
|
||||
self._call(proto.DebugLinkRecordScreen(target_directory=directory))
|
||||
|
||||
def stop_recording(self):
|
||||
self._call(proto.DebugLinkRecordScreen(target_directory=None))
|
||||
|
||||
@expect(proto.DebugLinkMemory, field="memory")
|
||||
def memory_read(self, address, length):
|
||||
return self._call(proto.DebugLinkMemoryRead(address=address, length=length))
|
||||
|
26
python/src/trezorlib/messages/DebugLinkRecordScreen.py
Normal file
26
python/src/trezorlib/messages/DebugLinkRecordScreen.py
Normal file
@ -0,0 +1,26 @@
|
||||
# Automatically generated by pb2py
|
||||
# fmt: off
|
||||
from .. import protobuf as p
|
||||
|
||||
if __debug__:
|
||||
try:
|
||||
from typing import Dict, List # noqa: F401
|
||||
from typing_extensions import Literal # noqa: F401
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
class DebugLinkRecordScreen(p.MessageType):
|
||||
MESSAGE_WIRE_TYPE = 9003
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
target_directory: str = None,
|
||||
) -> None:
|
||||
self.target_directory = target_directory
|
||||
|
||||
@classmethod
|
||||
def get_fields(cls) -> Dict:
|
||||
return {
|
||||
1: ('target_directory', p.UnicodeType, 0),
|
||||
}
|
26
python/src/trezorlib/messages/DebugLinkReseedRandom.py
Normal file
26
python/src/trezorlib/messages/DebugLinkReseedRandom.py
Normal file
@ -0,0 +1,26 @@
|
||||
# Automatically generated by pb2py
|
||||
# fmt: off
|
||||
from .. import protobuf as p
|
||||
|
||||
if __debug__:
|
||||
try:
|
||||
from typing import Dict, List # noqa: F401
|
||||
from typing_extensions import Literal # noqa: F401
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
class DebugLinkReseedRandom(p.MessageType):
|
||||
MESSAGE_WIRE_TYPE = 9002
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
value: int = None,
|
||||
) -> None:
|
||||
self.value = value
|
||||
|
||||
@classmethod
|
||||
def get_fields(cls) -> Dict:
|
||||
return {
|
||||
1: ('value', p.UVarintType, 0),
|
||||
}
|
@ -72,6 +72,8 @@ DebugLinkMemory = 111 # type: Literal[111]
|
||||
DebugLinkMemoryWrite = 112 # type: Literal[112]
|
||||
DebugLinkFlashErase = 113 # type: Literal[113]
|
||||
DebugLinkLayout = 9001 # type: Literal[9001]
|
||||
DebugLinkReseedRandom = 9002 # type: Literal[9002]
|
||||
DebugLinkRecordScreen = 9003 # type: Literal[9003]
|
||||
EthereumGetPublicKey = 450 # type: Literal[450]
|
||||
EthereumPublicKey = 451 # type: Literal[451]
|
||||
EthereumGetAddress = 56 # type: Literal[56]
|
||||
|
@ -47,6 +47,8 @@ from .DebugLinkLog import DebugLinkLog
|
||||
from .DebugLinkMemory import DebugLinkMemory
|
||||
from .DebugLinkMemoryRead import DebugLinkMemoryRead
|
||||
from .DebugLinkMemoryWrite import DebugLinkMemoryWrite
|
||||
from .DebugLinkRecordScreen import DebugLinkRecordScreen
|
||||
from .DebugLinkReseedRandom import DebugLinkReseedRandom
|
||||
from .DebugLinkState import DebugLinkState
|
||||
from .DebugLinkStop import DebugLinkStop
|
||||
from .DebugMoneroDiagAck import DebugMoneroDiagAck
|
||||
|
@ -37,6 +37,8 @@ MNEMONIC_SLIP39_ADVANCED_33 = [
|
||||
"wildlife deal beard romp alcohol space mild usual clothes union nuclear testify course research heat listen task location thank hospital slice smell failure fawn helpful priest ambition average recover lecture process dough stadium",
|
||||
"wildlife deal acrobat romp anxiety axis starting require metric flexible geology game drove editor edge screw helpful have huge holy making pitch unknown carve holiday numb glasses survive already tenant adapt goat fangs",
|
||||
]
|
||||
# External entropy mocked as received from trezorlib.
|
||||
EXTERNAL_ENTROPY = b"zlutoucky kun upel divoke ody" * 2
|
||||
# fmt: on
|
||||
|
||||
|
||||
|
@ -24,7 +24,9 @@ from trezorlib.device import apply_settings, wipe as wipe_device
|
||||
from trezorlib.messages.PassphraseSourceType import HOST as PASSPHRASE_ON_HOST
|
||||
from trezorlib.transport import enumerate_devices, get_transport
|
||||
|
||||
from . import ui_tests
|
||||
from .device_handler import BackgroundDeviceHandler
|
||||
from .ui_tests import report
|
||||
|
||||
|
||||
def get_device():
|
||||
@ -89,9 +91,18 @@ def client(request):
|
||||
" pytest -m 'not sd_card' <test path>"
|
||||
)
|
||||
|
||||
test_ui = request.config.getoption("ui")
|
||||
if test_ui not in ("", "record", "test"):
|
||||
raise ValueError("Invalid ui option.")
|
||||
run_ui_tests = not request.node.get_closest_marker("skip_ui") and test_ui
|
||||
|
||||
client.open()
|
||||
if run_ui_tests:
|
||||
# we need to reseed before the wipe
|
||||
client.debug.reseed(0)
|
||||
|
||||
wipe_device(client)
|
||||
|
||||
# fmt: off
|
||||
setup_params = dict(
|
||||
uninitialized=False,
|
||||
mnemonic=" ".join(["all"] * 12),
|
||||
@ -100,7 +111,6 @@ def client(request):
|
||||
needs_backup=False,
|
||||
no_backup=False,
|
||||
)
|
||||
# fmt: on
|
||||
|
||||
marker = request.node.get_closest_marker("setup_client")
|
||||
if marker:
|
||||
@ -127,11 +137,40 @@ def client(request):
|
||||
# ClearSession locks the device. We only do that if the PIN is set.
|
||||
client.clear_session()
|
||||
|
||||
client.open()
|
||||
if run_ui_tests:
|
||||
with ui_tests.screen_recording(client, request):
|
||||
yield client
|
||||
else:
|
||||
yield client
|
||||
|
||||
client.close()
|
||||
|
||||
|
||||
def pytest_sessionstart(session):
|
||||
if session.config.getoption("ui") == "test":
|
||||
report.clear_dir()
|
||||
|
||||
|
||||
def pytest_sessionfinish(session, exitstatus):
|
||||
if session.config.getoption("ui") == "test":
|
||||
report.index()
|
||||
|
||||
|
||||
def pytest_terminal_summary(terminalreporter, exitstatus, config):
|
||||
terminalreporter.writer.line(
|
||||
"\nUI tests summary: %s" % (report.REPORTS_PATH / "index.html")
|
||||
)
|
||||
|
||||
|
||||
def pytest_addoption(parser):
|
||||
parser.addoption(
|
||||
"--ui",
|
||||
action="store",
|
||||
default="",
|
||||
help="Enable UI intergration tests: 'record' or 'test'",
|
||||
)
|
||||
|
||||
|
||||
def pytest_configure(config):
|
||||
"""Called at testsuite setup time.
|
||||
|
||||
@ -144,6 +183,9 @@ def pytest_configure(config):
|
||||
"markers",
|
||||
'setup_client(mnemonic="all all all...", pin=None, passphrase=False, uninitialized=False): configure the client instance',
|
||||
)
|
||||
config.addinivalue_line(
|
||||
"markers", "skip_ui: skip UI integration checks for this test"
|
||||
)
|
||||
with open(os.path.join(os.path.dirname(__file__), "REGISTERED_MARKERS")) as f:
|
||||
for line in f:
|
||||
config.addinivalue_line("markers", line.strip())
|
||||
|
@ -25,9 +25,12 @@ from trezorlib import device, messages as proto
|
||||
from trezorlib.exceptions import TrezorFailure
|
||||
from trezorlib.messages import BackupType, ButtonRequestType as B
|
||||
|
||||
from ..common import click_through, generate_entropy, read_and_confirm_mnemonic
|
||||
|
||||
EXTERNAL_ENTROPY = b"zlutoucky kun upel divoke ody" * 2
|
||||
from ..common import (
|
||||
EXTERNAL_ENTROPY,
|
||||
click_through,
|
||||
generate_entropy,
|
||||
read_and_confirm_mnemonic,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.skip_t1
|
||||
|
@ -369,6 +369,7 @@ class TestMsgSigntx:
|
||||
)
|
||||
|
||||
@pytest.mark.setup_client(mnemonic=MNEMONIC12)
|
||||
@pytest.mark.skip_ui
|
||||
def test_lots_of_inputs(self, client):
|
||||
# Tests if device implements serialization of len(inputs) correctly
|
||||
# tx 4a7b7e0403ae5607e473949cfa03f09f2cd8b0f404bf99ce10b7303d86280bf7 : 100 UTXO for spending for unit tests
|
||||
@ -397,6 +398,7 @@ class TestMsgSigntx:
|
||||
)
|
||||
|
||||
@pytest.mark.setup_client(mnemonic=MNEMONIC12)
|
||||
@pytest.mark.skip_ui
|
||||
def test_lots_of_outputs(self, client):
|
||||
# Tests if device implements serialization of len(outputs) correctly
|
||||
|
||||
|
@ -15,13 +15,15 @@
|
||||
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
|
||||
|
||||
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
import shamir_mnemonic as shamir
|
||||
|
||||
from trezorlib import device, messages
|
||||
from trezorlib.messages import BackupType, ButtonRequestType as B
|
||||
|
||||
from ..common import click_through, read_and_confirm_mnemonic
|
||||
from ..common import EXTERNAL_ENTROPY, click_through, read_and_confirm_mnemonic
|
||||
|
||||
|
||||
def backup_flow_bip39(client):
|
||||
@ -178,6 +180,9 @@ VECTORS = [
|
||||
@pytest.mark.parametrize("backup_type, backup_flow", VECTORS)
|
||||
@pytest.mark.setup_client(uninitialized=True)
|
||||
def test_skip_backup_msg(client, backup_type, backup_flow):
|
||||
|
||||
os_urandom = mock.Mock(return_value=EXTERNAL_ENTROPY)
|
||||
with mock.patch("os.urandom", os_urandom), client:
|
||||
device.reset(
|
||||
client,
|
||||
skip_backup=True,
|
||||
@ -220,7 +225,8 @@ def test_skip_backup_manual(client, backup_type, backup_flow):
|
||||
yield # Confirm skip backup
|
||||
client.debug.press_no()
|
||||
|
||||
with client:
|
||||
os_urandom = mock.Mock(return_value=EXTERNAL_ENTROPY)
|
||||
with mock.patch("os.urandom", os_urandom), client:
|
||||
client.set_input_flow(reset_skip_input_flow)
|
||||
client.set_expected_responses(
|
||||
[
|
||||
|
@ -15,16 +15,19 @@
|
||||
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
|
||||
|
||||
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
|
||||
from trezorlib import btc, device, messages
|
||||
from trezorlib.messages import BackupType, ButtonRequestType as B
|
||||
from trezorlib.tools import parse_path
|
||||
|
||||
from ..common import click_through, read_and_confirm_mnemonic
|
||||
from ..common import EXTERNAL_ENTROPY, click_through, read_and_confirm_mnemonic
|
||||
|
||||
|
||||
@pytest.mark.skip_t1
|
||||
@pytest.mark.skip_ui
|
||||
@pytest.mark.setup_client(uninitialized=True)
|
||||
def test_reset_recovery(client):
|
||||
mnemonic = reset(client)
|
||||
@ -79,6 +82,8 @@ def reset(client, strength=128, skip_backup=False):
|
||||
)
|
||||
client.set_input_flow(input_flow)
|
||||
|
||||
os_urandom = mock.Mock(return_value=EXTERNAL_ENTROPY)
|
||||
with mock.patch("os.urandom", os_urandom), client:
|
||||
# No PIN, no passphrase, don't display random
|
||||
device.reset(
|
||||
client,
|
||||
|
@ -14,16 +14,24 @@
|
||||
# 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>.
|
||||
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
|
||||
from trezorlib import btc, device, messages
|
||||
from trezorlib.messages import BackupType, ButtonRequestType as B
|
||||
from trezorlib.tools import parse_path
|
||||
|
||||
from ..common import click_through, read_and_confirm_mnemonic, recovery_enter_shares
|
||||
from ..common import (
|
||||
EXTERNAL_ENTROPY,
|
||||
click_through,
|
||||
read_and_confirm_mnemonic,
|
||||
recovery_enter_shares,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.skip_t1
|
||||
@pytest.mark.skip_ui
|
||||
@pytest.mark.setup_client(uninitialized=True)
|
||||
def test_reset_recovery(client):
|
||||
mnemonics = reset(client)
|
||||
@ -89,7 +97,8 @@ def reset(client, strength=128):
|
||||
assert btn_code == B.Success
|
||||
client.debug.press_yes()
|
||||
|
||||
with client:
|
||||
os_urandom = mock.Mock(return_value=EXTERNAL_ENTROPY)
|
||||
with mock.patch("os.urandom", os_urandom), client:
|
||||
client.set_expected_responses(
|
||||
[
|
||||
messages.ButtonRequest(code=B.ResetDevice),
|
||||
|
@ -26,6 +26,7 @@ from ..common import click_through, read_and_confirm_mnemonic, recovery_enter_sh
|
||||
|
||||
|
||||
@pytest.mark.skip_t1
|
||||
@pytest.mark.skip_ui
|
||||
@pytest.mark.setup_client(uninitialized=True)
|
||||
def test_reset_recovery(client):
|
||||
mnemonics = reset(client)
|
||||
|
4
tests/ui_tests/.gitignore
vendored
Normal file
4
tests/ui_tests/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
*.png
|
||||
*.html
|
||||
*.zip
|
||||
reports/
|
112
tests/ui_tests/__init__.py
Normal file
112
tests/ui_tests/__init__.py
Normal file
@ -0,0 +1,112 @@
|
||||
import hashlib
|
||||
import re
|
||||
import shutil
|
||||
from contextlib import contextmanager
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from . import report
|
||||
|
||||
UI_TESTS_DIR = Path(__file__).parent.resolve()
|
||||
|
||||
|
||||
def get_test_name(node_id):
|
||||
# Test item name is usually function name, but when parametrization is used,
|
||||
# parameters are also part of the name. Some functions have very long parameter
|
||||
# names (tx hashes etc) that run out of maximum allowable filename length, so
|
||||
# we limit the name to first 100 chars. This is not a problem with txhashes.
|
||||
new_name = node_id.replace("tests/device_tests/", "")
|
||||
# remove ::TestClass:: if present because it is usually the same as the test file name
|
||||
new_name = re.sub(r"::.*?::", "-", new_name)
|
||||
new_name = new_name.replace("/", "-") # in case there is "/"
|
||||
return new_name[:100]
|
||||
|
||||
|
||||
def _check_fixture_directory(fixture_dir, screen_path):
|
||||
# create the fixture dir if it does not exist
|
||||
if not fixture_dir.exists():
|
||||
fixture_dir.mkdir()
|
||||
|
||||
# delete old files
|
||||
shutil.rmtree(screen_path, ignore_errors=True)
|
||||
screen_path.mkdir()
|
||||
|
||||
|
||||
def _process_recorded(screen_path):
|
||||
# create hash
|
||||
digest = _hash_files(screen_path)
|
||||
|
||||
(screen_path.parent / "hash.txt").write_text(digest)
|
||||
_rename_records(screen_path)
|
||||
|
||||
|
||||
def _rename_records(screen_path):
|
||||
# rename screenshots
|
||||
for index, record in enumerate(sorted(screen_path.iterdir())):
|
||||
record.replace(screen_path / f"{index:08}.png")
|
||||
|
||||
|
||||
def _hash_files(path):
|
||||
files = path.iterdir()
|
||||
hasher = hashlib.sha256()
|
||||
for file in sorted(files):
|
||||
hasher.update(file.read_bytes())
|
||||
|
||||
return hasher.digest().hex()
|
||||
|
||||
|
||||
def _process_tested(fixture_test_path, test_name):
|
||||
hash_file = fixture_test_path / "hash.txt"
|
||||
|
||||
if not hash_file.exists():
|
||||
raise ValueError("File hash.txt not found.")
|
||||
|
||||
expected_hash = hash_file.read_text()
|
||||
actual_path = fixture_test_path / "actual"
|
||||
actual_hash = _hash_files(actual_path)
|
||||
|
||||
_rename_records(actual_path)
|
||||
|
||||
if actual_hash != expected_hash:
|
||||
file_path = report.failed(
|
||||
fixture_test_path, test_name, actual_hash, expected_hash
|
||||
)
|
||||
|
||||
pytest.fail(
|
||||
"Hash of {} differs.\nExpected: {}\nActual: {}\nDiff file: {}".format(
|
||||
test_name, expected_hash, actual_hash, file_path
|
||||
)
|
||||
)
|
||||
else:
|
||||
report.passed(fixture_test_path, test_name, actual_hash)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def screen_recording(client, request):
|
||||
test_ui = request.config.getoption("ui")
|
||||
test_name = get_test_name(request.node.nodeid)
|
||||
fixture_test_path = UI_TESTS_DIR / "fixtures" / test_name
|
||||
|
||||
if test_ui == "record":
|
||||
screen_path = fixture_test_path / "recorded"
|
||||
elif test_ui == "test":
|
||||
screen_path = fixture_test_path / "actual"
|
||||
else:
|
||||
raise ValueError("Invalid 'ui' option.")
|
||||
|
||||
# remove previous files
|
||||
shutil.rmtree(screen_path, ignore_errors=True)
|
||||
screen_path.mkdir()
|
||||
|
||||
try:
|
||||
client.debug.start_recording(str(screen_path))
|
||||
yield
|
||||
finally:
|
||||
client.debug.stop_recording()
|
||||
if test_ui == "record":
|
||||
_process_recorded(screen_path)
|
||||
elif test_ui == "test":
|
||||
_process_tested(fixture_test_path, test_name)
|
||||
else:
|
||||
raise ValueError("Invalid 'ui' option.")
|
24
tests/ui_tests/download.py
Normal file
24
tests/ui_tests/download.py
Normal file
@ -0,0 +1,24 @@
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
import zipfile
|
||||
|
||||
RECORDS_WEBSITE = "https://firmware.corp.sldev.cz/ui_tests/"
|
||||
|
||||
|
||||
def fetch_recorded(recorded_hash, recorded_path):
|
||||
zip_src = RECORDS_WEBSITE + recorded_hash + ".zip"
|
||||
zip_dest = recorded_path / "recorded.zip"
|
||||
|
||||
try:
|
||||
urllib.request.urlretrieve(zip_src, zip_dest)
|
||||
except urllib.error.HTTPError:
|
||||
raise RuntimeError("No such recorded collection was found on '%s'." % zip_src)
|
||||
except urllib.error.URLError:
|
||||
raise RuntimeError(
|
||||
"Server firmware.corp.sldev.cz could not be found. Are you on VPN?"
|
||||
)
|
||||
|
||||
with zipfile.ZipFile(zip_dest, "r") as z:
|
||||
z.extractall(recorded_path)
|
||||
|
||||
zip_dest.unlink()
|
@ -0,0 +1 @@
|
||||
634ddda671de872d438cce58246154704a579e71c1137e3be298d7a1bf19e4dd
|
@ -0,0 +1 @@
|
||||
5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586
|
@ -0,0 +1 @@
|
||||
5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586
|
1
tests/ui_tests/fixtures/test_basic.py-test_ping/hash.txt
Normal file
1
tests/ui_tests/fixtures/test_basic.py-test_ping/hash.txt
Normal file
@ -0,0 +1 @@
|
||||
5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586
|
@ -0,0 +1 @@
|
||||
5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586
|
@ -0,0 +1 @@
|
||||
5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586
|
@ -0,0 +1 @@
|
||||
5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586
|
@ -0,0 +1 @@
|
||||
5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586
|
@ -0,0 +1 @@
|
||||
b698654871541258f97d58ada0f010b2d77b74829791566746cad619d3740a94
|
@ -0,0 +1 @@
|
||||
fb38537b921f8064f7ea6e1a584e70a8be74968a3be6726b7d36cf57de0d7865
|
@ -0,0 +1 @@
|
||||
4373cf99062b8e39369e273009cdbfae715d73d241605752a10c1ab57f2c8e77
|
@ -0,0 +1 @@
|
||||
b75b3c0103916bf4a2ec1aedad05e7b75a2ff1961a4ee40a7773b7f7d4f463ed
|
@ -0,0 +1 @@
|
||||
b0cc08c03ba2089d538e1dca1d4f949031100195a2a8ef5eb8e84542da817f7a
|
@ -0,0 +1 @@
|
||||
225b3da1acac6e9a65106fcc4a01de8a44de035aedb4dcc21c09f439199fdf40
|
@ -0,0 +1 @@
|
||||
93039a9472cfc9058563bd56e4a3dbe2e41af64744a61f6ee3255a04bd3a9366
|
@ -0,0 +1 @@
|
||||
14fcdd2ded299ca099a35966cc9f21204b31de8d6bab9ec91cb64537bd70440c
|
@ -0,0 +1 @@
|
||||
5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586
|
@ -0,0 +1 @@
|
||||
c53ae271ae6158320c85dfc5ef43693def6f9606a3e733db0abb78dca392b7bb
|
@ -0,0 +1 @@
|
||||
5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586
|
@ -0,0 +1 @@
|
||||
07e93c712f63190a9bdb01f30c10750afd264fd2f491d9f7b89c431b9550edc8
|
@ -0,0 +1 @@
|
||||
7b8bbe5ba7d7b07c95065608fb1cf9aeafcb3f9671835a6e5e5a6997ff4ff99b
|
@ -0,0 +1 @@
|
||||
813ad1b802dee1ace4dfa378edd840dbcea57c1a1b8eed67134def024c40a6e9
|
@ -0,0 +1 @@
|
||||
5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586
|
@ -0,0 +1 @@
|
||||
5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586
|
@ -0,0 +1 @@
|
||||
5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586
|
@ -0,0 +1 @@
|
||||
612dad8ab8762162a186ec9279d7de0bdfc589c52b4e4f4eba0545a00f21c3f0
|
@ -0,0 +1 @@
|
||||
612dad8ab8762162a186ec9279d7de0bdfc589c52b4e4f4eba0545a00f21c3f0
|
@ -0,0 +1 @@
|
||||
612dad8ab8762162a186ec9279d7de0bdfc589c52b4e4f4eba0545a00f21c3f0
|
@ -0,0 +1 @@
|
||||
5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586
|
@ -0,0 +1 @@
|
||||
5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586
|
@ -0,0 +1 @@
|
||||
5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586
|
@ -0,0 +1 @@
|
||||
5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586
|
@ -0,0 +1 @@
|
||||
612dad8ab8762162a186ec9279d7de0bdfc589c52b4e4f4eba0545a00f21c3f0
|
@ -0,0 +1 @@
|
||||
612dad8ab8762162a186ec9279d7de0bdfc589c52b4e4f4eba0545a00f21c3f0
|
@ -0,0 +1 @@
|
||||
612dad8ab8762162a186ec9279d7de0bdfc589c52b4e4f4eba0545a00f21c3f0
|
@ -0,0 +1 @@
|
||||
c8efc839222488aea6b0b1bc8cf595f348b1f9d77221b3017992b8c1733228cd
|
@ -0,0 +1 @@
|
||||
c8efc839222488aea6b0b1bc8cf595f348b1f9d77221b3017992b8c1733228cd
|
@ -0,0 +1 @@
|
||||
cd1a289b31604e366464951cda3b7ce90125ff3f23f98cd2f60caf96e03c37c2
|
@ -0,0 +1 @@
|
||||
f504163122424398b008ec86cbd219e543eea7889d52651e0e69f707b4a14649
|
@ -0,0 +1 @@
|
||||
f504163122424398b008ec86cbd219e543eea7889d52651e0e69f707b4a14649
|
@ -0,0 +1 @@
|
||||
f504163122424398b008ec86cbd219e543eea7889d52651e0e69f707b4a14649
|
@ -0,0 +1 @@
|
||||
0440233304d5589c5ef16a8d304297992220d6fb9413f1d2e3680b106db3fc0d
|
@ -0,0 +1 @@
|
||||
0440233304d5589c5ef16a8d304297992220d6fb9413f1d2e3680b106db3fc0d
|
@ -0,0 +1 @@
|
||||
b1aaeafb0dd82dea3c39cd4b27bc3ee70b1dff797460deb4294dd477a4b4aea2
|
@ -0,0 +1 @@
|
||||
625526b30bd45a9f05dd46ec459a908464649f808862445f4d845511bd90a944
|
@ -0,0 +1 @@
|
||||
68aa16f42a827b1e288ca109a59328440dc348298779c816efc293ed47753825
|
@ -0,0 +1 @@
|
||||
0a8089d97e7bb9e6292557ae803f1ab35f74d845653bb00389360dbbcdc1e74d
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user