Files
wehub-resource-sync 3e779be6f3
CI / lint (push) Failing after 13m4s
CI / test (3.11, ubuntu-latest) (push) Failing after 2m4s
CI / test (3.13, ubuntu-latest) (push) Successful in 13m30s
CI / test (3.14, ubuntu-latest) (push) Successful in 17m21s
CI / test (3.12, ubuntu-latest) (push) Successful in 17m55s
CI / discover-apps-ps (push) Successful in 1m56s
CI / test (3.9, ubuntu-latest) (push) Successful in 13m17s
CI / test (3.10, ubuntu-latest) (push) Successful in 26m21s
CI / audit (push) Successful in 13m38s
Deploy site / deploy (push) Has been cancelled
CI / test (3.14, ubuntu-24.04-arm) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:32:37 +08:00

118 lines
4.4 KiB
Python

# SPDX-License-Identifier: MIT
"""Tests for the usbredir live-USB passthrough path (core/usbredir.py)."""
from __future__ import annotations
import json
import pytest
from winpodx.core import usbredir as U
from winpodx.core.devices import DeviceConfig, HmpError
@pytest.fixture()
def statedir(tmp_path, monkeypatch):
"""Point usbredir's state dir at a throwaway tmp path."""
from winpodx.utils import paths
monkeypatch.setattr(paths, "data_dir", lambda: tmp_path)
return tmp_path / "usbredir"
def test_qom_id_stable():
assert U._qom_id(DeviceConfig("usb", "1058:2626")) == "wpxur-10582626"
def test_alloc_slot_empty(statedir):
assert U._alloc_slot() == 0
def test_alloc_slot_skips_used(statedir):
statedir.mkdir(parents=True, exist_ok=True)
(statedir / "a.json").write_text(json.dumps({"slot": 0}))
(statedir / "b.json").write_text(json.dumps({"slot": 1}))
assert U._alloc_slot() == 2
def test_attach_rejects_pci(statedir):
with pytest.raises(HmpError, match="only supports USB"):
U.attach("podman", "c", DeviceConfig("pci", "01:00.0"))
def test_attach_requires_usbredirect(statedir, monkeypatch):
monkeypatch.setattr(U, "usbredirect_path", lambda: None)
with pytest.raises(HmpError, match="usbredirect not found"):
U.attach("podman", "c", DeviceConfig("usb", "1058:2626"))
def test_attach_requires_privilege(statedir, monkeypatch):
monkeypatch.setattr(U, "usbredirect_path", lambda: "/usr/bin/usbredirect")
monkeypatch.setattr(U, "_privilege_wrapper", lambda: None)
with pytest.raises(HmpError, match="pkexec nor sudo"):
U.attach("podman", "c", DeviceConfig("usb", "1058:2626"))
def test_is_attached_false_without_state(statedir):
assert U.is_attached(DeviceConfig("usb", "1058:2626")) is False
def test_detach_idempotent_without_state(statedir, monkeypatch):
cleaned: list = []
monkeypatch.setattr(U, "_hmp_cleanup", lambda be, c, qom, drop_device: cleaned.append(qom))
# No state file -> must not crash, and still attempt HMP cleanup.
U.detach("podman", "c", DeviceConfig("usb", "1058:2626"))
assert cleaned == ["wpxur-10582626"]
def test_looks_like_error():
assert U._looks_like_error("Error: no such device")
assert U._looks_like_error("(qemu) could not open")
assert not U._looks_like_error("(qemu) ")
def test_attach_rolls_back_when_channel_never_connects(statedir, monkeypatch):
# chardev-add/device_add "succeed" (empty reply), processes are stubbed,
# but the channel never connects -> attach raises and leaves no state +
# runs HMP cleanup (no orphan device/chardev).
dev = DeviceConfig("usb", "1058:2626")
monkeypatch.setattr(U, "usbredirect_path", lambda: "/usr/bin/usbredirect")
monkeypatch.setattr(U, "_privilege_wrapper", lambda: ["sudo"])
monkeypatch.setattr(U, "hmp_command", lambda be, c, cmd, **kw: "(qemu) ")
monkeypatch.setattr(U, "_wait_relay_ready", lambda log, timeout: None)
monkeypatch.setattr(U, "_wait_chardev_connected", lambda be, c, qom, timeout: False)
monkeypatch.setattr(U, "_kill", lambda p: None)
class _FakeProc:
pid = 1234567
monkeypatch.setattr(U.subprocess, "Popen", lambda *a, **k: _FakeProc())
cleaned: list = []
monkeypatch.setattr(
U, "_hmp_cleanup", lambda be, c, qom, drop_device: cleaned.append((qom, drop_device))
)
with pytest.raises(HmpError, match="never connected"):
U.attach("podman", "c", dev)
assert not U._state_path(dev).exists()
assert cleaned and cleaned[0][0] == "wpxur-10582626"
def test_attach_writes_state_on_success(statedir, monkeypatch):
dev = DeviceConfig("usb", "1058:2626")
monkeypatch.setattr(U, "usbredirect_path", lambda: "/usr/bin/usbredirect")
monkeypatch.setattr(U, "_privilege_wrapper", lambda: ["sudo"])
monkeypatch.setattr(U, "hmp_command", lambda be, c, cmd, **kw: "(qemu) ")
monkeypatch.setattr(U, "_wait_relay_ready", lambda log, timeout: None)
monkeypatch.setattr(U, "_wait_chardev_connected", lambda be, c, qom, timeout: True)
class _FakeProc:
pid = 4242
monkeypatch.setattr(U.subprocess, "Popen", lambda *a, **k: _FakeProc())
U.attach("podman", "c", dev)
state = json.loads(U._state_path(dev).read_text())
assert state["did"] == "1058:2626"
assert state["qom"] == "wpxur-10582626"
assert state["relay_pid"] == 4242 and state["usbredirect_pid"] == 4242
assert state["qemu_port"] == U._QEMU_PORT_BASE and state["host_port"] == U._HOST_PORT_BASE