Kaydet (Commit) 076fc872 authored tarafından Victor Stinner's avatar Victor Stinner

Issue #18174: "python -m test --huntrleaks ..." now also checks for leak of

file descriptors. Patch written by Richard Oudkerk.
üst 6661d885
import errno
import os import os
import re import re
import sys import sys
...@@ -6,6 +7,36 @@ from inspect import isabstract ...@@ -6,6 +7,36 @@ from inspect import isabstract
from test import support from test import support
try:
MAXFD = os.sysconf("SC_OPEN_MAX")
except Exception:
MAXFD = 256
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
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
return count
def dash_R(the_module, test, indirect_test, huntrleaks): def dash_R(the_module, test, indirect_test, huntrleaks):
"""Run a test multiple times, looking for reference leaks. """Run a test multiple times, looking for reference leaks.
...@@ -42,20 +73,25 @@ def dash_R(the_module, test, indirect_test, huntrleaks): ...@@ -42,20 +73,25 @@ def dash_R(the_module, test, indirect_test, huntrleaks):
repcount = nwarmup + ntracked repcount = nwarmup + ntracked
rc_deltas = [0] * repcount rc_deltas = [0] * repcount
alloc_deltas = [0] * repcount alloc_deltas = [0] * repcount
fd_deltas = [0] * repcount
print("beginning", repcount, "repetitions", file=sys.stderr) print("beginning", repcount, "repetitions", file=sys.stderr)
print(("1234567890"*(repcount//10 + 1))[:repcount], file=sys.stderr, print(("1234567890"*(repcount//10 + 1))[:repcount], file=sys.stderr,
flush=True) flush=True)
# initialize variables to make pyflakes quiet # initialize variables to make pyflakes quiet
rc_before = alloc_before = 0 rc_before = alloc_before = fd_before = 0
for i in range(repcount): for i in range(repcount):
indirect_test() indirect_test()
alloc_after, rc_after = dash_R_cleanup(fs, ps, pic, zdc, abcs) alloc_after, rc_after, fd_after = dash_R_cleanup(fs, ps, pic, zdc,
abcs)
print('.', end='', flush=True) print('.', end='', flush=True)
if i >= nwarmup: if i >= nwarmup:
rc_deltas[i] = rc_after - rc_before rc_deltas[i] = rc_after - rc_before
alloc_deltas[i] = alloc_after - alloc_before alloc_deltas[i] = alloc_after - alloc_before
alloc_before, rc_before = alloc_after, rc_after fd_deltas[i] = fd_after - fd_before
alloc_before = alloc_after
rc_before = rc_after
fd_before = fd_after
print(file=sys.stderr) print(file=sys.stderr)
# These checkers return False on success, True on failure # These checkers return False on success, True on failure
def check_rc_deltas(deltas): def check_rc_deltas(deltas):
...@@ -71,7 +107,8 @@ def dash_R(the_module, test, indirect_test, huntrleaks): ...@@ -71,7 +107,8 @@ def dash_R(the_module, test, indirect_test, huntrleaks):
failed = False failed = False
for deltas, item_name, checker in [ for deltas, item_name, checker in [
(rc_deltas, 'references', check_rc_deltas), (rc_deltas, 'references', check_rc_deltas),
(alloc_deltas, 'memory blocks', check_alloc_deltas)]: (alloc_deltas, 'memory blocks', check_alloc_deltas),
(fd_deltas, 'file descriptors', check_rc_deltas)]:
if checker(deltas): if checker(deltas):
msg = '%s leaked %s %s, sum=%s' % ( msg = '%s leaked %s %s, sum=%s' % (
test, deltas[nwarmup:], item_name, sum(deltas)) test, deltas[nwarmup:], item_name, sum(deltas))
...@@ -151,7 +188,7 @@ def dash_R_cleanup(fs, ps, pic, zdc, abcs): ...@@ -151,7 +188,7 @@ def dash_R_cleanup(fs, ps, pic, zdc, abcs):
func1 = sys.getallocatedblocks func1 = sys.getallocatedblocks
func2 = sys.gettotalrefcount func2 = sys.gettotalrefcount
gc.collect() gc.collect()
return func1(), func2() return func1(), func2(), fd_count()
def warm_caches(): def warm_caches():
......
...@@ -383,27 +383,32 @@ class BaseTestCase(unittest.TestCase): ...@@ -383,27 +383,32 @@ class BaseTestCase(unittest.TestCase):
self.assertTrue(0 <= randseed <= 10000000, randseed) self.assertTrue(0 <= randseed <= 10000000, randseed)
return randseed return randseed
def run_command(self, args, input=None, exitcode=0): def run_command(self, args, input=None, exitcode=0, **kw):
if not input: if not input:
input = '' input = ''
if 'stderr' not in kw:
kw['stderr'] = subprocess.PIPE
proc = subprocess.run(args, proc = subprocess.run(args,
universal_newlines=True, universal_newlines=True,
input=input, input=input,
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.PIPE) **kw)
if proc.returncode != exitcode: if proc.returncode != exitcode:
self.fail("Command %s failed with exit code %s\n" msg = ("Command %s failed with exit code %s\n"
"\n" "\n"
"stdout:\n" "stdout:\n"
"---\n" "---\n"
"%s\n" "%s\n"
"---\n" "---\n"
"\n" % (str(args), proc.returncode, proc.stdout))
"stderr:\n" if proc.stderr:
"---\n" msg += ("\n"
"%s" "stderr:\n"
"---\n" "---\n"
% (str(args), proc.returncode, proc.stdout, proc.stderr)) "%s"
"---\n"
% proc.stderr)
self.fail(msg)
return proc return proc
...@@ -637,6 +642,36 @@ class ArgsTestCase(BaseTestCase): ...@@ -637,6 +642,36 @@ class ArgsTestCase(BaseTestCase):
output = self.run_tests('--forever', test, exitcode=1) output = self.run_tests('--forever', test, exitcode=1)
self.check_executed_tests(output, [test]*3, failed=test) self.check_executed_tests(output, [test]*3, failed=test)
def test_huntrleaks_fd_leak(self):
# test --huntrleaks for file descriptor leak
code = textwrap.dedent("""
import os
import unittest
class FDLeakTest(unittest.TestCase):
def test_leak(self):
fd = os.open(__file__, os.O_RDONLY)
# bug: never cloes the file descriptor
""")
test = self.create_test(code=code)
filename = 'reflog.txt'
self.addCleanup(support.unlink, filename)
output = self.run_tests('--huntrleaks', '3:3:', test,
exitcode=1,
stderr=subprocess.STDOUT)
self.check_executed_tests(output, [test], failed=test)
line = 'beginning 6 repetitions\n123456\n......\n'
self.check_line(output, re.escape(line))
line2 = '%s leaked [1, 1, 1] file descriptors, sum=3\n' % test
self.check_line(output, re.escape(line2))
with open(filename) as fp:
reflog = fp.read()
self.assertEqual(reflog, line2)
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()
...@@ -170,6 +170,9 @@ Documentation ...@@ -170,6 +170,9 @@ Documentation
Tests Tests
----- -----
- Issue #18174: ``python -m test --huntrleaks ...`` now also checks for leak of
file descriptors. Patch written by Richard Oudkerk.
- Issue #25260: Fix ``python -m test --coverage`` on Windows. Remove the - Issue #25260: Fix ``python -m test --coverage`` on Windows. Remove the
list of ignored directories. list of ignored directories.
......
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