feat(vm): add vm open to launch the VMware Workstation GUI
Open a VMX in the interactive Workstation GUI (not the headless vmrun path) for hands-on debugging of a clone. Spawns `vmware` detached (own session, stdio detached) and returns the child PID. Flags: --vmx (required), --power-on (-x), --fullscreen (-X), --new-window (-n), --display (sudo -u ci-runner strips $DISPLAY, so allow forcing it), --vmware-path. Warns when no X display is set. 7 tests; vm.py 96%. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -497,3 +497,94 @@ def test_vm_cleanup_backend_unavailable(monkeypatch: pytest.MonkeyPatch, tmp_pat
|
||||
)
|
||||
assert result.exit_code == 0
|
||||
assert "vmrun not available" in result.output
|
||||
|
||||
|
||||
# ── vm open ───────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
def _capture_launch(monkeypatch: pytest.MonkeyPatch) -> dict[str, Any]:
|
||||
"""Patch _launch_gui to record argv/env instead of spawning a process."""
|
||||
captured: dict[str, Any] = {}
|
||||
|
||||
def _fake(argv: list[str], env: dict[str, str]) -> int:
|
||||
captured["argv"] = argv
|
||||
captured["env"] = env
|
||||
return 4242
|
||||
|
||||
monkeypatch.setattr(vm_module, "_launch_gui", _fake)
|
||||
return captured
|
||||
|
||||
|
||||
def test_vm_open_missing_vmx_errors(tmp_path: Path) -> None:
|
||||
result = CliRunner().invoke(cli, ["vm", "open", "--vmx", str(tmp_path / "nope.vmx")])
|
||||
assert result.exit_code != 0
|
||||
assert "VMX file not found" in result.output
|
||||
|
||||
|
||||
def test_vm_open_happy_path(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
|
||||
vmx = tmp_path / "clone.vmx"
|
||||
vmx.write_text("config")
|
||||
cap = _capture_launch(monkeypatch)
|
||||
monkeypatch.setenv("DISPLAY", ":0")
|
||||
result = CliRunner().invoke(cli, ["vm", "open", "--vmx", str(vmx)])
|
||||
assert result.exit_code == 0, result.output
|
||||
assert cap["argv"][-1] == str(vmx)
|
||||
assert "-x" not in cap["argv"] and "-X" not in cap["argv"]
|
||||
assert "pid 4242" in result.output
|
||||
|
||||
|
||||
def test_vm_open_power_on_adds_x(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
|
||||
vmx = tmp_path / "clone.vmx"
|
||||
vmx.write_text("config")
|
||||
cap = _capture_launch(monkeypatch)
|
||||
monkeypatch.setenv("DISPLAY", ":0")
|
||||
result = CliRunner().invoke(cli, ["vm", "open", "--vmx", str(vmx), "--power-on"])
|
||||
assert result.exit_code == 0, result.output
|
||||
assert "-x" in cap["argv"]
|
||||
|
||||
|
||||
def test_vm_open_fullscreen_adds_capital_x(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
|
||||
vmx = tmp_path / "clone.vmx"
|
||||
vmx.write_text("config")
|
||||
cap = _capture_launch(monkeypatch)
|
||||
monkeypatch.setenv("DISPLAY", ":0")
|
||||
result = CliRunner().invoke(
|
||||
cli, ["vm", "open", "--vmx", str(vmx), "--fullscreen", "--new-window"]
|
||||
)
|
||||
assert result.exit_code == 0, result.output
|
||||
assert "-X" in cap["argv"] and "-n" in cap["argv"]
|
||||
|
||||
|
||||
def test_vm_open_display_option_overrides_env(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
|
||||
vmx = tmp_path / "clone.vmx"
|
||||
vmx.write_text("config")
|
||||
cap = _capture_launch(monkeypatch)
|
||||
monkeypatch.delenv("DISPLAY", raising=False)
|
||||
result = CliRunner().invoke(cli, ["vm", "open", "--vmx", str(vmx), "--display", ":1"])
|
||||
assert result.exit_code == 0, result.output
|
||||
assert cap["env"]["DISPLAY"] == ":1"
|
||||
assert "display=:1" in result.output
|
||||
|
||||
|
||||
def test_vm_open_warns_without_display(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
|
||||
vmx = tmp_path / "clone.vmx"
|
||||
vmx.write_text("config")
|
||||
_capture_launch(monkeypatch)
|
||||
monkeypatch.delenv("DISPLAY", raising=False)
|
||||
result = CliRunner().invoke(cli, ["vm", "open", "--vmx", str(vmx)])
|
||||
assert result.exit_code == 0, result.output
|
||||
assert "no X display" in result.output
|
||||
|
||||
|
||||
def test_vm_open_launch_failure_is_reported(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
|
||||
vmx = tmp_path / "clone.vmx"
|
||||
vmx.write_text("config")
|
||||
|
||||
def _boom(argv: list[str], env: dict[str, str]) -> int:
|
||||
raise OSError("vmware not found")
|
||||
|
||||
monkeypatch.setattr(vm_module, "_launch_gui", _boom)
|
||||
monkeypatch.setenv("DISPLAY", ":0")
|
||||
result = CliRunner().invoke(cli, ["vm", "open", "--vmx", str(vmx)])
|
||||
assert result.exit_code != 0
|
||||
assert "failed to launch VMware GUI" in result.output
|
||||
|
||||
Reference in New Issue
Block a user