mirror of
https://github.com/python/cpython.git
synced 2025-09-27 02:39:58 +00:00
Issue #18174: "python -m test --huntrleaks ..." now also checks for leak of
file descriptors. Patch written by Richard Oudkerk.
This commit is contained in:
parent
6661d885a3
commit
076fc872bc
3 changed files with 94 additions and 19 deletions
|
@ -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():
|
||||||
|
|
|
@ -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):
|
||||||
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
|
||||||
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.
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue