Skip to content

Commit eb30805

Browse files
authored
gh-154137: Add test.support.os_helper.handle_count() (#157637)
Use the new @ctypes.util.wrap_dll_function decorator to wrap kernel32 functions.
1 parent 993fd8e commit eb30805

3 files changed

Lines changed: 70 additions & 39 deletions

File tree

‎Lib/test/support/os_helper.py‎

Lines changed: 50 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
import warnings
1414

1515
from test import support
16+
if support.MS_WINDOWS:
17+
import _winapi
1618

1719

1820
# Filename used for testing
@@ -854,36 +856,72 @@ def __new__(mcls, name, bases, dct, *, source_date_epoch):
854856

855857
try:
856858
if support.MS_WINDOWS:
857-
import ctypes
859+
import ctypes.util
858860
kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)
859-
860-
ERROR_FILE_NOT_FOUND = 2
861-
DDD_REMOVE_DEFINITION = 2
862-
DDD_EXACT_MATCH_ON_REMOVE = 4
863-
DDD_NO_BROADCAST_SYSTEM = 8
864861
else:
865862
raise AttributeError
866863
except (ImportError, AttributeError):
867864
def subst_drive(path):
868865
raise unittest.SkipTest('ctypes or kernel32 is not available')
866+
867+
def handle_count():
868+
return 0
869869
else:
870+
ERROR_FILE_NOT_FOUND = 2
871+
DDD_REMOVE_DEFINITION = 2
872+
DDD_EXACT_MATCH_ON_REMOVE = 4
873+
DDD_NO_BROADCAST_SYSTEM = 8
874+
875+
@ctypes.util.wrap_dll_function(kernel32)
876+
def DefineDosDeviceW(
877+
dwFlags: ctypes.wintypes.DWORD,
878+
lpDeviceName: ctypes.c_wchar_p,
879+
lpTargetPath: ctypes.c_wchar_p,
880+
) -> ctypes.wintypes.BOOL:
881+
pass
882+
883+
@ctypes.util.wrap_dll_function(kernel32)
884+
def QueryDosDeviceW(
885+
lpDeviceName: ctypes.c_wchar_p,
886+
lpTargetPath: ctypes.c_wchar_p,
887+
ucchMax: ctypes.wintypes.DWORD,
888+
) -> ctypes.wintypes.DWORD:
889+
pass
890+
870891
@contextlib.contextmanager
871892
def subst_drive(path):
872893
"""Temporarily yield a substitute drive for a given path."""
873894
for c in reversed(string.ascii_uppercase):
874895
drive = f'{c}:'
875-
if (not kernel32.QueryDosDeviceW(drive, None, 0) and
896+
if (not QueryDosDeviceW(drive, None, 0) and
876897
ctypes.get_last_error() == ERROR_FILE_NOT_FOUND):
877898
break
878899
else:
879900
raise unittest.SkipTest('no available logical drive')
880-
if not kernel32.DefineDosDeviceW(
881-
DDD_NO_BROADCAST_SYSTEM, drive, path):
901+
902+
if not DefineDosDeviceW(DDD_NO_BROADCAST_SYSTEM, drive, path):
882903
raise ctypes.WinError(ctypes.get_last_error())
904+
883905
try:
884906
yield drive
885907
finally:
886-
if not kernel32.DefineDosDeviceW(
887-
DDD_REMOVE_DEFINITION | DDD_EXACT_MATCH_ON_REMOVE,
888-
drive, path):
908+
flags = DDD_REMOVE_DEFINITION | DDD_EXACT_MATCH_ON_REMOVE
909+
if not DefineDosDeviceW(flags, drive, path):
889910
raise ctypes.WinError(ctypes.get_last_error())
911+
912+
@ctypes.util.wrap_dll_function(kernel32)
913+
def GetProcessHandleCount(khProcess: ctypes.wintypes.HANDLE,
914+
pdwHandleCount: ctypes.wintypes.LPDWORD) -> ctypes.wintypes.BOOL:
915+
pass
916+
917+
del kernel32
918+
919+
def handle_count():
920+
# Pseudo-handle that doesn't need to be closed
921+
hproc = _winapi.GetCurrentProcess()
922+
923+
handle_count = ctypes.wintypes.DWORD()
924+
if not GetProcessHandleCount(hproc, ctypes.byref(handle_count)):
925+
raise ctypes.WinError(ctypes.get_last_error())
926+
927+
return handle_count.value

‎Lib/test/test_os/test_windows.py‎

Lines changed: 2 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -457,28 +457,7 @@ def test_unlink_removes_junction(self):
457457
class Win32NtTests(unittest.TestCase):
458458
def test_getfinalpathname_handles(self):
459459
nt = import_helper.import_module('nt')
460-
ctypes = import_helper.import_module('ctypes')
461-
# Ruff false positive -- it thinks we're redefining `ctypes` here
462-
import ctypes.wintypes # noqa: F811
463-
464-
kernel = ctypes.WinDLL('Kernel32.dll', use_last_error=True)
465-
@ctypes.util.wrap_dll_function(kernel)
466-
def GetCurrentProcess() -> ctypes.wintypes.HANDLE:
467-
pass
468-
469-
@ctypes.util.wrap_dll_function(kernel)
470-
def GetProcessHandleCount(khProcess: ctypes.wintypes.HANDLE,
471-
pdwHandleCount: ctypes.wintypes.LPDWORD) -> ctypes.wintypes.BOOL:
472-
pass
473-
474-
# This is a pseudo-handle that doesn't need to be closed
475-
hproc = GetCurrentProcess()
476-
477-
handle_count = ctypes.wintypes.DWORD()
478-
ok = GetProcessHandleCount(hproc, ctypes.byref(handle_count))
479-
self.assertEqual(1, ok)
480-
481-
before_count = handle_count.value
460+
before_count = os_helper.handle_count()
482461

483462
# The first two test the error path, __file__ tests the success path
484463
filenames = [
@@ -500,11 +479,7 @@ def GetProcessHandleCount(khProcess: ctypes.wintypes.HANDLE,
500479
except Exception:
501480
pass
502481

503-
ok = kernel.GetProcessHandleCount(hproc, ctypes.byref(handle_count))
504-
self.assertEqual(1, ok)
505-
506-
handle_delta = handle_count.value - before_count
507-
482+
handle_delta = os_helper.handle_count() - before_count
508483
self.assertEqual(0, handle_delta)
509484

510485
@support.requires_subprocess()

‎Lib/test/test_support.py‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@
2727
from test.support import socket_helper
2828
from test.support import warnings_helper
2929

30+
if support.MS_WINDOWS:
31+
import _winapi
32+
33+
3034
TESTFN = os_helper.TESTFN
3135

3236

@@ -624,6 +628,20 @@ def test_fd_count(self):
624628
os.close(fd)
625629
self.assertEqual(more - start, 1)
626630

631+
@unittest.skipUnless(support.MS_WINDOWS, "test specific to Windows")
632+
def test_handle_count(self):
633+
start = os_helper.handle_count()
634+
handle = _winapi.CreateFile(
635+
__file__, _winapi.GENERIC_READ,
636+
0, _winapi.NULL,
637+
_winapi.OPEN_EXISTING,
638+
0, _winapi.NULL)
639+
try:
640+
more = os_helper.handle_count()
641+
finally:
642+
_winapi.CloseHandle(handle)
643+
self.assertEqual(more - start, 1)
644+
627645
def check_print_warning(self, msg, expected):
628646
stderr = io.StringIO()
629647
with support.swap_attr(support.print_warning, 'orig_stderr', stderr):

0 commit comments

Comments
 (0)