robotcode-runner 2.7.0__py3-none-any.whl → 2.8.0.dev548__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.
- robotcode/runner/__version__.py +1 -1
- robotcode/runner/cli/_markdown.py +13 -0
- robotcode/runner/cli/_robot_version.py +28 -0
- robotcode/runner/cli/_search.py +37 -1
- robotcode/runner/cli/discover/_models.py +11 -1
- robotcode/runner/cli/discover/_render.py +108 -2
- robotcode/runner/cli/discover/discover.py +253 -34
- robotcode/runner/cli/libdoc.py +7 -5
- robotcode/runner/cli/rebot.py +32 -5
- robotcode/runner/cli/results/_models.py +4 -0
- robotcode/runner/cli/results/_render.py +9 -1
- robotcode/runner/cli/results/results.py +195 -43
- robotcode/runner/cli/robot.py +56 -8
- robotcode/runner/cli/testdoc.py +7 -4
- {robotcode_runner-2.7.0.dist-info → robotcode_runner-2.8.0.dev548.dist-info}/METADATA +7 -6
- robotcode_runner-2.8.0.dev548.dist-info/RECORD +25 -0
- {robotcode_runner-2.7.0.dist-info → robotcode_runner-2.8.0.dev548.dist-info}/WHEEL +1 -1
- robotcode_runner-2.7.0.dist-info/RECORD +0 -24
- {robotcode_runner-2.7.0.dist-info → robotcode_runner-2.8.0.dev548.dist-info}/entry_points.txt +0 -0
|
@@ -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 ..
|
|
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),
|
|
73
|
-
"Documentation / Metadata, or anything
|
|
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 = []
|
|
@@ -356,6 +371,7 @@ class Collector(SuiteVisitor):
|
|
|
356
371
|
end=Position(line=test.lineno - 1, character=0),
|
|
357
372
|
),
|
|
358
373
|
tags=sorted(set(normalize(str(t), ignore="_") for t in test.tags)) if test.tags else None,
|
|
374
|
+
metadata=get_test_metadata(test) or None,
|
|
359
375
|
rpa=self._current.rpa,
|
|
360
376
|
)
|
|
361
377
|
except ValueError as e:
|
|
@@ -477,6 +493,8 @@ def handle_options(
|
|
|
477
493
|
exclude_by_longname: Tuple[str, ...],
|
|
478
494
|
robot_options_and_args: Tuple[str, ...],
|
|
479
495
|
search_matcher: Optional[SearchMatcher] = None,
|
|
496
|
+
by_test_metadata: Tuple[str, ...] = (),
|
|
497
|
+
exclude_by_test_metadata: Tuple[str, ...] = (),
|
|
480
498
|
) -> Tuple[TestSuite, Collector, Optional[Dict[str, List[Diagnostic]]]]:
|
|
481
499
|
root_folder, profile, cmd_options = handle_robot_options(app, robot_options_and_args)
|
|
482
500
|
|
|
@@ -500,6 +518,8 @@ def handle_options(
|
|
|
500
518
|
by_longname,
|
|
501
519
|
exclude_by_longname,
|
|
502
520
|
search_matcher=search_matcher,
|
|
521
|
+
by_test_metadata=by_test_metadata,
|
|
522
|
+
exclude_by_test_metadata=exclude_by_test_metadata,
|
|
503
523
|
).parse_arguments((*cmd_options, "--runemptysuite", *robot_options_and_args))
|
|
504
524
|
|
|
505
525
|
settings = RobotSettings(options)
|
|
@@ -572,6 +592,29 @@ def handle_options(
|
|
|
572
592
|
raise UnknownError("Unexpected error happened.")
|
|
573
593
|
|
|
574
594
|
|
|
595
|
+
def _show_options(*, default: bool) -> List[Any]:
|
|
596
|
+
"""The flags of `all`, `tests` and `tasks` that add the tags and metadata lines in TEXT mode."""
|
|
597
|
+
return [
|
|
598
|
+
click.option(
|
|
599
|
+
"--show-tags / --no-show-tags",
|
|
600
|
+
"show_tags",
|
|
601
|
+
default=default,
|
|
602
|
+
show_default=True,
|
|
603
|
+
help="Show the tags that are present.",
|
|
604
|
+
),
|
|
605
|
+
click.option(
|
|
606
|
+
"--show-metadata / --no-show-metadata",
|
|
607
|
+
"show_metadata",
|
|
608
|
+
default=default,
|
|
609
|
+
show_default=True,
|
|
610
|
+
# test metadata exists since Robot Framework 7.5
|
|
611
|
+
cls=RobotVersionOption,
|
|
612
|
+
since=(7, 5),
|
|
613
|
+
help="Show the metadata of tests and tasks.",
|
|
614
|
+
),
|
|
615
|
+
]
|
|
616
|
+
|
|
617
|
+
|
|
575
618
|
def _filters_applied(search_substring: Optional[str], search_regex: Optional[str]) -> Optional[Dict[str, str]]:
|
|
576
619
|
if search_substring:
|
|
577
620
|
return {"search": search_substring}
|
|
@@ -585,13 +628,7 @@ def _filters_applied(search_substring: Optional[str], search_regex: Optional[str
|
|
|
585
628
|
add_help_option=True,
|
|
586
629
|
epilog="Use `-- --help` to see `robot` help.",
|
|
587
630
|
)
|
|
588
|
-
@
|
|
589
|
-
"--tags / --no-tags",
|
|
590
|
-
"show_tags",
|
|
591
|
-
default=True,
|
|
592
|
-
show_default=True,
|
|
593
|
-
help="Show the tags that are present.",
|
|
594
|
-
)
|
|
631
|
+
@add_options(*_show_options(default=True))
|
|
595
632
|
@add_options(*ROBOT_OPTIONS)
|
|
596
633
|
@add_options(*DISCOVER_SEARCH_OPTIONS)
|
|
597
634
|
@click.option(
|
|
@@ -606,10 +643,13 @@ def all(
|
|
|
606
643
|
app: Application,
|
|
607
644
|
full_paths: bool,
|
|
608
645
|
show_tags: bool,
|
|
646
|
+
show_metadata: bool,
|
|
609
647
|
search_substring: Optional[str],
|
|
610
648
|
search_regex: Optional[str],
|
|
611
649
|
by_longname: Tuple[str, ...],
|
|
612
650
|
exclude_by_longname: Tuple[str, ...],
|
|
651
|
+
by_test_metadata: Tuple[str, ...],
|
|
652
|
+
exclude_by_test_metadata: Tuple[str, ...],
|
|
613
653
|
robot_options_and_args: Tuple[str, ...],
|
|
614
654
|
) -> None:
|
|
615
655
|
"""\
|
|
@@ -629,7 +669,13 @@ def all(
|
|
|
629
669
|
|
|
630
670
|
matcher = make_search_matcher(search_substring, search_regex)
|
|
631
671
|
_suite, collector, diagnostics = handle_options(
|
|
632
|
-
app,
|
|
672
|
+
app,
|
|
673
|
+
by_longname,
|
|
674
|
+
exclude_by_longname,
|
|
675
|
+
robot_options_and_args,
|
|
676
|
+
search_matcher=matcher,
|
|
677
|
+
by_test_metadata=by_test_metadata,
|
|
678
|
+
exclude_by_test_metadata=exclude_by_test_metadata,
|
|
633
679
|
)
|
|
634
680
|
|
|
635
681
|
if collector.all.children:
|
|
@@ -639,6 +685,7 @@ def all(
|
|
|
639
685
|
collector.all.children[0],
|
|
640
686
|
collector.statistics,
|
|
641
687
|
show_tags=show_tags,
|
|
688
|
+
show_metadata=show_metadata,
|
|
642
689
|
full_paths=full_paths,
|
|
643
690
|
highlight=make_md_highlighter(search_substring, search_regex),
|
|
644
691
|
search_substring=search_substring,
|
|
@@ -666,15 +713,24 @@ def _test_or_tasks(
|
|
|
666
713
|
app: Application,
|
|
667
714
|
full_paths: bool,
|
|
668
715
|
show_tags: bool,
|
|
716
|
+
show_metadata: bool,
|
|
669
717
|
search_substring: Optional[str],
|
|
670
718
|
search_regex: Optional[str],
|
|
671
719
|
by_longname: Tuple[str, ...],
|
|
672
720
|
exclude_by_longname: Tuple[str, ...],
|
|
721
|
+
by_test_metadata: Tuple[str, ...],
|
|
722
|
+
exclude_by_test_metadata: Tuple[str, ...],
|
|
673
723
|
robot_options_and_args: Tuple[str, ...],
|
|
674
724
|
) -> None:
|
|
675
725
|
matcher = make_search_matcher(search_substring, search_regex)
|
|
676
726
|
_suite, collector, diagnostics = handle_options(
|
|
677
|
-
app,
|
|
727
|
+
app,
|
|
728
|
+
by_longname,
|
|
729
|
+
exclude_by_longname,
|
|
730
|
+
robot_options_and_args,
|
|
731
|
+
search_matcher=matcher,
|
|
732
|
+
by_test_metadata=by_test_metadata,
|
|
733
|
+
exclude_by_test_metadata=exclude_by_test_metadata,
|
|
678
734
|
)
|
|
679
735
|
|
|
680
736
|
if collector.all.children:
|
|
@@ -685,6 +741,7 @@ def _test_or_tasks(
|
|
|
685
741
|
collector.statistics,
|
|
686
742
|
selected_type=selected_type,
|
|
687
743
|
show_tags=show_tags,
|
|
744
|
+
show_metadata=show_metadata,
|
|
688
745
|
full_paths=full_paths,
|
|
689
746
|
highlight=make_md_highlighter(search_substring, search_regex),
|
|
690
747
|
search_substring=search_substring,
|
|
@@ -713,13 +770,7 @@ def _test_or_tasks(
|
|
|
713
770
|
add_help_option=True,
|
|
714
771
|
epilog="Use `-- --help` to see `robot` help.",
|
|
715
772
|
)
|
|
716
|
-
@
|
|
717
|
-
"--tags / --no-tags",
|
|
718
|
-
"show_tags",
|
|
719
|
-
default=False,
|
|
720
|
-
show_default=True,
|
|
721
|
-
help="Show the tags that are present.",
|
|
722
|
-
)
|
|
773
|
+
@add_options(*_show_options(default=False))
|
|
723
774
|
@click.option(
|
|
724
775
|
"--full-paths / --no-full-paths",
|
|
725
776
|
"full_paths",
|
|
@@ -734,10 +785,13 @@ def tests(
|
|
|
734
785
|
app: Application,
|
|
735
786
|
full_paths: bool,
|
|
736
787
|
show_tags: bool,
|
|
788
|
+
show_metadata: bool,
|
|
737
789
|
search_substring: Optional[str],
|
|
738
790
|
search_regex: Optional[str],
|
|
739
791
|
by_longname: Tuple[str, ...],
|
|
740
792
|
exclude_by_longname: Tuple[str, ...],
|
|
793
|
+
by_test_metadata: Tuple[str, ...],
|
|
794
|
+
exclude_by_test_metadata: Tuple[str, ...],
|
|
741
795
|
robot_options_and_args: Tuple[str, ...],
|
|
742
796
|
) -> None:
|
|
743
797
|
"""\
|
|
@@ -760,10 +814,13 @@ def tests(
|
|
|
760
814
|
app,
|
|
761
815
|
full_paths,
|
|
762
816
|
show_tags,
|
|
817
|
+
show_metadata,
|
|
763
818
|
search_substring,
|
|
764
819
|
search_regex,
|
|
765
820
|
by_longname,
|
|
766
821
|
exclude_by_longname,
|
|
822
|
+
by_test_metadata,
|
|
823
|
+
exclude_by_test_metadata,
|
|
767
824
|
robot_options_and_args,
|
|
768
825
|
)
|
|
769
826
|
|
|
@@ -773,13 +830,7 @@ def tests(
|
|
|
773
830
|
add_help_option=True,
|
|
774
831
|
epilog="Use `-- --help` to see `robot` help.",
|
|
775
832
|
)
|
|
776
|
-
@
|
|
777
|
-
"--tags / --no-tags",
|
|
778
|
-
"show_tags",
|
|
779
|
-
default=False,
|
|
780
|
-
show_default=True,
|
|
781
|
-
help="Show the tags that are present.",
|
|
782
|
-
)
|
|
833
|
+
@add_options(*_show_options(default=False))
|
|
783
834
|
@click.option(
|
|
784
835
|
"--full-paths / --no-full-paths",
|
|
785
836
|
"full_paths",
|
|
@@ -794,10 +845,13 @@ def tasks(
|
|
|
794
845
|
app: Application,
|
|
795
846
|
full_paths: bool,
|
|
796
847
|
show_tags: bool,
|
|
848
|
+
show_metadata: bool,
|
|
797
849
|
search_substring: Optional[str],
|
|
798
850
|
search_regex: Optional[str],
|
|
799
851
|
by_longname: Tuple[str, ...],
|
|
800
852
|
exclude_by_longname: Tuple[str, ...],
|
|
853
|
+
by_test_metadata: Tuple[str, ...],
|
|
854
|
+
exclude_by_test_metadata: Tuple[str, ...],
|
|
801
855
|
robot_options_and_args: Tuple[str, ...],
|
|
802
856
|
) -> None:
|
|
803
857
|
"""\
|
|
@@ -819,10 +873,13 @@ def tasks(
|
|
|
819
873
|
app,
|
|
820
874
|
full_paths,
|
|
821
875
|
show_tags,
|
|
876
|
+
show_metadata,
|
|
822
877
|
search_substring,
|
|
823
878
|
search_regex,
|
|
824
879
|
by_longname,
|
|
825
880
|
exclude_by_longname,
|
|
881
|
+
by_test_metadata,
|
|
882
|
+
exclude_by_test_metadata,
|
|
826
883
|
robot_options_and_args,
|
|
827
884
|
)
|
|
828
885
|
|
|
@@ -849,6 +906,8 @@ def suites(
|
|
|
849
906
|
search_regex: Optional[str],
|
|
850
907
|
by_longname: Tuple[str, ...],
|
|
851
908
|
exclude_by_longname: Tuple[str, ...],
|
|
909
|
+
by_test_metadata: Tuple[str, ...],
|
|
910
|
+
exclude_by_test_metadata: Tuple[str, ...],
|
|
852
911
|
robot_options_and_args: Tuple[str, ...],
|
|
853
912
|
) -> None:
|
|
854
913
|
"""\
|
|
@@ -868,7 +927,13 @@ def suites(
|
|
|
868
927
|
|
|
869
928
|
matcher = make_search_matcher(search_substring, search_regex)
|
|
870
929
|
_suite, collector, diagnostics = handle_options(
|
|
871
|
-
app,
|
|
930
|
+
app,
|
|
931
|
+
by_longname,
|
|
932
|
+
exclude_by_longname,
|
|
933
|
+
robot_options_and_args,
|
|
934
|
+
search_matcher=matcher,
|
|
935
|
+
by_test_metadata=by_test_metadata,
|
|
936
|
+
exclude_by_test_metadata=exclude_by_test_metadata,
|
|
872
937
|
)
|
|
873
938
|
|
|
874
939
|
if collector.all.children:
|
|
@@ -890,7 +955,8 @@ def suites(
|
|
|
890
955
|
else:
|
|
891
956
|
app.print_data(
|
|
892
957
|
ResultItem(
|
|
893
|
-
|
|
958
|
+
# a flat list: the suites without their nested suites and tests
|
|
959
|
+
[dataclasses.replace(suite, children=None) for suite in collector.suites],
|
|
894
960
|
diagnostics,
|
|
895
961
|
filters_applied=_filters_applied(search_substring, search_regex),
|
|
896
962
|
supports_parse_include=SUPPORTS_PARSE_INCLUDE,
|
|
@@ -945,6 +1011,8 @@ def tags(
|
|
|
945
1011
|
search_regex: Optional[str],
|
|
946
1012
|
by_longname: Tuple[str, ...],
|
|
947
1013
|
exclude_by_longname: Tuple[str, ...],
|
|
1014
|
+
by_test_metadata: Tuple[str, ...],
|
|
1015
|
+
exclude_by_test_metadata: Tuple[str, ...],
|
|
948
1016
|
robot_options_and_args: Tuple[str, ...],
|
|
949
1017
|
) -> None:
|
|
950
1018
|
"""\
|
|
@@ -965,7 +1033,13 @@ def tags(
|
|
|
965
1033
|
|
|
966
1034
|
matcher = make_search_matcher(search_substring, search_regex)
|
|
967
1035
|
_suite, collector, diagnostics = handle_options(
|
|
968
|
-
app,
|
|
1036
|
+
app,
|
|
1037
|
+
by_longname,
|
|
1038
|
+
exclude_by_longname,
|
|
1039
|
+
robot_options_and_args,
|
|
1040
|
+
search_matcher=matcher,
|
|
1041
|
+
by_test_metadata=by_test_metadata,
|
|
1042
|
+
exclude_by_test_metadata=exclude_by_test_metadata,
|
|
969
1043
|
)
|
|
970
1044
|
|
|
971
1045
|
if collector.all.children:
|
|
@@ -995,6 +1069,149 @@ def tags(
|
|
|
995
1069
|
)
|
|
996
1070
|
|
|
997
1071
|
|
|
1072
|
+
def _metadata_index(
|
|
1073
|
+
items: Iterable[Tuple[TestItem, Optional[Mapping[str, str]]]],
|
|
1074
|
+
) -> Dict[str, Dict[str, List[TestItem]]]:
|
|
1075
|
+
"""name → value → items, built from the same entries `--by-test-metadata`
|
|
1076
|
+
matches. Names and values that compare equal are one entry, shown in the
|
|
1077
|
+
first spelling found; both are sorted by their normalised form."""
|
|
1078
|
+
names: Dict[str, Tuple[str, Dict[str, Tuple[str, List[TestItem]]]]] = {}
|
|
1079
|
+
for item, metadata in items:
|
|
1080
|
+
for name, line in metadata_entries(metadata or {}):
|
|
1081
|
+
_name, values = names.setdefault(normalize(name, ignore="_"), (name, {}))
|
|
1082
|
+
_value, value_items = values.setdefault(normalize(line, ignore="_"), (line, []))
|
|
1083
|
+
if not value_items or value_items[-1] is not item:
|
|
1084
|
+
value_items.append(item)
|
|
1085
|
+
return {
|
|
1086
|
+
name: {value: value_items for _, (value, value_items) in sorted(values.items())}
|
|
1087
|
+
for _, (name, values) in sorted(names.items())
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
|
|
1091
|
+
@discover.command(
|
|
1092
|
+
context_settings={"allow_extra_args": True, "ignore_unknown_options": True},
|
|
1093
|
+
add_help_option=True,
|
|
1094
|
+
epilog="Use `-- --help` to see `robot` help.",
|
|
1095
|
+
# test metadata exists since Robot Framework 7.5
|
|
1096
|
+
cls=RobotVersionCommand,
|
|
1097
|
+
since=(7, 5),
|
|
1098
|
+
)
|
|
1099
|
+
@click.option(
|
|
1100
|
+
"--values / --no-values",
|
|
1101
|
+
"show_values",
|
|
1102
|
+
default=False,
|
|
1103
|
+
show_default=True,
|
|
1104
|
+
help="Show the values of each metadata name.",
|
|
1105
|
+
)
|
|
1106
|
+
@click.option(
|
|
1107
|
+
"--tests / --no-tests",
|
|
1108
|
+
"show_tests",
|
|
1109
|
+
default=False,
|
|
1110
|
+
show_default=True,
|
|
1111
|
+
help="Show the tests that have the metadata.",
|
|
1112
|
+
)
|
|
1113
|
+
@click.option(
|
|
1114
|
+
"--tasks / --no-tasks",
|
|
1115
|
+
"show_tasks",
|
|
1116
|
+
default=False,
|
|
1117
|
+
show_default=True,
|
|
1118
|
+
help="Show the tasks that have the metadata.",
|
|
1119
|
+
)
|
|
1120
|
+
@click.option(
|
|
1121
|
+
"--suites / --no-suites",
|
|
1122
|
+
"show_suites",
|
|
1123
|
+
default=False,
|
|
1124
|
+
show_default=True,
|
|
1125
|
+
help="Show the suites that have the metadata.",
|
|
1126
|
+
)
|
|
1127
|
+
@click.option(
|
|
1128
|
+
"--full-paths / --no-full-paths",
|
|
1129
|
+
"full_paths",
|
|
1130
|
+
default=False,
|
|
1131
|
+
show_default=True,
|
|
1132
|
+
help="Show full paths instead of relative.",
|
|
1133
|
+
)
|
|
1134
|
+
@add_options(*ROBOT_OPTIONS)
|
|
1135
|
+
@add_options(*DISCOVER_SEARCH_OPTIONS)
|
|
1136
|
+
@pass_application
|
|
1137
|
+
def metadata(
|
|
1138
|
+
app: Application,
|
|
1139
|
+
show_values: bool,
|
|
1140
|
+
show_tests: bool,
|
|
1141
|
+
show_tasks: bool,
|
|
1142
|
+
show_suites: bool,
|
|
1143
|
+
full_paths: bool,
|
|
1144
|
+
search_substring: Optional[str],
|
|
1145
|
+
search_regex: Optional[str],
|
|
1146
|
+
by_longname: Tuple[str, ...],
|
|
1147
|
+
exclude_by_longname: Tuple[str, ...],
|
|
1148
|
+
by_test_metadata: Tuple[str, ...],
|
|
1149
|
+
exclude_by_test_metadata: Tuple[str, ...],
|
|
1150
|
+
robot_options_and_args: Tuple[str, ...],
|
|
1151
|
+
) -> None:
|
|
1152
|
+
"""\
|
|
1153
|
+
Discover the metadata of tests, tasks and suites with the selected
|
|
1154
|
+
configuration, profiles, options and arguments.
|
|
1155
|
+
|
|
1156
|
+
Test and task metadata is what `--by-test-metadata` selects by; suite
|
|
1157
|
+
metadata, including `--metadata` given to Robot, is listed in its own
|
|
1158
|
+
section.
|
|
1159
|
+
|
|
1160
|
+
\b
|
|
1161
|
+
Examples:
|
|
1162
|
+
```
|
|
1163
|
+
robotcode discover metadata
|
|
1164
|
+
robotcode discover metadata --values --tests
|
|
1165
|
+
robotcode --format json discover metadata
|
|
1166
|
+
```
|
|
1167
|
+
"""
|
|
1168
|
+
|
|
1169
|
+
matcher = make_search_matcher(search_substring, search_regex)
|
|
1170
|
+
_suite, collector, diagnostics = handle_options(
|
|
1171
|
+
app,
|
|
1172
|
+
by_longname,
|
|
1173
|
+
exclude_by_longname,
|
|
1174
|
+
robot_options_and_args,
|
|
1175
|
+
search_matcher=matcher,
|
|
1176
|
+
by_test_metadata=by_test_metadata,
|
|
1177
|
+
exclude_by_test_metadata=exclude_by_test_metadata,
|
|
1178
|
+
)
|
|
1179
|
+
|
|
1180
|
+
if collector.all.children:
|
|
1181
|
+
test_metadata = _metadata_index((item, item.metadata) for item in collector.test_and_tasks)
|
|
1182
|
+
suite_metadata = _metadata_index(collector.suite_metadata)
|
|
1183
|
+
|
|
1184
|
+
if app.config.output_format is None or app.config.output_format == OutputFormat.TEXT:
|
|
1185
|
+
app.echo_as_markdown(
|
|
1186
|
+
_render.render_metadata(
|
|
1187
|
+
test_metadata,
|
|
1188
|
+
suite_metadata,
|
|
1189
|
+
collector.statistics,
|
|
1190
|
+
show_values=show_values,
|
|
1191
|
+
show_tests=show_tests,
|
|
1192
|
+
show_tasks=show_tasks,
|
|
1193
|
+
show_suites=show_suites,
|
|
1194
|
+
full_paths=full_paths,
|
|
1195
|
+
highlight=make_md_highlighter(search_substring, search_regex),
|
|
1196
|
+
search_substring=search_substring,
|
|
1197
|
+
search_regex=search_regex,
|
|
1198
|
+
diagnostics=diagnostics,
|
|
1199
|
+
show_diagnostics=app.show_diagnostics,
|
|
1200
|
+
root_folder=app.root_folder,
|
|
1201
|
+
)
|
|
1202
|
+
)
|
|
1203
|
+
|
|
1204
|
+
else:
|
|
1205
|
+
app.print_data(
|
|
1206
|
+
MetadataResult(
|
|
1207
|
+
test_metadata,
|
|
1208
|
+
suite_metadata,
|
|
1209
|
+
filters_applied=_filters_applied(search_substring, search_regex),
|
|
1210
|
+
),
|
|
1211
|
+
remove_defaults=True,
|
|
1212
|
+
)
|
|
1213
|
+
|
|
1214
|
+
|
|
998
1215
|
@discover.command(add_help_option=True)
|
|
999
1216
|
@pass_application
|
|
1000
1217
|
def info(app: Application) -> None:
|
|
@@ -1086,8 +1303,10 @@ def files(
|
|
|
1086
1303
|
# Match the other discover subcommands: chdir into root_folder so
|
|
1087
1304
|
# `get_rel_source` produces paths relative to the project root rather
|
|
1088
1305
|
# than to whatever the caller's process cwd happens to be.
|
|
1089
|
-
with app.chdir(root_folder):
|
|
1090
|
-
|
|
1306
|
+
with app.chdir(root_folder) as orig_folder:
|
|
1307
|
+
# Without duplicates, in the given order: the files come out in the order of the paths. Paths from the
|
|
1308
|
+
# command line count from the folder the command was started in, the configured ones from the root.
|
|
1309
|
+
search_paths = dict.fromkeys(
|
|
1091
1310
|
(
|
|
1092
1311
|
(
|
|
1093
1312
|
[*(app.config.default_paths if app.config.default_paths else ())]
|
|
@@ -1097,7 +1316,7 @@ def files(
|
|
|
1097
1316
|
else [profile.paths]
|
|
1098
1317
|
)
|
|
1099
1318
|
if not paths
|
|
1100
|
-
else [str(p) for p in paths]
|
|
1319
|
+
else [str(p if orig_folder is None else Path(orig_folder, p)) for p in paths]
|
|
1101
1320
|
)
|
|
1102
1321
|
)
|
|
1103
1322
|
if not search_paths:
|
robotcode/runner/cli/libdoc.py
CHANGED
|
@@ -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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
|
robotcode/runner/cli/rebot.py
CHANGED
|
@@ -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
|
|
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
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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()
|
|
@@ -76,6 +76,7 @@ class TestResultItem(CamelSnakeMixin):
|
|
|
76
76
|
message: str
|
|
77
77
|
full_message: Optional[str] = None
|
|
78
78
|
tags: Optional[List[str]] = None
|
|
79
|
+
metadata: Optional[Dict[str, str]] = None
|
|
79
80
|
elapsed_seconds: Optional[float] = None
|
|
80
81
|
start_time: Optional[str] = None
|
|
81
82
|
source: Optional[str] = None
|
|
@@ -152,6 +153,9 @@ class LogTest(CamelSnakeMixin):
|
|
|
152
153
|
# suite. Lets the TEXT renderer group tests under suite headers and
|
|
153
154
|
# JSON consumers cross-reference `LogResult.suites`.
|
|
154
155
|
suite: Optional[str] = None
|
|
156
|
+
# The test's `[Metadata]` (Robot Framework 7.5+), same shape as
|
|
157
|
+
# `LogSuite.metadata`.
|
|
158
|
+
metadata: Optional[Dict[str, str]] = None
|
|
155
159
|
|
|
156
160
|
|
|
157
161
|
@dataclass
|
|
@@ -29,6 +29,7 @@ from .._markdown import (
|
|
|
29
29
|
make_md_highlighter,
|
|
30
30
|
md_escape,
|
|
31
31
|
md_table,
|
|
32
|
+
metadata_md,
|
|
32
33
|
path_paren,
|
|
33
34
|
timing_suffix,
|
|
34
35
|
)
|
|
@@ -51,7 +52,8 @@ from ._models import (
|
|
|
51
52
|
# `show`); `LogTest` is the `log`-specific record carrying a body tree.
|
|
52
53
|
# Both expose the fields the per-test renderers need (`full_name`,
|
|
53
54
|
# `status`, `message`, `source`, `rel_source`, `lineno`, `elapsed_seconds`,
|
|
54
|
-
# `start_time`), so we treat them interchangeably at the renderer
|
|
55
|
+
# `start_time`, `metadata`), so we treat them interchangeably at the renderer
|
|
56
|
+
# level.
|
|
55
57
|
_TestLike = Union[TestResultItem, LogTest]
|
|
56
58
|
|
|
57
59
|
_LEVEL_ORDER = {"TRACE": 0, "DEBUG": 1, "INFO": 2, "WARN": 3, "ERROR": 4, "FAIL": 5}
|
|
@@ -172,6 +174,7 @@ def render_show(
|
|
|
172
174
|
data: ShowResult,
|
|
173
175
|
*,
|
|
174
176
|
show_tags: bool,
|
|
177
|
+
show_metadata: bool = False,
|
|
175
178
|
full_paths: bool = False,
|
|
176
179
|
show_timing: bool = False,
|
|
177
180
|
sort_field: Optional[str] = None,
|
|
@@ -197,6 +200,8 @@ def render_show(
|
|
|
197
200
|
if show_tags and t.tags:
|
|
198
201
|
tags = ", ".join(highlight_md(md_escape(tag), highlight) for tag in t.tags)
|
|
199
202
|
out.append(f" - _Tags:_ {tags}")
|
|
203
|
+
if show_metadata and t.metadata:
|
|
204
|
+
out.append(f" - _Metadata:_ {metadata_md(t.metadata, highlight)}")
|
|
200
205
|
|
|
201
206
|
if data.truncated:
|
|
202
207
|
out.append("")
|
|
@@ -490,6 +495,9 @@ def _test_header_md(
|
|
|
490
495
|
head += _source_paren(t, full_paths=full_paths)
|
|
491
496
|
head += f" {bold_status(t.status)}"
|
|
492
497
|
head += timing_suffix(t.elapsed_seconds, t.start_time, show_timing=show_timing)
|
|
498
|
+
if t.metadata:
|
|
499
|
+
# a paragraph, not a list: a list would merge with the list of the test body below
|
|
500
|
+
head += f"\n\n_Metadata:_ {metadata_md(t.metadata, highlight)}"
|
|
493
501
|
return head
|
|
494
502
|
|
|
495
503
|
|