mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-11-28 18:38:39 +00:00
tests: introduce UI tests for core
This commit is contained in:
parent
bc0c10bf3e
commit
51ef963738
15
ci/test.yml
15
ci/test.yml
@ -38,6 +38,21 @@ core unix unit test:
|
|||||||
- cd core
|
- cd core
|
||||||
- pipenv run make test
|
- 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}
|
||||||
|
artifacts:
|
||||||
|
name: core-unix-device-test.log
|
||||||
|
paths:
|
||||||
|
- trezor.log
|
||||||
|
expire_in: 1 week
|
||||||
|
|
||||||
core unix device test:
|
core unix device test:
|
||||||
stage: test
|
stage: test
|
||||||
<<: *only_changes_core
|
<<: *only_changes_core
|
||||||
|
@ -39,6 +39,24 @@ message DebugLinkLayout {
|
|||||||
repeated string lines = 1;
|
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
|
* Request: Computer asks for device state
|
||||||
* @start
|
* @start
|
||||||
|
@ -107,6 +107,8 @@ enum MessageType {
|
|||||||
MessageType_DebugLinkMemoryWrite = 112 [(wire_debug_in) = true];
|
MessageType_DebugLinkMemoryWrite = 112 [(wire_debug_in) = true];
|
||||||
MessageType_DebugLinkFlashErase = 113 [(wire_debug_in) = true];
|
MessageType_DebugLinkFlashErase = 113 [(wire_debug_in) = true];
|
||||||
MessageType_DebugLinkLayout = 9001 [(wire_debug_out) = true];
|
MessageType_DebugLinkLayout = 9001 [(wire_debug_out) = true];
|
||||||
|
MessageType_DebugLinkReseedRandom = 9002 [(wire_debug_in) = true];
|
||||||
|
MessageType_DebugLinkRecordScreen = 9003 [(wire_debug_in) = true];
|
||||||
|
|
||||||
// Ethereum
|
// Ethereum
|
||||||
MessageType_EthereumGetPublicKey = 450 [(wire_in) = true];
|
MessageType_EthereumGetPublicKey = 450 [(wire_in) = true];
|
||||||
|
@ -79,6 +79,15 @@ test_emu_fido2: ## run fido2 device tests
|
|||||||
test_emu_click: ## run click tests
|
test_emu_click: ## run click tests
|
||||||
cd tests ; ./run_tests_click_emu.sh $(TESTOPTS)
|
cd tests ; ./run_tests_click_emu.sh $(TESTOPTS)
|
||||||
|
|
||||||
|
test_emu_ui: # run ui integration tests
|
||||||
|
cd tests ; ./run_tests_device_emu.sh --test_screen=test-hash -m "not skip_ui" $(TESTOPTS)
|
||||||
|
|
||||||
|
test_emu_ui_hash: # create hashes of ui integration test fixtures
|
||||||
|
cd tests ; ./run_tests_device_emu.sh --test_screen=hash -m "not skip_ui" $(TESTOPTS)
|
||||||
|
|
||||||
|
test_emu_ui_record: # record a full set of new ui fixtures
|
||||||
|
cd tests ; ./run_tests_device_emu.sh --test_screen=record -m "not skip_ui" $(TESTOPTS)
|
||||||
|
|
||||||
pylint: ## run pylint on application sources and tests
|
pylint: ## run pylint on application sources and tests
|
||||||
pylint -E $(shell find src tests -name *.py)
|
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,
|
STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorcrypto_random_shuffle_obj,
|
||||||
mod_trezorcrypto_random_shuffle);
|
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[] = {
|
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___name__), MP_ROM_QSTR(MP_QSTR_random)},
|
||||||
{MP_ROM_QSTR(MP_QSTR_uniform),
|
{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_PTR(&mod_trezorcrypto_random_bytes_obj)},
|
||||||
{MP_ROM_QSTR(MP_QSTR_shuffle),
|
{MP_ROM_QSTR(MP_QSTR_shuffle),
|
||||||
MP_ROM_PTR(&mod_trezorcrypto_random_shuffle_obj)},
|
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,
|
STATIC MP_DEFINE_CONST_DICT(mod_trezorcrypto_random_globals,
|
||||||
mod_trezorcrypto_random_globals_table);
|
mod_trezorcrypto_random_globals_table);
|
||||||
|
@ -499,3 +499,5 @@ void display_refresh(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const char *display_save(const char *prefix) { return NULL; }
|
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_Surface *BUFFER;
|
||||||
static SDL_Texture *TEXTURE, *BACKGROUND;
|
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_display_res_x = DISPLAY_RESX, sdl_display_res_y = DISPLAY_RESY;
|
||||||
int sdl_touch_offset_x, sdl_touch_offset_y;
|
int sdl_touch_offset_x, sdl_touch_offset_y;
|
||||||
|
|
||||||
@ -219,7 +221,6 @@ const char *display_save(const char *prefix) {
|
|||||||
}
|
}
|
||||||
static int count;
|
static int count;
|
||||||
static char filename[256];
|
static char filename[256];
|
||||||
static SDL_Surface *prev;
|
|
||||||
// take a cropped view of the screen contents
|
// take a cropped view of the screen contents
|
||||||
const SDL_Rect rect = {0, 0, DISPLAY_RESX, DISPLAY_RESY};
|
const SDL_Rect rect = {0, 0, DISPLAY_RESX, DISPLAY_RESY};
|
||||||
SDL_Surface *crop = SDL_CreateRGBSurface(
|
SDL_Surface *crop = SDL_CreateRGBSurface(
|
||||||
@ -228,16 +229,21 @@ const char *display_save(const char *prefix) {
|
|||||||
BUFFER->format->Amask);
|
BUFFER->format->Amask);
|
||||||
SDL_BlitSurface(BUFFER, &rect, crop, NULL);
|
SDL_BlitSurface(BUFFER, &rect, crop, NULL);
|
||||||
// compare with previous screen, skip if equal
|
// compare with previous screen, skip if equal
|
||||||
if (prev != NULL) {
|
if (PREV_SAVED != NULL) {
|
||||||
if (memcmp(prev->pixels, crop->pixels, crop->pitch * crop->h) == 0) {
|
if (memcmp(PREV_SAVED->pixels, crop->pixels, crop->pitch * crop->h) == 0) {
|
||||||
SDL_FreeSurface(crop);
|
SDL_FreeSurface(crop);
|
||||||
return filename;
|
return filename;
|
||||||
}
|
}
|
||||||
SDL_FreeSurface(prev);
|
SDL_FreeSurface(PREV_SAVED);
|
||||||
}
|
}
|
||||||
// save to png
|
// save to png
|
||||||
snprintf(filename, sizeof(filename), "%s%08d.png", prefix, count++);
|
snprintf(filename, sizeof(filename), "%s%08d.png", prefix, count++);
|
||||||
IMG_SavePNG(crop, filename);
|
IMG_SavePNG(crop, filename);
|
||||||
prev = crop;
|
PREV_SAVED = crop;
|
||||||
return filename;
|
return filename;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void display_clear_save(void) {
|
||||||
|
SDL_FreeSurface(PREV_SAVED);
|
||||||
|
PREV_SAVED = NULL;
|
||||||
|
}
|
||||||
|
@ -69,6 +69,7 @@
|
|||||||
void display_init(void);
|
void display_init(void);
|
||||||
void display_refresh(void);
|
void display_refresh(void);
|
||||||
const char *display_save(const char *prefix);
|
const char *display_save(const char *prefix);
|
||||||
|
void display_clear_save(void);
|
||||||
|
|
||||||
// provided by common
|
// 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,
|
STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_trezorui_Display_save_obj,
|
||||||
mod_trezorui_Display_save);
|
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[] = {
|
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_clear), MP_ROM_PTR(&mod_trezorui_Display_clear_obj)},
|
||||||
{MP_ROM_QSTR(MP_QSTR_refresh),
|
{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_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_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_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_WIDTH), MP_ROM_INT(DISPLAY_RESX)},
|
||||||
{MP_ROM_QSTR(MP_QSTR_HEIGHT), MP_ROM_INT(DISPLAY_RESY)},
|
{MP_ROM_QSTR(MP_QSTR_HEIGHT), MP_ROM_INT(DISPLAY_RESY)},
|
||||||
{MP_ROM_QSTR(MP_QSTR_FONT_SIZE), MP_ROM_INT(FONT_SIZE)},
|
{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).
|
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.
|
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
|
import storage.device
|
||||||
from trezor import ui, workflow
|
from trezor import ui, utils, workflow
|
||||||
from trezor.crypto import bip39, slip39
|
from trezor.crypto import bip39, slip39
|
||||||
from trezor.messages import BackupType
|
from trezor.messages import BackupType
|
||||||
|
|
||||||
@ -34,7 +34,7 @@ def get_seed(passphrase: str = "", progress_bar: bool = True) -> bytes:
|
|||||||
raise ValueError("Mnemonic not set")
|
raise ValueError("Mnemonic not set")
|
||||||
|
|
||||||
render_func = None
|
render_func = None
|
||||||
if progress_bar:
|
if progress_bar and not utils.DISABLE_ANIMATION:
|
||||||
_start_progress()
|
_start_progress()
|
||||||
render_func = _render_progress
|
render_func = _render_progress
|
||||||
|
|
||||||
@ -62,11 +62,11 @@ def _start_progress() -> None:
|
|||||||
ui.backlight_fade(ui.BACKLIGHT_DIM)
|
ui.backlight_fade(ui.BACKLIGHT_DIM)
|
||||||
ui.display.clear()
|
ui.display.clear()
|
||||||
ui.header("Please wait")
|
ui.header("Please wait")
|
||||||
ui.display.refresh()
|
ui.refresh()
|
||||||
ui.backlight_fade(ui.BACKLIGHT_NORMAL)
|
ui.backlight_fade(ui.BACKLIGHT_NORMAL)
|
||||||
|
|
||||||
|
|
||||||
def _render_progress(progress: int, total: int) -> None:
|
def _render_progress(progress: int, total: int) -> None:
|
||||||
p = 1000 * progress // total
|
p = 1000 * progress // total
|
||||||
ui.display.loader(p, False, 18, ui.WHITE, ui.BG)
|
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")
|
halt("debug mode inactive")
|
||||||
|
|
||||||
if __debug__:
|
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 import MessageType, DebugSwipeDirection
|
||||||
from trezor.messages.DebugLinkLayout import DebugLinkLayout
|
from trezor.messages.DebugLinkLayout import DebugLinkLayout
|
||||||
|
from trezor import config, crypto, log, loop, utils
|
||||||
|
from trezor.messages.Success import Success
|
||||||
|
|
||||||
if False:
|
if False:
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
from trezor.messages.DebugLinkDecision import DebugLinkDecision
|
from trezor.messages.DebugLinkDecision import DebugLinkDecision
|
||||||
from trezor.messages.DebugLinkGetState import DebugLinkGetState
|
from trezor.messages.DebugLinkGetState import DebugLinkGetState
|
||||||
|
from trezor.messages.DebugLinkRecordScreen import DebugLinkRecordScreen
|
||||||
|
from trezor.messages.DebugLinkReseedRandom import DebugLinkReseedRandom
|
||||||
from trezor.messages.DebugLinkState import DebugLinkState
|
from trezor.messages.DebugLinkState import DebugLinkState
|
||||||
|
|
||||||
|
save_screen = False
|
||||||
|
save_screen_directory = "."
|
||||||
|
|
||||||
reset_internal_entropy = None # type: Optional[bytes]
|
reset_internal_entropy = None # type: Optional[bytes]
|
||||||
reset_current_words = loop.chan()
|
reset_current_words = loop.chan()
|
||||||
reset_word_index = loop.chan()
|
reset_word_index = loop.chan()
|
||||||
@ -30,6 +37,10 @@ if __debug__:
|
|||||||
layout_change_chan = loop.chan()
|
layout_change_chan = loop.chan()
|
||||||
current_content = None # type: Optional[List[str]]
|
current_content = None # type: Optional[List[str]]
|
||||||
|
|
||||||
|
def screenshot() -> None:
|
||||||
|
if utils.SAVE_SCREEN or save_screen:
|
||||||
|
ui.display.save(save_screen_directory + "/refresh-")
|
||||||
|
|
||||||
def notify_layout_change(layout: ui.Layout) -> None:
|
def notify_layout_change(layout: ui.Layout) -> None:
|
||||||
global current_content
|
global current_content
|
||||||
current_content = layout.read_content()
|
current_content = layout.read_content()
|
||||||
@ -104,12 +115,35 @@ if __debug__:
|
|||||||
m.reset_word = " ".join(await reset_current_words.take())
|
m.reset_word = " ".join(await reset_current_words.take())
|
||||||
return m
|
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:
|
def boot() -> None:
|
||||||
# wipe storage when debug build is used on real hardware
|
# wipe storage when debug build is used on real hardware
|
||||||
if not utils.EMULATOR:
|
if not utils.EMULATOR:
|
||||||
config.wipe()
|
config.wipe()
|
||||||
|
|
||||||
|
wire.add(MessageType.LoadDevice, __name__, "load_device")
|
||||||
wire.register(MessageType.DebugLinkDecision, dispatch_DebugLinkDecision)
|
wire.register(MessageType.DebugLinkDecision, dispatch_DebugLinkDecision)
|
||||||
wire.register(MessageType.DebugLinkGetState, dispatch_DebugLinkGetState)
|
wire.register(MessageType.DebugLinkGetState, dispatch_DebugLinkGetState)
|
||||||
|
wire.register(MessageType.DebugLinkReseedRandom, dispatch_DebugLinkReseedRandom)
|
||||||
wire.add(MessageType.LoadDevice, __name__, "load_device")
|
wire.register(MessageType.DebugLinkRecordScreen, dispatch_DebugLinkRecordScreen)
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
from trezor import ui
|
from trezor import ui, utils
|
||||||
|
|
||||||
_progress = 0
|
_progress = 0
|
||||||
_steps = 0
|
_steps = 0
|
||||||
@ -24,5 +24,7 @@ def report_init():
|
|||||||
|
|
||||||
|
|
||||||
def report():
|
def report():
|
||||||
|
if utils.DISABLE_ANIMATION:
|
||||||
|
return
|
||||||
p = 1000 * _progress // _steps
|
p = 1000 * _progress // _steps
|
||||||
ui.display.loader(p, False, 18, ui.WHITE, ui.BG)
|
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]
|
DebugLinkMemoryWrite = 112 # type: Literal[112]
|
||||||
DebugLinkFlashErase = 113 # type: Literal[113]
|
DebugLinkFlashErase = 113 # type: Literal[113]
|
||||||
DebugLinkLayout = 9001 # type: Literal[9001]
|
DebugLinkLayout = 9001 # type: Literal[9001]
|
||||||
|
DebugLinkReseedRandom = 9002 # type: Literal[9002]
|
||||||
|
DebugLinkRecordScreen = 9003 # type: Literal[9003]
|
||||||
if not utils.BITCOIN_ONLY:
|
if not utils.BITCOIN_ONLY:
|
||||||
EthereumGetPublicKey = 450 # type: Literal[450]
|
EthereumGetPublicKey = 450 # type: Literal[450]
|
||||||
EthereumPublicKey = 451 # type: Literal[451]
|
EthereumPublicKey = 451 # type: Literal[451]
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
from trezor import ui
|
from trezor import ui, utils
|
||||||
|
|
||||||
if False:
|
if False:
|
||||||
from typing import Any, Optional
|
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.display.text_center(
|
||||||
ui.WIDTH // 2, 37, message, ui.BOLD, ui.FG, ui.BG, ui.WIDTH
|
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)
|
ui.display.loader(progress, False, 0, ui.FG, ui.BG)
|
||||||
|
|
||||||
if seconds != _previous_seconds:
|
if seconds != _previous_seconds:
|
||||||
@ -42,6 +44,6 @@ def show_pin_timeout(seconds: int, progress: int, message: str) -> bool:
|
|||||||
)
|
)
|
||||||
_previous_seconds = seconds
|
_previous_seconds = seconds
|
||||||
|
|
||||||
ui.display.refresh()
|
ui.refresh()
|
||||||
_previous_progress = progress
|
_previous_progress = progress
|
||||||
return False
|
return False
|
||||||
|
@ -39,18 +39,21 @@ _alert_in_progress = False
|
|||||||
|
|
||||||
# in debug mode, display an indicator in top right corner
|
# in debug mode, display an indicator in top right corner
|
||||||
if __debug__:
|
if __debug__:
|
||||||
|
from apps.debug import screenshot
|
||||||
|
|
||||||
def debug_display_refresh() -> None:
|
def refresh() -> None:
|
||||||
display.bar(Display.WIDTH - 8, 0, 8, 8, 0xF800)
|
display.bar(Display.WIDTH - 8, 0, 8, 8, 0xF800)
|
||||||
display.refresh()
|
display.refresh()
|
||||||
if utils.SAVE_SCREEN:
|
screenshot()
|
||||||
display.save("refresh")
|
|
||||||
|
|
||||||
|
else:
|
||||||
|
refresh = display.refresh
|
||||||
|
|
||||||
loop.after_step_hook = debug_display_refresh
|
|
||||||
|
|
||||||
# in both debug and production, emulator needs to draw the screen explicitly
|
# in both debug and production, emulator needs to draw the screen explicitly
|
||||||
elif utils.EMULATOR:
|
if utils.EMULATOR:
|
||||||
loop.after_step_hook = display.refresh
|
loop.after_step_hook = refresh
|
||||||
|
|
||||||
|
|
||||||
def lerpi(a: int, b: int, t: float) -> int:
|
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:
|
def backlight_fade(val: int, delay: int = 14000, step: int = 15) -> None:
|
||||||
if __debug__:
|
if __debug__:
|
||||||
if utils.DISABLE_FADE:
|
if utils.DISABLE_ANIMATION:
|
||||||
display.backlight(val)
|
display.backlight(val)
|
||||||
return
|
return
|
||||||
current = display.backlight()
|
current = display.backlight()
|
||||||
@ -346,7 +349,7 @@ class Layout(Component):
|
|||||||
# Display is usually refreshed after every loop step, but here we are
|
# Display is usually refreshed after every loop step, but here we are
|
||||||
# rendering everything synchronously, so refresh it manually and turn
|
# rendering everything synchronously, so refresh it manually and turn
|
||||||
# the brightness on again.
|
# the brightness on again.
|
||||||
display.refresh()
|
refresh()
|
||||||
backlight_fade(style.BACKLIGHT_NORMAL)
|
backlight_fade(style.BACKLIGHT_NORMAL)
|
||||||
sleep = loop.sleep(_RENDER_DELAY_US)
|
sleep = loop.sleep(_RENDER_DELAY_US)
|
||||||
while True:
|
while True:
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
from micropython import const
|
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.button import Button, ButtonCancel, ButtonConfirm, ButtonDefault
|
||||||
from trezor.ui.loader import Loader, LoaderDefault
|
from trezor.ui.loader import Loader, LoaderDefault
|
||||||
|
|
||||||
@ -150,12 +150,18 @@ class ConfirmPageable(Confirm):
|
|||||||
t = ui.pulse(PULSE_PERIOD)
|
t = ui.pulse(PULSE_PERIOD)
|
||||||
c = ui.blend(ui.GREY, ui.DARK_GREY, t)
|
c = ui.blend(ui.GREY, ui.DARK_GREY, t)
|
||||||
icon = res.load(ui.ICON_SWIPE_RIGHT)
|
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)
|
ui.display.icon(18, 68, icon, c, ui.BG)
|
||||||
|
|
||||||
if not self.pageable.is_last():
|
if not self.pageable.is_last():
|
||||||
t = ui.pulse(PULSE_PERIOD, PULSE_PERIOD // 2)
|
t = ui.pulse(PULSE_PERIOD, PULSE_PERIOD // 2)
|
||||||
c = ui.blend(ui.GREY, ui.DARK_GREY, t)
|
c = ui.blend(ui.GREY, ui.DARK_GREY, t)
|
||||||
icon = res.load(ui.ICON_SWIPE_LEFT)
|
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)
|
ui.display.icon(205, 68, icon, c, ui.BG)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import utime
|
import utime
|
||||||
from micropython import const
|
from micropython import const
|
||||||
|
|
||||||
from trezor import res, ui
|
from trezor import res, ui, utils
|
||||||
from trezor.ui import display
|
from trezor.ui import display
|
||||||
|
|
||||||
if False:
|
if False:
|
||||||
@ -74,13 +74,13 @@ class Loader(ui.Component):
|
|||||||
else:
|
else:
|
||||||
s = self.active_style
|
s = self.active_style
|
||||||
|
|
||||||
Y = const(-24)
|
_Y = const(-24)
|
||||||
|
|
||||||
if s.icon is None:
|
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:
|
else:
|
||||||
display.loader(
|
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):
|
if (r == 0) and (self.stop_ms is not None):
|
||||||
self.start_ms = None
|
self.start_ms = None
|
||||||
@ -107,5 +107,8 @@ class LoadingAnimation(ui.Layout):
|
|||||||
self.loader.start()
|
self.loader.start()
|
||||||
self.loader.dispatch(event, x, y)
|
self.loader.dispatch(event, x, y)
|
||||||
|
|
||||||
|
if utils.DISABLE_ANIMATION:
|
||||||
|
self.on_finish()
|
||||||
|
|
||||||
def on_finish(self) -> None:
|
def on_finish(self) -> None:
|
||||||
raise ui.Result(None)
|
raise ui.Result(None)
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
from trezor import loop, ui
|
from trezor import loop, ui, utils
|
||||||
|
|
||||||
if False:
|
if False:
|
||||||
from typing import Tuple
|
from typing import Tuple
|
||||||
@ -7,6 +7,9 @@ if False:
|
|||||||
class Popup(ui.Layout):
|
class Popup(ui.Layout):
|
||||||
def __init__(self, content: ui.Component, time_ms: int = 0) -> None:
|
def __init__(self, content: ui.Component, time_ms: int = 0) -> None:
|
||||||
self.content = content
|
self.content = content
|
||||||
|
if utils.DISABLE_ANIMATION:
|
||||||
|
self.time_ms = 0
|
||||||
|
else:
|
||||||
self.time_ms = time_ms
|
self.time_ms = time_ms
|
||||||
|
|
||||||
def dispatch(self, event: int, x: int, y: int) -> None:
|
def dispatch(self, event: int, x: int, y: int) -> None:
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
from micropython import const
|
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.button import Button, ButtonCancel, ButtonConfirm, ButtonDefault
|
||||||
from trezor.ui.confirm import CANCELLED, CONFIRMED
|
from trezor.ui.confirm import CANCELLED, CONFIRMED
|
||||||
from trezor.ui.swipe import SWIPE_DOWN, SWIPE_UP, SWIPE_VERTICAL, Swipe
|
from trezor.ui.swipe import SWIPE_DOWN, SWIPE_UP, SWIPE_VERTICAL, Swipe
|
||||||
@ -32,6 +32,11 @@ def render_scrollbar(pages: int, page: int) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def render_swipe_icon() -> None:
|
def render_swipe_icon() -> None:
|
||||||
|
if utils.DISABLE_ANIMATION:
|
||||||
|
icon = res.load(ui.ICON_SWIPE)
|
||||||
|
ui.display.icon(70, 205, icon, ui.GREY, ui.BG)
|
||||||
|
return
|
||||||
|
|
||||||
PULSE_PERIOD = const(1200000)
|
PULSE_PERIOD = const(1200000)
|
||||||
|
|
||||||
icon = res.load(ui.ICON_SWIPE)
|
icon = res.load(ui.ICON_SWIPE)
|
||||||
|
@ -14,17 +14,18 @@ from trezorutils import ( # type: ignore[attr-defined] # noqa: F401
|
|||||||
set_mode_unprivileged,
|
set_mode_unprivileged,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
DISABLE_ANIMATION = 0
|
||||||
|
|
||||||
if __debug__:
|
if __debug__:
|
||||||
if EMULATOR:
|
if EMULATOR:
|
||||||
import uos
|
import uos
|
||||||
|
|
||||||
TEST = int(uos.getenv("TREZOR_TEST") or "0")
|
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")
|
SAVE_SCREEN = int(uos.getenv("TREZOR_SAVE_SCREEN") or "0")
|
||||||
LOG_MEMORY = int(uos.getenv("TREZOR_LOG_MEMORY") or "0")
|
LOG_MEMORY = int(uos.getenv("TREZOR_LOG_MEMORY") or "0")
|
||||||
else:
|
else:
|
||||||
TEST = 0
|
TEST = 0
|
||||||
DISABLE_FADE = 0
|
|
||||||
SAVE_SCREEN = 0
|
SAVE_SCREEN = 0
|
||||||
LOG_MEMORY = 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}"
|
MICROPYTHON="${MICROPYTHON:-$CORE_DIR/build/unix/micropython}"
|
||||||
TREZOR_SRC="${CORE_DIR}/src"
|
TREZOR_SRC="${CORE_DIR}/src"
|
||||||
|
|
||||||
DISABLE_FADE=1
|
DISABLE_ANIMATION=1
|
||||||
PYOPT="${PYOPT:-0}"
|
PYOPT="${PYOPT:-0}"
|
||||||
upy_pid=""
|
upy_pid=""
|
||||||
|
|
||||||
@ -22,7 +22,7 @@ if [[ $RUN_TEST_EMU > 0 ]]; then
|
|||||||
echo "Starting emulator: $MICROPYTHON $ARGS ${MAIN}"
|
echo "Starting emulator: $MICROPYTHON $ARGS ${MAIN}"
|
||||||
|
|
||||||
TREZOR_TEST=1 \
|
TREZOR_TEST=1 \
|
||||||
TREZOR_DISABLE_FADE=$DISABLE_FADE \
|
TREZOR_DISABLE_ANIMATION=$DISABLE_ANIMATION \
|
||||||
$MICROPYTHON $ARGS "${MAIN}" &> "${TREZOR_LOGFILE}" &
|
$MICROPYTHON $ARGS "${MAIN}" &> "${TREZOR_LOGFILE}" &
|
||||||
upy_pid=$!
|
upy_pid=$!
|
||||||
cd -
|
cd -
|
||||||
|
@ -6,7 +6,6 @@ CORE_DIR="$(SHELL_SESSION_FILE='' && cd "$( dirname "${BASH_SOURCE[0]}" )/.." >/
|
|||||||
MICROPYTHON="${MICROPYTHON:-$CORE_DIR/build/unix/micropython}"
|
MICROPYTHON="${MICROPYTHON:-$CORE_DIR/build/unix/micropython}"
|
||||||
TREZOR_SRC="${CORE_DIR}/src"
|
TREZOR_SRC="${CORE_DIR}/src"
|
||||||
|
|
||||||
DISABLE_FADE=1
|
|
||||||
PYOPT="${PYOPT:-0}"
|
PYOPT="${PYOPT:-0}"
|
||||||
upy_pid=""
|
upy_pid=""
|
||||||
|
|
||||||
@ -22,7 +21,7 @@ if [[ $RUN_TEST_EMU > 0 ]]; then
|
|||||||
echo "Starting emulator: $MICROPYTHON $ARGS ${MAIN}"
|
echo "Starting emulator: $MICROPYTHON $ARGS ${MAIN}"
|
||||||
|
|
||||||
TREZOR_TEST=1 \
|
TREZOR_TEST=1 \
|
||||||
TREZOR_DISABLE_FADE=$DISABLE_FADE \
|
TREZOR_DISABLE_ANIMATION=1 \
|
||||||
$MICROPYTHON $ARGS "${MAIN}" &> "${TREZOR_LOGFILE}" &
|
$MICROPYTHON $ARGS "${MAIN}" &> "${TREZOR_LOGFILE}" &
|
||||||
upy_pid=$!
|
upy_pid=$!
|
||||||
cd -
|
cd -
|
||||||
|
@ -6,7 +6,7 @@ CORE_DIR="$(SHELL_SESSION_FILE='' && cd "$( dirname "${BASH_SOURCE[0]}" )/.." >/
|
|||||||
MICROPYTHON="${MICROPYTHON:-$CORE_DIR/build/unix/micropython}"
|
MICROPYTHON="${MICROPYTHON:-$CORE_DIR/build/unix/micropython}"
|
||||||
TREZOR_SRC="${CORE_DIR}/src"
|
TREZOR_SRC="${CORE_DIR}/src"
|
||||||
|
|
||||||
DISABLE_FADE=1
|
DISABLE_ANIMATION=1
|
||||||
PYOPT="${PYOPT:-0}"
|
PYOPT="${PYOPT:-0}"
|
||||||
upy_pid=""
|
upy_pid=""
|
||||||
|
|
||||||
@ -22,7 +22,7 @@ if [[ $RUN_TEST_EMU > 0 ]]; then
|
|||||||
echo "Starting emulator: $MICROPYTHON $ARGS ${MAIN}"
|
echo "Starting emulator: $MICROPYTHON $ARGS ${MAIN}"
|
||||||
|
|
||||||
TREZOR_TEST=1 \
|
TREZOR_TEST=1 \
|
||||||
TREZOR_DISABLE_FADE=$DISABLE_FADE \
|
TREZOR_DISABLE_ANIMATION=$DISABLE_ANIMATION \
|
||||||
$MICROPYTHON $ARGS "${MAIN}" &> "${TREZOR_LOGFILE}" &
|
$MICROPYTHON $ARGS "${MAIN}" &> "${TREZOR_LOGFILE}" &
|
||||||
upy_pid=$!
|
upy_pid=$!
|
||||||
cd -
|
cd -
|
||||||
|
@ -8,7 +8,7 @@ CORE_DIR="$(SHELL_SESSION_FILE='' && cd "$( dirname "${BASH_SOURCE[0]}" )/.." >/
|
|||||||
MICROPYTHON="${MICROPYTHON:-$CORE_DIR/build/unix/micropython}"
|
MICROPYTHON="${MICROPYTHON:-$CORE_DIR/build/unix/micropython}"
|
||||||
TREZOR_SRC="${CORE_DIR}/src"
|
TREZOR_SRC="${CORE_DIR}/src"
|
||||||
|
|
||||||
DISABLE_FADE=1
|
DISABLE_ANIMATION=1
|
||||||
PYOPT="${PYOPT:-0}"
|
PYOPT="${PYOPT:-0}"
|
||||||
upy_pid=""
|
upy_pid=""
|
||||||
|
|
||||||
@ -24,7 +24,7 @@ if [[ $RUN_TEST_EMU > 0 ]]; then
|
|||||||
echo "Starting emulator: $MICROPYTHON $ARGS ${MAIN}"
|
echo "Starting emulator: $MICROPYTHON $ARGS ${MAIN}"
|
||||||
|
|
||||||
TREZOR_TEST=1 \
|
TREZOR_TEST=1 \
|
||||||
TREZOR_DISABLE_FADE=$DISABLE_FADE \
|
TREZOR_DISABLE_ANIMATION=$DISABLE_ANIMATION \
|
||||||
$MICROPYTHON $ARGS "${MAIN}" &> "${TREZOR_LOGFILE}" &
|
$MICROPYTHON $ARGS "${MAIN}" &> "${TREZOR_LOGFILE}" &
|
||||||
upy_pid=$!
|
upy_pid=$!
|
||||||
cd -
|
cd -
|
||||||
|
@ -6,7 +6,7 @@ CORE_DIR="$(SHELL_SESSION_FILE='' && cd "$( dirname "${BASH_SOURCE[0]}" )/.." >/
|
|||||||
MICROPYTHON="${MICROPYTHON:-$CORE_DIR/build/unix/micropython}"
|
MICROPYTHON="${MICROPYTHON:-$CORE_DIR/build/unix/micropython}"
|
||||||
TREZOR_SRC="${CORE_DIR}/src"
|
TREZOR_SRC="${CORE_DIR}/src"
|
||||||
|
|
||||||
DISABLE_FADE=1
|
DISABLE_ANIMATION=1
|
||||||
PYOPT="${PYOPT:-0}"
|
PYOPT="${PYOPT:-0}"
|
||||||
upy_pid=""
|
upy_pid=""
|
||||||
|
|
||||||
@ -22,7 +22,7 @@ if [[ $RUN_TEST_EMU > 0 ]]; then
|
|||||||
echo "Starting emulator: $MICROPYTHON $ARGS ${MAIN}"
|
echo "Starting emulator: $MICROPYTHON $ARGS ${MAIN}"
|
||||||
|
|
||||||
TREZOR_TEST=1 \
|
TREZOR_TEST=1 \
|
||||||
TREZOR_DISABLE_FADE=$DISABLE_FADE \
|
TREZOR_DISABLE_ANIMATION=$DISABLE_ANIMATION \
|
||||||
$MICROPYTHON $ARGS "${MAIN}" &> "${TREZOR_LOGFILE}" &
|
$MICROPYTHON $ARGS "${MAIN}" &> "${TREZOR_LOGFILE}" &
|
||||||
upy_pid=$!
|
upy_pid=$!
|
||||||
cd -
|
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.
|
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
|
#### Tests
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ ifneq ($(V),1)
|
|||||||
Q := @
|
Q := @
|
||||||
endif
|
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)
|
ifeq ($(BITCOIN_ONLY), 1)
|
||||||
SKIPPED_MESSAGES += Ethereum Lisk NEM Stellar
|
SKIPPED_MESSAGES += Ethereum Lisk NEM Stellar
|
||||||
|
@ -15,7 +15,8 @@ DebugLinkMemory.memory max_size:1024
|
|||||||
DebugLinkMemoryWrite.memory max_size:1024
|
DebugLinkMemoryWrite.memory max_size:1024
|
||||||
|
|
||||||
# unused fields
|
# unused fields
|
||||||
DebugLinkState.layout_lines max_count:0
|
DebugLinkState.layout_lines max_count:10
|
||||||
DebugLinkState.layout_lines max_size:1
|
DebugLinkState.layout_lines max_size:30
|
||||||
DebugLinkLayout.lines max_size:1
|
DebugLinkLayout.lines max_count:10
|
||||||
DebugLinkLayout.lines max_count:0
|
DebugLinkLayout.lines max_size:30
|
||||||
|
DebugLinkRecordScreen.target_directory max_size:16
|
||||||
|
@ -138,6 +138,15 @@ class DebugLink:
|
|||||||
def stop(self):
|
def stop(self):
|
||||||
self._call(proto.DebugLinkStop(), nowait=True)
|
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")
|
@expect(proto.DebugLinkMemory, field="memory")
|
||||||
def memory_read(self, address, length):
|
def memory_read(self, address, length):
|
||||||
return self._call(proto.DebugLinkMemoryRead(address=address, length=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]
|
DebugLinkMemoryWrite = 112 # type: Literal[112]
|
||||||
DebugLinkFlashErase = 113 # type: Literal[113]
|
DebugLinkFlashErase = 113 # type: Literal[113]
|
||||||
DebugLinkLayout = 9001 # type: Literal[9001]
|
DebugLinkLayout = 9001 # type: Literal[9001]
|
||||||
|
DebugLinkReseedRandom = 9002 # type: Literal[9002]
|
||||||
|
DebugLinkRecordScreen = 9003 # type: Literal[9003]
|
||||||
EthereumGetPublicKey = 450 # type: Literal[450]
|
EthereumGetPublicKey = 450 # type: Literal[450]
|
||||||
EthereumPublicKey = 451 # type: Literal[451]
|
EthereumPublicKey = 451 # type: Literal[451]
|
||||||
EthereumGetAddress = 56 # type: Literal[56]
|
EthereumGetAddress = 56 # type: Literal[56]
|
||||||
|
@ -47,6 +47,8 @@ from .DebugLinkLog import DebugLinkLog
|
|||||||
from .DebugLinkMemory import DebugLinkMemory
|
from .DebugLinkMemory import DebugLinkMemory
|
||||||
from .DebugLinkMemoryRead import DebugLinkMemoryRead
|
from .DebugLinkMemoryRead import DebugLinkMemoryRead
|
||||||
from .DebugLinkMemoryWrite import DebugLinkMemoryWrite
|
from .DebugLinkMemoryWrite import DebugLinkMemoryWrite
|
||||||
|
from .DebugLinkRecordScreen import DebugLinkRecordScreen
|
||||||
|
from .DebugLinkReseedRandom import DebugLinkReseedRandom
|
||||||
from .DebugLinkState import DebugLinkState
|
from .DebugLinkState import DebugLinkState
|
||||||
from .DebugLinkStop import DebugLinkStop
|
from .DebugLinkStop import DebugLinkStop
|
||||||
from .DebugMoneroDiagAck import DebugMoneroDiagAck
|
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 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",
|
"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
|
# fmt: on
|
||||||
|
|
||||||
|
|
||||||
|
@ -14,7 +14,13 @@
|
|||||||
# 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 filecmp
|
||||||
|
import hashlib
|
||||||
|
import itertools
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
|
from contextlib import contextmanager
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
@ -48,8 +54,124 @@ def get_device():
|
|||||||
raise RuntimeError("No debuggable device found")
|
raise RuntimeError("No debuggable device found")
|
||||||
|
|
||||||
|
|
||||||
|
def _get_test_dirname(node):
|
||||||
|
# This composes the dirname from the test module name and test item name.
|
||||||
|
# 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.
|
||||||
|
node_name = re.sub(r"\W+", "_", node.name)[:100]
|
||||||
|
node_module_name = node.getparent(pytest.Module).name
|
||||||
|
return "{}_{}".format(node_module_name, node_name)
|
||||||
|
|
||||||
|
|
||||||
|
def _check_screen_fixtures_dir(fixture_dir):
|
||||||
|
if fixture_dir.exists():
|
||||||
|
# remove old fixtures
|
||||||
|
for fixture in fixture_dir.iterdir():
|
||||||
|
fixture.unlink()
|
||||||
|
else:
|
||||||
|
# create the fixture dir, if not present
|
||||||
|
fixture_dir.mkdir()
|
||||||
|
|
||||||
|
|
||||||
|
def _record_screen_fixtures(fixture_dir, test_dir):
|
||||||
|
_check_screen_fixtures_dir(fixture_dir)
|
||||||
|
|
||||||
|
# move recorded screenshots into fixture directory
|
||||||
|
records = sorted(test_dir.iterdir())
|
||||||
|
for index, record in enumerate(sorted(records)):
|
||||||
|
fixture = fixture_dir / "{:08}.png".format(index)
|
||||||
|
record.replace(fixture)
|
||||||
|
|
||||||
|
|
||||||
|
def _hash_screen_fixtures(fixture_dir, test_dir):
|
||||||
|
_check_screen_fixtures_dir(fixture_dir)
|
||||||
|
|
||||||
|
# hash recorded screenshots
|
||||||
|
records = sorted(test_dir.iterdir())
|
||||||
|
digest = _hash_files(records)
|
||||||
|
|
||||||
|
with open(fixture_dir / "hash.txt", "w") as f:
|
||||||
|
f.write(digest)
|
||||||
|
|
||||||
|
|
||||||
|
def _hash_files(files):
|
||||||
|
hasher = hashlib.sha256()
|
||||||
|
for file in sorted(files):
|
||||||
|
with open(file, "rb") as f:
|
||||||
|
content = f.read()
|
||||||
|
hasher.update(content)
|
||||||
|
|
||||||
|
return hasher.digest().hex()
|
||||||
|
|
||||||
|
|
||||||
|
def _assert_screen_recording(fixture_dir, test_dir):
|
||||||
|
fixtures = sorted(fixture_dir.iterdir())
|
||||||
|
records = sorted(test_dir.iterdir())
|
||||||
|
|
||||||
|
if not fixtures:
|
||||||
|
return
|
||||||
|
|
||||||
|
for fixture, image in itertools.zip_longest(fixtures, records):
|
||||||
|
if fixture is None:
|
||||||
|
pytest.fail("Missing fixture for image {}".format(image))
|
||||||
|
if image is None:
|
||||||
|
pytest.fail("Missing image for fixture {}".format(fixture))
|
||||||
|
if not filecmp.cmp(fixture, image):
|
||||||
|
pytest.fail("Image {} and fixture {} differ".format(image, fixture))
|
||||||
|
|
||||||
|
|
||||||
|
def _assert_screen_hashes(fixture_dir, test_dir):
|
||||||
|
records = sorted(test_dir.iterdir())
|
||||||
|
hash_file = fixture_dir / "hash.txt"
|
||||||
|
|
||||||
|
if not hash_file.exists():
|
||||||
|
raise ValueError("File hash.txt not found.")
|
||||||
|
|
||||||
|
with open(hash_file, "r") as f:
|
||||||
|
expected_hash = f.read()
|
||||||
|
|
||||||
|
actual_hash = _hash_files(records)
|
||||||
|
|
||||||
|
if actual_hash != expected_hash:
|
||||||
|
pytest.fail(
|
||||||
|
"Hash of {} differs.\nExpected: {}\nActual: {}".format(
|
||||||
|
fixture_dir.name, expected_hash, actual_hash
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def _screen_recording(client, request, tmp_path):
|
||||||
|
if not request.node.get_closest_marker("skip_ui"):
|
||||||
|
test_screen = request.config.getoption("test_screen")
|
||||||
|
else:
|
||||||
|
test_screen = ""
|
||||||
|
fixture_root = Path(__file__) / "../ui_tests"
|
||||||
|
|
||||||
|
try:
|
||||||
|
if test_screen:
|
||||||
|
client.debug.start_recording(str(tmp_path))
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
if test_screen:
|
||||||
|
client.debug.stop_recording()
|
||||||
|
fixture_path = fixture_root.resolve() / _get_test_dirname(request.node)
|
||||||
|
if test_screen == "record":
|
||||||
|
_record_screen_fixtures(fixture_path, tmp_path)
|
||||||
|
elif test_screen == "hash":
|
||||||
|
_hash_screen_fixtures(fixture_path, tmp_path)
|
||||||
|
elif test_screen == "test-hash":
|
||||||
|
_assert_screen_hashes(fixture_path, tmp_path)
|
||||||
|
elif test_screen == "test-record":
|
||||||
|
_assert_screen_recording(fixture_path, tmp_path)
|
||||||
|
else:
|
||||||
|
raise ValueError("Invalid test_screen option.")
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="function")
|
@pytest.fixture(scope="function")
|
||||||
def client(request):
|
def client(request, tmp_path):
|
||||||
"""Client fixture.
|
"""Client fixture.
|
||||||
|
|
||||||
Every test function that requires a client instance will get it from here.
|
Every test function that requires a client instance will get it from here.
|
||||||
@ -99,6 +221,7 @@ def client(request):
|
|||||||
passphrase=False,
|
passphrase=False,
|
||||||
needs_backup=False,
|
needs_backup=False,
|
||||||
no_backup=False,
|
no_backup=False,
|
||||||
|
random_seed=None,
|
||||||
)
|
)
|
||||||
# fmt: on
|
# fmt: on
|
||||||
|
|
||||||
@ -128,10 +251,25 @@ def client(request):
|
|||||||
client.clear_session()
|
client.clear_session()
|
||||||
|
|
||||||
client.open()
|
client.open()
|
||||||
|
|
||||||
|
if setup_params["random_seed"] is not None:
|
||||||
|
client.debug.reseed(setup_params["random_seed"])
|
||||||
|
|
||||||
|
with _screen_recording(client, request, tmp_path):
|
||||||
yield client
|
yield client
|
||||||
|
|
||||||
client.close()
|
client.close()
|
||||||
|
|
||||||
|
|
||||||
|
def pytest_addoption(parser):
|
||||||
|
parser.addoption(
|
||||||
|
"--test_screen",
|
||||||
|
action="store",
|
||||||
|
default="",
|
||||||
|
help="Enable UI intergration tests: 'record', 'hash' or 'test-hash' and 'test-record'",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def pytest_configure(config):
|
def pytest_configure(config):
|
||||||
"""Called at testsuite setup time.
|
"""Called at testsuite setup time.
|
||||||
|
|
||||||
@ -144,6 +282,9 @@ def pytest_configure(config):
|
|||||||
"markers",
|
"markers",
|
||||||
'setup_client(mnemonic="all all all...", pin=None, passphrase=False, uninitialized=False): configure the client instance',
|
'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:
|
with open(os.path.join(os.path.dirname(__file__), "REGISTERED_MARKERS")) as f:
|
||||||
for line in f:
|
for line in f:
|
||||||
config.addinivalue_line("markers", line.strip())
|
config.addinivalue_line("markers", line.strip())
|
||||||
|
@ -32,7 +32,7 @@ from ..common import (
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.skip_t1 # TODO we want this for t1 too
|
@pytest.mark.skip_t1 # TODO we want this for t1 too
|
||||||
@pytest.mark.setup_client(needs_backup=True, mnemonic=MNEMONIC12)
|
@pytest.mark.setup_client(needs_backup=True, mnemonic=MNEMONIC12, random_seed=0)
|
||||||
def test_backup_bip39(client):
|
def test_backup_bip39(client):
|
||||||
assert client.features.needs_backup is True
|
assert client.features.needs_backup is True
|
||||||
mnemonic = None
|
mnemonic = None
|
||||||
@ -71,7 +71,9 @@ def test_backup_bip39(client):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.skip_t1
|
@pytest.mark.skip_t1
|
||||||
@pytest.mark.setup_client(needs_backup=True, mnemonic=MNEMONIC_SLIP39_BASIC_20_3of6)
|
@pytest.mark.setup_client(
|
||||||
|
needs_backup=True, mnemonic=MNEMONIC_SLIP39_BASIC_20_3of6, random_seed=0
|
||||||
|
)
|
||||||
def test_backup_slip39_basic(client):
|
def test_backup_slip39_basic(client):
|
||||||
assert client.features.needs_backup is True
|
assert client.features.needs_backup is True
|
||||||
mnemonics = []
|
mnemonics = []
|
||||||
@ -136,7 +138,9 @@ def test_backup_slip39_basic(client):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.skip_t1
|
@pytest.mark.skip_t1
|
||||||
@pytest.mark.setup_client(needs_backup=True, mnemonic=MNEMONIC_SLIP39_ADVANCED_20)
|
@pytest.mark.setup_client(
|
||||||
|
needs_backup=True, mnemonic=MNEMONIC_SLIP39_ADVANCED_20, random_seed=0
|
||||||
|
)
|
||||||
def test_backup_slip39_advanced(client):
|
def test_backup_slip39_advanced(client):
|
||||||
assert client.features.needs_backup is True
|
assert client.features.needs_backup is True
|
||||||
mnemonics = []
|
mnemonics = []
|
||||||
|
@ -100,7 +100,7 @@ def _check_wipe_code(client, pin, wipe_code):
|
|||||||
device.change_pin(client)
|
device.change_pin(client)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.setup_client(pin=PIN4)
|
@pytest.mark.setup_client(pin=PIN4, random_seed=0)
|
||||||
def test_set_remove_wipe_code(client):
|
def test_set_remove_wipe_code(client):
|
||||||
# Test set wipe code.
|
# Test set wipe code.
|
||||||
assert client.features.wipe_code_protection is False
|
assert client.features.wipe_code_protection is False
|
||||||
@ -143,6 +143,7 @@ def test_set_remove_wipe_code(client):
|
|||||||
assert client.features.wipe_code_protection is False
|
assert client.features.wipe_code_protection is False
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.setup_client(random_seed=0)
|
||||||
def test_set_wipe_code_mismatch(client):
|
def test_set_wipe_code_mismatch(client):
|
||||||
# Let's set a wipe code.
|
# Let's set a wipe code.
|
||||||
def input_flow():
|
def input_flow():
|
||||||
@ -170,7 +171,7 @@ def test_set_wipe_code_mismatch(client):
|
|||||||
assert client.features.wipe_code_protection is False
|
assert client.features.wipe_code_protection is False
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.setup_client(pin=PIN4)
|
@pytest.mark.setup_client(pin=PIN4, random_seed=0)
|
||||||
def test_set_wipe_code_to_pin(client):
|
def test_set_wipe_code_to_pin(client):
|
||||||
def input_flow():
|
def input_flow():
|
||||||
yield # do you want to set the wipe code?
|
yield # do you want to set the wipe code?
|
||||||
@ -201,6 +202,7 @@ def test_set_wipe_code_to_pin(client):
|
|||||||
_check_wipe_code(client, PIN4, WIPE_CODE4)
|
_check_wipe_code(client, PIN4, WIPE_CODE4)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.setup_client(random_seed=0)
|
||||||
def test_set_pin_to_wipe_code(client):
|
def test_set_pin_to_wipe_code(client):
|
||||||
# Set wipe code.
|
# Set wipe code.
|
||||||
with client:
|
with client:
|
||||||
@ -221,7 +223,10 @@ def test_set_pin_to_wipe_code(client):
|
|||||||
device.change_pin(client)
|
device.change_pin(client)
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: this UI test should not be skipped, but when setting random_seed=0 it fails
|
||||||
|
# on device id match and I am not sure why
|
||||||
@pytest.mark.setup_client(pin=PIN4)
|
@pytest.mark.setup_client(pin=PIN4)
|
||||||
|
@pytest.mark.skip_ui
|
||||||
def test_wipe_code_activate(client):
|
def test_wipe_code_activate(client):
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
@ -96,6 +96,7 @@ def _check_no_pin(client):
|
|||||||
assert client.features.pin_protection is False
|
assert client.features.pin_protection is False
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.setup_client(random_seed=0)
|
||||||
def test_set_pin(client):
|
def test_set_pin(client):
|
||||||
assert client.features.pin_protection is False
|
assert client.features.pin_protection is False
|
||||||
|
|
||||||
@ -116,7 +117,7 @@ def test_set_pin(client):
|
|||||||
_check_pin(client, PIN6)
|
_check_pin(client, PIN6)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.setup_client(pin=PIN4)
|
@pytest.mark.setup_client(pin=PIN4, random_seed=0)
|
||||||
def test_change_pin(client):
|
def test_change_pin(client):
|
||||||
assert client.features.pin_protection is True
|
assert client.features.pin_protection is True
|
||||||
|
|
||||||
@ -139,7 +140,7 @@ def test_change_pin(client):
|
|||||||
_check_pin(client, PIN6)
|
_check_pin(client, PIN6)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.setup_client(pin=PIN4)
|
@pytest.mark.setup_client(pin=PIN4, random_seed=0)
|
||||||
def test_remove_pin(client):
|
def test_remove_pin(client):
|
||||||
assert client.features.pin_protection is True
|
assert client.features.pin_protection is True
|
||||||
|
|
||||||
@ -161,6 +162,7 @@ def test_remove_pin(client):
|
|||||||
_check_no_pin(client)
|
_check_no_pin(client)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.setup_client(random_seed=0)
|
||||||
def test_set_failed(client):
|
def test_set_failed(client):
|
||||||
assert client.features.pin_protection is False
|
assert client.features.pin_protection is False
|
||||||
|
|
||||||
@ -194,7 +196,7 @@ def test_set_failed(client):
|
|||||||
_check_no_pin(client)
|
_check_no_pin(client)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.setup_client(pin=PIN4)
|
@pytest.mark.setup_client(pin=PIN4, random_seed=0)
|
||||||
def test_change_failed(client):
|
def test_change_failed(client):
|
||||||
assert client.features.pin_protection is True
|
assert client.features.pin_protection is True
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ from ..common import MNEMONIC12
|
|||||||
|
|
||||||
@pytest.mark.skip_t1
|
@pytest.mark.skip_t1
|
||||||
class TestMsgRecoverydeviceT2:
|
class TestMsgRecoverydeviceT2:
|
||||||
@pytest.mark.setup_client(uninitialized=True)
|
@pytest.mark.setup_client(uninitialized=True, random_seed=0)
|
||||||
def test_pin_passphrase(self, client):
|
def test_pin_passphrase(self, client):
|
||||||
mnemonic = MNEMONIC12.split(" ")
|
mnemonic = MNEMONIC12.split(" ")
|
||||||
ret = client.call_raw(
|
ret = client.call_raw(
|
||||||
|
@ -66,7 +66,7 @@ def test_secret(client, shares, secret):
|
|||||||
assert debug.read_mnemonic_secret().hex() == secret
|
assert debug.read_mnemonic_secret().hex() == secret
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.setup_client(uninitialized=True)
|
@pytest.mark.setup_client(uninitialized=True, random_seed=0)
|
||||||
def test_extra_share_entered(client):
|
def test_extra_share_entered(client):
|
||||||
debug = client.debug
|
debug = client.debug
|
||||||
|
|
||||||
|
@ -70,7 +70,7 @@ def test_secret(client, shares, secret):
|
|||||||
assert debug.read_mnemonic_secret().hex() == secret
|
assert debug.read_mnemonic_secret().hex() == secret
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.setup_client(uninitialized=True)
|
@pytest.mark.setup_client(uninitialized=True, random_seed=0)
|
||||||
def test_recover_with_pin_passphrase(client):
|
def test_recover_with_pin_passphrase(client):
|
||||||
debug = client.debug
|
debug = client.debug
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@ EXTERNAL_ENTROPY = b"zlutoucky kun upel divoke ody" * 2
|
|||||||
|
|
||||||
@pytest.mark.skip_t1
|
@pytest.mark.skip_t1
|
||||||
class TestMsgResetDeviceT2:
|
class TestMsgResetDeviceT2:
|
||||||
@pytest.mark.setup_client(uninitialized=True)
|
@pytest.mark.setup_client(uninitialized=True, random_seed=0)
|
||||||
def test_reset_device(self, client):
|
def test_reset_device(self, client):
|
||||||
mnemonic = None
|
mnemonic = None
|
||||||
strength = 128
|
strength = 128
|
||||||
@ -110,7 +110,7 @@ class TestMsgResetDeviceT2:
|
|||||||
with pytest.raises(TrezorFailure, match="ProcessError: Seed already backed up"):
|
with pytest.raises(TrezorFailure, match="ProcessError: Seed already backed up"):
|
||||||
device.backup(client)
|
device.backup(client)
|
||||||
|
|
||||||
@pytest.mark.setup_client(uninitialized=True)
|
@pytest.mark.setup_client(uninitialized=True, random_seed=0)
|
||||||
def test_reset_device_pin(self, client):
|
def test_reset_device_pin(self, client):
|
||||||
mnemonic = None
|
mnemonic = None
|
||||||
strength = 128
|
strength = 128
|
||||||
@ -207,7 +207,7 @@ class TestMsgResetDeviceT2:
|
|||||||
assert resp.pin_protection is True
|
assert resp.pin_protection is True
|
||||||
assert resp.passphrase_protection is True
|
assert resp.passphrase_protection is True
|
||||||
|
|
||||||
@pytest.mark.setup_client(uninitialized=True)
|
@pytest.mark.setup_client(uninitialized=True, random_seed=0)
|
||||||
def test_failed_pin(self, client):
|
def test_failed_pin(self, client):
|
||||||
# external_entropy = b'zlutoucky kun upel divoke ody' * 2
|
# external_entropy = b'zlutoucky kun upel divoke ody' * 2
|
||||||
strength = 128
|
strength = 128
|
||||||
|
@ -31,7 +31,7 @@ EXTERNAL_ENTROPY = b"zlutoucky kun upel divoke ody" * 2
|
|||||||
@pytest.mark.skip_t1
|
@pytest.mark.skip_t1
|
||||||
class TestMsgResetDeviceT2:
|
class TestMsgResetDeviceT2:
|
||||||
# TODO: test with different options
|
# TODO: test with different options
|
||||||
@pytest.mark.setup_client(uninitialized=True)
|
@pytest.mark.setup_client(uninitialized=True, random_seed=0)
|
||||||
def test_reset_device_slip39_advanced(self, client):
|
def test_reset_device_slip39_advanced(self, client):
|
||||||
strength = 128
|
strength = 128
|
||||||
word_count = 20
|
word_count = 20
|
||||||
|
@ -25,15 +25,18 @@ from trezorlib import device, messages as proto
|
|||||||
from trezorlib.exceptions import TrezorFailure
|
from trezorlib.exceptions import TrezorFailure
|
||||||
from trezorlib.messages import BackupType, ButtonRequestType as B
|
from trezorlib.messages import BackupType, ButtonRequestType as B
|
||||||
|
|
||||||
from ..common import click_through, generate_entropy, read_and_confirm_mnemonic
|
from ..common import (
|
||||||
|
EXTERNAL_ENTROPY,
|
||||||
EXTERNAL_ENTROPY = b"zlutoucky kun upel divoke ody" * 2
|
click_through,
|
||||||
|
generate_entropy,
|
||||||
|
read_and_confirm_mnemonic,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skip_t1
|
@pytest.mark.skip_t1
|
||||||
class TestMsgResetDeviceT2:
|
class TestMsgResetDeviceT2:
|
||||||
# TODO: test with different options
|
# TODO: test with different options
|
||||||
@pytest.mark.setup_client(uninitialized=True)
|
@pytest.mark.setup_client(uninitialized=True, random_seed=0)
|
||||||
def test_reset_device_slip39_basic(self, client):
|
def test_reset_device_slip39_basic(self, client):
|
||||||
strength = 128
|
strength = 128
|
||||||
member_threshold = 3
|
member_threshold = 3
|
||||||
|
@ -369,6 +369,7 @@ class TestMsgSigntx:
|
|||||||
)
|
)
|
||||||
|
|
||||||
@pytest.mark.setup_client(mnemonic=MNEMONIC12)
|
@pytest.mark.setup_client(mnemonic=MNEMONIC12)
|
||||||
|
@pytest.mark.skip_ui
|
||||||
def test_lots_of_inputs(self, client):
|
def test_lots_of_inputs(self, client):
|
||||||
# Tests if device implements serialization of len(inputs) correctly
|
# Tests if device implements serialization of len(inputs) correctly
|
||||||
# tx 4a7b7e0403ae5607e473949cfa03f09f2cd8b0f404bf99ce10b7303d86280bf7 : 100 UTXO for spending for unit tests
|
# tx 4a7b7e0403ae5607e473949cfa03f09f2cd8b0f404bf99ce10b7303d86280bf7 : 100 UTXO for spending for unit tests
|
||||||
@ -397,6 +398,7 @@ class TestMsgSigntx:
|
|||||||
)
|
)
|
||||||
|
|
||||||
@pytest.mark.setup_client(mnemonic=MNEMONIC12)
|
@pytest.mark.setup_client(mnemonic=MNEMONIC12)
|
||||||
|
@pytest.mark.skip_ui
|
||||||
def test_lots_of_outputs(self, client):
|
def test_lots_of_outputs(self, client):
|
||||||
# Tests if device implements serialization of len(outputs) correctly
|
# 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>.
|
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
|
||||||
|
|
||||||
|
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
import shamir_mnemonic as shamir
|
import shamir_mnemonic as shamir
|
||||||
|
|
||||||
from trezorlib import device, messages
|
from trezorlib import device, messages
|
||||||
from trezorlib.messages import BackupType, ButtonRequestType as B
|
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):
|
def backup_flow_bip39(client):
|
||||||
@ -176,8 +178,11 @@ VECTORS = [
|
|||||||
|
|
||||||
@pytest.mark.skip_t1
|
@pytest.mark.skip_t1
|
||||||
@pytest.mark.parametrize("backup_type, backup_flow", VECTORS)
|
@pytest.mark.parametrize("backup_type, backup_flow", VECTORS)
|
||||||
@pytest.mark.setup_client(uninitialized=True)
|
@pytest.mark.setup_client(uninitialized=True, random_seed=0)
|
||||||
def test_skip_backup_msg(client, backup_type, backup_flow):
|
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(
|
device.reset(
|
||||||
client,
|
client,
|
||||||
skip_backup=True,
|
skip_backup=True,
|
||||||
@ -208,7 +213,7 @@ def test_skip_backup_msg(client, backup_type, backup_flow):
|
|||||||
|
|
||||||
@pytest.mark.skip_t1
|
@pytest.mark.skip_t1
|
||||||
@pytest.mark.parametrize("backup_type, backup_flow", VECTORS)
|
@pytest.mark.parametrize("backup_type, backup_flow", VECTORS)
|
||||||
@pytest.mark.setup_client(uninitialized=True)
|
@pytest.mark.setup_client(uninitialized=True, random_seed=0)
|
||||||
def test_skip_backup_manual(client, backup_type, backup_flow):
|
def test_skip_backup_manual(client, backup_type, backup_flow):
|
||||||
def reset_skip_input_flow():
|
def reset_skip_input_flow():
|
||||||
yield # Confirm Recovery
|
yield # Confirm Recovery
|
||||||
@ -220,7 +225,8 @@ def test_skip_backup_manual(client, backup_type, backup_flow):
|
|||||||
yield # Confirm skip backup
|
yield # Confirm skip backup
|
||||||
client.debug.press_no()
|
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_input_flow(reset_skip_input_flow)
|
||||||
client.set_expected_responses(
|
client.set_expected_responses(
|
||||||
[
|
[
|
||||||
|
@ -15,17 +15,20 @@
|
|||||||
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
|
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
|
||||||
|
|
||||||
|
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from trezorlib import btc, device, messages
|
from trezorlib import btc, device, messages
|
||||||
from trezorlib.messages import BackupType, ButtonRequestType as B
|
from trezorlib.messages import BackupType, ButtonRequestType as B
|
||||||
from trezorlib.tools import parse_path
|
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_t1
|
||||||
@pytest.mark.setup_client(uninitialized=True)
|
@pytest.mark.skip_ui
|
||||||
|
@pytest.mark.setup_client(uninitialized=True, random_seed=0)
|
||||||
def test_reset_recovery(client):
|
def test_reset_recovery(client):
|
||||||
mnemonic = reset(client)
|
mnemonic = reset(client)
|
||||||
address_before = btc.get_address(client, "Bitcoin", parse_path("44'/0'/0'/0/0"))
|
address_before = btc.get_address(client, "Bitcoin", parse_path("44'/0'/0'/0/0"))
|
||||||
@ -79,6 +82,8 @@ def reset(client, strength=128, skip_backup=False):
|
|||||||
)
|
)
|
||||||
client.set_input_flow(input_flow)
|
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
|
# No PIN, no passphrase, don't display random
|
||||||
device.reset(
|
device.reset(
|
||||||
client,
|
client,
|
||||||
|
@ -14,17 +14,25 @@
|
|||||||
# 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>.
|
||||||
|
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from trezorlib import btc, device, messages
|
from trezorlib import btc, device, messages
|
||||||
from trezorlib.messages import BackupType, ButtonRequestType as B
|
from trezorlib.messages import BackupType, ButtonRequestType as B
|
||||||
from trezorlib.tools import parse_path
|
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_t1
|
||||||
@pytest.mark.setup_client(uninitialized=True)
|
@pytest.mark.skip_ui
|
||||||
|
@pytest.mark.setup_client(uninitialized=True, random_seed=0)
|
||||||
def test_reset_recovery(client):
|
def test_reset_recovery(client):
|
||||||
mnemonics = reset(client)
|
mnemonics = reset(client)
|
||||||
address_before = btc.get_address(client, "Bitcoin", parse_path("44'/0'/0'/0/0"))
|
address_before = btc.get_address(client, "Bitcoin", parse_path("44'/0'/0'/0/0"))
|
||||||
@ -89,7 +97,8 @@ def reset(client, strength=128):
|
|||||||
assert btn_code == B.Success
|
assert btn_code == B.Success
|
||||||
client.debug.press_yes()
|
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(
|
client.set_expected_responses(
|
||||||
[
|
[
|
||||||
messages.ButtonRequest(code=B.ResetDevice),
|
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_t1
|
||||||
|
@pytest.mark.skip_ui
|
||||||
@pytest.mark.setup_client(uninitialized=True)
|
@pytest.mark.setup_client(uninitialized=True)
|
||||||
def test_reset_recovery(client):
|
def test_reset_recovery(client):
|
||||||
mnemonics = reset(client)
|
mnemonics = reset(client)
|
||||||
|
@ -0,0 +1 @@
|
|||||||
|
b696f69c57970f113b4a5f26473493da99d11b672741efc41b213c8844b3c3c0
|
@ -0,0 +1 @@
|
|||||||
|
5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586
|
1
tests/ui_tests/test_basic.py_test_features/hash.txt
Normal file
1
tests/ui_tests/test_basic.py_test_features/hash.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586
|
1
tests/ui_tests/test_basic.py_test_ping/hash.txt
Normal file
1
tests/ui_tests/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 @@
|
|||||||
|
23205f8049143e3b8426c2c641ae06d0d1abb74bb957068f17ad4dfb5172c8e0
|
@ -0,0 +1 @@
|
|||||||
|
42ef69a79450eeec396e36f7fd13f89163c0a1cda167fe27811a613ea98a1b3a
|
@ -0,0 +1 @@
|
|||||||
|
b63863667bfbd65effdab66e47fd007c3bf0f5b183966c00e05527dfc4f5a2bf
|
@ -0,0 +1 @@
|
|||||||
|
444af44427fd2e4de1069643b8a1d73d49de9f685d884101b0b654851b9e7c84
|
@ -0,0 +1 @@
|
|||||||
|
86a586907d8879e641661709e38ad9208e3e9feb40ef0024f0922fd33a5ee826
|
@ -0,0 +1 @@
|
|||||||
|
225b3da1acac6e9a65106fcc4a01de8a44de035aedb4dcc21c09f439199fdf40
|
@ -0,0 +1 @@
|
|||||||
|
93039a9472cfc9058563bd56e4a3dbe2e41af64744a61f6ee3255a04bd3a9366
|
@ -0,0 +1 @@
|
|||||||
|
14fcdd2ded299ca099a35966cc9f21204b31de8d6bab9ec91cb64537bd70440c
|
@ -0,0 +1 @@
|
|||||||
|
5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586
|
@ -0,0 +1 @@
|
|||||||
|
43b1c496210d785bb032107ed5f647f5bd4471ca6b8bdd905afd8d34560bc03a
|
@ -0,0 +1 @@
|
|||||||
|
5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586
|
@ -0,0 +1 @@
|
|||||||
|
a683bcaaa1469625167d0c9e0848e3785b0b0e82b4c904eb3c6bfcbb1d7bd262
|
@ -0,0 +1 @@
|
|||||||
|
737ac35c04567c6342ab3d34aac7ca1f99d4bcb15574a1d60b35215390e86857
|
@ -0,0 +1 @@
|
|||||||
|
d6d6bddda46fe2b43da4e11ca7cee24fb1f77f267f672b82bfb9951d749d5a26
|
@ -0,0 +1 @@
|
|||||||
|
5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586
|
@ -0,0 +1 @@
|
|||||||
|
5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586
|
@ -0,0 +1 @@
|
|||||||
|
5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586
|
@ -0,0 +1 @@
|
|||||||
|
d9568d6a6fdaa12b6c3f582a97cc986718aeed94c334af07066f6499d9cd0519
|
@ -0,0 +1 @@
|
|||||||
|
d9568d6a6fdaa12b6c3f582a97cc986718aeed94c334af07066f6499d9cd0519
|
@ -0,0 +1 @@
|
|||||||
|
d9568d6a6fdaa12b6c3f582a97cc986718aeed94c334af07066f6499d9cd0519
|
@ -0,0 +1 @@
|
|||||||
|
5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586
|
@ -0,0 +1 @@
|
|||||||
|
5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586
|
@ -0,0 +1 @@
|
|||||||
|
5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586
|
@ -0,0 +1 @@
|
|||||||
|
5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586
|
@ -0,0 +1 @@
|
|||||||
|
d9568d6a6fdaa12b6c3f582a97cc986718aeed94c334af07066f6499d9cd0519
|
@ -0,0 +1 @@
|
|||||||
|
d9568d6a6fdaa12b6c3f582a97cc986718aeed94c334af07066f6499d9cd0519
|
@ -0,0 +1 @@
|
|||||||
|
d9568d6a6fdaa12b6c3f582a97cc986718aeed94c334af07066f6499d9cd0519
|
@ -0,0 +1 @@
|
|||||||
|
992d564b69e7cbd326ca7d5910e221dae8cfe952fbf3eaf162750049254b6fa7
|
@ -0,0 +1 @@
|
|||||||
|
992d564b69e7cbd326ca7d5910e221dae8cfe952fbf3eaf162750049254b6fa7
|
@ -0,0 +1 @@
|
|||||||
|
768642f114cb5b0062f0074d8ab21435efeacea6b65c6ea2ebe65b3e62417e5e
|
@ -0,0 +1 @@
|
|||||||
|
fe7055240ecba7d47b81acc4b896bc1376ef40bfbe17153b5ab496ffa7cc4acf
|
@ -0,0 +1 @@
|
|||||||
|
fe7055240ecba7d47b81acc4b896bc1376ef40bfbe17153b5ab496ffa7cc4acf
|
@ -0,0 +1 @@
|
|||||||
|
fe7055240ecba7d47b81acc4b896bc1376ef40bfbe17153b5ab496ffa7cc4acf
|
@ -0,0 +1 @@
|
|||||||
|
136823301f0137eb6979b4800cff84eededa9e66c88a4eb852a176ce37aa1f9f
|
@ -0,0 +1 @@
|
|||||||
|
136823301f0137eb6979b4800cff84eededa9e66c88a4eb852a176ce37aa1f9f
|
@ -0,0 +1 @@
|
|||||||
|
c4ed9a1be34e7d0e1dbd0f4bf9a3835121a9444bf5c6d840cf7aab60461ae732
|
@ -0,0 +1 @@
|
|||||||
|
dfa63984406f8f8ab0fbe9986564f82c7d960b87fa991818501d166989c2dca7
|
@ -0,0 +1 @@
|
|||||||
|
1d548189e9801c7c4421a52c36805c9f34751c126aa21ac87d6b62679c9f4ba4
|
@ -0,0 +1 @@
|
|||||||
|
e69158befea51d888aabe1681edfcdaacc1c7edbb2d90bb265600ffda20ad30d
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user