Skip to content

Commit e5d895b

Browse files
committed
feat: reporting suites and tests with the same name when tests are discovered
1 parent e0f8e6b commit e5d895b

File tree

1 file changed

+82
-31
lines changed
  • packages/runner/src/robotcode/runner/cli/discover

1 file changed

+82
-31
lines changed

packages/runner/src/robotcode/runner/cli/discover/discover.py

Lines changed: 82 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,16 @@
1717
from robot.output import LOGGER, Message
1818
from robot.running.builder import TestSuiteBuilder
1919
from robot.running.builder.builders import SuiteStructureParser
20+
from robot.utils import NormalizedDict
2021
from robot.utils.filereader import FileReader
2122
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+
)
2330
from robotcode.core.uri import Uri
2431
from robotcode.plugin import Application, OutputFormat, UnknownError, pass_application
2532
from robotcode.plugin.click_helper.types import add_options
@@ -47,9 +54,13 @@ def _patch() -> None:
4754

4855
if get_robot_version() <= (6, 1):
4956
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+
)
5160
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+
)
5364

5465
old_validate_test_counts = TestSuiteBuilder._validate_test_counts
5566

@@ -74,11 +85,15 @@ def build_suite(self: SuiteStructureParser, structure: Any) -> Tuple[TestSuite,
7485
from robot.running.builder.parsers import format_name
7586

7687
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,
7891
), TestDefaults(parent_defaults)
7992

8093
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,
8297
), TestDefaults(parent_defaults)
8398

8499
SuiteStructureParser._build_suite = build_suite
@@ -95,7 +110,9 @@ def _validate_execution_mode(self: SuiteStructureParser, suite: TestSuite) -> No
95110

96111
elif get_robot_version() >= (6, 1):
97112
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+
)
99116

100117
old_validate_not_empty = TestSuiteBuilder._validate_not_empty
101118

@@ -115,7 +132,9 @@ def build_suite_file(self: SuiteStructureParser, structure: SuiteFile) -> TestSu
115132
except DataError as e:
116133
LOGGER.error(str(e))
117134
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,
119138
)
120139

121140
SuiteStructureParser._build_suite_file = build_suite_file
@@ -130,7 +149,9 @@ def build_suite_directory(
130149
except DataError as e:
131150
LOGGER.error(str(e))
132151
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,
134155
), TestDefaults(self.parent_defaults)
135156

136157
SuiteStructureParser._build_suite_directory = build_suite_directory
@@ -214,8 +235,22 @@ def __init__(self) -> None:
214235
self.tests: List[TestItem] = []
215236
self.tags: Dict[str, List[TestItem]] = defaultdict(list)
216237
self.statistics = Statistics()
238+
self._collected = [NormalizedDict(ignore="_")]
217239

218240
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="_"))
219254
try:
220255
item = TestItem(
221256
type="suite",
@@ -253,7 +288,18 @@ def visit_suite(self, suite: TestSuite) -> None:
253288
if suite.tests:
254289
self.statistics.suites_with_tests += 1
255290

291+
def end_suite(self, _suite: TestSuite) -> None:
292+
self._collected.pop()
293+
256294
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+
257303
if self._current.children is None:
258304
self._current.children = []
259305
try:
@@ -284,7 +330,10 @@ def visit_test(self, test: TestCase) -> None:
284330

285331
@click.group(invoke_without_command=False)
286332
@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,
288337
)
289338
@pass_application
290339
def discover(app: Application, read_from_stdin: bool) -> None:
@@ -304,7 +353,9 @@ def discover(app: Application, read_from_stdin: bool) -> None:
304353
app.verbose(f"Read data from stdin: {_stdin_data!r}")
305354

306355

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+
)
308359
RE_PARSING_FAILED_MATCHER = re.compile(r"Parsing\s'(?P<file>.*)'\sfailed:(?P<message>.*)")
309360

310361

@@ -321,7 +372,10 @@ def build_diagnostics(messages: List[Message]) -> Dict[str, List[Diagnostic]]:
321372
result: Dict[str, List[Diagnostic]] = {}
322373

323374
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,
325379
) -> None:
326380
source_uri = str(Uri.from_path(Path(source_uri).resolve() if source_uri else Path.cwd()))
327381

@@ -343,7 +397,12 @@ def add_diagnostic(
343397

344398
for message in messages:
345399
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+
)
347406
elif match := RE_PARSING_FAILED_MATCHER.match(message.message):
348407
add_diagnostic(message, match.group("file"), text=match.group("message").strip())
349408
else:
@@ -357,7 +416,7 @@ def handle_options(
357416
by_longname: Tuple[str, ...],
358417
exclude_by_longname: Tuple[str, ...],
359418
robot_options_and_args: Tuple[str, ...],
360-
) -> Tuple[TestSuite, Optional[Dict[str, List[Diagnostic]]]]:
419+
) -> Tuple[TestSuite, Collector, Optional[Dict[str, List[Diagnostic]]]]:
361420
root_folder, profile, cmd_options = handle_robot_options(
362421
app, by_longname, exclude_by_longname, robot_options_and_args
363422
)
@@ -418,7 +477,10 @@ def handle_options(
418477
suite.visit(ModelModifier(settings.pre_run_modifiers, settings.run_empty_suite, LOGGER))
419478
suite.configure(**settings.suite_config)
420479

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)
422484

423485
except Information as err:
424486
app.echo(str(err))
@@ -458,18 +520,16 @@ def all(
458520
```
459521
"""
460522

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)
465524

466525
if collector.all.children:
467526
if app.config.output_format is None or app.config.output_format == OutputFormat.TEXT:
468527
tests_or_tasks = "Task" if suite.rpa else "Test"
469528

470529
def print(item: TestItem, indent: int = 0) -> Iterable[str]:
471530
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",
473533
)
474534

475535
if item.type == "test":
@@ -529,10 +589,7 @@ def tests(
529589
```
530590
"""
531591

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)
536593

537594
if collector.all.children:
538595
if app.config.output_format is None or app.config.output_format == OutputFormat.TEXT:
@@ -576,10 +633,7 @@ def suites(
576633
```
577634
"""
578635

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)
583637

584638
if collector.all.children:
585639
if app.config.output_format is None or app.config.output_format == OutputFormat.TEXT:
@@ -630,10 +684,7 @@ def tags(
630684
```
631685
"""
632686

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)
637688

638689
if collector.all.children:
639690
if app.config.output_format is None or app.config.output_format == OutputFormat.TEXT:

0 commit comments

Comments
 (0)