122 lines
4.6 KiB
Python
122 lines
4.6 KiB
Python
"""See & drive tools with fake QMP; screendump writes a synthetic PNG."""
|
|
|
|
import struct
|
|
|
|
import pytest
|
|
from fastmcp import Client
|
|
from fastmcp.exceptions import ToolError
|
|
|
|
from conftest import FakeQMPClient, result_data, write_registry
|
|
from mcqemu.server import mcp
|
|
from test_lifecycle import seeded_record
|
|
|
|
|
|
def fake_png(width: int, height: int) -> bytes:
|
|
"""Just enough PNG for signature + IHDR dimension parsing."""
|
|
return (
|
|
b"\x89PNG\r\n\x1a\n"
|
|
+ b"\x00\x00\x00\x0dIHDR"
|
|
+ struct.pack(">II", width, height)
|
|
+ b"\x08\x06\x00\x00\x00"
|
|
+ b"\x00" * 16
|
|
)
|
|
|
|
|
|
def arm_screendump(width=640, height=480):
|
|
def handler(args):
|
|
with open(args["filename"], "wb") as f:
|
|
f.write(fake_png(width, height))
|
|
return {}
|
|
|
|
FakeQMPClient.responses["screendump"] = handler
|
|
|
|
|
|
async def test_screenshot_returns_png_image(dirs, fake_qmp):
|
|
arm_screendump()
|
|
write_registry(dirs, seeded_record(dirs, "vm1"))
|
|
async with Client(mcp) as client:
|
|
result = await client.call_tool("vm_screenshot", {"name": "vm1"})
|
|
block = result.content[0]
|
|
assert block.type == "image"
|
|
assert block.mimeType == "image/png"
|
|
call = next(a for c, a in FakeQMPClient.calls if c == "screendump")
|
|
assert call["format"] == "png"
|
|
|
|
|
|
async def test_send_keys_chords(dirs, fake_qmp):
|
|
FakeQMPClient.responses["send-key"] = {}
|
|
write_registry(dirs, seeded_record(dirs, "vm1"))
|
|
async with Client(mcp) as client:
|
|
data = result_data(
|
|
await client.call_tool(
|
|
"vm_send_keys", {"name": "vm1", "keys": ["ctrl-alt-f2", "enter"], "delay_ms": 0}
|
|
)
|
|
)
|
|
assert data["keys_sent"] == 2
|
|
sent = [a["keys"] for c, a in FakeQMPClient.calls if c == "send-key"]
|
|
assert sent[0] == [
|
|
{"type": "qcode", "data": "ctrl"},
|
|
{"type": "qcode", "data": "alt"},
|
|
{"type": "qcode", "data": "f2"},
|
|
]
|
|
assert sent[1] == [{"type": "qcode", "data": "ret"}]
|
|
|
|
|
|
async def test_send_keys_unknown_key(dirs, fake_qmp):
|
|
write_registry(dirs, seeded_record(dirs, "vm1"))
|
|
async with Client(mcp) as client:
|
|
with pytest.raises(ToolError, match="Unknown key"):
|
|
await client.call_tool("vm_send_keys", {"name": "vm1", "keys": ["warpdrive"]})
|
|
|
|
|
|
async def test_type_text_with_shift_and_enter(dirs, fake_qmp):
|
|
FakeQMPClient.responses["send-key"] = {}
|
|
write_registry(dirs, seeded_record(dirs, "vm1"))
|
|
async with Client(mcp) as client:
|
|
data = result_data(
|
|
await client.call_tool(
|
|
"vm_type_text", {"name": "vm1", "text": "Hi!", "enter": True, "delay_ms": 0}
|
|
)
|
|
)
|
|
assert data["characters_typed"] == 4 # H, i, !, Enter
|
|
sent = [a["keys"] for c, a in FakeQMPClient.calls if c == "send-key"]
|
|
assert sent[0] == [{"type": "qcode", "data": "shift"}, {"type": "qcode", "data": "h"}]
|
|
assert sent[1] == [{"type": "qcode", "data": "i"}]
|
|
assert sent[2] == [{"type": "qcode", "data": "shift"}, {"type": "qcode", "data": "1"}]
|
|
assert sent[3] == [{"type": "qcode", "data": "ret"}]
|
|
|
|
|
|
async def test_click_scales_to_abs_space(dirs, fake_qmp):
|
|
arm_screendump(width=640, height=480)
|
|
FakeQMPClient.responses["input-send-event"] = {}
|
|
write_registry(dirs, seeded_record(dirs, "vm1"))
|
|
async with Client(mcp) as client:
|
|
data = result_data(await client.call_tool("vm_click", {"name": "vm1", "x": 320, "y": 240}))
|
|
assert "left at (320, 240)" in data["clicked"]
|
|
events = [a["events"] for c, a in FakeQMPClient.calls if c == "input-send-event"]
|
|
move = events[0]
|
|
assert move[0]["data"] == {"axis": "x", "value": int(320 * 32767 / 639)}
|
|
assert move[1]["data"] == {"axis": "y", "value": int(240 * 32767 / 479)}
|
|
assert events[1][0]["data"] == {"down": True, "button": "left"}
|
|
assert events[2][0]["data"] == {"down": False, "button": "left"}
|
|
|
|
|
|
async def test_click_outside_display_rejected(dirs, fake_qmp):
|
|
arm_screendump(width=640, height=480)
|
|
write_registry(dirs, seeded_record(dirs, "vm1"))
|
|
async with Client(mcp) as client:
|
|
with pytest.raises(ToolError, match="outside"):
|
|
await client.call_tool("vm_click", {"name": "vm1", "x": 9999, "y": 10})
|
|
|
|
|
|
async def test_serial_read_tail(dirs, fake_qmp):
|
|
record = seeded_record(dirs, "vm1", serial_log=str(dirs.state / "serial.log"))
|
|
(dirs.state / "serial.log").write_text("\n".join(f"line{i}" for i in range(100)))
|
|
write_registry(dirs, record)
|
|
async with Client(mcp) as client:
|
|
data = result_data(
|
|
await client.call_tool("vm_serial_read", {"name": "vm1", "tail_lines": 3})
|
|
)
|
|
assert data["total_lines"] == 100
|
|
assert data["tail"] == "line97\nline98\nline99"
|