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

file descriptors. Patch written by Richard Oudkerk.
This commit is contained in:
Victor Stinner 2015-10-03 00:20:56 +02:00
parent 6661d885a3
commit 076fc872bc
3 changed files with 94 additions and 19 deletions

View file

@ -1,3 +1,4 @@
import errno
import os import os
import re import re
import sys import sys
@ -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):
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):
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):
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():

View file

@ -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))
if proc.stderr:
msg += ("\n"
"stderr:\n" "stderr:\n"
"---\n" "---\n"
"%s" "%s"
"---\n" "---\n"
% (str(args), proc.returncode, proc.stdout, proc.stderr)) % proc.stderr)
self.fail(msg)
return proc return proc
@ -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()

View file

@ -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.