diff --git a/AGENTS.md b/AGENTS.md index d40a7e9..716006b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -48,7 +48,9 @@ run and destroys it at session end. Run them with `tox -e integration` or `.venv/bin/pytest tests/integration`. Throughput comparison against - native VDDK lives under `tests/perf/` (`tox -e perf`). + native VDDK lives under `tests/perf/` (`tox -e perf`). Repeated + connect/open/close leak checks live under `tests/stress/` + (`tox -e stress`). ## Other rules diff --git a/README.md b/README.md index aa34b42..4135367 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,7 @@ VDDK-shaped handle. | `openvixdisklib/fastlz.py` | FastLZ NFC adapter (pip `pyfastlz`) | | `tests/integration/` | Live pytest suite against a lab vCenter | | `tests/perf/` | Throughput comparison of openvixdisklib vs VDDK | +| `tests/stress/` | Repeated connect/open/close leak check | | `tests/integration/vixdisklib.py` | Native VDDK wrapper used only to cross-check | | `docs/` | Protocol notes and reverse-engineering steps | @@ -114,6 +115,14 @@ plain and FastLZ): tox -e perf ``` +Repeat ``connect`` / ``open`` / write-read one sector / ``close`` / +``disconnect`` 200 times (one process, sequential, like many VMs) and +assert the fd count does not grow: + +```bash +tox -e stress +``` + VDDK cross-check tests skip when `libvixDiskLib` is not loadable from `.vddk`. `tox -e integration` sets `LD_LIBRARY_PATH` to that directory and clears `LD_PRELOAD`. For a direct pytest run, do the same. diff --git a/tests/stress/__init__.py b/tests/stress/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/stress/test_connect_open_close.py b/tests/stress/test_connect_open_close.py new file mode 100644 index 0000000..dea6a19 --- /dev/null +++ b/tests/stress/test_connect_open_close.py @@ -0,0 +1,72 @@ +# Copyright 2026 Cloudbase Solutions Srl +# All Rights Reserved. + +"""Repeat connect/open/close against the lab to catch leaked sockets.""" + +from __future__ import annotations + +import gc +import os + +import pytest + +from openvixdisklib import openvixdisklib as vixdisklib +from tests.integration.base import SECTOR_SIZE, LabEnv, pattern_bytes + +# Sequential ConnectEx + Open, as when migrating many VMs in one process. +_ITERATIONS = 200 +# Noise from pytest/OpenSSL; one leaked socket per iteration is 200. +_FD_SLACK = 8 + + +def _fd_count() -> int: + """Return the number of open file descriptors in this process.""" + return len(os.listdir("/proc/self/fd")) + + +@pytest.mark.skipif( + not os.path.isdir("/proc/self/fd"), + reason="fd leak check needs /proc/self/fd", +) +class TestConnectOpenReadWriteClose: + def test_repeated_connect_open_read_write_close_does_not_leak_fds( + self, + lab: LabEnv, + ) -> None: + """Connect, open, write/read one sector, and close ``_ITERATIONS`` times. + + Each cycle is a full ``VixDiskLib_ConnectEx`` / ``Open`` / + ``Close`` / ``Disconnect`` for the lab VM (ticket, authd, NFC). + After the loop the process fd count must not have grown by more + than ``_FD_SLACK``. + """ + handle = vixdisklib.VixDiskLibHandle( + vixdisklib_compatibility_version="8.0", config_path=None + ) + write_buf = vixdisklib.get_buffer(SECTOR_SIZE) + read_buf = vixdisklib.get_buffer(SECTOR_SIZE) + kwargs = lab.vixdisklib_connect_kwargs( + { + "allow_untrusted": lab.allow_untrusted, + "transport_modes": "nbdssl", + } + ) + gc.collect() + fds_before = _fd_count() + for i in range(_ITERATIONS): + expected = pattern_bytes(SECTOR_SIZE, f"STRESS-{i}-".encode()) + write_buf[:SECTOR_SIZE] = expected + with ( + handle.connect(**kwargs) as conn, + handle.open(conn, lab.disk_path, flags=0) as disk, + ): + handle.write(disk, 0, 1, write_buf) + read_buf[:SECTOR_SIZE] = b"\xa5" * SECTOR_SIZE + handle.read(disk, 0, 1, read_buf) + assert read_buf.raw[:SECTOR_SIZE] == expected + gc.collect() + fds_after = _fd_count() + assert fds_after <= fds_before + _FD_SLACK, ( + f"open fds grew from {fds_before} to {fds_after} after " + f"{_ITERATIONS} connect/open/close cycles" + ) diff --git a/tox.ini b/tox.ini index 3528df9..a7bf7a2 100644 --- a/tox.ini +++ b/tox.ini @@ -60,6 +60,15 @@ setenv = commands = python -m pytest -v -s tests/perf/ {posargs} +[testenv:stress] +description = Repeat connect/open/close against the lab to detect socket leaks. +sitepackages = True +usedevelop = True +setenv = + {[testenv:integration]setenv} +commands = + python -m pytest -v tests/stress/ {posargs} + [testenv:fmt] description = Format the code based on the coding style standards. deps =