From 347b7fef6756f52058fce004e70f446e21f834da Mon Sep 17 00:00:00 2001 From: Andreas Stefl Date: Tue, 27 Oct 2026 21:56:14 +0100 Subject: [PATCH] Report a directory missing in the reference, instead of a traceback collect_tasks made each path relative to the root of tree A. A directory that only tree B has is not below that root, so relative_to raised a ValueError, and the comparison stopped with a traceback. The walk now carries the relative path itself, which is the same for both trees. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01FW6WZZjzbcNvNppdDrFxKB --- src/htmlcmp/compare_output_cli.py | 25 +++++++++---------------- tests/test_collect_tasks.py | 30 ++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 16 deletions(-) create mode 100644 tests/test_collect_tasks.py diff --git a/src/htmlcmp/compare_output_cli.py b/src/htmlcmp/compare_output_cli.py index 159412f..7560134 100644 --- a/src/htmlcmp/compare_output_cli.py +++ b/src/htmlcmp/compare_output_cli.py @@ -57,21 +57,19 @@ def __init__(self, rel: Path, kind: str, reason: str): def collect_tasks( - a: Path, b: Path, root: Path = None, diff_output: Path = None, retries: int = 1 + a: Path, b: Path, rel: Path = Path(), diff_output: Path = None, retries: int = 1 ) -> tuple[list[Task], list[Failure]]: """Walk both trees once and return (comparable tasks, structural failures). Structural failures are files/directories present on only one side; they are - known to differ up-front and never get submitted to the executor. + known to differ up-front and never get submitted to the executor. `rel` is + the path of `a` and `b` below the roots of the walk. """ if not isinstance(a, Path) or not isinstance(b, Path): raise TypeError("Both arguments must be of type Path") if not a.is_dir() or not b.is_dir(): raise FileNotFoundError("Both arguments must be directories") - if root is None: - root = a - tasks: list[Task] = [] failures: list[Failure] = [] @@ -86,11 +84,10 @@ def collect_tasks( } for name in sorted(left_files | right_files): - rel = (a / name).relative_to(root) if name in left_files and name in right_files: tasks.append( Task( - rel, + rel / name, a / name, b / name, None if diff_output is None else diff_output / name, @@ -98,9 +95,9 @@ def collect_tasks( ) ) elif name in left_files: - failures.append(Failure(rel, "missing", "missing in monitored (B)")) + failures.append(Failure(rel / name, "missing", "missing in monitored (B)")) else: - failures.append(Failure(rel, "missing", "missing in reference (A)")) + failures.append(Failure(rel / name, "missing", "missing in reference (A)")) left_dirs = {name for name in left if (a / name).is_dir()} right_dirs = {name for name in right if (b / name).is_dir()} @@ -109,7 +106,7 @@ def collect_tasks( sub_tasks, sub_failures = collect_tasks( a / name, b / name, - root=root, + rel=rel / name, diff_output=None if diff_output is None else diff_output / name, retries=retries, ) @@ -117,13 +114,9 @@ def collect_tasks( failures.extend(sub_failures) for name in sorted(left_dirs - right_dirs): - failures.append( - Failure((a / name).relative_to(root), "missing", "missing in monitored (B)") - ) + failures.append(Failure(rel / name, "missing", "missing in monitored (B)")) for name in sorted(right_dirs - left_dirs): - failures.append( - Failure((b / name).relative_to(root), "missing", "missing in reference (A)") - ) + failures.append(Failure(rel / name, "missing", "missing in reference (A)")) return tasks, failures diff --git a/tests/test_collect_tasks.py b/tests/test_collect_tasks.py new file mode 100644 index 0000000..734f93c --- /dev/null +++ b/tests/test_collect_tasks.py @@ -0,0 +1,30 @@ +"""A file or directory on one side only is a failure, named below the roots.""" + +from pathlib import Path + +from htmlcmp.compare_output_cli import collect_tasks + + +def write(path: Path): + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text("") + + +def test_one_sided_entries_are_named_below_the_roots(tmp_path): + a, b = tmp_path / "a", tmp_path / "b" + write(a / "both" / "page.html") + write(b / "both" / "page.html") + write(a / "both" / "only_a.html") + write(b / "both" / "only_b.html") + write(a / "dir_a" / "page.html") + write(b / "dir_b" / "page.html") + + tasks, failures = collect_tasks(a, b) + + assert [task.rel for task in tasks] == [Path("both/page.html")] + assert sorted((str(f.rel), f.reason) for f in failures) == [ + ("both/only_a.html", "missing in monitored (B)"), + ("both/only_b.html", "missing in reference (A)"), + ("dir_a", "missing in monitored (B)"), + ("dir_b", "missing in reference (A)"), + ]