Skip to content
This repository was archived by the owner on Jan 23, 2026. It is now read-only.

Commit be4d299

Browse files
committed
Add tests for qemu resize
1 parent 157604c commit be4d299

1 file changed

Lines changed: 110 additions & 0 deletions

File tree

  • packages/jumpstarter-driver-qemu/jumpstarter_driver_qemu

packages/jumpstarter-driver-qemu/jumpstarter_driver_qemu/driver_test.py

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1+
import json
12
import os
23
import platform
34
import sys
45
import tarfile
56
from pathlib import Path
7+
from types import SimpleNamespace
8+
from unittest.mock import AsyncMock, patch
69

710
import pytest
811
import requests
@@ -13,6 +16,12 @@
1316
from jumpstarter.common.utils import serve
1417

1518

19+
@pytest.fixture
20+
def anyio_backend():
21+
"""Use only asyncio backend for anyio tests."""
22+
return "asyncio"
23+
24+
1625
@pytest.fixture(scope="session")
1726
def ovmf(tmpdir_factory):
1827
tmp_path = tmpdir_factory.mktemp("ovmf")
@@ -91,3 +100,104 @@ def test_driver_qemu(tmp_path, ovmf):
91100
assert s.run("uname -r").stdout.strip() == f"6.11.4-301.fc41.{arch}"
92101

93102
qemu.power.off()
103+
104+
105+
@pytest.fixture
106+
def resize_test():
107+
"""Create a Qemu driver with a sparse root disk, cleanup after test."""
108+
driver = None
109+
110+
def _create(disk_size, current_size_gb):
111+
nonlocal driver
112+
driver = Qemu(disk_size=disk_size)
113+
root = Path(driver._tmp_dir.name) / "root"
114+
root.write_bytes(b"")
115+
os.truncate(root, current_size_gb * 1024**3)
116+
return driver, current_size_gb * 1024**3
117+
118+
yield _create
119+
120+
if driver:
121+
driver._tmp_dir.cleanup()
122+
123+
124+
def _mock_qemu_img_info(virtual_size):
125+
"""Return a mock for run_process that simulates qemu-img info."""
126+
async def mock(cmd, **kwargs):
127+
result = AsyncMock()
128+
result.returncode = 0
129+
result.stdout = json.dumps({"format": "raw", "virtual-size": virtual_size}).encode()
130+
result.check_returncode = lambda: None
131+
return result
132+
return mock
133+
134+
135+
@pytest.mark.anyio
136+
async def test_resize_shrink_blocked(resize_test):
137+
"""Shrinking disk should raise RuntimeError."""
138+
driver, current = resize_test("10G", 20) # requested: 10G, current: 20G
139+
140+
with patch("jumpstarter_driver_qemu.driver.run_process", side_effect=_mock_qemu_img_info(current)):
141+
with pytest.raises(RuntimeError, match="Shrinking disk is not supported"):
142+
await driver.children["power"].on()
143+
144+
145+
@pytest.mark.anyio
146+
async def test_resize_insufficient_space_blocked(resize_test):
147+
"""Resize beyond available host space should raise RuntimeError."""
148+
driver, current = resize_test("100G", 10) # requested: 100G, current: 10G
149+
150+
mock_usage = SimpleNamespace(free=5 * 1024**3) # only 5G free
151+
152+
with patch("jumpstarter_driver_qemu.driver.run_process", side_effect=_mock_qemu_img_info(current)):
153+
with patch("jumpstarter_driver_qemu.driver.shutil.disk_usage", return_value=mock_usage):
154+
with pytest.raises(RuntimeError, match="Not enough disk space"):
155+
await driver.children["power"].on()
156+
157+
158+
@pytest.mark.anyio
159+
async def test_resize_succeeds(resize_test):
160+
"""Resize should call qemu-img resize with correct size."""
161+
driver, current = resize_test("20G", 10) # requested: 20G, current: 10G
162+
mock_usage = SimpleNamespace(free=50 * 1024**3)
163+
164+
with patch("jumpstarter_driver_qemu.driver.run_process", side_effect=_mock_qemu_img_info(current)) as mock_run:
165+
with patch("jumpstarter_driver_qemu.driver.shutil.disk_usage", return_value=mock_usage):
166+
# Mock Popen to stop before actually starting QEMU VM
167+
with patch("jumpstarter_driver_qemu.driver.Popen", side_effect=RuntimeError("mock popen")):
168+
with pytest.raises(RuntimeError, match="mock popen"):
169+
await driver.children["power"].on()
170+
171+
# Find the resize call and verify size argument
172+
resize_calls = [c for c in mock_run.call_args_list if "resize" in c.args[0]]
173+
assert resize_calls, "qemu-img resize should be called"
174+
resize_cmd = resize_calls[0].args[0] # ['qemu-img', 'resize', path, size]
175+
assert resize_cmd[-1] == str(20 * 1024**3)
176+
177+
178+
def test_set_disk_size_valid():
179+
"""Valid size strings should be accepted."""
180+
driver = Qemu()
181+
driver.set_disk_size("20G")
182+
assert driver.disk_size == "20G"
183+
184+
185+
def test_set_disk_size_invalid():
186+
"""Invalid size strings should raise ValueError."""
187+
driver = Qemu()
188+
with pytest.raises(ValueError, match="Invalid size"):
189+
driver.set_disk_size("invalid")
190+
191+
192+
def test_set_memory_size_valid():
193+
"""Valid size strings should be accepted."""
194+
driver = Qemu()
195+
driver.set_memory_size("2G")
196+
assert driver.mem == "2G"
197+
198+
199+
def test_set_memory_size_invalid():
200+
"""Invalid size strings should raise ValueError."""
201+
driver = Qemu()
202+
with pytest.raises(ValueError, match="Invalid size"):
203+
driver.set_memory_size("invalid")

0 commit comments

Comments
 (0)