1
1
from __future__ import annotations
2
2
3
3
import itertools
4
+ import re
4
5
import threading
5
6
import weakref
6
7
from collections import deque
7
8
from enum import Enum
8
9
from pathlib import Path
9
- from typing import Any , Deque , Dict , List , Literal , NamedTuple , Optional
10
+ from typing import Any , Deque , Dict , List , Literal , NamedTuple , Optional , Union
10
11
11
12
from ...utils .event import event
12
13
from ...utils .logging import LoggingDescriptor
13
14
from ..types import (
14
15
Breakpoint ,
15
16
ContinuedEvent ,
16
17
ContinuedEventBody ,
18
+ EvaluateArgumentContext ,
17
19
Event ,
18
20
OutputCategory ,
19
21
OutputEvent ,
31
33
Thread ,
32
34
ValueFormat ,
33
35
Variable ,
36
+ VariablePresentationHint ,
34
37
)
35
38
36
39
40
+ class EvaluateResult (NamedTuple ):
41
+ result : str
42
+ type : Optional [str ] = None
43
+ presentation_hint : Optional [VariablePresentationHint ] = None
44
+ variables_reference : int = 0
45
+ named_variables : Optional [int ] = None
46
+ indexed_variables : Optional [int ] = None
47
+ memory_reference : Optional [str ] = None
48
+
49
+
37
50
class State (Enum ):
38
51
Stopped = 0
39
52
Running = 1
@@ -61,7 +74,7 @@ class StackTraceResult(NamedTuple):
61
74
62
75
class StackFrameEntry :
63
76
def __init__ (
64
- self , context : weakref .ref [Any ], name : str , type : str , source : str , line : int , column : int = 1
77
+ self , context : weakref .ref [Any ], name : str , type : str , source : Optional [ str ] , line : int , column : int = 1
65
78
) -> None :
66
79
self .context = context
67
80
self .name = name
@@ -281,7 +294,7 @@ def set_breakpoints(
281
294
282
295
return []
283
296
284
- def process_state (self , source : str , line_no : int , type : str ) -> None :
297
+ def process_state (self , source : Optional [ str ] , line_no : Optional [ int ] , type : Optional [ str ] ) -> None :
285
298
if self .state == State .Stopped :
286
299
return
287
300
@@ -336,21 +349,22 @@ def process_state(self, source: str, line_no: int, type: str) -> None:
336
349
)
337
350
self .requested_state = RequestedState .Nothing
338
351
339
- source = str (Path (source ).resolve ())
340
- if source in self .breakpoints :
341
- breakpoints = [v for v in self .breakpoints [source ].breakpoints if v .line == line_no ]
342
- if len (breakpoints ) > 0 :
343
- self .state = State .Paused
344
- self .send_event (
345
- self ,
346
- StoppedEvent (
347
- body = StoppedEventBody (
348
- reason = StoppedReason .BREAKPOINT ,
349
- thread_id = threading .current_thread ().ident ,
350
- hit_breakpoint_ids = [id (v ) for v in breakpoints ],
351
- )
352
- ),
353
- )
352
+ if source is not None :
353
+ source = str (Path (source ).resolve ())
354
+ if source in self .breakpoints :
355
+ breakpoints = [v for v in self .breakpoints [source ].breakpoints if v .line == line_no ]
356
+ if len (breakpoints ) > 0 :
357
+ self .state = State .Paused
358
+ self .send_event (
359
+ self ,
360
+ StoppedEvent (
361
+ body = StoppedEventBody (
362
+ reason = StoppedReason .BREAKPOINT ,
363
+ thread_id = threading .current_thread ().ident ,
364
+ hit_breakpoint_ids = [id (v ) for v in breakpoints ],
365
+ )
366
+ ),
367
+ )
354
368
355
369
@_logger .call
356
370
def wait_for_running (self ) -> None :
@@ -367,7 +381,8 @@ def start_output_group(self, name: str, attributes: Dict[str, Any], type: Option
367
381
OutputEvent (
368
382
body = OutputEventBody (
369
383
output = f"{ (type + ' ' ) if type else '' } { name } \n " ,
370
- category = OutputCategory .CONSOLE ,
384
+ # category=OutputCategory.CONSOLE,
385
+ category = "log" ,
371
386
group = OutputGroup .STARTCOLLAPSED ,
372
387
source = Source (name = name , path = source ) if source else None ,
373
388
line = line_no ,
@@ -385,27 +400,52 @@ def end_output_group(self, name: str, attributes: Dict[str, Any]) -> None:
385
400
OutputEvent (
386
401
body = OutputEventBody (
387
402
output = "" ,
388
- category = OutputCategory .CONSOLE ,
403
+ # category=OutputCategory.CONSOLE,
404
+ category = "log" ,
389
405
group = OutputGroup .END ,
390
406
source = Source (name = name , path = source ) if source else None ,
391
407
line = line_no ,
392
408
)
393
409
),
394
410
)
395
411
396
- def start_suite (self , name : str , attributes : Dict [str , Any ]) -> None :
412
+ def add_stackframe_entry (
413
+ self , name : str , type : str , source : Optional [str ], line : Optional [int ], column : Optional [int ] = 1
414
+ ) -> StackFrameEntry :
397
415
from robot .running .context import EXECUTION_CONTEXTS
398
416
417
+ if source is None or line is None or column is None :
418
+ for v in self .stack_frames :
419
+ if source is None :
420
+ source = v .source
421
+ if line is None :
422
+ line = v .line
423
+ if column is None :
424
+ column = v .column
425
+ if source is not None and line is not None and column is not None :
426
+ break
427
+
428
+ result = StackFrameEntry (
429
+ weakref .ref (EXECUTION_CONTEXTS .current ),
430
+ name ,
431
+ type ,
432
+ source ,
433
+ line if line is not None else 0 ,
434
+ column if column is not None else 0 ,
435
+ )
436
+ self .stack_frames .appendleft (result )
437
+
438
+ return result
439
+
440
+ def start_suite (self , name : str , attributes : Dict [str , Any ]) -> None :
399
441
source = attributes .get ("source" , None )
400
442
line_no = attributes .get ("lineno" , 1 )
401
443
longname = attributes .get ("longname" , "" )
402
444
type = "SUITE"
403
445
404
- self .stack_frames .appendleft (
405
- StackFrameEntry (weakref .ref (EXECUTION_CONTEXTS .current ), longname , type , source , line_no )
406
- )
446
+ entry = self .add_stackframe_entry (longname , type , source , line_no )
407
447
408
- self .process_state (source , line_no , type )
448
+ self .process_state (entry . source , entry . line , entry . type )
409
449
410
450
self .wait_for_running ()
411
451
@@ -414,18 +454,14 @@ def end_suite(self, name: str, attributes: Dict[str, Any]) -> None:
414
454
self .stack_frames .popleft ()
415
455
416
456
def start_test (self , name : str , attributes : Dict [str , Any ]) -> None :
417
- from robot .running .context import EXECUTION_CONTEXTS
418
-
419
457
source = attributes .get ("source" , None )
420
458
line_no = attributes .get ("lineno" , 1 )
421
459
longname = attributes .get ("longname" , "" )
422
460
type = "TEST"
423
461
424
- self .stack_frames .appendleft (
425
- StackFrameEntry (weakref .ref (EXECUTION_CONTEXTS .current ), longname , type , source , line_no )
426
- )
462
+ entry = self .add_stackframe_entry (longname , type , source , line_no )
427
463
428
- self .process_state (source , line_no , type )
464
+ self .process_state (entry . source , entry . line , entry . type )
429
465
430
466
self .wait_for_running ()
431
467
@@ -434,8 +470,6 @@ def end_test(self, name: str, attributes: Dict[str, Any]) -> None:
434
470
self .stack_frames .popleft ()
435
471
436
472
def start_keyword (self , name : str , attributes : Dict [str , Any ]) -> None :
437
- from robot .running .context import EXECUTION_CONTEXTS
438
-
439
473
status = attributes .get ("status" , "" )
440
474
441
475
if status == "NOT RUN" :
@@ -446,11 +480,9 @@ def start_keyword(self, name: str, attributes: Dict[str, Any]) -> None:
446
480
kwname = attributes .get ("kwname" , "" )
447
481
type = attributes .get ("type" , "KEYWORD" )
448
482
449
- self .stack_frames .appendleft (
450
- StackFrameEntry (weakref .ref (EXECUTION_CONTEXTS .current ), kwname , type , source , line_no )
451
- )
483
+ entry = self .add_stackframe_entry (kwname , type , source , line_no )
452
484
453
- self .process_state (source , line_no , type )
485
+ self .process_state (entry . source , entry . line , entry . type )
454
486
455
487
self .wait_for_running ()
456
488
@@ -480,13 +512,19 @@ def get_stack_trace(
480
512
) -> StackTraceResult :
481
513
start_frame = start_frame or 0
482
514
levels = start_frame + 1 + (levels or len (self .stack_frames ))
483
- return StackTraceResult (
484
- [
485
- StackFrame (id = v .id , name = v .name , line = v .line , column = v .column , source = Source (path = v .source ))
486
- for v in itertools .islice (self .stack_frames , start_frame , levels )
487
- ],
488
- len (self .stack_frames ),
489
- )
515
+
516
+ frames = [
517
+ StackFrame (
518
+ id = v .id ,
519
+ name = v .name ,
520
+ line = v .line ,
521
+ column = v .column ,
522
+ source = Source (path = v .source ) if v .source is not None else None ,
523
+ )
524
+ for v in itertools .islice (self .stack_frames , start_frame , levels )
525
+ ]
526
+
527
+ return StackTraceResult (frames , len (frames ))
490
528
491
529
def log_message (self , message : Dict [str , Any ]) -> None :
492
530
if self .output_log :
@@ -609,3 +647,38 @@ def get_variables(
609
647
]
610
648
611
649
return result
650
+
651
+ VARS_RE = re .compile (r"^[$@&%]\{.*\}$" )
652
+
653
+ def evaluate (
654
+ self ,
655
+ expression : str ,
656
+ frame_id : Optional [int ] = None ,
657
+ context : Union [EvaluateArgumentContext , str , None ] = None ,
658
+ format : Optional [ValueFormat ] = None ,
659
+ ) -> EvaluateResult :
660
+ from robot .running .context import EXECUTION_CONTEXTS
661
+ from robot .variables .evaluation import evaluate_expression
662
+
663
+ evaluate_context : Any = None
664
+
665
+ if frame_id is not None :
666
+ evaluate_context = (
667
+ next ((v .context () for v in self .stack_frames if v .id == frame_id ), None )
668
+ if frame_id is not None
669
+ else None
670
+ )
671
+
672
+ if evaluate_context is None :
673
+ evaluate_context = EXECUTION_CONTEXTS .current
674
+
675
+ try :
676
+ vars = evaluate_context .variables .current if frame_id is not None else evaluate_context .variables ._global
677
+ if self .VARS_RE .match (expression .strip ()):
678
+ result = vars .replace_string (expression )
679
+ else :
680
+ result = evaluate_expression (vars .replace_string (expression ), vars .store )
681
+ except BaseException as e :
682
+ result = e
683
+
684
+ return EvaluateResult (repr (result ), repr (type (result )))
0 commit comments