17
17
from robot .output import LOGGER , Message
18
18
from robot .running .builder import TestSuiteBuilder
19
19
from robot .running .builder .builders import SuiteStructureParser
20
+ from robot .utils import NormalizedDict
20
21
from robot .utils .filereader import FileReader
21
22
from robotcode .core .dataclasses import from_json
22
- from robotcode .core .lsp .types import Diagnostic , DiagnosticSeverity , DocumentUri , Position , Range
23
+ from robotcode .core .lsp .types import (
24
+ Diagnostic ,
25
+ DiagnosticSeverity ,
26
+ DocumentUri ,
27
+ Position ,
28
+ Range ,
29
+ )
23
30
from robotcode .core .uri import Uri
24
31
from robotcode .plugin import Application , OutputFormat , UnknownError , pass_application
25
32
from robotcode .plugin .click_helper .types import add_options
@@ -47,9 +54,13 @@ def _patch() -> None:
47
54
48
55
if get_robot_version () <= (6 , 1 ):
49
56
if get_robot_version () > (5 , 0 ) and get_robot_version () < (6 , 0 ) or get_robot_version () < (5 , 0 ):
50
- from robot .running .builder .testsettings import TestDefaults # pyright: ignore[reportMissingImports]
57
+ from robot .running .builder .testsettings import (
58
+ TestDefaults ,
59
+ )
51
60
else :
52
- from robot .running .builder .settings import Defaults as TestDefaults # pyright: ignore[reportMissingImports]
61
+ from robot .running .builder .settings import (
62
+ Defaults as TestDefaults ,
63
+ )
53
64
54
65
old_validate_test_counts = TestSuiteBuilder ._validate_test_counts
55
66
@@ -74,11 +85,15 @@ def build_suite(self: SuiteStructureParser, structure: Any) -> Tuple[TestSuite,
74
85
from robot .running .builder .parsers import format_name
75
86
76
87
return ErroneousTestSuite (
77
- error_message = str (e ), name = format_name (structure .source ), source = structure .source
88
+ error_message = str (e ),
89
+ name = format_name (structure .source ),
90
+ source = structure .source ,
78
91
), TestDefaults (parent_defaults )
79
92
80
93
return ErroneousTestSuite (
81
- error_message = str (e ), name = TestSuite .name_from_source (structure .source ), source = structure .source
94
+ error_message = str (e ),
95
+ name = TestSuite .name_from_source (structure .source ),
96
+ source = structure .source ,
82
97
), TestDefaults (parent_defaults )
83
98
84
99
SuiteStructureParser ._build_suite = build_suite
@@ -95,7 +110,9 @@ def _validate_execution_mode(self: SuiteStructureParser, suite: TestSuite) -> No
95
110
96
111
elif get_robot_version () >= (6 , 1 ):
97
112
from robot .parsing .suitestructure import SuiteDirectory , SuiteFile
98
- from robot .running .builder .settings import TestDefaults # pyright: ignore[reportMissingImports]
113
+ from robot .running .builder .settings import (
114
+ TestDefaults ,
115
+ )
99
116
100
117
old_validate_not_empty = TestSuiteBuilder ._validate_not_empty
101
118
@@ -115,7 +132,9 @@ def build_suite_file(self: SuiteStructureParser, structure: SuiteFile) -> TestSu
115
132
except DataError as e :
116
133
LOGGER .error (str (e ))
117
134
return ErroneousTestSuite (
118
- error_message = str (e ), name = TestSuite .name_from_source (structure .source ), source = structure .source
135
+ error_message = str (e ),
136
+ name = TestSuite .name_from_source (structure .source ),
137
+ source = structure .source ,
119
138
)
120
139
121
140
SuiteStructureParser ._build_suite_file = build_suite_file
@@ -130,7 +149,9 @@ def build_suite_directory(
130
149
except DataError as e :
131
150
LOGGER .error (str (e ))
132
151
return ErroneousTestSuite (
133
- error_message = str (e ), name = TestSuite .name_from_source (structure .source ), source = structure .source
152
+ error_message = str (e ),
153
+ name = TestSuite .name_from_source (structure .source ),
154
+ source = structure .source ,
134
155
), TestDefaults (self .parent_defaults )
135
156
136
157
SuiteStructureParser ._build_suite_directory = build_suite_directory
@@ -214,8 +235,22 @@ def __init__(self) -> None:
214
235
self .tests : List [TestItem ] = []
215
236
self .tags : Dict [str , List [TestItem ]] = defaultdict (list )
216
237
self .statistics = Statistics ()
238
+ self ._collected = [NormalizedDict (ignore = "_" )]
217
239
218
240
def visit_suite (self , suite : TestSuite ) -> None :
241
+ if suite .name in self ._collected [- 1 ] and suite .parent .source :
242
+ LOGGER .warn (
243
+ (
244
+ f"Warning in { 'file' if Path (suite .parent .source ).is_file () else 'folder' } "
245
+ f"'{ suite .parent .source } ': "
246
+ if suite .source and Path (suite .parent .source ).exists ()
247
+ else ""
248
+ )
249
+ + f"Multiple suites with name '{ suite .name } ' in suite '{ suite .parent .longname } '."
250
+ )
251
+
252
+ self ._collected [- 1 ][suite .name ] = True
253
+ self ._collected .append (NormalizedDict (ignore = "_" ))
219
254
try :
220
255
item = TestItem (
221
256
type = "suite" ,
@@ -253,7 +288,18 @@ def visit_suite(self, suite: TestSuite) -> None:
253
288
if suite .tests :
254
289
self .statistics .suites_with_tests += 1
255
290
291
+ def end_suite (self , _suite : TestSuite ) -> None :
292
+ self ._collected .pop ()
293
+
256
294
def visit_test (self , test : TestCase ) -> None :
295
+ if test .name in self ._collected [- 1 ]:
296
+ LOGGER .warn (
297
+ f"Warning in file '{ test .source } ' on line { test .lineno } : "
298
+ f"Multiple { 'task' if test .parent .rpa else 'test' } s with name '{ test .name } ' in suite "
299
+ f"'{ test .parent .longname } '."
300
+ )
301
+ self ._collected [- 1 ][test .name ] = True
302
+
257
303
if self ._current .children is None :
258
304
self ._current .children = []
259
305
try :
@@ -284,7 +330,10 @@ def visit_test(self, test: TestCase) -> None:
284
330
285
331
@click .group (invoke_without_command = False )
286
332
@click .option (
287
- "--read-from-stdin" , is_flag = True , help = "Read file contents from stdin. This is an internal option." , hidden = True
333
+ "--read-from-stdin" ,
334
+ is_flag = True ,
335
+ help = "Read file contents from stdin. This is an internal option." ,
336
+ hidden = True ,
288
337
)
289
338
@pass_application
290
339
def discover (app : Application , read_from_stdin : bool ) -> None :
@@ -304,7 +353,9 @@ def discover(app: Application, read_from_stdin: bool) -> None:
304
353
app .verbose (f"Read data from stdin: { _stdin_data !r} " )
305
354
306
355
307
- RE_IN_FILE_LINE_MATCHER = re .compile (r".+\sin\sfile\s'(?P<file>.*)'\son\sline\s(?P<line>\d+):(?P<message>.*)" )
356
+ RE_IN_FILE_LINE_MATCHER = re .compile (
357
+ r".+\sin\s(file|folder)\s'(?P<file>.*)'(\son\sline\s(?P<line>\d+))?:(?P<message>.*)"
358
+ )
308
359
RE_PARSING_FAILED_MATCHER = re .compile (r"Parsing\s'(?P<file>.*)'\sfailed:(?P<message>.*)" )
309
360
310
361
@@ -321,7 +372,10 @@ def build_diagnostics(messages: List[Message]) -> Dict[str, List[Diagnostic]]:
321
372
result : Dict [str , List [Diagnostic ]] = {}
322
373
323
374
def add_diagnostic (
324
- message : Message , source_uri : Optional [str ] = None , line : Optional [int ] = None , text : Optional [str ] = None
375
+ message : Message ,
376
+ source_uri : Optional [str ] = None ,
377
+ line : Optional [int ] = None ,
378
+ text : Optional [str ] = None ,
325
379
) -> None :
326
380
source_uri = str (Uri .from_path (Path (source_uri ).resolve () if source_uri else Path .cwd ()))
327
381
@@ -343,7 +397,12 @@ def add_diagnostic(
343
397
344
398
for message in messages :
345
399
if match := RE_IN_FILE_LINE_MATCHER .match (message .message ):
346
- add_diagnostic (message , match .group ("file" ), int (match .group ("line" )), text = match .group ("message" ).strip ())
400
+ add_diagnostic (
401
+ message ,
402
+ match .group ("file" ),
403
+ int (match .group ("line" )) if match .group ("line" ) is not None else None ,
404
+ text = match .group ("message" ).strip (),
405
+ )
347
406
elif match := RE_PARSING_FAILED_MATCHER .match (message .message ):
348
407
add_diagnostic (message , match .group ("file" ), text = match .group ("message" ).strip ())
349
408
else :
@@ -357,7 +416,7 @@ def handle_options(
357
416
by_longname : Tuple [str , ...],
358
417
exclude_by_longname : Tuple [str , ...],
359
418
robot_options_and_args : Tuple [str , ...],
360
- ) -> Tuple [TestSuite , Optional [Dict [str , List [Diagnostic ]]]]:
419
+ ) -> Tuple [TestSuite , Collector , Optional [Dict [str , List [Diagnostic ]]]]:
361
420
root_folder , profile , cmd_options = handle_robot_options (
362
421
app , by_longname , exclude_by_longname , robot_options_and_args
363
422
)
@@ -418,7 +477,10 @@ def handle_options(
418
477
suite .visit (ModelModifier (settings .pre_run_modifiers , settings .run_empty_suite , LOGGER ))
419
478
suite .configure (** settings .suite_config )
420
479
421
- return suite , build_diagnostics (diagnostics_logger .messages )
480
+ collector = Collector ()
481
+ suite .visit (collector )
482
+
483
+ return suite , collector , build_diagnostics (diagnostics_logger .messages )
422
484
423
485
except Information as err :
424
486
app .echo (str (err ))
@@ -458,18 +520,16 @@ def all(
458
520
```
459
521
"""
460
522
461
- suite , diagnostics = handle_options (app , by_longname , exclude_by_longname , robot_options_and_args )
462
-
463
- collector = Collector ()
464
- suite .visit (collector )
523
+ suite , collector , diagnostics = handle_options (app , by_longname , exclude_by_longname , robot_options_and_args )
465
524
466
525
if collector .all .children :
467
526
if app .config .output_format is None or app .config .output_format == OutputFormat .TEXT :
468
527
tests_or_tasks = "Task" if suite .rpa else "Test"
469
528
470
529
def print (item : TestItem , indent : int = 0 ) -> Iterable [str ]:
471
530
type = click .style (
472
- item .type .capitalize () if item .type == "suite" else tests_or_tasks .capitalize (), fg = "green"
531
+ item .type .capitalize () if item .type == "suite" else tests_or_tasks .capitalize (),
532
+ fg = "green" ,
473
533
)
474
534
475
535
if item .type == "test" :
@@ -529,10 +589,7 @@ def tests(
529
589
```
530
590
"""
531
591
532
- suite , diagnostics = handle_options (app , by_longname , exclude_by_longname , robot_options_and_args )
533
-
534
- collector = Collector ()
535
- suite .visit (collector )
592
+ suite , collector , diagnostics = handle_options (app , by_longname , exclude_by_longname , robot_options_and_args )
536
593
537
594
if collector .all .children :
538
595
if app .config .output_format is None or app .config .output_format == OutputFormat .TEXT :
@@ -576,10 +633,7 @@ def suites(
576
633
```
577
634
"""
578
635
579
- suite , diagnostics = handle_options (app , by_longname , exclude_by_longname , robot_options_and_args )
580
-
581
- collector = Collector ()
582
- suite .visit (collector )
636
+ suite , collector , diagnostics = handle_options (app , by_longname , exclude_by_longname , robot_options_and_args )
583
637
584
638
if collector .all .children :
585
639
if app .config .output_format is None or app .config .output_format == OutputFormat .TEXT :
@@ -630,10 +684,7 @@ def tags(
630
684
```
631
685
"""
632
686
633
- suite , _diagnostics = handle_options (app , by_longname , exclude_by_longname , robot_options_and_args )
634
-
635
- collector = Collector ()
636
- suite .visit (collector )
687
+ _suite , collector , _diagnostics = handle_options (app , by_longname , exclude_by_longname , robot_options_and_args )
637
688
638
689
if collector .all .children :
639
690
if app .config .output_format is None or app .config .output_format == OutputFormat .TEXT :
0 commit comments