@@ -212,7 +212,11 @@ def __new__(cls, *args: Any, **kwargs: Any) -> Any:
212212
213213 def __init__ (self ) -> None :
214214 self .breakpoints : Dict [str , BreakpointsEntry ] = {}
215+
215216 self .exception_breakpoints : Set [ExceptionBreakpointsEntry ] = set ()
217+ self .exception_breakpoints .add (
218+ ExceptionBreakpointsEntry ((), (ExceptionFilterOptions ("uncaughted_failed_keyword" ),), ())
219+ )
216220
217221 self .main_thread : Optional [threading .Thread ] = None
218222 self .full_stack_frames : Deque [StackFrameEntry ] = deque ()
@@ -528,14 +532,14 @@ def process_start_state(self, source: str, line_no: int, type: str, status: str)
528532 ),
529533 )
530534
531- def process_end_state (self , status : str , filter_id : str , description : str , text : Optional [str ]) -> None :
535+ def process_end_state (self , status : str , filter_id : Set [ str ] , description : str , text : Optional [str ]) -> None :
532536 if (
533537 not self .terminated
534538 and status == "FAIL"
535539 and any (
536540 v
537541 for v in self .exception_breakpoints
538- if v .filter_options is not None and any (o for o in v .filter_options if o .filter_id == filter_id )
542+ if v .filter_options and any (o for o in v .filter_options if o .filter_id in filter_id )
539543 )
540544 ):
541545 self .state = State .Paused
@@ -633,6 +637,11 @@ def add_stackframe_entry(
633637 longname = longname ,
634638 )
635639
640+ self .full_stack_frames .appendleft (result )
641+
642+ if type in ["KEYWORD" ] and source is None and line is None and column is None :
643+ return result
644+
636645 if type in ["SUITE" , "TEST" ]:
637646 self .stack_frames .appendleft (result )
638647 elif type in ["KEYWORD" , "SETUP" , "TEARDOWN" ] and isinstance (handler , UserKeywordHandler ):
@@ -644,8 +653,6 @@ def add_stackframe_entry(
644653 if self .stack_frames :
645654 self .stack_frames [0 ].stack_frames .appendleft (result )
646655
647- self .full_stack_frames .appendleft (result )
648-
649656 return result
650657
651658 def remove_stackframe_entry (
@@ -654,12 +661,17 @@ def remove_stackframe_entry(
654661 type : str ,
655662 source : Optional [str ],
656663 line : Optional [int ],
657- column : Optional [int ] = 1 ,
664+ column : Optional [int ] = None ,
658665 * ,
659666 handler : Any = None ,
660667 ) -> None :
661668 from robot .running .userkeyword import UserKeywordHandler
662669
670+ self .full_stack_frames .popleft ()
671+
672+ if type in ["KEYWORD" ] and source is None and line is None and column is None :
673+ return
674+
663675 if type in ["SUITE" , "TEST" ]:
664676 self .stack_frames .popleft ()
665677 elif type in ["KEYWORD" , "SETUP" , "TEARDOWN" ] and isinstance (handler , UserKeywordHandler ):
@@ -671,8 +683,6 @@ def remove_stackframe_entry(
671683 if self .stack_frames :
672684 self .stack_frames [0 ].stack_frames .popleft ()
673685
674- self .full_stack_frames .popleft ()
675-
676686 def start_suite (self , name : str , attributes : Dict [str , Any ]) -> None :
677687 source = attributes .get ("source" , None )
678688 line_no = attributes .get ("lineno" , 1 )
@@ -707,12 +717,13 @@ def end_suite(self, name: str, attributes: Dict[str, Any]) -> None:
707717 if self .debug :
708718 status = attributes .get ("status" , "" )
709719
710- self .process_end_state (
711- status ,
712- "failed_suite" ,
713- "Suite failed." ,
714- f"Suite failed{ f': { v } ' if (v := attributes .get ('message' , None )) else '' } " ,
715- )
720+ if status == "FAIL" :
721+ self .process_end_state (
722+ status ,
723+ {"failed_suite" },
724+ "Suite failed." ,
725+ f"Suite failed{ f': { v } ' if (v := attributes .get ('message' , None )) else '' } " ,
726+ )
716727
717728 source = attributes .get ("source" , None )
718729 line_no = attributes .get ("lineno" , 1 )
@@ -739,12 +750,13 @@ def end_test(self, name: str, attributes: Dict[str, Any]) -> None:
739750 if self .debug :
740751 status = attributes .get ("status" , "" )
741752
742- self .process_end_state (
743- status ,
744- "failed_test" ,
745- "Test failed." ,
746- f"Test failed{ f': { v } ' if (v := attributes .get ('message' , None )) else '' } " ,
747- )
753+ if status == "FAIL" :
754+ self .process_end_state (
755+ status ,
756+ {"failed_test" },
757+ "Test failed." ,
758+ f"Test failed{ f': { v } ' if (v := attributes .get ('message' , None )) else '' } " ,
759+ )
748760
749761 source = attributes .get ("source" , None )
750762 line_no = attributes .get ("lineno" , 1 )
@@ -784,17 +796,36 @@ def start_keyword(self, name: str, attributes: Dict[str, Any]) -> None:
784796
785797 self .wait_for_running ()
786798
799+ CAUGHTED_KEYWORDS = [
800+ "BuiltIn.Run Keyword And Expect Error" ,
801+ "BuiltIn.Run Keyword And Ignore Error" ,
802+ "BuiltIn.Run Keyword And Warn On Failure" ,
803+ "BuiltIn.Wait Until Keyword Succeeds" ,
804+ "BuiltIn.Run Keyword And Continue On Failure" ,
805+ ]
806+
807+ def in_caughted_keyword (self ) -> bool :
808+ r = next (
809+ (
810+ v
811+ for v in itertools .islice (self .full_stack_frames , 1 , None )
812+ if v .type == "KEYWORD" and v .longname in self .CAUGHTED_KEYWORDS
813+ ),
814+ None ,
815+ )
816+ return r is None
817+
787818 def end_keyword (self , name : str , attributes : Dict [str , Any ]) -> None :
788819 from robot .running .context import EXECUTION_CONTEXTS
789820
790821 type = attributes .get ("type" , None )
791822 if self .debug :
792823 status = attributes .get ("status" , "" )
793824
794- if status != "NOT RUN " and type in ["KEYWORD" , "SETUP" , "TEARDOWN" ]:
825+ if status == "FAIL " and type in ["KEYWORD" , "SETUP" , "TEARDOWN" ]:
795826 self .process_end_state (
796827 status ,
797- "failed_keyword" ,
828+ { "failed_keyword" , * ({ "uncaughted_failed_keyword" } if self . in_caughted_keyword () else {})} ,
798829 "Keyword failed." ,
799830 f"Keyword failed: { self .last_fail_message } " if self .last_fail_message else "Keyword failed." ,
800831 )
@@ -1149,7 +1180,7 @@ def set_exception_breakpoints(
11491180
11501181 if filter_options is not None :
11511182 for option in filter_options :
1152- if option .filter_id in ["failed_keyword" , "failed_test" , "failed_suite" ]:
1183+ if option .filter_id in ["failed_keyword" , "uncaughted_failed_keyword" , " failed_test" , "failed_suite" ]:
11531184 entry = ExceptionBreakpointsEntry (
11541185 tuple (filters ),
11551186 tuple (filter_options ) if filter_options is not None else None ,
0 commit comments