Skip to content

Commit 043842c

Browse files
committed
feat(debugger): debugger does not stop on errors caught in TRY/EXCEPT blocks
To stop on these errors you have to switch on the exception breakpoint "Failed Keywords". Closes #45
1 parent b18856f commit 043842c

File tree

1 file changed

+80
-52
lines changed

1 file changed

+80
-52
lines changed

packages/debugger/src/robotcode/debugger/debugger.py

Lines changed: 80 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import pathlib
77
import re
88
import threading
9+
import time
910
import weakref
1011
from collections import deque
1112
from enum import Enum
@@ -254,7 +255,7 @@ def __init__(self) -> None:
254255
self.full_stack_frames: Deque[StackFrameEntry] = deque()
255256
self.stack_frames: Deque[StackFrameEntry] = deque()
256257
self.condition = threading.Condition()
257-
self.state: State = State.Stopped
258+
self._state: State = State.Stopped
258259
self.requested_state: RequestedState = RequestedState.Nothing
259260
self.stop_stack_len = 0
260261
self._robot_report_file: Optional[str] = None
@@ -282,6 +283,18 @@ def __init__(self) -> None:
282283
self._after_evaluate_keyword_event.set()
283284
self.expression_mode = False
284285

286+
@property
287+
def state(self) -> State:
288+
return self._state
289+
290+
@state.setter
291+
def state(self, value: State) -> None:
292+
if self._state == value:
293+
# if state is not changed, do nothing and wait a little bit to avoid busy loop
294+
time.sleep(0.01)
295+
296+
self._state = value
297+
285298
@property
286299
def debug(self) -> bool:
287300
return self._debug
@@ -476,8 +489,8 @@ def process_start_state(self, source: str, line_no: int, type: str, status: str)
476489

477490
elif self.requested_state == RequestedState.Next:
478491
if len(self.full_stack_frames) <= self.stop_stack_len:
479-
self.state = State.Paused
480492
self.requested_state = RequestedState.Nothing
493+
self.state = State.Paused
481494

482495
self.send_event(
483496
self,
@@ -490,8 +503,8 @@ def process_start_state(self, source: str, line_no: int, type: str, status: str)
490503
)
491504

492505
elif self.requested_state == RequestedState.StepIn:
493-
self.state = State.Paused
494506
self.requested_state = RequestedState.Nothing
507+
self.state = State.Paused
495508

496509
self.send_event(
497510
self,
@@ -504,8 +517,8 @@ def process_start_state(self, source: str, line_no: int, type: str, status: str)
504517
)
505518

506519
elif self.requested_state == RequestedState.StepOut and len(self.full_stack_frames) <= self.stop_stack_len:
507-
self.state = State.Paused
508520
self.requested_state = RequestedState.Nothing
521+
self.state = State.Paused
509522

510523
self.send_event(
511524
self,
@@ -570,8 +583,8 @@ def process_start_state(self, source: str, line_no: int, type: str, status: str)
570583
)
571584
return
572585

573-
self.state = State.Paused
574586
self.requested_state = RequestedState.Nothing
587+
self.state = State.Paused
575588

576589
self.send_event(
577590
self,
@@ -588,49 +601,6 @@ def process_end_state(self, status: str, filter_id: Set[str], description: str,
588601
if self.state == State.Stopped:
589602
return
590603

591-
if self.requested_state == RequestedState.Next:
592-
if len(self.full_stack_frames) <= self.stop_stack_len:
593-
self.state = State.Paused
594-
self.requested_state = RequestedState.Nothing
595-
596-
self.send_event(
597-
self,
598-
StoppedEvent(
599-
body=StoppedEventBody(
600-
reason=StoppedReason.STEP,
601-
thread_id=threading.current_thread().ident,
602-
)
603-
),
604-
)
605-
606-
elif self.requested_state == RequestedState.StepIn:
607-
self.state = State.Paused
608-
self.requested_state = RequestedState.Nothing
609-
610-
self.send_event(
611-
self,
612-
StoppedEvent(
613-
body=StoppedEventBody(
614-
reason=StoppedReason.STEP,
615-
thread_id=threading.current_thread().ident,
616-
)
617-
),
618-
)
619-
620-
elif self.requested_state == RequestedState.StepOut and len(self.full_stack_frames) <= self.stop_stack_len:
621-
self.state = State.Paused
622-
self.requested_state = RequestedState.Nothing
623-
624-
self.send_event(
625-
self,
626-
StoppedEvent(
627-
body=StoppedEventBody(
628-
reason=StoppedReason.STEP,
629-
thread_id=threading.current_thread().ident,
630-
)
631-
),
632-
)
633-
634604
if (
635605
not self.terminated
636606
and status == "FAIL"
@@ -642,8 +612,8 @@ def process_end_state(self, status: str, filter_id: Set[str], description: str,
642612
):
643613
reason = StoppedReason.EXCEPTION
644614

645-
self.state = State.Paused
646615
self.requested_state = RequestedState.Nothing
616+
self.state = State.Paused
647617

648618
self.send_event(
649619
self,
@@ -683,6 +653,7 @@ def wait_for_running(self) -> None:
683653
continue
684654

685655
if self.requested_state == RequestedState.Running:
656+
self.requested_state = RequestedState.Nothing
686657
self.state = State.Running
687658
if self.main_thread is not None and self.main_thread.ident is not None:
688659
self.send_event(
@@ -691,7 +662,6 @@ def wait_for_running(self) -> None:
691662
body=ContinuedEventBody(thread_id=self.main_thread.ident, all_threads_continued=True)
692663
),
693664
)
694-
self.requested_state = RequestedState.Nothing
695665
continue
696666

697667
break
@@ -938,7 +908,7 @@ def start_keyword(self, name: str, attributes: Dict[str, Any]) -> None:
938908
"BuiltIn.Run Keyword And Continue On Failure",
939909
]
940910

941-
def in_caughted_keyword(self) -> bool:
911+
def is_not_caughted_by_keyword(self) -> bool:
942912
r = next(
943913
(
944914
v
@@ -949,6 +919,56 @@ def in_caughted_keyword(self) -> bool:
949919
)
950920
return r is None
951921

922+
__matchers: Optional[Dict[str, Callable[[str, str], bool]]] = None
923+
924+
def _get_matcher(self, pattern_type: str) -> Optional[Callable[[str, str], bool]]:
925+
from robot.utils import Matcher
926+
927+
if self.__matchers is None:
928+
self.__matchers: Dict[str, Callable[[str, str], bool]] = {
929+
"GLOB": lambda m, p: bool(Matcher(p, spaceless=False, caseless=False).match(m)),
930+
"LITERAL": lambda m, p: m == p,
931+
"REGEXP": lambda m, p: re.match(rf"{p}\Z", m) is not None,
932+
"START": lambda m, p: m.startswith(p),
933+
}
934+
935+
return self.__matchers.get(pattern_type.upper(), None)
936+
937+
def _should_run_except(self, branch: Any, error: str) -> bool:
938+
if not branch.patterns:
939+
return True
940+
941+
if branch.pattern_type:
942+
pattern_type = EXECUTION_CONTEXTS.current.variables.replace_string(branch.pattern_type)
943+
else:
944+
pattern_type = "LITERAL"
945+
946+
matcher = self._get_matcher(pattern_type)
947+
948+
if not matcher:
949+
return False
950+
951+
for pattern in branch.patterns:
952+
if matcher(error, EXECUTION_CONTEXTS.current.variables.replace_string(pattern)):
953+
return True
954+
955+
return False
956+
957+
def is_not_caugthed_by_except(self, message: Optional[str]) -> bool:
958+
if get_robot_version() >= (5, 0):
959+
from robot.running.model import Try
960+
961+
if not message:
962+
return True
963+
964+
if EXECUTION_CONTEXTS.current.steps:
965+
for branch in [f.data for f in reversed(EXECUTION_CONTEXTS.current.steps) if isinstance(f.data, Try)]:
966+
for except_branch in branch.except_branches:
967+
if self._should_run_except(except_branch, message):
968+
return False
969+
970+
return True
971+
952972
def end_keyword(self, name: str, attributes: Dict[str, Any]) -> None:
953973
type = attributes.get("type", None)
954974
if self.debug:
@@ -957,7 +977,15 @@ def end_keyword(self, name: str, attributes: Dict[str, Any]) -> None:
957977
if status == "FAIL" and type in ["KEYWORD", "SETUP", "TEARDOWN"]:
958978
self.process_end_state(
959979
status,
960-
{"failed_keyword", *({"uncaught_failed_keyword"} if self.in_caughted_keyword() else {})},
980+
{
981+
"failed_keyword",
982+
*(
983+
{"uncaught_failed_keyword"}
984+
if self.is_not_caughted_by_keyword()
985+
and self.is_not_caugthed_by_except(self.last_fail_message)
986+
else {}
987+
),
988+
},
961989
"Keyword failed.",
962990
f"Keyword failed: {self.last_fail_message}" if self.last_fail_message else "Keyword failed.",
963991
)

0 commit comments

Comments
 (0)