robotcode-runner 2.7.0__py3-none-any.whl → 2.8.0.dev8__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,3 +1,4 @@
1
+ import dataclasses
1
2
  import os
2
3
  import platform
3
4
  import re
@@ -10,6 +11,7 @@ from typing import (
10
11
  Dict,
11
12
  Iterable,
12
13
  List,
14
+ Mapping,
13
15
  MutableMapping,
14
16
  Optional,
15
17
  Tuple,
@@ -39,6 +41,7 @@ from robotcode.core.uri import Uri
39
41
  from robotcode.core.utils.cli import show_hidden_arguments
40
42
  from robotcode.core.utils.dataclasses import from_json
41
43
  from robotcode.core.utils.path import normalized_path
44
+ from robotcode.modifiers.metadata_modifiers import metadata_entries
42
45
  from robotcode.plugin import (
43
46
  Application,
44
47
  OutputFormat,
@@ -49,10 +52,16 @@ from robotcode.plugin.click_helper.types import add_options
49
52
  from robotcode.robot.utils import RF_VERSION
50
53
 
51
54
  from .._markdown import make_md_highlighter
52
- from .._search import SearchMatcher, make_search_matcher
55
+ from .._robot_version import RobotVersionCommand, RobotVersionOption
56
+ from .._search import (
57
+ SearchMatcher,
58
+ get_test_metadata,
59
+ make_search_matcher,
60
+ named_metadata,
61
+ )
53
62
  from ..robot import ROBOT_OPTIONS, ROBOT_VERSION_OPTIONS, RobotFrameworkEx, handle_robot_options
54
63
  from . import _render
55
- from ._models import Info, ResultItem, Statistics, TagsResult, TestItem
64
+ from ._models import Info, MetadataResult, ResultItem, Statistics, TagsResult, TestItem
56
65
 
57
66
  # Robot Framework 6.1 introduced `--parseinclude`. Before that, the `--suite`
58
67
  # option implicitly restricted which files were parsed, so an explicit include
@@ -69,8 +78,9 @@ DISCOVER_SEARCH_OPTIONS = [
69
78
  help=(
70
79
  "Only include items where TEXT case-insensitively matches the "
71
80
  "name, full name, source path, documentation, template name, "
72
- "timeout, any tag (normalisation-aware), the parent suite's "
73
- "Documentation / Metadata, or anything inside the test body "
81
+ "timeout, any tag (normalisation-aware), test metadata names and "
82
+ "values, the parent suite's Documentation / Metadata, or anything "
83
+ "inside the test body "
74
84
  "— keyword names, keyword arguments, assigned variables, "
75
85
  "FOR/WHILE/IF conditions, VAR/RETURN values, EXCEPT patterns, "
76
86
  "GROUP names. Mutually exclusive with `--search-regex`."
@@ -264,6 +274,8 @@ class Collector(SuiteVisitor):
264
274
  self.test_and_tasks: List[TestItem] = []
265
275
  self.tags: Dict[str, List[TestItem]] = defaultdict(list)
266
276
  self.normalized_tags: Dict[str, List[TestItem]] = defaultdict(list)
277
+ # Suite items carry no metadata; `discover metadata` lists it separately.
278
+ self.suite_metadata: List[Tuple[TestItem, Dict[str, str]]] = []
267
279
  self.statistics = Statistics()
268
280
  self._collected: List[MutableMapping[str, Any]] = [NormalizedDict(ignore="_")]
269
281
 
@@ -289,7 +301,7 @@ class Collector(SuiteVisitor):
289
301
  name=suite.name,
290
302
  longname=suite.longname,
291
303
  uri=str(Uri.from_path(absolute_path)) if absolute_path else None,
292
- source=str(suite.source),
304
+ source=str(suite.source) if suite.source else None,
293
305
  rel_source=get_rel_source(suite.source),
294
306
  range=(
295
307
  Range(
@@ -307,6 +319,9 @@ class Collector(SuiteVisitor):
307
319
  raise ValueError(f"Error while parsing suite {suite.source}: {e}") from e
308
320
 
309
321
  self.suites.append(item)
322
+ suite_metadata = named_metadata(suite.metadata)
323
+ if suite_metadata:
324
+ self.suite_metadata.append((item, suite_metadata))
310
325
 
311
326
  if self._current.children is None:
312
327
  self._current.children = []
@@ -330,9 +345,13 @@ class Collector(SuiteVisitor):
330
345
  self._collected.pop()
331
346
 
332
347
  def visit_test(self, test: TestCase) -> None:
348
+ # A custom parser or a pre-run modifier may create a test without a line number. Robot Framework's lines start
349
+ # at 1, so such a test gets 0 in its ID, as the debugger's events do, and no line and no range.
350
+ lineno = test.lineno or None
351
+
333
352
  if test.name in self._collected[-1]:
334
353
  LOGGER.warn(
335
- f"Warning in file '{test.source}' on line {test.lineno}: "
354
+ f"Warning in file '{test.source}'{f' on line {lineno}' if lineno else ''}: "
336
355
  f"Multiple {'task' if test.parent.rpa else 'test'}s with name '{test.name}' in suite "
337
356
  f"'{test.parent.longname}'."
338
357
  )
@@ -344,18 +363,23 @@ class Collector(SuiteVisitor):
344
363
  absolute_path = normalized_path(Path(test.source)) if test.source is not None else None
345
364
  item = TestItem(
346
365
  type="task" if self._current.rpa else "test",
347
- id=f"{absolute_path or ''};{test.longname};{test.lineno}",
366
+ id=f"{absolute_path or ''};{test.longname};{lineno or 0}",
348
367
  name=test.name,
349
368
  longname=test.longname,
350
- lineno=test.lineno,
369
+ lineno=lineno,
351
370
  uri=str(Uri.from_path(absolute_path)) if absolute_path else None,
352
371
  source=str(test.source),
353
372
  rel_source=get_rel_source(test.source),
354
- range=Range(
355
- start=Position(line=test.lineno - 1, character=0),
356
- end=Position(line=test.lineno - 1, character=0),
373
+ range=(
374
+ Range(
375
+ start=Position(line=lineno - 1, character=0),
376
+ end=Position(line=lineno - 1, character=0),
377
+ )
378
+ if lineno
379
+ else None
357
380
  ),
358
381
  tags=sorted(set(normalize(str(t), ignore="_") for t in test.tags)) if test.tags else None,
382
+ metadata=get_test_metadata(test) or None,
359
383
  rpa=self._current.rpa,
360
384
  )
361
385
  except ValueError as e:
@@ -477,6 +501,8 @@ def handle_options(
477
501
  exclude_by_longname: Tuple[str, ...],
478
502
  robot_options_and_args: Tuple[str, ...],
479
503
  search_matcher: Optional[SearchMatcher] = None,
504
+ by_test_metadata: Tuple[str, ...] = (),
505
+ exclude_by_test_metadata: Tuple[str, ...] = (),
480
506
  ) -> Tuple[TestSuite, Collector, Optional[Dict[str, List[Diagnostic]]]]:
481
507
  root_folder, profile, cmd_options = handle_robot_options(app, robot_options_and_args)
482
508
 
@@ -500,6 +526,8 @@ def handle_options(
500
526
  by_longname,
501
527
  exclude_by_longname,
502
528
  search_matcher=search_matcher,
529
+ by_test_metadata=by_test_metadata,
530
+ exclude_by_test_metadata=exclude_by_test_metadata,
503
531
  ).parse_arguments((*cmd_options, "--runemptysuite", *robot_options_and_args))
504
532
 
505
533
  settings = RobotSettings(options)
@@ -572,6 +600,29 @@ def handle_options(
572
600
  raise UnknownError("Unexpected error happened.")
573
601
 
574
602
 
603
+ def _show_options(*, default: bool) -> List[Any]:
604
+ """The flags of `all`, `tests` and `tasks` that add the tags and metadata lines in TEXT mode."""
605
+ return [
606
+ click.option(
607
+ "--show-tags / --no-show-tags",
608
+ "show_tags",
609
+ default=default,
610
+ show_default=True,
611
+ help="Show the tags that are present.",
612
+ ),
613
+ click.option(
614
+ "--show-metadata / --no-show-metadata",
615
+ "show_metadata",
616
+ default=default,
617
+ show_default=True,
618
+ # test metadata exists since Robot Framework 7.5
619
+ cls=RobotVersionOption,
620
+ since=(7, 5),
621
+ help="Show the metadata of tests and tasks.",
622
+ ),
623
+ ]
624
+
625
+
575
626
  def _filters_applied(search_substring: Optional[str], search_regex: Optional[str]) -> Optional[Dict[str, str]]:
576
627
  if search_substring:
577
628
  return {"search": search_substring}
@@ -585,13 +636,7 @@ def _filters_applied(search_substring: Optional[str], search_regex: Optional[str
585
636
  add_help_option=True,
586
637
  epilog="Use `-- --help` to see `robot` help.",
587
638
  )
588
- @click.option(
589
- "--tags / --no-tags",
590
- "show_tags",
591
- default=True,
592
- show_default=True,
593
- help="Show the tags that are present.",
594
- )
639
+ @add_options(*_show_options(default=True))
595
640
  @add_options(*ROBOT_OPTIONS)
596
641
  @add_options(*DISCOVER_SEARCH_OPTIONS)
597
642
  @click.option(
@@ -606,10 +651,13 @@ def all(
606
651
  app: Application,
607
652
  full_paths: bool,
608
653
  show_tags: bool,
654
+ show_metadata: bool,
609
655
  search_substring: Optional[str],
610
656
  search_regex: Optional[str],
611
657
  by_longname: Tuple[str, ...],
612
658
  exclude_by_longname: Tuple[str, ...],
659
+ by_test_metadata: Tuple[str, ...],
660
+ exclude_by_test_metadata: Tuple[str, ...],
613
661
  robot_options_and_args: Tuple[str, ...],
614
662
  ) -> None:
615
663
  """\
@@ -629,7 +677,13 @@ def all(
629
677
 
630
678
  matcher = make_search_matcher(search_substring, search_regex)
631
679
  _suite, collector, diagnostics = handle_options(
632
- app, by_longname, exclude_by_longname, robot_options_and_args, search_matcher=matcher
680
+ app,
681
+ by_longname,
682
+ exclude_by_longname,
683
+ robot_options_and_args,
684
+ search_matcher=matcher,
685
+ by_test_metadata=by_test_metadata,
686
+ exclude_by_test_metadata=exclude_by_test_metadata,
633
687
  )
634
688
 
635
689
  if collector.all.children:
@@ -639,6 +693,7 @@ def all(
639
693
  collector.all.children[0],
640
694
  collector.statistics,
641
695
  show_tags=show_tags,
696
+ show_metadata=show_metadata,
642
697
  full_paths=full_paths,
643
698
  highlight=make_md_highlighter(search_substring, search_regex),
644
699
  search_substring=search_substring,
@@ -666,15 +721,24 @@ def _test_or_tasks(
666
721
  app: Application,
667
722
  full_paths: bool,
668
723
  show_tags: bool,
724
+ show_metadata: bool,
669
725
  search_substring: Optional[str],
670
726
  search_regex: Optional[str],
671
727
  by_longname: Tuple[str, ...],
672
728
  exclude_by_longname: Tuple[str, ...],
729
+ by_test_metadata: Tuple[str, ...],
730
+ exclude_by_test_metadata: Tuple[str, ...],
673
731
  robot_options_and_args: Tuple[str, ...],
674
732
  ) -> None:
675
733
  matcher = make_search_matcher(search_substring, search_regex)
676
734
  _suite, collector, diagnostics = handle_options(
677
- app, by_longname, exclude_by_longname, robot_options_and_args, search_matcher=matcher
735
+ app,
736
+ by_longname,
737
+ exclude_by_longname,
738
+ robot_options_and_args,
739
+ search_matcher=matcher,
740
+ by_test_metadata=by_test_metadata,
741
+ exclude_by_test_metadata=exclude_by_test_metadata,
678
742
  )
679
743
 
680
744
  if collector.all.children:
@@ -685,6 +749,7 @@ def _test_or_tasks(
685
749
  collector.statistics,
686
750
  selected_type=selected_type,
687
751
  show_tags=show_tags,
752
+ show_metadata=show_metadata,
688
753
  full_paths=full_paths,
689
754
  highlight=make_md_highlighter(search_substring, search_regex),
690
755
  search_substring=search_substring,
@@ -713,13 +778,7 @@ def _test_or_tasks(
713
778
  add_help_option=True,
714
779
  epilog="Use `-- --help` to see `robot` help.",
715
780
  )
716
- @click.option(
717
- "--tags / --no-tags",
718
- "show_tags",
719
- default=False,
720
- show_default=True,
721
- help="Show the tags that are present.",
722
- )
781
+ @add_options(*_show_options(default=False))
723
782
  @click.option(
724
783
  "--full-paths / --no-full-paths",
725
784
  "full_paths",
@@ -734,10 +793,13 @@ def tests(
734
793
  app: Application,
735
794
  full_paths: bool,
736
795
  show_tags: bool,
796
+ show_metadata: bool,
737
797
  search_substring: Optional[str],
738
798
  search_regex: Optional[str],
739
799
  by_longname: Tuple[str, ...],
740
800
  exclude_by_longname: Tuple[str, ...],
801
+ by_test_metadata: Tuple[str, ...],
802
+ exclude_by_test_metadata: Tuple[str, ...],
741
803
  robot_options_and_args: Tuple[str, ...],
742
804
  ) -> None:
743
805
  """\
@@ -760,10 +822,13 @@ def tests(
760
822
  app,
761
823
  full_paths,
762
824
  show_tags,
825
+ show_metadata,
763
826
  search_substring,
764
827
  search_regex,
765
828
  by_longname,
766
829
  exclude_by_longname,
830
+ by_test_metadata,
831
+ exclude_by_test_metadata,
767
832
  robot_options_and_args,
768
833
  )
769
834
 
@@ -773,13 +838,7 @@ def tests(
773
838
  add_help_option=True,
774
839
  epilog="Use `-- --help` to see `robot` help.",
775
840
  )
776
- @click.option(
777
- "--tags / --no-tags",
778
- "show_tags",
779
- default=False,
780
- show_default=True,
781
- help="Show the tags that are present.",
782
- )
841
+ @add_options(*_show_options(default=False))
783
842
  @click.option(
784
843
  "--full-paths / --no-full-paths",
785
844
  "full_paths",
@@ -794,10 +853,13 @@ def tasks(
794
853
  app: Application,
795
854
  full_paths: bool,
796
855
  show_tags: bool,
856
+ show_metadata: bool,
797
857
  search_substring: Optional[str],
798
858
  search_regex: Optional[str],
799
859
  by_longname: Tuple[str, ...],
800
860
  exclude_by_longname: Tuple[str, ...],
861
+ by_test_metadata: Tuple[str, ...],
862
+ exclude_by_test_metadata: Tuple[str, ...],
801
863
  robot_options_and_args: Tuple[str, ...],
802
864
  ) -> None:
803
865
  """\
@@ -819,10 +881,13 @@ def tasks(
819
881
  app,
820
882
  full_paths,
821
883
  show_tags,
884
+ show_metadata,
822
885
  search_substring,
823
886
  search_regex,
824
887
  by_longname,
825
888
  exclude_by_longname,
889
+ by_test_metadata,
890
+ exclude_by_test_metadata,
826
891
  robot_options_and_args,
827
892
  )
828
893
 
@@ -849,6 +914,8 @@ def suites(
849
914
  search_regex: Optional[str],
850
915
  by_longname: Tuple[str, ...],
851
916
  exclude_by_longname: Tuple[str, ...],
917
+ by_test_metadata: Tuple[str, ...],
918
+ exclude_by_test_metadata: Tuple[str, ...],
852
919
  robot_options_and_args: Tuple[str, ...],
853
920
  ) -> None:
854
921
  """\
@@ -868,7 +935,13 @@ def suites(
868
935
 
869
936
  matcher = make_search_matcher(search_substring, search_regex)
870
937
  _suite, collector, diagnostics = handle_options(
871
- app, by_longname, exclude_by_longname, robot_options_and_args, search_matcher=matcher
938
+ app,
939
+ by_longname,
940
+ exclude_by_longname,
941
+ robot_options_and_args,
942
+ search_matcher=matcher,
943
+ by_test_metadata=by_test_metadata,
944
+ exclude_by_test_metadata=exclude_by_test_metadata,
872
945
  )
873
946
 
874
947
  if collector.all.children:
@@ -890,7 +963,8 @@ def suites(
890
963
  else:
891
964
  app.print_data(
892
965
  ResultItem(
893
- collector.suites,
966
+ # a flat list: the suites without their nested suites and tests
967
+ [dataclasses.replace(suite, children=None) for suite in collector.suites],
894
968
  diagnostics,
895
969
  filters_applied=_filters_applied(search_substring, search_regex),
896
970
  supports_parse_include=SUPPORTS_PARSE_INCLUDE,
@@ -945,6 +1019,8 @@ def tags(
945
1019
  search_regex: Optional[str],
946
1020
  by_longname: Tuple[str, ...],
947
1021
  exclude_by_longname: Tuple[str, ...],
1022
+ by_test_metadata: Tuple[str, ...],
1023
+ exclude_by_test_metadata: Tuple[str, ...],
948
1024
  robot_options_and_args: Tuple[str, ...],
949
1025
  ) -> None:
950
1026
  """\
@@ -965,7 +1041,13 @@ def tags(
965
1041
 
966
1042
  matcher = make_search_matcher(search_substring, search_regex)
967
1043
  _suite, collector, diagnostics = handle_options(
968
- app, by_longname, exclude_by_longname, robot_options_and_args, search_matcher=matcher
1044
+ app,
1045
+ by_longname,
1046
+ exclude_by_longname,
1047
+ robot_options_and_args,
1048
+ search_matcher=matcher,
1049
+ by_test_metadata=by_test_metadata,
1050
+ exclude_by_test_metadata=exclude_by_test_metadata,
969
1051
  )
970
1052
 
971
1053
  if collector.all.children:
@@ -995,6 +1077,149 @@ def tags(
995
1077
  )
996
1078
 
997
1079
 
1080
+ def _metadata_index(
1081
+ items: Iterable[Tuple[TestItem, Optional[Mapping[str, str]]]],
1082
+ ) -> Dict[str, Dict[str, List[TestItem]]]:
1083
+ """name → value → items, built from the same entries `--by-test-metadata`
1084
+ matches. Names and values that compare equal are one entry, shown in the
1085
+ first spelling found; both are sorted by their normalised form."""
1086
+ names: Dict[str, Tuple[str, Dict[str, Tuple[str, List[TestItem]]]]] = {}
1087
+ for item, metadata in items:
1088
+ for name, line in metadata_entries(metadata or {}):
1089
+ _name, values = names.setdefault(normalize(name, ignore="_"), (name, {}))
1090
+ _value, value_items = values.setdefault(normalize(line, ignore="_"), (line, []))
1091
+ if not value_items or value_items[-1] is not item:
1092
+ value_items.append(item)
1093
+ return {
1094
+ name: {value: value_items for _, (value, value_items) in sorted(values.items())}
1095
+ for _, (name, values) in sorted(names.items())
1096
+ }
1097
+
1098
+
1099
+ @discover.command(
1100
+ context_settings={"allow_extra_args": True, "ignore_unknown_options": True},
1101
+ add_help_option=True,
1102
+ epilog="Use `-- --help` to see `robot` help.",
1103
+ # test metadata exists since Robot Framework 7.5
1104
+ cls=RobotVersionCommand,
1105
+ since=(7, 5),
1106
+ )
1107
+ @click.option(
1108
+ "--values / --no-values",
1109
+ "show_values",
1110
+ default=False,
1111
+ show_default=True,
1112
+ help="Show the values of each metadata name.",
1113
+ )
1114
+ @click.option(
1115
+ "--tests / --no-tests",
1116
+ "show_tests",
1117
+ default=False,
1118
+ show_default=True,
1119
+ help="Show the tests that have the metadata.",
1120
+ )
1121
+ @click.option(
1122
+ "--tasks / --no-tasks",
1123
+ "show_tasks",
1124
+ default=False,
1125
+ show_default=True,
1126
+ help="Show the tasks that have the metadata.",
1127
+ )
1128
+ @click.option(
1129
+ "--suites / --no-suites",
1130
+ "show_suites",
1131
+ default=False,
1132
+ show_default=True,
1133
+ help="Show the suites that have the metadata.",
1134
+ )
1135
+ @click.option(
1136
+ "--full-paths / --no-full-paths",
1137
+ "full_paths",
1138
+ default=False,
1139
+ show_default=True,
1140
+ help="Show full paths instead of relative.",
1141
+ )
1142
+ @add_options(*ROBOT_OPTIONS)
1143
+ @add_options(*DISCOVER_SEARCH_OPTIONS)
1144
+ @pass_application
1145
+ def metadata(
1146
+ app: Application,
1147
+ show_values: bool,
1148
+ show_tests: bool,
1149
+ show_tasks: bool,
1150
+ show_suites: bool,
1151
+ full_paths: bool,
1152
+ search_substring: Optional[str],
1153
+ search_regex: Optional[str],
1154
+ by_longname: Tuple[str, ...],
1155
+ exclude_by_longname: Tuple[str, ...],
1156
+ by_test_metadata: Tuple[str, ...],
1157
+ exclude_by_test_metadata: Tuple[str, ...],
1158
+ robot_options_and_args: Tuple[str, ...],
1159
+ ) -> None:
1160
+ """\
1161
+ Discover the metadata of tests, tasks and suites with the selected
1162
+ configuration, profiles, options and arguments.
1163
+
1164
+ Test and task metadata is what `--by-test-metadata` selects by; suite
1165
+ metadata, including `--metadata` given to Robot, is listed in its own
1166
+ section.
1167
+
1168
+ \b
1169
+ Examples:
1170
+ ```
1171
+ robotcode discover metadata
1172
+ robotcode discover metadata --values --tests
1173
+ robotcode --format json discover metadata
1174
+ ```
1175
+ """
1176
+
1177
+ matcher = make_search_matcher(search_substring, search_regex)
1178
+ _suite, collector, diagnostics = handle_options(
1179
+ app,
1180
+ by_longname,
1181
+ exclude_by_longname,
1182
+ robot_options_and_args,
1183
+ search_matcher=matcher,
1184
+ by_test_metadata=by_test_metadata,
1185
+ exclude_by_test_metadata=exclude_by_test_metadata,
1186
+ )
1187
+
1188
+ if collector.all.children:
1189
+ test_metadata = _metadata_index((item, item.metadata) for item in collector.test_and_tasks)
1190
+ suite_metadata = _metadata_index(collector.suite_metadata)
1191
+
1192
+ if app.config.output_format is None or app.config.output_format == OutputFormat.TEXT:
1193
+ app.echo_as_markdown(
1194
+ _render.render_metadata(
1195
+ test_metadata,
1196
+ suite_metadata,
1197
+ collector.statistics,
1198
+ show_values=show_values,
1199
+ show_tests=show_tests,
1200
+ show_tasks=show_tasks,
1201
+ show_suites=show_suites,
1202
+ full_paths=full_paths,
1203
+ highlight=make_md_highlighter(search_substring, search_regex),
1204
+ search_substring=search_substring,
1205
+ search_regex=search_regex,
1206
+ diagnostics=diagnostics,
1207
+ show_diagnostics=app.show_diagnostics,
1208
+ root_folder=app.root_folder,
1209
+ )
1210
+ )
1211
+
1212
+ else:
1213
+ app.print_data(
1214
+ MetadataResult(
1215
+ test_metadata,
1216
+ suite_metadata,
1217
+ filters_applied=_filters_applied(search_substring, search_regex),
1218
+ ),
1219
+ remove_defaults=True,
1220
+ )
1221
+
1222
+
998
1223
  @discover.command(add_help_option=True)
999
1224
  @pass_application
1000
1225
  def info(app: Application) -> None:
@@ -1086,8 +1311,10 @@ def files(
1086
1311
  # Match the other discover subcommands: chdir into root_folder so
1087
1312
  # `get_rel_source` produces paths relative to the project root rather
1088
1313
  # than to whatever the caller's process cwd happens to be.
1089
- with app.chdir(root_folder):
1090
- search_paths = set(
1314
+ with app.chdir(root_folder) as orig_folder:
1315
+ # Without duplicates, in the given order: the files come out in the order of the paths. Paths from the
1316
+ # command line count from the folder the command was started in, the configured ones from the root.
1317
+ search_paths = dict.fromkeys(
1091
1318
  (
1092
1319
  (
1093
1320
  [*(app.config.default_paths if app.config.default_paths else ())]
@@ -1097,7 +1324,7 @@ def files(
1097
1324
  else [profile.paths]
1098
1325
  )
1099
1326
  if not paths
1100
- else [str(p) for p in paths]
1327
+ else [str(p if orig_folder is None else Path(orig_folder, p)) for p in paths]
1101
1328
  )
1102
1329
  )
1103
1330
  if not search_paths:
@@ -6,6 +6,7 @@ from robot.libdoc import USAGE, LibDoc
6
6
  from robot.version import get_full_version
7
7
 
8
8
  from robotcode.plugin import Application, pass_application
9
+ from robotcode.plugin.click_helper.exit_codes import robot_exit_codes
9
10
  from robotcode.robot.config.loader import load_robot_config_from_path
10
11
  from robotcode.robot.config.model import LibDocProfile
11
12
  from robotcode.robot.config.utils import get_config_files
@@ -35,6 +36,7 @@ class LibDocEx(LibDoc):
35
36
  return super().main(arguments, **options)
36
37
 
37
38
 
39
+ @robot_exit_codes
38
40
  @click.command(
39
41
  context_settings={"allow_extra_args": True, "ignore_unknown_options": True},
40
42
  add_help_option=True,
@@ -80,11 +82,11 @@ def libdoc(app: Application, robot_options_and_args: Tuple[str, ...]) -> None:
80
82
  except (TypeError, ValueError) as e:
81
83
  raise click.ClickException(str(e)) from e
82
84
 
83
- libdoc_options = profile.libdoc
84
- if libdoc_options is None:
85
- libdoc_options = LibDocProfile()
86
-
87
- libdoc_options.add_options(profile)
85
+ # Of the settings outside of `[libdoc]`, only `python-path` means the same for `libdoc`; `name` and `quiet`
86
+ # there are `robot` options. `[libdoc]` comes last, so it wins and its `extend-` settings add to it.
87
+ libdoc_options = LibDocProfile(python_path=profile.python_path)
88
+ if profile.libdoc is not None:
89
+ libdoc_options.add_options(profile.libdoc)
88
90
 
89
91
  options = libdoc_options.build_command_line()
90
92
 
@@ -7,6 +7,7 @@ from robot.rebot import USAGE, Rebot
7
7
  from robot.version import get_full_version
8
8
 
9
9
  from robotcode.plugin import Application, pass_application
10
+ from robotcode.plugin.click_helper.exit_codes import robot_exit_codes
10
11
  from robotcode.robot.config.loader import load_robot_config_from_path
11
12
  from robotcode.robot.config.model import RebotProfile
12
13
  from robotcode.robot.config.utils import get_config_files
@@ -14,6 +15,25 @@ from robotcode.robot.utils import RF_VERSION
14
15
 
15
16
  from ..__version__ import __version__
16
17
 
18
+ # The settings that Robot Framework does not pass from `robot` to `rebot` after a run (`not_copied` in
19
+ # `RobotSettings.get_rebot_settings`).
20
+ NOT_PASSED_TO_REBOT = (
21
+ "includes",
22
+ "excludes",
23
+ "tests",
24
+ "suites",
25
+ "parse_include",
26
+ "name",
27
+ "doc",
28
+ "metadata",
29
+ "set_tag",
30
+ "output",
31
+ "log_level",
32
+ "timestamp_outputs",
33
+ "console",
34
+ "quiet",
35
+ )
36
+
17
37
 
18
38
  class RebotEx(Rebot):
19
39
  def __init__(self, dry: bool) -> None:
@@ -27,7 +47,7 @@ class RebotEx(Rebot):
27
47
  line_end = "\n"
28
48
  raise Information(
29
49
  "Dry run, not executing any commands. "
30
- f"Would execute libdoc with the following options and arguments:\n"
50
+ f"Would execute rebot with the following options and arguments:\n"
31
51
  f"{line_end.join((*(f'{k} = {v!r}' for k, v in options.items()), *arguments))}"
32
52
  )
33
53
 
@@ -37,6 +57,7 @@ class RebotEx(Rebot):
37
57
  return super().main(datasources, **options)
38
58
 
39
59
 
60
+ @robot_exit_codes
40
61
  @click.command(
41
62
  context_settings={"allow_extra_args": True, "ignore_unknown_options": True},
42
63
  add_help_option=True,
@@ -82,11 +103,17 @@ def rebot(app: Application, robot_options_and_args: Tuple[str, ...]) -> None:
82
103
  except (TypeError, ValueError) as e:
83
104
  raise click.ClickException(str(e)) from e
84
105
 
85
- rebot_options = profile.rebot
86
- if rebot_options is None:
87
- rebot_options = RebotProfile()
88
-
106
+ # Of the settings outside of `[rebot]`, `rebot` gets the ones that Robot Framework passes to it after a
107
+ # run; `[rebot]` comes last, so it wins and its `extend-` settings add to them.
108
+ rebot_options = RebotProfile()
89
109
  rebot_options.add_options(profile)
110
+ for name in NOT_PASSED_TO_REBOT:
111
+ setattr(rebot_options, name, None)
112
+ # as Robot Framework does, `run-empty-suite` becomes `process-empty-suite`
113
+ if profile.run_empty_suite is not None:
114
+ rebot_options.process_empty_suite = profile.run_empty_suite
115
+ if profile.rebot is not None:
116
+ rebot_options.add_options(profile.rebot)
90
117
 
91
118
  try:
92
119
  options = rebot_options.build_command_line()