fix(platform): an unreadable GPU classifies as unknown, so requires.gpu fails open as documented
When every DRM card's vendor file or every Windows display adapter's MatchingDeviceId was unreadable, classify_gpu_vendors returned 'none' and availability refused with unsupported_gpu, contradicting the documented 'unreadable GPU passes' rule. Windows now records an unreadable numbered adapter as an empty id so the classifier sees it. Also drops the type-narrowing assert in availability() and makes _action's gpu_label required.
This commit is contained in:
@@ -233,7 +233,12 @@ _GPU_SCAN_LIMIT = 16
|
||||
|
||||
|
||||
def classify_gpu_vendors(vendor_ids: list[str]) -> str:
|
||||
"""Return the highest-priority GPU class for PCI vendor ids, or ``none``."""
|
||||
"""Return the highest-priority GPU class for PCI vendor ids, or ``none``.
|
||||
|
||||
``unknown`` when devices were seen but none of their vendor ids could be read.
|
||||
"""
|
||||
if vendor_ids and not any(v.strip() for v in vendor_ids):
|
||||
return "unknown"
|
||||
found = {_GPU_PCI_VENDORS.get(v.strip().lower().removeprefix("0x")) for v in vendor_ids}
|
||||
return next((name for name in _GPU_PRIORITY if name in found), "none")
|
||||
|
||||
@@ -263,6 +268,8 @@ def _windows_gpu_vendor_ids() -> list[str] | None:
|
||||
device_id = _winreg_str(f"{_DISPLAY_CLASS_KEY}\\{subkey}", "MatchingDeviceId").upper()
|
||||
if "VEN_" in device_id:
|
||||
vendors.append(device_id.split("VEN_", 1)[1][:4])
|
||||
elif subkey.isdigit(): # an adapter whose id could not be read
|
||||
vendors.append("")
|
||||
except OSError:
|
||||
return None
|
||||
return vendors
|
||||
|
||||
@@ -89,9 +89,8 @@ def availability(entry: _HasRequirements, *, os_family: str | None = None) -> Av
|
||||
# the machines that have the GPU, and the MCP connection check still applies.
|
||||
if gpu is not None and facts.gpu_class() not in (gpu, "unknown"):
|
||||
return Availability("unsupported_gpu", min_version=entry.min_version)
|
||||
if not entry.requires_app:
|
||||
if definition is None:
|
||||
return Availability("no_requirements")
|
||||
assert definition is not None
|
||||
resolver = AppResolver(definition)
|
||||
res = resolver.locate()
|
||||
looked_at = res.command[0] if res.command else (res.candidates[0].value if res.candidates else None)
|
||||
|
||||
@@ -220,7 +220,7 @@ def test_meminfo_total_is_parsed_in_bytes():
|
||||
|
||||
@pytest.mark.parametrize(("vendors", "gpu"), [
|
||||
(["0x8086", "0x10de"], "nvidia"), (["0x8086", "0x1002"], "amd"), (["0x8086"], "intel"),
|
||||
(["0x1af4"], "none"), ([], "none"), (["10DE"], "nvidia"),
|
||||
(["0x1af4"], "none"), ([], "none"), (["10DE"], "nvidia"), ([""], "unknown"), (["", "0x10de"], "nvidia"),
|
||||
])
|
||||
def test_gpu_vendor_priority_prefers_the_discrete_card(vendors, gpu):
|
||||
assert facts.classify_gpu_vendors(vendors) == gpu
|
||||
|
||||
@@ -108,7 +108,7 @@ def liveness_for(server_name: str) -> Liveness:
|
||||
return Liveness("static")
|
||||
|
||||
|
||||
def _action(state: LivenessState, app_name: str, gpu_label: str = "") -> tuple[str, Retry]:
|
||||
def _action(state: LivenessState, app_name: str, gpu_label: str) -> tuple[str, Retry]:
|
||||
actions: dict[LivenessState, tuple[str, Retry]] = {
|
||||
"app_not_running": (f"Start {app_name}, then try again.", "after_user_action"),
|
||||
"hermes_not_connected": (f"Reconnect {app_name} in Hermes, then try again.", "after_user_action"),
|
||||
|
||||
Reference in New Issue
Block a user