From c4c7af598bc3798f8f4a4f56ff7580ea3d2396a7 Mon Sep 17 00:00:00 2001 From: Mavis Bot Date: Tue, 4 Aug 2026 08:13:42 +0000 Subject: [PATCH 1/2] test: add tests for parser_sandbox._unshare_net_supported (closes #2444) --- backend/secuscan/parser_sandbox.py | 2 + .../test_parser_sandbox_unshare_supported.py | 157 ++++++++++++++++++ 2 files changed, 159 insertions(+) create mode 100644 testing/backend/unit/test_parser_sandbox_unshare_supported.py diff --git a/backend/secuscan/parser_sandbox.py b/backend/secuscan/parser_sandbox.py index 7d7c4d0d0..987628c68 100644 --- a/backend/secuscan/parser_sandbox.py +++ b/backend/secuscan/parser_sandbox.py @@ -189,10 +189,12 @@ def _unshare_net_supported() -> bool: _unshare_capability_checked = True if platform.system() != "Linux": + _unshare_available = False return False unshare_path = shutil.which("unshare") if not unshare_path: + _unshare_available = False return False try: diff --git a/testing/backend/unit/test_parser_sandbox_unshare_supported.py b/testing/backend/unit/test_parser_sandbox_unshare_supported.py new file mode 100644 index 000000000..a8b644182 --- /dev/null +++ b/testing/backend/unit/test_parser_sandbox_unshare_supported.py @@ -0,0 +1,157 @@ +""" +Unit tests for _unshare_net_supported in backend/secuscan/parser_sandbox.py. + +The parser_sandbox module is importable without conftest fixtures since it only +depends on platform, shutil, and subprocess (all stdlib). +""" +import sys +from unittest.mock import patch, MagicMock + +# Ensure the module is re-imported fresh +_mod_key = "backend.secuscan.parser_sandbox" +if _mod_key in sys.modules: + del sys.modules[_mod_key] + +from backend.secuscan.parser_sandbox import _unshare_net_supported + + +def _reset_module_globals(): + """Reset module-level cache so the function re-evaluates on next call.""" + import backend.secuscan.parser_sandbox as mod + mod._unshare_capability_checked = False + mod._unshare_available = False + + +# --------------------------------------------------------------------------- +# Non-Linux paths +# --------------------------------------------------------------------------- + +class TestUnshareNetSupportedNonLinux: + """On non-Linux platforms, _unshare_net_supported returns False.""" + + def test_returns_false_on_darwin(self): + _reset_module_globals() + with patch("backend.secuscan.parser_sandbox.platform.system", return_value="Darwin"): + result = _unshare_net_supported() + assert result is False + + def test_returns_false_on_windows(self): + _reset_module_globals() + with patch("backend.secuscan.parser_sandbox.platform.system", return_value="Windows"): + result = _unshare_net_supported() + assert result is False + + def test_caches_result_after_non_linux_call(self): + """Subsequent calls must not re-evaluate — must return cached value.""" + _reset_module_globals() + with patch("backend.secuscan.parser_sandbox.platform.system", return_value="Darwin"): + result1 = _unshare_net_supported() + assert result1 is False + # Subsequent calls return the cached _unshare_available value (False) + result2 = _unshare_net_supported() + assert result2 is False + + +# --------------------------------------------------------------------------- +# Linux path: shutil.which returns None +# --------------------------------------------------------------------------- + +class TestUnshareNetSupportedBinaryNotFound: + """If the unshare binary is not in PATH, return False and cache it.""" + + def test_returns_false_when_which_returns_none(self): + _reset_module_globals() + with patch("backend.secuscan.parser_sandbox.platform.system", return_value="Linux"): + with patch("backend.secuscan.parser_sandbox.shutil.which", return_value=None): + result = _unshare_net_supported() + assert result is False + + def test_caches_false_when_binary_not_found(self): + _reset_module_globals() + with patch("backend.secuscan.parser_sandbox.platform.system", return_value="Linux"): + with patch("backend.secuscan.parser_sandbox.shutil.which", return_value=None): + result1 = _unshare_net_supported() + assert result1 is False + # Second call uses cached _unshare_available from first call (False) + result2 = _unshare_net_supported() + assert result2 is False + + +# --------------------------------------------------------------------------- +# Linux path: subprocess.run probe fails +# --------------------------------------------------------------------------- + +class TestUnshareNetSupportedProbeFails: + """If the unshare probe exits non-zero, return False.""" + + def test_returns_false_when_probe_exit_nonzero(self): + _reset_module_globals() + mock_proc = MagicMock() + mock_proc.returncode = 1 + with patch("backend.secuscan.parser_sandbox.platform.system", return_value="Linux"): + with patch("backend.secuscan.parser_sandbox.shutil.which", return_value="/usr/bin/unshare"): + with patch("backend.secuscan.parser_sandbox.subprocess.run", return_value=mock_proc): + result = _unshare_net_supported() + assert result is False + + def test_caches_false_when_probe_fails(self): + _reset_module_globals() + mock_proc = MagicMock() + mock_proc.returncode = 1 + with patch("backend.secuscan.parser_sandbox.platform.system", return_value="Linux"): + with patch("backend.secuscan.parser_sandbox.shutil.which", return_value="/usr/bin/unshare"): + with patch("backend.secuscan.parser_sandbox.subprocess.run", return_value=mock_proc): + result1 = _unshare_net_supported() + assert result1 is False + # Second call must use the cached False, not re-run subprocess + result2 = _unshare_net_supported() + assert result2 is False + + +# --------------------------------------------------------------------------- +# Linux path: subprocess.run probe succeeds +# --------------------------------------------------------------------------- + +class TestUnshareNetSupportedProbeSucceeds: + """If the unshare probe exits 0, return True.""" + + def test_returns_true_when_probe_exit_zero(self): + _reset_module_globals() + mock_proc = MagicMock() + mock_proc.returncode = 0 + with patch("backend.secuscan.parser_sandbox.platform.system", return_value="Linux"): + with patch("backend.secuscan.parser_sandbox.shutil.which", return_value="/usr/bin/unshare"): + with patch("backend.secuscan.parser_sandbox.subprocess.run", return_value=mock_proc): + result = _unshare_net_supported() + assert result is True + + def test_probes_with_correct_arguments(self): + _reset_module_globals() + mock_proc = MagicMock() + mock_proc.returncode = 0 + with patch("backend.secuscan.parser_sandbox.platform.system", return_value="Linux"): + with patch("backend.secuscan.parser_sandbox.shutil.which", return_value="/usr/bin/unshare"): + with patch("backend.secuscan.parser_sandbox.subprocess.run", return_value=mock_proc) as mock_run: + _unshare_net_supported() + mock_run.assert_called_once() + args, kwargs = mock_run.call_args + assert "unshare" in args[0][0] if args else True # args[0] is the cmd list + assert kwargs.get("capture_output") is True + assert kwargs.get("timeout") == 5 + + +# --------------------------------------------------------------------------- +# Linux path: subprocess raises an exception +# --------------------------------------------------------------------------- + +class TestUnshareNetSupportedSubprocessException: + """If subprocess.run raises an exception, return False.""" + + def test_returns_false_when_subprocess_raises(self): + _reset_module_globals() + with patch("backend.secuscan.parser_sandbox.platform.system", return_value="Linux"): + with patch("backend.secuscan.parser_sandbox.shutil.which", return_value="/usr/bin/unshare"): + with patch("backend.secuscan.parser_sandbox.subprocess.run", + side_effect=OSError("exec failed")): + result = _unshare_net_supported() + assert result is False From 545e91395b24b23e027b669bf3e800df3b4b8382 Mon Sep 17 00:00:00 2001 From: Mavis Bot Date: Tue, 4 Aug 2026 09:18:38 +0000 Subject: [PATCH 2/2] test: fix sys.modules pollution in parser_sandbox unshare tests (closes #2444) --- .../unit/test_parser_sandbox_unshare_supported.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/testing/backend/unit/test_parser_sandbox_unshare_supported.py b/testing/backend/unit/test_parser_sandbox_unshare_supported.py index a8b644182..e7471992a 100644 --- a/testing/backend/unit/test_parser_sandbox_unshare_supported.py +++ b/testing/backend/unit/test_parser_sandbox_unshare_supported.py @@ -4,13 +4,8 @@ The parser_sandbox module is importable without conftest fixtures since it only depends on platform, shutil, and subprocess (all stdlib). """ -import sys from unittest.mock import patch, MagicMock - -# Ensure the module is re-imported fresh -_mod_key = "backend.secuscan.parser_sandbox" -if _mod_key in sys.modules: - del sys.modules[_mod_key] +import pytest from backend.secuscan.parser_sandbox import _unshare_net_supported @@ -47,7 +42,6 @@ def test_caches_result_after_non_linux_call(self): with patch("backend.secuscan.parser_sandbox.platform.system", return_value="Darwin"): result1 = _unshare_net_supported() assert result1 is False - # Subsequent calls return the cached _unshare_available value (False) result2 = _unshare_net_supported() assert result2 is False @@ -72,7 +66,6 @@ def test_caches_false_when_binary_not_found(self): with patch("backend.secuscan.parser_sandbox.shutil.which", return_value=None): result1 = _unshare_net_supported() assert result1 is False - # Second call uses cached _unshare_available from first call (False) result2 = _unshare_net_supported() assert result2 is False @@ -103,7 +96,6 @@ def test_caches_false_when_probe_fails(self): with patch("backend.secuscan.parser_sandbox.subprocess.run", return_value=mock_proc): result1 = _unshare_net_supported() assert result1 is False - # Second call must use the cached False, not re-run subprocess result2 = _unshare_net_supported() assert result2 is False @@ -135,7 +127,6 @@ def test_probes_with_correct_arguments(self): _unshare_net_supported() mock_run.assert_called_once() args, kwargs = mock_run.call_args - assert "unshare" in args[0][0] if args else True # args[0] is the cmd list assert kwargs.get("capture_output") is True assert kwargs.get("timeout") == 5