@@ -104,6 +104,12 @@ def global_id(self) -> int:
104
104
return id (self ._global_marker )
105
105
106
106
107
+ class HitCountEntry (NamedTuple ):
108
+ source : str
109
+ line : int
110
+ type : str
111
+
112
+
107
113
class Debugger :
108
114
__instance = None
109
115
__lock = threading .RLock ()
@@ -147,6 +153,7 @@ def __init__(self) -> None:
147
153
self .output_messages : bool = False
148
154
self .output_log : bool = False
149
155
self .group_output : bool = False
156
+ self .hit_counts : Dict [HitCountEntry , int ] = {}
150
157
151
158
@property
152
159
def robot_report_file (self ) -> Optional [str ]:
@@ -294,7 +301,7 @@ def set_breakpoints(
294
301
295
302
return []
296
303
297
- def process_state (self , source : Optional [ str ] , line_no : Optional [ int ] , type : Optional [ str ] ) -> None :
304
+ def process_state (self , source : str , line_no : int , type : str ) -> None :
298
305
from robot .running .context import EXECUTION_CONTEXTS
299
306
from robot .variables .evaluation import evaluate_expression
300
307
@@ -359,6 +366,7 @@ def process_state(self, source: Optional[str], line_no: Optional[int], type: Opt
359
366
if len (breakpoints ) > 0 :
360
367
for point in breakpoints :
361
368
if point .condition is not None :
369
+ hit = False
362
370
try :
363
371
vars = EXECUTION_CONTEXTS .current .variables .current
364
372
hit = bool (evaluate_expression (vars .replace_string (point .condition ), vars .store ))
@@ -367,18 +375,48 @@ def process_state(self, source: Optional[str], line_no: Optional[int], type: Opt
367
375
368
376
if not hit :
369
377
return
370
-
371
- self .state = State .Paused
372
- self .send_event (
373
- self ,
374
- StoppedEvent (
375
- body = StoppedEventBody (
376
- reason = StoppedReason .BREAKPOINT ,
377
- thread_id = threading .current_thread ().ident ,
378
- hit_breakpoint_ids = [id (v ) for v in breakpoints ],
378
+ if point .hit_condition is not None :
379
+ hit = False
380
+ entry = HitCountEntry (source , line_no , type )
381
+ if entry not in self .hit_counts :
382
+ self .hit_counts [entry ] = 0
383
+ self .hit_counts [entry ] += 1
384
+ try :
385
+ hit = self .hit_counts [entry ] != int (point .hit_condition )
386
+ except BaseException :
387
+ hit = False
388
+ if not hit :
389
+ return
390
+ if point .log_message :
391
+ vars = EXECUTION_CONTEXTS .current .variables .current
392
+ try :
393
+ message = vars .replace_string (point .log_message )
394
+ except BaseException as e :
395
+ message = f"{ point .log_message } \n Error: { e } "
396
+ self .send_event (
397
+ self ,
398
+ OutputEvent (
399
+ body = OutputEventBody (
400
+ output = message ,
401
+ category = OutputCategory .CONSOLE ,
402
+ source = Source (path = source ) if source else None ,
403
+ line = line_no ,
404
+ )
405
+ ),
406
+ )
407
+ return
408
+ else :
409
+ self .state = State .Paused
410
+ self .send_event (
411
+ self ,
412
+ StoppedEvent (
413
+ body = StoppedEventBody (
414
+ reason = StoppedReason .BREAKPOINT ,
415
+ thread_id = threading .current_thread ().ident ,
416
+ hit_breakpoint_ids = [id (v ) for v in breakpoints ],
417
+ )
418
+ ),
379
419
)
380
- ),
381
- )
382
420
383
421
@_logger .call
384
422
def wait_for_running (self ) -> None :
@@ -398,7 +436,7 @@ def start_output_group(self, name: str, attributes: Dict[str, Any], type: Option
398
436
# category=OutputCategory.CONSOLE,
399
437
category = "log" ,
400
438
group = OutputGroup .STARTCOLLAPSED ,
401
- source = Source (name = name , path = source ) if source else None ,
439
+ source = Source (path = source ) if source else None ,
402
440
line = line_no ,
403
441
)
404
442
),
@@ -417,7 +455,7 @@ def end_output_group(self, name: str, attributes: Dict[str, Any]) -> None:
417
455
# category=OutputCategory.CONSOLE,
418
456
category = "log" ,
419
457
group = OutputGroup .END ,
420
- source = Source (name = name , path = source ) if source else None ,
458
+ source = Source (path = source ) if source else None ,
421
459
line = line_no ,
422
460
)
423
461
),
@@ -459,9 +497,10 @@ def start_suite(self, name: str, attributes: Dict[str, Any]) -> None:
459
497
460
498
entry = self .add_stackframe_entry (longname , type , source , line_no )
461
499
462
- self .process_state (entry .source , entry .line , entry .type )
500
+ if entry .source :
501
+ self .process_state (entry .source , entry .line , entry .type )
463
502
464
- self .wait_for_running ()
503
+ self .wait_for_running ()
465
504
466
505
def end_suite (self , name : str , attributes : Dict [str , Any ]) -> None :
467
506
if self .stack_frames :
@@ -475,9 +514,10 @@ def start_test(self, name: str, attributes: Dict[str, Any]) -> None:
475
514
476
515
entry = self .add_stackframe_entry (longname , type , source , line_no )
477
516
478
- self .process_state (entry .source , entry .line , entry .type )
517
+ if entry .source :
518
+ self .process_state (entry .source , entry .line , entry .type )
479
519
480
- self .wait_for_running ()
520
+ self .wait_for_running ()
481
521
482
522
def end_test (self , name : str , attributes : Dict [str , Any ]) -> None :
483
523
if self .stack_frames :
@@ -496,9 +536,10 @@ def start_keyword(self, name: str, attributes: Dict[str, Any]) -> None:
496
536
497
537
entry = self .add_stackframe_entry (kwname , type , source , line_no )
498
538
499
- self .process_state (entry .source , entry .line , entry .type )
539
+ if entry .source :
540
+ self .process_state (entry .source , entry .line , entry .type )
500
541
501
- self .wait_for_running ()
542
+ self .wait_for_running ()
502
543
503
544
def end_keyword (self , name : str , attributes : Dict [str , Any ]) -> None :
504
545
status = attributes .get ("status" , "" )
0 commit comments