headless.py adjustments

This commit is contained in:
scgbckbone 2024-03-01 18:59:35 +01:00 committed by doc-hex
parent 33f2bb0d79
commit 9799dd2455
6 changed files with 58 additions and 37 deletions

View File

@ -43,6 +43,8 @@ def pytest_addoption(parser):
default=False, help="fake_txn produces PSBTv2")
parser.addoption("--Q", action="store_true", default=False,
help="Uses Q simulator when running 'login_settings_tests' module")
parser.addoption("--headless", action="store_true", default=False,
help="Simulator is running in headless mode")
# to make bitcoind produce psbt v2 one currently needs https://github.com/achow101/bitcoin/tree/psbt2
# or wait until https://github.com/bitcoin/bitcoin/pull/21283 merged and released
@ -432,7 +434,7 @@ def cap_story(dev):
@pytest.fixture(scope='module')
def cap_image(sim_exec, is_q1):
def cap_image(request, sim_exec, is_q1):
def flip(raw):
reorg = bytearray(128*64)
@ -449,11 +451,13 @@ def cap_image(sim_exec, is_q1):
from PIL import Image
if is_q1:
if request.config.getoption('--headless'):
raise pytest.skip("headless mode")
# trigger simulator to capture a snapshot into a named file, read it.
fn = os.path.realpath(f'./debug/snap-{random.randint(1E6, 9E6)}.png')
try:
sim_exec(f"from glob import dis; dis.dis.save_snapshot({fn!r})")
while 1:
for _ in range(20):
time.sleep(0.010)
try:
rv = Image.open(fn)

View File

@ -18,6 +18,7 @@ python run_sim_tests.py -m all --onetime --veryslow # run all tests (
python run_sim_tests.py -m test_multisig.py -k cosigning # run only tests that match expression from test_multisig.py
python run_sim_tests.py -m test_export.py --pdb # run only export tests and attach debugger
python run_sim_tests.py -m test_attended.py --q1 -w 6 --login # run attended test + all login tests
python run_sim_tests.py -w 6 --q1 --headless # run in headless mode (skips QR code checks)
Onetime/veryslow tests are completely separated form the rest of the test suite.
@ -98,7 +99,7 @@ def is_ok(ec: ExitCode) -> bool:
def _run_pytest_tests(test_module: str, pytest_marks: str, pytest_k: str, pdb: bool,
failed_first: bool, psbt2=False, is_Q=False) -> ExitCode:
failed_first: bool, psbt2=False, is_Q=False, headless=False) -> ExitCode:
cmd_list = [
"--cache-clear", "-m", pytest_marks, "--sim",
test_module if test_module is not None else ""
@ -113,19 +114,21 @@ def _run_pytest_tests(test_module: str, pytest_marks: str, pytest_k: str, pdb: b
cmd_list.append("--psbt2")
if is_Q:
cmd_list.insert(0, "--Q") # only changes behavior in login_settings_test
if headless:
cmd_list.append("--headless")
return pytest.main(cmd_list)
def _run_coldcard_tests(test_module: str, simulator_args: List[str], pytest_marks: str,
pytest_k: str, pdb: bool, failed_first: bool, psbt2=False,
is_Q=False) -> ExitCode:
is_Q=False, headless=False) -> ExitCode:
if simulator_args:
sim = ColdcardSimulator(args=simulator_args)
sim = ColdcardSimulator(args=simulator_args, headless=headless)
sim.start()
time.sleep(1)
exit_code = _run_pytest_tests(test_module, pytest_marks, pytest_k, pdb,
failed_first, psbt2, is_Q)
failed_first, psbt2, is_Q, headless)
if simulator_args:
sim.stop()
@ -135,11 +138,11 @@ def _run_coldcard_tests(test_module: str, simulator_args: List[str], pytest_mark
def run_coldcard_tests(test_module=None, simulator_args=None, pytest_k=None, pdb=False,
failed_first=False, psbt2=False, is_Q=False,
pytest_marks="not onetime and not veryslow and not manual"):
failed_first=False, psbt2=False, is_Q=False, headless=False,
pytest_marks="not onetime and not veryslow and not manual"):
failed = []
exit_code = _run_coldcard_tests(test_module, simulator_args, pytest_marks, pytest_k,
pdb, failed_first, psbt2, is_Q)
pdb, failed_first, psbt2, is_Q, headless)
if not is_ok(exit_code):
# no success, no nothing - give failed another try, each alone with its own simulator
last_failed = get_last_failed()
@ -147,7 +150,8 @@ def run_coldcard_tests(test_module=None, simulator_args=None, pytest_k=None, pdb
exit_codes = []
for failed_test in last_failed:
exit_code_2 = _run_coldcard_tests(failed_test, simulator_args, pytest_marks,
pytest_k, pdb, failed_first, psbt2, is_Q)
pytest_k, pdb, failed_first, psbt2, is_Q,
headless)
exit_codes.append(exit_code_2)
if not is_ok(exit_code_2):
failed.append(failed_test)
@ -169,16 +173,17 @@ class PytestCollectMarked:
class ColdcardSimulator:
def __init__(self, path=None, args=None):
def __init__(self, path=None, args=None, headless=False):
self.proc = None
self.args = args
self.path = "/tmp/ckcc-simulator.sock" if path is None else path
self.headless = headless
def start(self, start_wait=None):
# here we are in testing directory
cmd_list = [
"python",
"simulator.py"
"headless.py" if self.headless else "simulator.py"
]
if self.args is not None:
cmd_list.extend(self.args)
@ -223,6 +228,8 @@ def main():
help="Collect marked test and print them to stdout")
parser.add_argument("-k", "--pytest-k", type=str, metavar="EXPRESSION", default=None,
help="only run tests which match the given substring expression")
parser.add_argument("--headless", action="store_true", default=False,
help="run simulator instance in headless mode")
args = parser.parse_args()
if args.sim_init_wait:
@ -285,7 +292,8 @@ def main():
ec, failed_tests = run_coldcard_tests(test_module, simulator_args=test_args,
pytest_k=args.pytest_k, pdb=args.pdb,
failed_first=args.ff, psbt2=args.psbt2)
failed_first=args.ff, psbt2=args.psbt2,
headless=args.headless)
result.append((test_module, ec, failed_tests))
print("Done", test_module)
print(80 * "=")
@ -296,7 +304,8 @@ def main():
ec, failed_tests = run_coldcard_tests(test_module=None, pytest_marks="veryslow",
pytest_k=args.pytest_k, pdb=args.pdb,
simulator_args=DEFAULT_SIMULATOR_ARGS,
failed_first=args.ff, psbt2=args.psbt2)
failed_first=args.ff, psbt2=args.psbt2,
headless=args.headless)
result.append(("veryslow", ec, failed_tests))
# run onetime is specified (each test against its own simulator)
@ -307,20 +316,22 @@ def main():
ec, failed_tests = run_coldcard_tests(test_module=onetime_test, pdb=args.pdb,
failed_first=args.ff, pytest_marks="onetime",
simulator_args=DEFAULT_SIMULATOR_ARGS,
psbt2=args.psbt2)
psbt2=args.psbt2, headless=args.headless)
result.append((f"onetime: {onetime_test}", ec, failed_tests))
if args.login:
print("start login settings tests")
ec, failed_tests = run_coldcard_tests(test_module="login_settings_tests.py", pdb=args.pdb,
failed_first=args.ff, pytest_k=args.pytest_k,
is_Q=True if args.q1 else False)
is_Q=True if args.q1 else False,
headless=args.headless)
result.append((f"login_settings_tests", ec, failed_tests))
if args.clone:
print("start clone tests")
ec, failed_tests = run_coldcard_tests(test_module="clone_tests.py", pdb=args.pdb,
failed_first=args.ff, pytest_k=args.pytest_k)
failed_first=args.ff, pytest_k=args.pytest_k,
headless=args.headless)
result.append((f"clone_tests", ec, failed_tests))
print("All done")

View File

@ -13,7 +13,7 @@ from mnemonic import Mnemonic
def backup_system(settings_set, settings_remove, goto_home, pick_menu_item,
cap_story, need_keypress, cap_screen_qr, pass_word_quiz,
get_setting, seed_story_to_words, press_cancel, is_q1,
press_select):
press_select, request):
def doit(reuse_pw=False, save_pw=False, st=None, ct=False):
# st -> seed type
# ct -> cleartext backup
@ -73,7 +73,7 @@ def backup_system(settings_set, settings_remove, goto_home, pick_menu_item,
print("Passphrase: %s" % ' '.join(words))
if 'QR Code' in body:
if 'QR Code' in body and not request.config.getoption('--headless'):
need_keypress(KEY_QR if is_q1 else '1')
got_qr = cap_screen_qr().decode('ascii').lower().split()
assert [w[0:4] for w in words] == got_qr
@ -108,11 +108,11 @@ def backup_system(settings_set, settings_remove, goto_home, pick_menu_item,
def test_make_backup(multisig, goto_home, pick_menu_item, cap_story, need_keypress, st,
open_microsd, microsd_path, unit_test, cap_menu, word_menu_entry,
pass_word_quiz, reset_seed_words, import_ms_wallet, get_setting,
cap_screen_qr, reuse_pw, save_pw, settings_set, settings_remove,
reuse_pw, save_pw, settings_set, settings_remove, press_select,
generate_ephemeral_words, set_bip39_pw, verify_backup_file,
check_and_decrypt_backup, restore_backup_cs, clear_ms, seedvault,
restore_main_seed, import_ephemeral_xprv, backup_system,
press_cancel, press_select):
press_cancel):
# Make an encrypted 7z backup, verify it, and even restore it!
clear_ms()
reset_seed_words()

View File

@ -173,7 +173,8 @@ def pass_word_quiz(need_keypress, cap_story, press_select):
])
def test_import_seed(goto_home, pick_menu_item, cap_story, need_keypress, unit_test,
cap_menu, word_menu_entry, seed_words, xfp, get_secrets, is_q1,
reset_seed_words, cap_screen_qr, qr_quality_check, expect_ftux):
reset_seed_words, cap_screen_qr, qr_quality_check, expect_ftux,
request):
unit_test('devtest/clear_seed.py')
@ -198,9 +199,10 @@ def test_import_seed(goto_home, pick_menu_item, cap_story, need_keypress, unit_t
v = get_secrets()
assert f'Press {KEY_QR if is_q1 else "(3)"} to show QR code' in body
need_keypress(KEY_QR if is_q1 else '3')
qr = cap_screen_qr().decode('ascii')
assert qr == v['xpub']
if not request.config.getoption('--headless'):
need_keypress(KEY_QR if is_q1 else '3')
qr = cap_screen_qr().decode('ascii')
assert qr == v['xpub']
assert v['mnemonic'] == seed_words
reset_seed_words()
@ -259,7 +261,7 @@ def test_all_bip39_words(pos, goto_home, pick_menu_item, cap_story, unit_test,
def test_import_from_dice(count, nwords, goto_home, pick_menu_item, cap_story, need_keypress,
unit_test, cap_menu, word_menu_entry, get_secrets, reset_seed_words,
cap_screen, cap_screen_qr, qr_quality_check, expect_ftux, press_select,
press_cancel, is_q1, seed_story_to_words):
press_cancel, is_q1, seed_story_to_words, request):
import random
from hashlib import sha256
@ -308,11 +310,12 @@ def test_import_from_dice(count, nwords, goto_home, pick_menu_item, cap_story, n
else:
words = [i[4:4+4].upper() for i in re.findall(r'[ 0-9][0-9]: \w*', body)]
need_keypress(KEY_QR if is_q1 else '1')
if not request.config.getoption('--headless'):
need_keypress(KEY_QR if is_q1 else '1')
qr = cap_screen_qr()
assert qr.decode('ascii').split() == words
press_cancel() # close QR
qr = cap_screen_qr()
assert qr.decode('ascii').split() == words
press_cancel() # close QR
need_keypress('6')
time.sleep(0.1)
@ -620,7 +623,7 @@ def test_bip39_complex(target, goto_home, pick_menu_item, cap_story,
def test_show_seed(mode, b39_word, goto_home, pick_menu_item, cap_story, need_keypress,
sim_exec, cap_menu, get_secrets, cap_screen_qr, set_bip39_pw,
set_encoded_secret, qr_quality_check, reset_seed_words,
press_select, is_q1, seed_story_to_words):
press_select, is_q1, seed_story_to_words, request):
reset_seed_words()
if mode == 'words':
@ -694,9 +697,11 @@ def test_show_seed(mode, b39_word, goto_home, pick_menu_item, cap_story, need_ke
if not is_q1:
assert '(1) to view as QR Code' in body
need_keypress(KEY_QR if is_q1 else '1')
qr = cap_screen_qr().decode('ascii')
assert qr == qr_expect
if not request.config.getoption('--headless'):
need_keypress(KEY_QR if is_q1 else '1')
qr = cap_screen_qr().decode('ascii')
assert qr == qr_expect
press_select() # clear screen

View File

@ -42,7 +42,7 @@ def start():
'-X', 'heapsize=9m',
'-i', '../sim_boot.py',
str(oled_w), '-1', str(led_w)] \
+ sys.argv[1:]
+ sys.argv[1:] + ["--headless"]
args = dict(env=env, pass_fds=[oled_w, led_w], shell=False)

View File

@ -23,8 +23,9 @@ genuine_led = True
from sim_secel import SEState
SE_STATE = SEState()
# Provide a way to dump few hundred/4k bytes of data from QR or NFC simulated read
data_pipe = uasyncio.StreamReader(open(int(sys.argv[4]), 'rb'))
if not "--headless" in sys.argv:
# Provide a way to dump few hundred/4k bytes of data from QR or NFC simulated read
data_pipe = uasyncio.StreamReader(open(int(sys.argv[4]), 'rb'))
# HACK: reduce size of heap in Unix simulator to be more similar to
# actual hardware, so we can enjoy those out-of-memory errors too!