Skip to content

Commit e4ff456

Browse files
committed
fix(runner): keep relSource in results JSON regardless of --full-paths
Aligns `results` with `discover`, which always carries both `source` (absolute) and `relSource` in its JSON output. Two changes: - `_make_test_item` / `_make_diff_change` / `LogTest` stop conditionally setting `rel_source=None` when `--full-paths` is on. The flag is now purely a TEXT-rendering hint. - `_rel_to_cwd` falls back to the original path (instead of `None`) when the source isn't anchored under cwd, mirroring `discover.get_rel_source`. Consumers like the VS Code extension can now rely on `relSource` being present whenever `source` is. `_make_diff_change` and `_make_test_item` no longer take `full_paths`, since the parameter became unused after the change. Call sites updated. Plus acceptance tests covering the harmonised behaviour on `summary`, `show`, `log` and `diff`, and a TEXT-only `--full-paths` test on `discover tags` (the last subcommand without coverage).
1 parent b92acdc commit e4ff456

6 files changed

Lines changed: 107 additions & 27 deletions

File tree

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

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -264,11 +264,7 @@ def summary(
264264
)
265265
match = make_search_matcher(search_substring, search_regex)
266266
counts = _collect_counts(execution.suite, status_filters, match)
267-
failures = (
268-
_collect_failures(execution.suite, status_filters, full_paths=full_paths, match=match)
269-
if show_failures
270-
else None
271-
)
267+
failures = _collect_failures(execution.suite, status_filters, match=match) if show_failures else None
272268
exec_msg_counts = _count_execution_messages(getattr(execution, "errors", None))
273269
msg_counts = _count_all_messages(execution)
274270

@@ -419,7 +415,7 @@ def show(
419415
wanted = _normalise_statuses(status_filters)
420416
match = make_search_matcher(search_substring, search_regex)
421417
all_items = [
422-
_make_test_item(t, message_chars=message_chars, full_paths=full_paths)
418+
_make_test_item(t, message_chars=message_chars)
423419
for t in _iter_all_tests(execution.suite)
424420
if (not wanted or t.status in wanted) and (match is None or _raw_test_search_matches(t, match))
425421
]
@@ -635,7 +631,7 @@ def log(
635631
body=body,
636632
artifacts=artefacts or None,
637633
source=src_str,
638-
rel_source=None if full_paths else _rel_to_cwd(src_str),
634+
rel_source=_rel_to_cwd(src_str),
639635
lineno=getattr(test, "lineno", None) or None,
640636
elapsed_seconds=_elapsed_seconds(test),
641637
start_time=_iso(_start_time(test)),
@@ -1008,11 +1004,11 @@ def _eligible(t: Any) -> bool:
10081004
for name, cur in current_tests.items():
10091005
base = baseline_tests.get(name)
10101006
if base is None:
1011-
added.append(_make_diff_change(name, None, cur, message_chars=message_chars, full_paths=full_paths))
1007+
added.append(_make_diff_change(name, None, cur, message_chars=message_chars))
10121008
continue
10131009
if base.status == cur.status:
10141010
continue
1015-
change = _make_diff_change(name, base, cur, message_chars=message_chars, full_paths=full_paths)
1011+
change = _make_diff_change(name, base, cur, message_chars=message_chars)
10161012
if base.status == "PASS" and cur.status in ("FAIL", "ERROR"):
10171013
new_failures.append(change)
10181014
elif base.status in ("FAIL", "ERROR", "SKIP") and cur.status == "PASS":
@@ -1022,7 +1018,7 @@ def _eligible(t: Any) -> bool:
10221018

10231019
for name, base in baseline_tests.items():
10241020
if name not in current_tests:
1025-
removed.append(_make_diff_change(name, base, None, message_chars=message_chars, full_paths=full_paths))
1021+
removed.append(_make_diff_change(name, base, None, message_chars=message_chars))
10261022

10271023
selected = {c.lower() for c in only_categories} if only_categories else None
10281024

@@ -1072,7 +1068,6 @@ def _make_diff_change(
10721068
current_test: Any,
10731069
*,
10741070
message_chars: int,
1075-
full_paths: bool,
10761071
) -> DiffChange:
10771072
"""Build a DiffChange capturing baseline/current status, message, and source."""
10781073
src_test = current_test if current_test is not None else baseline_test
@@ -1089,7 +1084,7 @@ def _make_diff_change(
10891084
if current_test is not None
10901085
else None,
10911086
source=src_str,
1092-
rel_source=None if full_paths else _rel_to_cwd(src_str),
1087+
rel_source=_rel_to_cwd(src_str),
10931088
lineno=getattr(src_test, "lineno", None) or None,
10941089
)
10951090

@@ -1471,23 +1466,29 @@ def _sort_items(items: List[TestResultItem], field: Optional[str], reverse: bool
14711466

14721467

14731468
def _rel_to_cwd(p: Optional[str]) -> Optional[str]:
1469+
"""Return the source path as relative-to-cwd if possible, else the
1470+
original path. Mirrors `discover.get_rel_source`: `rel_source` in
1471+
JSON is always non-None when `source` is set, falling back to the
1472+
absolute path when the source isn't anchored under cwd. The schema
1473+
invariant matters for consumers like the VS Code extension which
1474+
rely on `relSource` being present.
1475+
"""
14741476
if p is None:
14751477
return None
14761478
try:
14771479
return str(Path(p).relative_to(Path.cwd()).as_posix())
14781480
except ValueError:
1479-
return None
1481+
return p
14801482

14811483

14821484
def _make_file_info(path: Path) -> ResultFileInfo:
1483-
rel = _rel_to_cwd(str(path))
14841485
return ResultFileInfo(
14851486
source=str(path),
1486-
rel_source=rel if rel and rel != str(path) else None,
1487+
rel_source=_rel_to_cwd(str(path)),
14871488
)
14881489

14891490

1490-
def _make_test_item(test: Any, *, message_chars: int, full_paths: bool) -> TestResultItem:
1491+
def _make_test_item(test: Any, *, message_chars: int) -> TestResultItem:
14911492
msg = test.message or ""
14921493
first_line = msg.splitlines()[0] if msg else ""
14931494
truncated_msg = first_line
@@ -1513,7 +1514,7 @@ def _make_test_item(test: Any, *, message_chars: int, full_paths: bool) -> TestR
15131514
elapsed_seconds=_elapsed_seconds(test),
15141515
start_time=_iso(_start_time(test)),
15151516
source=src_str,
1516-
rel_source=None if full_paths else rel_src,
1517+
rel_source=rel_src,
15171518
lineno=getattr(test, "lineno", None) or None,
15181519
)
15191520

@@ -1522,7 +1523,6 @@ def _collect_failures(
15221523
suite: Any,
15231524
status_filters: Tuple[str, ...],
15241525
*,
1525-
full_paths: bool = False,
15261526
match: Optional["SearchMatcher"] = None,
15271527
) -> List[TestResultItem]:
15281528
"""Return all failed tests (post-tree-filter, after status filter) with msgs."""
@@ -1535,7 +1535,7 @@ def _collect_failures(
15351535
continue
15361536
if match is not None and not _raw_test_search_matches(t, match):
15371537
continue
1538-
out.append(_make_test_item(t, message_chars=0, full_paths=full_paths))
1538+
out.append(_make_test_item(t, message_chars=0))
15391539
return out
15401540

15411541

tests/robotcode/runner/cli/discover/test_tags.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,3 +68,14 @@ def test_tags_dict_values_are_testitems(json_discover: JsonRunner, tagged_suite:
6868
assert "type" in item
6969
assert "longname" in item
7070
assert "source" in item
71+
72+
73+
def test_tags_full_paths_text_uses_absolute(robotcode_cli: CliRunner, tagged_suite: Path) -> None:
74+
"""`--full-paths --tests` in TEXT mode prints the absolute child path."""
75+
abs_str = str(tagged_suite.resolve())
76+
full = robotcode_cli(
77+
["--root", str(tagged_suite.parent), "discover", "tags", "--tests", "--full-paths", str(tagged_suite)]
78+
)
79+
short = robotcode_cli(["--root", str(tagged_suite.parent), "discover", "tags", "--tests", str(tagged_suite)])
80+
assert abs_str in full.stdout
81+
assert abs_str not in short.stdout

tests/robotcode/runner/cli/results/test_diff.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,3 +212,33 @@ def test_diff_identity_yields_no_changes(robotcode_cli: CliRunner, diff_baseline
212212
assert data.get("statusChanges", []) == []
213213
assert data.get("added", []) == []
214214
assert data.get("removed", []) == []
215+
216+
217+
# ---------------------------------------------------------------------------
218+
# --full-paths
219+
# ---------------------------------------------------------------------------
220+
221+
222+
def test_diff_full_paths_keeps_rel_source(
223+
robotcode_cli: CliRunner,
224+
diff_baseline_output: Path,
225+
diff_current_output: Path,
226+
) -> None:
227+
"""`--full-paths` doesn't strip `relSource` from the JSON — both
228+
fields are always present so consumers have a stable schema."""
229+
data = _diff_via_cli(robotcode_cli, diff_baseline_output, diff_current_output, "--full-paths")
230+
new_failure = data["newFailures"][0]
231+
src = new_failure.get("source")
232+
assert src is not None
233+
assert Path(src).is_absolute()
234+
assert new_failure.get("relSource") is not None
235+
236+
237+
def test_diff_default_includes_rel_source(
238+
robotcode_cli: CliRunner,
239+
diff_baseline_output: Path,
240+
diff_current_output: Path,
241+
) -> None:
242+
data = _diff_via_cli(robotcode_cli, diff_baseline_output, diff_current_output)
243+
new_failure = data["newFailures"][0]
244+
assert new_failure.get("relSource") is not None

tests/robotcode/runner/cli/results/test_log.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -220,13 +220,21 @@ def test_log_max_depth_collapses_text(text_result: CliRunner, basic_output: Path
220220
assert "--max-depth 1" in plain
221221

222222

223-
def test_log_full_paths(json_result: JsonRunner, basic_output: Path) -> None:
224-
"""`--full-paths` makes `tests[].source` absolute."""
223+
def test_log_full_paths_keeps_rel_source(json_result: JsonRunner, basic_output: Path) -> None:
224+
"""`--full-paths` is a TEXT-render hint; JSON keeps both `source`
225+
(absolute) and `relSource` so consumers have a stable schema."""
225226
data = json_result("log", "--full-paths", output_path=basic_output)
226227
for t in data["tests"]:
227228
src = t.get("source")
228229
assert src is not None
229230
assert Path(src).is_absolute()
231+
assert t.get("relSource") is not None
232+
233+
234+
def test_log_default_includes_rel_source(json_result: JsonRunner, basic_output: Path) -> None:
235+
data = json_result("log", output_path=basic_output)
236+
for t in data["tests"]:
237+
assert t.get("relSource") is not None
230238

231239

232240
# ---------------------------------------------------------------------------

tests/robotcode/runner/cli/results/test_show.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -185,20 +185,22 @@ def test_show_search_highlight_visible_in_text(text_result: CliRunner, basic_out
185185
# ---------------------------------------------------------------------------
186186

187187

188-
def test_show_full_paths(json_result: JsonRunner, basic_output: Path) -> None:
189-
"""`--full-paths` makes `tests[].source` an absolute path."""
188+
def test_show_full_paths_keeps_both_source_fields(json_result: JsonRunner, basic_output: Path) -> None:
189+
"""`--full-paths` only affects TEXT rendering. JSON always carries
190+
both `source` (absolute) and `relSource` (relative-to-cwd or omitted
191+
when not anchored) so consumers like the VS Code extension can
192+
consistently rely on the schema."""
190193
data = json_result("show", "--full-paths", output_path=basic_output)
191194
for t in data["tests"]:
192195
src = t.get("source")
193196
assert src is not None
194197
assert Path(src).is_absolute()
195198

196199

197-
def test_show_no_full_paths_uses_relative(json_result: JsonRunner, basic_output: Path) -> None:
198-
"""Without the flag, sources are tracked relative (or absolute when no anchor)."""
200+
def test_show_default_includes_rel_source(json_result: JsonRunner, basic_output: Path) -> None:
201+
"""Without the flag, JSON has both fields (relSource present when
202+
the source is anchored under cwd)."""
199203
data = json_result("show", output_path=basic_output)
200-
# We don't assert relative vs absolute strictly — depends on the run cwd —
201-
# but the field must at least be present and non-empty for every test.
202204
for t in data["tests"]:
203205
assert get_field(t, "source", "relSource") is not None
204206

tests/robotcode/runner/cli/results/test_summary.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,32 @@ def test_summary_text_output_contains_counts(text_result: CliRunner, basic_outpu
5454
# Counts are rendered as a line containing the totals
5555
assert "5" in plain # total
5656
assert "FAIL" in plain # overall status
57+
58+
59+
# ---------------------------------------------------------------------------
60+
# --full-paths
61+
# ---------------------------------------------------------------------------
62+
63+
64+
def test_summary_full_paths_keeps_rel_source(json_result: JsonRunner, basic_output: Path) -> None:
65+
"""`--full-paths` is a TEXT-rendering hint; JSON always carries
66+
both `source` (absolute) and `relSource` so consumers like the
67+
VS Code extension can rely on the schema being stable across the
68+
flag."""
69+
data = json_result("summary", "--failures", "--full-paths", output_path=basic_output)
70+
failures = data["failures"]
71+
assert failures, "expected at least one failure"
72+
failure = failures[0]
73+
src = get_field(failure, "source")
74+
assert src is not None
75+
assert Path(src).is_absolute()
76+
# rel_source is *still* present (it would have been omitted under
77+
# the old behaviour where `--full-paths` cleared it).
78+
assert get_field(failure, "relSource", "rel_source") is not None
79+
80+
81+
def test_summary_default_includes_rel_source(json_result: JsonRunner, basic_output: Path) -> None:
82+
data = json_result("summary", "--failures", output_path=basic_output)
83+
failures = data["failures"]
84+
assert failures
85+
assert get_field(failures[0], "relSource", "rel_source") is not None

0 commit comments

Comments
 (0)