Skip to content

Commit 116377b

Browse files
committed
test(core): don't fetch DebugLinkState by default
In case the main workflow is restarting after a `DebugLinkDecision`, the next `DebugLinkGetState` handling becomes inherently racy. I would suggest making the state fetching explicit, in order to avoid the "restart" race condition. [no changelog]
1 parent 9d1d062 commit 116377b

13 files changed

+110
-87
lines changed

python/src/trezorlib/debuglink.py

+33-12
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ def __call__(
6565
self,
6666
hold_ms: int | None = None,
6767
wait: bool | None = None,
68-
) -> "LayoutContent": ...
68+
) -> "LayoutContent | None": ...
6969

7070
InputFlowType = Generator[None, messages.ButtonRequest, None]
7171

@@ -417,10 +417,11 @@ def input_func(
417417
self: "DebugLink",
418418
hold_ms: int | None = None,
419419
wait: bool | None = None,
420-
) -> LayoutContent:
420+
return_layout: bool = False,
421+
) -> LayoutContent | None:
421422
__tracebackhide__ = True # for pytest # pylint: disable=W0612
422423
decision.hold_ms = hold_ms
423-
return self._decision(decision, wait=wait)
424+
return self._decision(decision, wait=wait, return_layout=return_layout)
424425

425426
return input_func # type: ignore [Parameter name mismatch]
426427

@@ -635,9 +636,13 @@ def read_reset_word(self) -> str:
635636
return state.reset_word
636637

637638
def _decision(
638-
self, decision: messages.DebugLinkDecision, wait: bool | None = None
639-
) -> LayoutContent:
640-
"""Send a debuglink decision and returns the resulting layout.
639+
self,
640+
decision: messages.DebugLinkDecision,
641+
*,
642+
wait: bool | None = None,
643+
return_layout: bool = False,
644+
) -> LayoutContent | None:
645+
"""Send a debuglink decision and optionally returns the resulting layout.
641646
642647
If hold_ms is set, an additional 200ms is added to account for processing
643648
delays. (This is needed for hold-to-confirm to trigger reliably.)
@@ -657,8 +662,12 @@ def _decision(
657662
setting `wait=False` -- useful when, e.g., you are causing the next layout to be
658663
deliberately delayed.
659664
"""
665+
if wait is not None:
666+
return_layout = True
667+
660668
if not self.allow_interactions:
661-
return self.wait_layout()
669+
layout = self.wait_layout()
670+
return layout if return_layout else None
662671

663672
if decision.hold_ms is not None:
664673
decision.hold_ms += 200
@@ -671,7 +680,9 @@ def _decision(
671680
wait_type = DebugWaitType.IMMEDIATE
672681
else:
673682
wait_type = self.input_wait_type
674-
return self._snapshot_core(wait_type)
683+
684+
if return_layout:
685+
return self._snapshot_core(wait_type)
675686

676687
press_yes = _make_input_func(button=messages.DebugButton.YES)
677688
"""Confirm current layout. See `_decision` for more details."""
@@ -698,20 +709,30 @@ def _decision(
698709
)
699710
"""Press right button. See `_decision` for more details."""
700711

701-
def input(self, word: str, wait: bool | None = None) -> LayoutContent:
712+
def input(
713+
self, word: str, *, wait: bool | None = None, return_layout: bool = False
714+
) -> LayoutContent | None:
702715
"""Send text input to the device. See `_decision` for more details."""
703-
return self._decision(messages.DebugLinkDecision(input=word), wait)
716+
return self._decision(
717+
messages.DebugLinkDecision(input=word),
718+
wait=wait,
719+
return_layout=return_layout,
720+
)
704721

705722
def click(
706723
self,
707724
click: Tuple[int, int],
725+
*,
708726
hold_ms: int | None = None,
709727
wait: bool | None = None,
710-
) -> LayoutContent:
728+
return_layout: bool = False,
729+
) -> LayoutContent | None:
711730
"""Send a click to the device. See `_decision` for more details."""
712731
x, y = click
713732
return self._decision(
714-
messages.DebugLinkDecision(x=x, y=y, hold_ms=hold_ms), wait
733+
messages.DebugLinkDecision(x=x, y=y, hold_ms=hold_ms),
734+
wait=wait,
735+
return_layout=return_layout,
715736
)
716737

717738
def _snapshot_core(

tests/click_tests/common.py

+12-12
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,11 @@ def get_char_category(char: str) -> PassphraseCategory:
4949

5050
def go_next(debug: "DebugLink") -> LayoutContent:
5151
if debug.layout_type is LayoutType.Bolt:
52-
return debug.click(buttons.OK)
52+
return debug.click(buttons.OK, return_layout=True)
5353
elif debug.layout_type is LayoutType.Caesar:
54-
return debug.press_right()
54+
return debug.press_right(return_layout=True)
5555
elif debug.layout_type is LayoutType.Delizia:
56-
return debug.swipe_up()
56+
return debug.swipe_up(return_layout=True)
5757
else:
5858
raise RuntimeError("Unknown model")
5959

@@ -64,19 +64,19 @@ def tap_to_confirm(debug: "DebugLink") -> LayoutContent:
6464
elif debug.layout_type is LayoutType.Caesar:
6565
return debug.read_layout()
6666
elif debug.layout_type is LayoutType.Delizia:
67-
return debug.click(buttons.TAP_TO_CONFIRM)
67+
return debug.click(buttons.TAP_TO_CONFIRM, return_layout=True)
6868
else:
6969
raise RuntimeError("Unknown model")
7070

7171

7272
def go_back(debug: "DebugLink", r_middle: bool = False) -> LayoutContent:
7373
if debug.layout_type in (LayoutType.Bolt, LayoutType.Delizia):
74-
return debug.click(buttons.CANCEL)
74+
return debug.click(buttons.CANCEL, return_layout=True)
7575
elif debug.layout_type is LayoutType.Caesar:
7676
if r_middle:
77-
return debug.press_middle()
77+
return debug.press_middle(return_layout=True)
7878
else:
79-
return debug.press_left()
79+
return debug.press_left(return_layout=True)
8080
else:
8181
raise RuntimeError("Unknown model")
8282

@@ -108,10 +108,10 @@ def navigate_to_action_and_press(
108108

109109
if steps < 0:
110110
for _ in range(-steps):
111-
layout = debug.press_left()
111+
debug.press_left()
112112
else:
113113
for _ in range(steps):
114-
layout = debug.press_right()
114+
debug.press_right()
115115

116116
# Press or hold
117117
debug.press_middle(hold_ms=hold_ms)
@@ -125,11 +125,11 @@ def _carousel_steps(current_index: int, wanted_index: int, length: int) -> int:
125125

126126
def unlock_gesture(debug: "DebugLink") -> LayoutContent:
127127
if debug.layout_type is LayoutType.Bolt:
128-
return debug.click(buttons.OK)
128+
return debug.click(buttons.OK, return_layout=True)
129129
elif debug.layout_type is LayoutType.Caesar:
130-
return debug.press_right()
130+
return debug.press_right(return_layout=True)
131131
elif debug.layout_type is LayoutType.Delizia:
132-
return debug.click(buttons.TAP_TO_CONFIRM)
132+
return debug.click(buttons.TAP_TO_CONFIRM, return_layout=True)
133133
else:
134134
raise RuntimeError("Unknown model")
135135

tests/click_tests/recovery.py

+20-20
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ def enter_word(
2323
if debug.layout_type is LayoutType.Delizia and not is_slip39 and len(word) > 4:
2424
# T3T1 (delizia) BIP39 keyboard allows to "confirm" only if the word is fully written, you need to click the word to auto-complete
2525
debug.click(buttons.CONFIRM_WORD)
26-
return debug.click(buttons.CONFIRM_WORD)
26+
return debug.click(buttons.CONFIRM_WORD, return_layout=True)
2727
elif debug.layout_type is LayoutType.Caesar:
2828
letter_index = 0
2929
layout = debug.read_layout()
@@ -32,16 +32,16 @@ def enter_word(
3232
while layout.find_values_by_key("letter_choices"):
3333
letter = word[letter_index]
3434
while not layout.get_middle_choice() == letter:
35-
layout = debug.press_right()
35+
layout = debug.press_right(return_layout=True)
3636

37-
layout = debug.press_middle()
37+
layout = debug.press_middle(return_layout=True)
3838
letter_index += 1
3939

4040
# Word choices
4141
while not layout.get_middle_choice() == word:
42-
layout = debug.press_right()
42+
layout = debug.press_right(return_layout=True)
4343

44-
return debug.press_middle()
44+
return debug.press_middle(return_layout=True)
4545
else:
4646
raise ValueError("Unknown model")
4747

@@ -78,15 +78,15 @@ def select_bolt() -> "LayoutContent":
7878
coords = coords_map.get(num_of_words)
7979
if coords is None:
8080
raise ValueError("Invalid num_of_words")
81-
return debug.click(coords)
81+
return debug.click(coords, return_layout=True)
8282

8383
def select_caesar() -> "LayoutContent":
8484
# navigate to the number and confirm it
8585
word_options = (20, 33) if unlock_repeated_backup else (12, 18, 20, 24, 33)
8686
index = word_options.index(num_of_words)
8787
for _ in range(index):
8888
debug.press_right()
89-
return debug.press_middle()
89+
return debug.press_middle(return_layout=True)
9090

9191
def select_delizia() -> "LayoutContent":
9292
# click the button from ValuePad
@@ -103,17 +103,17 @@ def select_delizia() -> "LayoutContent":
103103
coords = coords_map.get(num_of_words)
104104
if coords is None:
105105
raise ValueError("Invalid num_of_words")
106-
return debug.click(coords)
106+
return debug.click(coords, return_layout=True)
107107

108108
if debug.layout_type is LayoutType.Bolt:
109109
assert debug.read_layout().text_content() == TR.recovery__num_of_words
110-
layout = select_bolt()
110+
layout = select_bolt(return_layout=True)
111111
elif debug.layout_type is LayoutType.Caesar:
112-
layout = debug.press_right()
112+
layout = debug.press_right(return_layout=True)
113113
assert layout.title() == TR.word_count__title
114-
layout = select_caesar()
114+
layout = select_caesar(return_layout=True)
115115
elif debug.layout_type is LayoutType.Delizia:
116-
layout = select_delizia()
116+
layout = select_delizia(return_layout=True)
117117
else:
118118
raise ValueError("Unknown model")
119119

@@ -150,12 +150,12 @@ def enter_share(
150150
assert TR.translate(before_title) in debug.read_layout().title()
151151
layout = debug.read_layout()
152152
for _ in range(layout.page_count()):
153-
layout = debug.press_right()
153+
layout = debug.press_right(return_layout=True)
154154
elif debug.layout_type is LayoutType.Delizia:
155-
layout = debug.swipe_up()
155+
layout = debug.swipe_up(return_layout=True)
156156
else:
157157
assert TR.translate(before_title) in debug.read_layout().title()
158-
layout = debug.click(buttons.OK)
158+
layout = debug.click(buttons.OK, return_layout=True)
159159

160160
assert "MnemonicKeyboard" in layout.all_components()
161161

@@ -236,13 +236,13 @@ def enter_seed_previous_correct(
236236
layout = debug.read_layout()
237237

238238
while layout.get_middle_choice() not in DELETE_BTNS:
239-
layout = debug.press_right()
240-
layout = debug.press_middle()
239+
layout = debug.press_right(return_layout=True)
240+
layout = debug.press_middle(return_layout=True)
241241

242242
for _ in range(len(bad_word)):
243243
while layout.get_middle_choice() not in DELETE_BTNS:
244-
layout = debug.press_left()
245-
layout = debug.press_middle()
244+
layout = debug.press_left(return_layout=True)
245+
layout = debug.press_middle(return_layout=True)
246246
elif debug.layout_type is LayoutType.Delizia:
247247
debug.click(buttons.RECOVERY_DELETE) # Top-left
248248
for _ in range(len(bad_word)):
@@ -278,7 +278,7 @@ def prepare_enter_seed(
278278
elif debug.layout_type is LayoutType.Caesar:
279279
debug.press_right()
280280
debug.press_right()
281-
layout = debug.press_right()
281+
layout = debug.press_right(return_layout=True)
282282
assert "MnemonicKeyboard" in layout.all_components()
283283

284284

tests/click_tests/reset.py

+10-8
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ def set_selection(debug: "DebugLink", button: tuple[int, int], diff: int) -> Non
7979
in TR.reset__title_number_of_shares + TR.words__title_threshold
8080
):
8181
# Special info screens
82-
layout = debug.press_right()
82+
layout = debug.press_right(return_layout=True)
8383
assert "NumberInput" in layout.all_components()
8484
if button == buttons.reset_minus(debug.model.internal_name):
8585
for _ in range(diff):
@@ -102,7 +102,7 @@ def read_words(debug: "DebugLink", do_htc: bool = True) -> list[str]:
102102
layout = debug.read_layout()
103103
for _ in range(layout.page_count() - 1):
104104
words.extend(layout.seed_words())
105-
layout = debug.swipe_up()
105+
layout = debug.swipe_up(return_layout=True)
106106
assert layout is not None
107107
if debug.layout_type in (LayoutType.Bolt, LayoutType.Delizia):
108108
words.extend(layout.seed_words())
@@ -130,7 +130,7 @@ def confirm_words(debug: "DebugLink", words: list[str]) -> None:
130130
if debug.layout_type is LayoutType.Delizia:
131131
debug.swipe_up()
132132

133-
layout = debug.read_layout()
133+
layout = debug.read_layout(return_layout=True)
134134
if debug.layout_type is LayoutType.Bolt:
135135
assert TR.regexp("reset__select_word_x_of_y_template").match(
136136
layout.text_content()
@@ -147,7 +147,9 @@ def confirm_words(debug: "DebugLink", words: list[str]) -> None:
147147
]
148148
wanted_word = words[word_pos - 1].lower()
149149
button_pos = btn_texts.index(wanted_word)
150-
layout = debug.click(buttons.RESET_WORD_CHECK[button_pos])
150+
layout = debug.click(
151+
buttons.RESET_WORD_CHECK[button_pos], return_layout=True
152+
)
151153
elif debug.layout_type is LayoutType.Delizia:
152154
assert TR.regexp("reset__select_word_x_of_y_template").match(layout.subtitle())
153155
for _ in range(3):
@@ -162,10 +164,10 @@ def confirm_words(debug: "DebugLink", words: list[str]) -> None:
162164
]
163165
wanted_word = words[word_pos - 1].lower()
164166
button_pos = btn_texts.index(wanted_word)
165-
layout = debug.click(buttons.VERTICAL_MENU[button_pos])
167+
layout = debug.click(buttons.VERTICAL_MENU[button_pos], return_layout=True)
166168
elif debug.layout_type is LayoutType.Caesar:
167169
assert TR.reset__select_correct_word in layout.text_content()
168-
layout = debug.press_right()
170+
layout = debug.press_right(return_layout=True)
169171
for _ in range(3):
170172
# "SELECT 2ND WORD"
171173
# ^
@@ -176,9 +178,9 @@ def confirm_words(debug: "DebugLink", words: list[str]) -> None:
176178
wanted_word = words[word_pos - 1].lower()
177179

178180
while not layout.get_middle_choice() == wanted_word:
179-
layout = debug.press_right()
181+
layout = debug.press_right(return_layout=True)
180182

181-
layout = debug.press_middle()
183+
layout = debug.press_middle(return_layout=True)
182184

183185

184186
def validate_mnemonics(mnemonics: list[str], expected_ems: bytes) -> None:

0 commit comments

Comments
 (0)