Commit 64856ad8 authored by Victor Stinner's avatar Victor Stinner Committed by GitHub

bpo-18174: regrtest -R 3:3 now also detects FD leak (#7409)

"python -m test --huntrleaks ..." now also checks for leak of file
descriptors.
Co-Authored-By: default avatarRichard Oudkerk <shibturn@gmail.com>
parent 27058190
...@@ -1416,37 +1416,59 @@ def dash_R(the_module, test, indirect_test, huntrleaks): ...@@ -1416,37 +1416,59 @@ def dash_R(the_module, test, indirect_test, huntrleaks):
nwarmup, ntracked, fname = huntrleaks nwarmup, ntracked, fname = huntrleaks
fname = os.path.join(support.SAVEDCWD, fname) fname = os.path.join(support.SAVEDCWD, fname)
repcount = nwarmup + ntracked repcount = nwarmup + ntracked
rc_deltas = [0] * ntracked
fd_deltas = [0] * ntracked
print >> sys.stderr, "beginning", repcount, "repetitions" print >> sys.stderr, "beginning", repcount, "repetitions"
print >> sys.stderr, ("1234567890"*(repcount//10 + 1))[:repcount] print >> sys.stderr, ("1234567890"*(repcount//10 + 1))[:repcount]
dash_R_cleanup(fs, ps, pic, zdc, abcs) dash_R_cleanup(fs, ps, pic, zdc, abcs)
# initialize variables to make pyflakes quiet
rc_before = fd_before = 0
for i in range(repcount): for i in range(repcount):
rc_before = sys.gettotalrefcount()
run_the_test() run_the_test()
sys.stderr.write('.') sys.stderr.write('.')
dash_R_cleanup(fs, ps, pic, zdc, abcs) dash_R_cleanup(fs, ps, pic, zdc, abcs)
rc_after = sys.gettotalrefcount() rc_after = sys.gettotalrefcount()
fd_after = support.fd_count()
if i >= nwarmup: if i >= nwarmup:
deltas.append(rc_after - rc_before) rc_deltas[i - nwarmup] = rc_after - rc_before
fd_deltas[i - nwarmup] = fd_after - fd_before
rc_before = rc_after
fd_before = fd_after
print >> sys.stderr print >> sys.stderr
# bpo-30776: Try to ignore false positives: # These checkers return False on success, True on failure
# def check_rc_deltas(deltas):
# [3, 0, 0] # Checker for reference counters and memomry blocks.
# [0, 1, 0] #
# [8, -8, 1] # bpo-30776: Try to ignore false positives:
# #
# Expected leaks: # [3, 0, 0]
# # [0, 1, 0]
# [5, 5, 6] # [8, -8, 1]
# [10, 1, 1] #
if all(delta >= 1 for delta in deltas): # Expected leaks:
msg = '%s leaked %s references, sum=%s' % (test, deltas, sum(deltas)) #
print >> sys.stderr, msg # [5, 5, 6]
with open(fname, "a") as refrep: # [10, 1, 1]
print >> refrep, msg return all(delta >= 1 for delta in deltas)
refrep.flush()
return True def check_fd_deltas(deltas):
return False return any(deltas)
failed = False
for deltas, item_name, checker in [
(rc_deltas, 'references', check_rc_deltas),
(fd_deltas, 'file descriptors', check_fd_deltas)
]:
if checker(deltas):
msg = '%s leaked %s %s, sum=%s' % (test, deltas, item_name, sum(deltas))
print >> sys.stderr, msg
with open(fname, "a") as refrep:
print >> refrep, msg
refrep.flush()
failed = True
return failed
def dash_R_cleanup(fs, ps, pic, zdc, abcs): def dash_R_cleanup(fs, ps, pic, zdc, abcs):
import gc, copy_reg import gc, copy_reg
......
...@@ -2062,6 +2062,63 @@ def _crash_python(): ...@@ -2062,6 +2062,63 @@ def _crash_python():
_testcapi._read_null() _testcapi._read_null()
def fd_count():
"""Count the number of open file descriptors.
"""
if sys.platform.startswith(('linux', 'freebsd')):
try:
names = os.listdir("/proc/self/fd")
return len(names)
except FileNotFoundError:
pass
old_modes = None
if sys.platform == 'win32':
# bpo-25306, bpo-31009: Call CrtSetReportMode() to not kill the process
# on invalid file descriptor if Python is compiled in debug mode
try:
import msvcrt
msvcrt.CrtSetReportMode
except (AttributeError, ImportError):
# no msvcrt or a release build
pass
else:
old_modes = {}
for report_type in (msvcrt.CRT_WARN,
msvcrt.CRT_ERROR,
msvcrt.CRT_ASSERT):
old_modes[report_type] = msvcrt.CrtSetReportMode(report_type, 0)
MAXFD = 256
if hasattr(os, 'sysconf'):
try:
MAXFD = os.sysconf("SC_OPEN_MAX")
except OSError:
pass
try:
count = 0
for fd in range(MAXFD):
try:
# Prefer dup() over fstat(). fstat() can require input/output
# whereas dup() doesn't.
fd2 = os.dup(fd)
except OSError as e:
if e.errno != errno.EBADF:
raise
else:
os.close(fd2)
count += 1
finally:
if old_modes is not None:
for report_type in (msvcrt.CRT_WARN,
msvcrt.CRT_ERROR,
msvcrt.CRT_ASSERT):
msvcrt.CrtSetReportMode(report_type, old_modes[report_type])
return count
class SaveSignals: class SaveSignals:
""" """
Save an restore signal handlers. Save an restore signal handlers.
......
...@@ -511,6 +511,24 @@ class ArgsTestCase(BaseTestCase): ...@@ -511,6 +511,24 @@ class ArgsTestCase(BaseTestCase):
""") """)
self.check_leak(code, 'references') self.check_leak(code, 'references')
@unittest.skipUnless(Py_DEBUG, 'need a debug build')
def test_huntrleaks_fd_leak(self):
# test --huntrleaks for file descriptor leak
code = textwrap.dedent("""
import os
import unittest
from test import support
class FDLeakTest(unittest.TestCase):
def test_leak(self):
fd = os.open(__file__, os.O_RDONLY)
# bug: never close the file descriptor
def test_main():
support.run_unittest(FDLeakTest)
""")
self.check_leak(code, 'file descriptors')
def test_list_tests(self): def test_list_tests(self):
# test --list-tests # test --list-tests
tests = [self.create_test() for i in range(5)] tests = [self.create_test() for i in range(5)]
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment