Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/rdc/commands/doctor.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,14 @@ def _import_renderdoc() -> tuple[Any | None, CheckResult]:
False,
f"incompatible at {diag.candidate_path} -- rebuild renderdoc for current Python",
)
if diag is not None and diag.result == ProbeResult.IMPORT_FAILED:
return None, CheckResult(
"renderdoc-module",
False,
f"found at {diag.candidate_path} but failed to import"
" -- likely built for a different Python (ABI mismatch);"
" rebuild for current Python",
)
Comment on lines +64 to +71

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Prevent generic build hint for ABI/import failures.

Line 64-71 now returns a precise ABI/import-failure detail, but doctor_cmd() still prints the generic “build from source” hint for every renderdoc-module failure. That keeps the old misleading guidance in the IMPORT_FAILED path.

Suggested fix
diff --git a/src/rdc/commands/doctor.py b/src/rdc/commands/doctor.py
@@
-            if result.name == "renderdoc-module":
+            if result.name == "renderdoc-module" and result.detail == "not found in search paths":
                 click.echo(f"  hint: {_RENDERDOC_BUILD_HINT}", err=True)
             else:
                 hint = HINT_MAP.get(result.name)
                 if hint:
                     click.echo(f"  hint: {hint}", err=True)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/rdc/commands/doctor.py` around lines 64 - 71, The generic “build from
source” hint is still being printed by doctor_cmd() for renderdoc-module
failures even when the check already indicates an IMPORT_FAILED/ABI issue;
update doctor_cmd to suppress that generic hint for the renderdoc-module
IMPORT_FAILED case by checking the CheckResult for the renderdoc-module check
and skipping the extra hint when its message indicates a failed import/ABI
mismatch (e.g., check.name == "renderdoc-module" and "failed to import" in
check.message). Alternatively add a suppress_hint flag to CheckResult and set it
in the IMPORT_FAILED branch (in the code that returns the CheckResult for
renderdoc-module) and have doctor_cmd respect that flag and not append the
build-from-source guidance.

return None, CheckResult("renderdoc-module", False, "not found in search paths")

version = getattr(module, "GetVersionString", lambda: "unknown")()
Expand Down
14 changes: 14 additions & 0 deletions src/rdc/discover.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,16 @@ def _is_arm_studio_dir(directory: str) -> bool:
return any("arm-performance-studio" in p.lower() for p in parts)


def _has_renderdoc_module(directory: str) -> bool:
"""Return True if *directory* holds an importable renderdoc module artifact."""
d = Path(directory)
return (
(d / "renderdoc.py").is_file()
or bool(list(d.glob("renderdoc*.so")))
or bool(list(d.glob("renderdoc*.pyd")))
)


def _preload_librenderdoc(directory: str) -> None:
"""Preload librenderdoc.so with RTLD_GLOBAL for ARM PS patched module.

Expand Down Expand Up @@ -174,6 +184,10 @@ def find_renderdoc() -> ModuleType | None:
_diagnostic = outcome
elif outcome.result == ProbeResult.TIMEOUT:
_diagnostic = outcome
elif outcome.result == ProbeResult.IMPORT_FAILED and _has_renderdoc_module(path):
# A module file is present but won't load (e.g. built for another
# Python); keep it so the caller can tell this from "not found".
_diagnostic = outcome

if crash_prone_candidates:
_diagnostic = ProbeOutcome(
Expand Down
58 changes: 58 additions & 0 deletions tests/unit/test_discover.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,64 @@ def mock_which(cmd: str):
assert diag.result == ProbeResult.CRASH_PRONE
assert diag.candidate_path == str(crash_dir)

def test_diagnostic_set_after_import_failed(
self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None:
"""A module that exists but fails to import (ABI mismatch) is recorded.

Without this, an import-failed candidate left no diagnostic and the
caller could not distinguish it from "nothing found at all".
"""
bad_dir = tmp_path / "abi-mismatch"
bad_dir.mkdir()
# A module artifact is present -- the import failure is a real load
# failure (ABI mismatch), not "no module here".
(bad_dir / "renderdoc.so").write_text("fake")

def mock_renderdoc_search_paths():
return [str(bad_dir)]

def mock_which(cmd: str):
return None

monkeypatch.setattr("rdc.discover._try_import", lambda: None)
monkeypatch.setattr(
"rdc.discover._probe_candidate",
lambda path, timeout=5.0: ProbeOutcome(ProbeResult.IMPORT_FAILED, str(bad_dir)),
)
monkeypatch.setattr("rdc.discover._try_import_from", lambda d: None)
monkeypatch.setattr("rdc._platform.renderdoc_search_paths", mock_renderdoc_search_paths)
monkeypatch.setattr("rdc.discover.shutil.which", mock_which)

result = find_renderdoc()

assert result is None
diag = _get_diagnostic()
assert diag is not None
assert diag.result == ProbeResult.IMPORT_FAILED
assert diag.candidate_path == str(bad_dir)

def test_import_failed_without_module_leaves_no_diagnostic(
self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None:
"""A dir with no module (e.g. the renderdoccmd sibling /usr/bin) probes
as IMPORT_FAILED but must not be reported as "found but failed to import".
"""
empty_dir = tmp_path / "no-module"
empty_dir.mkdir()

monkeypatch.setattr("rdc.discover._try_import", lambda: None)
monkeypatch.setattr(
"rdc.discover._probe_candidate",
lambda path, timeout=5.0: ProbeOutcome(ProbeResult.IMPORT_FAILED, str(empty_dir)),
)
monkeypatch.setattr("rdc.discover._try_import_from", lambda d: None)
monkeypatch.setattr("rdc._platform.renderdoc_search_paths", lambda: [str(empty_dir)])
monkeypatch.setattr("rdc.discover.shutil.which", lambda cmd: None)

assert find_renderdoc() is None
assert _get_diagnostic() is None


class TestArmStudioDir:
"""_is_arm_studio_dir detects ARM PS directory layout."""
Expand Down
26 changes: 26 additions & 0 deletions tests/unit/test_doctor.py
Original file line number Diff line number Diff line change
Expand Up @@ -650,6 +650,32 @@ def test_crash_prone_shows_path_and_rebuild_hint(
assert crash_path in result.detail
assert "rebuild" in result.detail.lower() or "incompatible" in result.detail.lower()

def test_import_failed_shows_path_and_abi_hint(
self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None:
"""When a module exists but fails to import, name the path and ABI cause.

Regression: an ABI-mismatched module (built for another Python) used to
be reported as the generic "not found in search paths".
"""
bad_path = str(tmp_path / "abi-mismatch")
monkeypatch.setattr(
"rdc.commands.doctor.find_renderdoc",
lambda: None,
)
monkeypatch.setattr(
"rdc.commands.doctor._get_diagnostic",
lambda: ProbeOutcome(ProbeResult.IMPORT_FAILED, bad_path),
)

_, result = _import_renderdoc()

assert result.ok is False
assert bad_path in result.detail
assert "import" in result.detail.lower()
assert "python" in result.detail.lower()
assert "not found" not in result.detail.lower()

def test_no_diagnostic_shows_not_found(self, monkeypatch: pytest.MonkeyPatch) -> None:
"""When no diagnostic available, show generic not found message."""
monkeypatch.setattr(
Expand Down
Loading