From bd82659674b2a0d981d3a16880b6fbbf7ced8de9 Mon Sep 17 00:00:00 2001 From: Morten Brekkevold Date: Wed, 13 May 2026 13:56:33 +0200 Subject: [PATCH 1/2] Run snmpsim via uvx in isolated Python 3.11 environment snmpsim has a severe performance regression on Python 3.13+ where the new `dbm.sqlite3` default backend causes every write during index building to fsync individually, resulting in ~25x slowdown. The `snmpsim` fixture now prefers running via `uvx --python=3.11` in an isolated environment. Falls back to a locally installed `snmpsim-command-responder` with a warning on Python 3.13+. Also detects premature process exit in the wait loop instead of spinning indefinitely. --- tests/integration/conftest.py | 92 +++++++++++++++++++++++++++++------ 1 file changed, 78 insertions(+), 14 deletions(-) diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 3a1f16b71c..03e8cf0c01 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -1,12 +1,15 @@ +import importlib.metadata import importlib.util import io +import os import re import shlex +import subprocess +import sys +import time from itertools import cycle from pathlib import Path from shutil import which -import subprocess -import time import toml import pytest @@ -297,28 +300,89 @@ def snmpsim(): by the test that declares a dependency to this fixture. Data fixtures are loaded from the snmp_fixtures subdirectory. """ - snmpsimd = which('snmpsim-command-responder') - assert snmpsimd, "Could not find snmpsimd.py" workspace = str(Path(__file__).resolve().parent.parent.parent) - proc = subprocess.Popen( - [ - snmpsimd, - '--data-dir={}/tests/integration/snmp_fixtures'.format(workspace), - '--log-level=error', - '--agent-udpv4-endpoint=127.0.0.1:1024', - ], - env={'HOME': workspace}, - ) + command = _build_snmpsim_command(workspace) + env = {**os.environ, 'HOME': workspace} + proc = subprocess.Popen(command, env=env) while not _lookfor('0100007F:0400', '/proc/net/udp'): print("Still waiting for snmpsimd to listen for queries") - proc.poll() + if proc.poll() is not None: + pytest.fail( + f"snmpsim process exited prematurely (exit code {proc.returncode})" + ) time.sleep(0.1) yield proc.kill() +def _build_snmpsim_command(workspace): + """Returns the command list to start snmpsim-command-responder. + + Prefers running via uvx in an isolated Python 3.11 environment to avoid a + known performance regression in snmpsim on Python 3.13+ + (https://github.com/lextudio/pysnmp/issues/223). Falls back to a locally + installed snmpsim-command-responder if uvx is not available. + """ + data_dir = f'{workspace}/tests/integration/snmp_fixtures' + snmpsim_args = [ + f'--data-dir={data_dir}', + '--log-level=error', + '--agent-udpv4-endpoint=127.0.0.1:1024', + ] + + if which('uvx') and _uv_has_python('3.11'): + snmpsim_pkg = _installed_snmpsim_spec() + return [ + 'uvx', + '--python=3.11', + f'--from={snmpsim_pkg}', + 'snmpsim-command-responder', + ] + snmpsim_args + + snmpsimd = which('snmpsim-command-responder') + if not snmpsimd: + pytest.skip("Neither uvx nor snmpsim-command-responder found") + + if sys.version_info >= (3, 13): + import warnings + + warnings.warn( + "Running snmpsim under Python 3.13+ without uvx. " + "This is known to be extremely slow due to a dbm.sqlite3 " + "performance regression " + "(https://github.com/lextudio/pysnmp/issues/223). " + "Expect many SNMP-dependent tests to fail with timeouts. " + "Install uv to run snmpsim in an isolated Python 3.11 " + "environment automatically.", + stacklevel=1, + ) + + return [snmpsimd] + snmpsim_args + + +def _uv_has_python(version): + """Returns True if uv can find the given Python version.""" + result = subprocess.run( + ['uv', 'python', 'find', version], + capture_output=True, + ) + return result.returncode == 0 + + +def _installed_snmpsim_spec(): + """Returns a pip specifier for the locally installed snmpsim version. + + Falls back to an unpinned 'snmpsim' if the package is not installed. + """ + try: + version = importlib.metadata.version('snmpsim') + return f'snmpsim=={version}' + except importlib.metadata.PackageNotFoundError: + return 'snmpsim' + + @pytest.fixture() def snmp_agent_proxy(snmpsim, snmp_ports): """Returns an AgentProxy instance prepared to talk to localhost. The open() method From 6b1abd3046296b3f99ba3aab1fb78f4ca41bfeb6 Mon Sep 17 00:00:00 2001 From: Morten Brekkevold Date: Mon, 18 May 2026 10:21:28 +0200 Subject: [PATCH 2/2] Update function name As suggested in review Co-authored-by: Johanna England --- tests/integration/conftest.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 03e8cf0c01..5b5a2b1bbc 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -333,7 +333,7 @@ def _build_snmpsim_command(workspace): ] if which('uvx') and _uv_has_python('3.11'): - snmpsim_pkg = _installed_snmpsim_spec() + snmpsim_pkg = _get_installed_snmpsim_spec() return [ 'uvx', '--python=3.11', @@ -371,7 +371,7 @@ def _uv_has_python(version): return result.returncode == 0 -def _installed_snmpsim_spec(): +def _get_installed_snmpsim_spec(): """Returns a pip specifier for the locally installed snmpsim version. Falls back to an unpinned 'snmpsim' if the package is not installed.