66import pathlib
77import re
88import threading
9+ import time
910import weakref
1011from collections import deque
1112from 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