firmware/testing/core_fixtures.py
2025-06-11 08:32:22 -04:00

149 lines
4.7 KiB
Python

# (c) Copyright 2024 by Coinkite Inc. This file is covered by license found in COPYING-CC.
#
# Base for some pytest fixtures.
# Allows testers to escape pytest scoping.
# Fixtures in conftest.py are (mostly) session/module scoped
# as multiple tests are intended to be run on single simulator instance.
# Below functions are injected with proper scoped `device` in conftest.py
# using funtools.partial.
#
import time
from charcodes import *
from ckcc_protocol.client import CCProtocolPacker
def _sim_exec(device, cmd, binary=False, timeout=60000):
s = device.send_recv(b'EXEC' + cmd.encode('utf-8'), timeout=timeout, encrypt=False)
if binary: return s
# print(f'sim_exec: {cmd!r} -> {s!r}')
return s.decode('utf-8') if not isinstance(s, str) else s
def _sim_eval(device, cmd, binary=False, timeout=None):
s = device.send_recv(b'EVAL' + cmd.encode('utf-8'), timeout=timeout)
if binary: return s
return s.decode('utf-8')
def _cap_story(device):
cmd = "RV.write('\0'.join(sim_display.story or []))"
rv = _sim_exec(device, cmd)
return rv.split('\0', 1) if rv else ('','')
def _cap_menu(device):
rv = _sim_exec(device, 'from ux import the_ux; RV.write(repr('
'[i.label for i in the_ux.top_of_stack().items]))')
if 'Traceback' in rv:
raise RuntimeError(rv)
return eval(rv)
def _cap_screen(device):
return _sim_exec(device, 'RV.write(sim_display.full_contents)')
def _need_keypress(device, k, timeout=None):
device.send_recv(CCProtocolPacker.sim_keypress(k.encode('ascii')), timeout=timeout)
def _press_select(device, is_Q, timeout=None):
btn = KEY_ENTER if is_Q else "y"
_need_keypress(device, btn, timeout=timeout)
def _dev_hw_label(device):
# gets a short string that labels product: mk4 / q1, etc
v = device.send_recv(CCProtocolPacker.version()).split()
return v[4]
def _pick_menu_item(device, is_Q, text):
print(f"PICK menu item: {text}")
WRAP_IF_OVER = 16 # see ../shared/menu.py .. this is larger of 10 or 16
_need_keypress(device, KEY_HOME if is_Q else "0")
m = _cap_menu(device)
if text not in m:
raise KeyError(text, "%r not in menu: %r" % (text, m))
if is_Q:
# double check we're looking at this menu, not stale data
# added strip as cap_screen does not contain whitespaces
# that are present in coundown chooser
# find menu item that does not contain triple dot char
target = [mi for mi in m if "" not in mi]
if target:
assert target[0][0:33].strip() in _cap_screen(device), 'not in menu mode'
else:
print("⋯ in all menu items - not sure about free - but continue")
m_pos = m.index(text)
if len(m) > WRAP_IF_OVER and m_pos > (len(m)//2):
# use wrap around, work up from bottom
for n in range(len(m) - m_pos):
_need_keypress(device, KEY_UP)
time.sleep(.01) # required
_press_select(device, is_Q)
time.sleep(.01) # required
else:
# go down
for n in range(m_pos):
_need_keypress(device, KEY_DOWN)
time.sleep(.01) # required
_press_select(device, is_Q)
time.sleep(.01) # required
def _enter_complex(device, is_Q, target, apply=False, b39pass=True):
if b39pass:
try:
_pick_menu_item(device, is_Q, 'Edit Phrase')
except:
assert is_Q
if is_Q:
for ch in target:
_need_keypress(device, ch)
time.sleep(.1)
_press_select(device, is_Q)
return
symbols = ' !"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~'
for pos, d in enumerate(target):
time.sleep(.01) # required
if d.isalpha():
if pos != 0: # A is already default first
_need_keypress(device, '1')
if d.islower():
time.sleep(.01) # required
_need_keypress(device, '1')
cnt = ord(d.lower()) - ord('a')
elif d.isdigit():
_need_keypress(device, '2')
if d == '0':
time.sleep(.01) # required
_need_keypress(device, '8')
cnt = 0
else:
cnt = ord(d) - ord('1')
else:
assert d in symbols
if pos == 0:
_need_keypress(device, '3')
cnt = symbols.find(d)
for i in range(cnt):
time.sleep(.01) # required
_need_keypress(device, '5')
if pos != len(target) - 1:
time.sleep(.01) # required
_need_keypress(device, '9')
time.sleep(0.01) # required
_press_select(device, is_Q)
if apply:
_pick_menu_item(device, is_Q, "APPLY")
# EOF