summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBruce Ashfield <bruce.ashfield@gmail.com>2026-01-08 18:03:47 +0000
committerBruce Ashfield <bruce.ashfield@gmail.com>2026-02-09 03:32:52 +0000
commitdd13b2b6df049df622838aa3e5303ddbe1446fde (patch)
tree07f1fe954162d2d7b236c7b9b448e11f8108b9f2
parent351930159f2b19f9371956e8278e00de89835357 (diff)
downloadmeta-virtualization-dd13b2b6df049df622838aa3e5303ddbe1446fde.tar.gz
tests: add cleanup for orphan QEMU and stale test state
Add session-scoped autouse fixture that at session start: 1. Kills any QEMU processes holding ports used by tests (8080, 8081, 8888, etc.) - handles orphans from manual testing or crashed runs 2. Cleans up corrupt test state directories (docker-state.img with "needs journal recovery") to ensure tests start fresh This ensures tests don't fail due to leftover state from previous runs or manual testing. Signed-off-by: Bruce Ashfield <bruce.ashfield@gmail.com>
-rw-r--r--tests/conftest.py98
1 files changed, 98 insertions, 0 deletions
diff --git a/tests/conftest.py b/tests/conftest.py
index 71b86430..49958879 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -96,6 +96,51 @@ signal.signal(signal.SIGINT, _signal_handler)
96signal.signal(signal.SIGTERM, _signal_handler) 96signal.signal(signal.SIGTERM, _signal_handler)
97 97
98 98
99# Ports used by tests that need to be free
100TEST_PORTS = [8080, 8081, 8888, 8001, 8002, 9999, 7777, 6666]
101
102
103def _cleanup_orphan_qemu_on_ports():
104 """
105 Kill any QEMU processes holding ports used by tests.
106 This handles cases where a previous test run or manual testing left
107 orphan QEMU processes that would block test port bindings.
108 """
109 import re
110
111 try:
112 # Get listening sockets
113 result = subprocess.run(
114 ["ss", "-tlnp"],
115 capture_output=True,
116 text=True,
117 timeout=5
118 )
119 if result.returncode != 0:
120 return
121
122 for line in result.stdout.splitlines():
123 # Check if any test port is in use
124 for port in TEST_PORTS:
125 if f":{port}" in line and "qemu" in line.lower():
126 # Extract PID from ss output (format: users:(("qemu...",pid=12345,fd=...)))
127 match = re.search(r'pid=(\d+)', line)
128 if match:
129 pid = int(match.group(1))
130 try:
131 os.kill(pid, signal.SIGTERM)
132 import time
133 time.sleep(0.5)
134 # Force kill if still running
135 if Path(f"/proc/{pid}").exists():
136 os.kill(pid, signal.SIGKILL)
137 except (ProcessLookupError, PermissionError):
138 pass
139 break
140 except (subprocess.TimeoutExpired, FileNotFoundError):
141 pass
142
143
99def pytest_addoption(parser): 144def pytest_addoption(parser):
100 """Add custom command line options.""" 145 """Add custom command line options."""
101 # vdkr/vpdmn options 146 # vdkr/vpdmn options
@@ -183,6 +228,59 @@ def pytest_addoption(parser):
183 ) 228 )
184 229
185 230
231def _cleanup_stale_test_state():
232 """
233 Clean up stale or corrupt test state directories.
234 This ensures tests start with a clean slate if previous runs crashed.
235 """
236 for state_base in [TEST_STATE_BASE, VPDMN_TEST_STATE_BASE]:
237 state_path = Path(state_base)
238 if not state_path.exists():
239 continue
240
241 for arch_dir in state_path.glob("*"):
242 if not arch_dir.is_dir():
243 continue
244
245 docker_state = arch_dir / "docker-state.img"
246 daemon_pid = arch_dir / "daemon.pid"
247
248 # Check if daemon is actually running
249 daemon_running = False
250 if daemon_pid.exists():
251 try:
252 pid = int(daemon_pid.read_text().strip())
253 daemon_running = Path(f"/proc/{pid}").exists()
254 except (ValueError, OSError):
255 pass
256
257 # If daemon not running but state exists, it's stale - clean it
258 if not daemon_running and docker_state.exists():
259 # Check if docker-state.img needs journal recovery (corrupt)
260 try:
261 result = subprocess.run(
262 ["file", str(docker_state)],
263 capture_output=True,
264 text=True,
265 timeout=5
266 )
267 if "needs journal recovery" in result.stdout:
268 # State is corrupt, clean it up
269 shutil.rmtree(arch_dir, ignore_errors=True)
270 except (subprocess.TimeoutExpired, FileNotFoundError):
271 pass
272
273
274@pytest.fixture(scope="session", autouse=True)
275def cleanup_orphan_qemu():
276 """Clean up orphan QEMU processes and stale test state at session start."""
277 _cleanup_orphan_qemu_on_ports()
278 _cleanup_stale_test_state()
279 yield
280 # Also clean up at end of session
281 _cleanup_orphan_qemu_on_ports()
282
283
186@pytest.fixture(scope="session") 284@pytest.fixture(scope="session")
187def vdkr_dir(request): 285def vdkr_dir(request):
188 """Path to vdkr standalone directory.""" 286 """Path to vdkr standalone directory."""