zapretgui/tests/test_windivert_conflict_detection.py
2026-07-16 16:06:03 +03:00

250 lines
10 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import os
import unittest
from unittest.mock import patch
from winws_runtime.health import launch_conflicts
def _make_holder_env(records, modules_by_pid):
return (
patch.object(launch_conflicts, "iter_process_records_winapi", return_value=records),
patch.object(
launch_conflicts,
"iter_process_module_paths_winapi",
side_effect=lambda pid: modules_by_pid.get(int(pid), []),
),
)
class OwnDirsRegistryTests(unittest.TestCase):
def setUp(self) -> None:
self._saved_dirs = set(launch_conflicts._OWN_WINDIVERT_DIRS)
launch_conflicts._OWN_WINDIVERT_DIRS.clear()
def tearDown(self) -> None:
launch_conflicts._OWN_WINDIVERT_DIRS.clear()
launch_conflicts._OWN_WINDIVERT_DIRS.update(self._saved_dirs)
def test_registered_dir_marks_nested_paths_as_own(self) -> None:
launch_conflicts.register_own_windivert_dirs(r"C:\Program Files\Zapret2")
own_file = os.path.join(r"C:\Program Files\Zapret2", "bin", "WinDivert64.sys")
self.assertTrue(launch_conflicts._is_own_path(own_file))
self.assertFalse(launch_conflicts._is_own_path(r"C:\GoodbyeDPI\WinDivert64.sys"))
def test_prefix_of_dir_name_is_not_own(self) -> None:
launch_conflicts.register_own_windivert_dirs(r"C:\Zapret")
foreign = os.path.join(r"C:\ZapretOther", "WinDivert64.sys")
self.assertFalse(launch_conflicts._is_own_path(foreign))
def test_empty_and_blank_dirs_are_ignored(self) -> None:
launch_conflicts.register_own_windivert_dirs("", " ")
self.assertEqual(launch_conflicts._OWN_WINDIVERT_DIRS, set())
class WinDivertHolderProcessesTests(unittest.TestCase):
def setUp(self) -> None:
self._saved_dirs = set(launch_conflicts._OWN_WINDIVERT_DIRS)
launch_conflicts._OWN_WINDIVERT_DIRS.clear()
def tearDown(self) -> None:
launch_conflicts._OWN_WINDIVERT_DIRS.clear()
launch_conflicts._OWN_WINDIVERT_DIRS.update(self._saved_dirs)
def test_foreign_process_with_windivert_dll_is_reported(self) -> None:
records = [(1234, "GoodbyeDPI.exe"), (5678, "notepad.exe")]
modules = {
1234: [
r"C:\GoodbyeDPI\GoodbyeDPI.exe",
r"C:\Windows\System32\kernel32.dll",
r"C:\GoodbyeDPI\WinDivert64.dll",
],
5678: [r"C:\Windows\notepad.exe", r"C:\Windows\System32\kernel32.dll"],
}
records_patch, modules_patch = _make_holder_env(records, modules)
with records_patch, modules_patch:
holders = launch_conflicts.find_windivert_holder_processes()
self.assertEqual(len(holders), 1)
self.assertEqual(holders[0]["name"], "GoodbyeDPI.exe")
self.assertEqual(holders[0]["pid"], 1234)
self.assertEqual(holders[0]["exe"], r"C:\GoodbyeDPI\GoodbyeDPI.exe")
self.assertEqual(holders[0]["windivert_dll"], r"C:\GoodbyeDPI\WinDivert64.dll")
def test_own_process_is_not_reported(self) -> None:
launch_conflicts.register_own_windivert_dirs(r"C:\Zapret2")
records = [(1234, "winws2.exe")]
modules = {
1234: [
os.path.join(r"C:\Zapret2", "exe", "winws2.exe"),
os.path.join(r"C:\Zapret2", "bin", "WinDivert64.dll"),
],
}
records_patch, modules_patch = _make_holder_env(records, modules)
with records_patch, modules_patch:
holders = launch_conflicts.find_windivert_holder_processes()
self.assertEqual(holders, [])
def test_current_process_pid_is_skipped(self) -> None:
own_pid = os.getpid()
records = [(own_pid, "python.exe")]
modules = {own_pid: [r"C:\Python\python.exe", r"C:\Python\WinDivert64.dll"]}
records_patch, modules_patch = _make_holder_env(records, modules)
with records_patch, modules_patch:
holders = launch_conflicts.find_windivert_holder_processes()
self.assertEqual(holders, [])
def test_foreign_winws_from_other_folder_is_reported(self) -> None:
launch_conflicts.register_own_windivert_dirs(r"C:\Zapret2")
records = [(4321, "winws.exe")]
modules = {
4321: [
r"C:\OtherZapret\winws.exe",
r"C:\OtherZapret\WinDivert.dll",
],
}
records_patch, modules_patch = _make_holder_env(records, modules)
with records_patch, modules_patch:
holders = launch_conflicts.find_windivert_holder_processes()
self.assertEqual(len(holders), 1)
self.assertEqual(holders[0]["exe"], r"C:\OtherZapret\winws.exe")
class ServiceImagePathTests(unittest.TestCase):
def test_nt_prefix_and_quotes_are_stripped(self) -> None:
self.assertEqual(
launch_conflicts._normalize_service_image_path(r"\??\C:\GoodbyeDPI\WinDivert64.sys"),
r"C:\GoodbyeDPI\WinDivert64.sys",
)
self.assertEqual(
launch_conflicts._normalize_service_image_path('"C:\\A B\\WinDivert64.sys"'),
r"C:\A B\WinDivert64.sys",
)
self.assertEqual(launch_conflicts._normalize_service_image_path(None), "")
def test_returns_empty_list_when_winreg_is_unavailable(self) -> None:
# На не-Windows системах функция должна тихо вернуть пустой список.
result = launch_conflicts.find_foreign_windivert_service_paths()
self.assertIsInstance(result, list)
class ConflictHintTests(unittest.TestCase):
def test_hint_prefers_live_holder_process(self) -> None:
holder = {
"name": "GoodbyeDPI.exe",
"exe": r"C:\GoodbyeDPI\GoodbyeDPI.exe",
"windivert_dll": r"C:\GoodbyeDPI\WinDivert64.dll",
"pid": 1234,
}
with (
patch.object(launch_conflicts, "find_windivert_holder_processes", return_value=[holder]),
patch.object(launch_conflicts, "find_foreign_windivert_service_paths", return_value=[]),
):
hint = launch_conflicts.build_windivert_conflict_hint()
self.assertIsNotNone(hint)
assert hint is not None
self.assertIn("GoodbyeDPI.exe", hint)
self.assertIn("PID 1234", hint)
def test_hint_falls_back_to_foreign_driver_path(self) -> None:
with (
patch.object(launch_conflicts, "find_windivert_holder_processes", return_value=[]),
patch.object(
launch_conflicts,
"find_foreign_windivert_service_paths",
return_value=[r"C:\GoodbyeDPI\WinDivert64.sys"],
),
):
hint = launch_conflicts.build_windivert_conflict_hint()
self.assertIsNotNone(hint)
assert hint is not None
self.assertIn(r"C:\GoodbyeDPI\WinDivert64.sys", hint)
def test_hint_is_none_when_nothing_found(self) -> None:
with (
patch.object(launch_conflicts, "find_windivert_holder_processes", return_value=[]),
patch.object(launch_conflicts, "find_foreign_windivert_service_paths", return_value=[]),
):
self.assertIsNone(launch_conflicts.build_windivert_conflict_hint())
class DynamicLaunchAdviceTests(unittest.TestCase):
def test_advice_uses_dynamic_detection_when_blacklist_is_empty(self) -> None:
holder = {
"name": "GoodbyeDPI.exe",
"exe": r"C:\GoodbyeDPI\GoodbyeDPI.exe",
"windivert_dll": r"C:\GoodbyeDPI\WinDivert64.dll",
"pid": 1234,
}
with (
patch.object(launch_conflicts, "check_conflicting_processes", return_value=[]),
patch.object(launch_conflicts, "find_windivert_holder_processes", return_value=[holder]),
patch.object(launch_conflicts, "find_foreign_windivert_service_paths", return_value=[]),
):
advice = launch_conflicts.build_launch_conflict_advice()
self.assertIsNotNone(advice)
assert advice is not None
cause, solution = advice
self.assertIn("GoodbyeDPI.exe", cause)
self.assertIn("Закройте эту программу", solution)
def test_advice_reports_foreign_driver_when_no_process_found(self) -> None:
with (
patch.object(launch_conflicts, "check_conflicting_processes", return_value=[]),
patch.object(launch_conflicts, "find_windivert_holder_processes", return_value=[]),
patch.object(
launch_conflicts,
"find_foreign_windivert_service_paths",
return_value=[r"C:\GoodbyeDPI\WinDivert64.sys"],
),
):
advice = launch_conflicts.build_launch_conflict_advice()
self.assertIsNotNone(advice)
assert advice is not None
cause, _solution = advice
self.assertIn(r"C:\GoodbyeDPI\WinDivert64.sys", cause)
def test_advice_is_none_when_nothing_found(self) -> None:
with (
patch.object(launch_conflicts, "check_conflicting_processes", return_value=[]),
patch.object(launch_conflicts, "find_windivert_holder_processes", return_value=[]),
patch.object(launch_conflicts, "find_foreign_windivert_service_paths", return_value=[]),
):
self.assertIsNone(launch_conflicts.build_launch_conflict_advice())
class ReadinessErrorConflictSuffixTests(unittest.TestCase):
def test_conflict_hint_is_appended_to_readiness_error(self) -> None:
from winws_runtime.health import windivert_diagnostics
hint = "Возможный конфликт: GoodbyeDPI.exe (PID 1234, C:\\GoodbyeDPI\\GoodbyeDPI.exe) держит WinDivert — закройте эту программу"
with patch.object(launch_conflicts, "build_windivert_conflict_hint", return_value=hint):
message = windivert_diagnostics.describe_windivert_readiness_failure(None)
self.assertIn("WinDivert ещё не готов к открытию фильтра", message)
self.assertIn("GoodbyeDPI.exe", message)
def test_error_stays_clean_without_conflicts(self) -> None:
from winws_runtime.health import windivert_diagnostics
with patch.object(launch_conflicts, "build_windivert_conflict_hint", return_value=None):
message = windivert_diagnostics.describe_windivert_readiness_failure(None)
self.assertEqual(message, "WinDivert ещё не готов к открытию фильтра")
if __name__ == "__main__":
unittest.main()