summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGiampaolo Rodola <g.rodola@gmail.com>2020-05-04 20:01:36 +0200
committerGiampaolo Rodola <g.rodola@gmail.com>2020-05-04 20:01:36 +0200
commit15b35646d95b617b04a2e556f1b0902d3f64c3cb (patch)
tree730f7f789628ddd841e961649bd412d60f700a6f
parent240b15c172538e34a3b073a5c68b232658248049 (diff)
downloadpsutil-15b35646d95b617b04a2e556f1b0902d3f64c3cb.tar.gz
have mem leak test class check num of fds/handles
-rw-r--r--psutil/tests/__init__.py24
-rwxr-xr-xpsutil/tests/test_memory_leaks.py41
-rw-r--r--psutil/tests/test_testutils.py25
3 files changed, 43 insertions, 47 deletions
diff --git a/psutil/tests/__init__.py b/psutil/tests/__init__.py
index ea283f6e..91a519c1 100644
--- a/psutil/tests/__init__.py
+++ b/psutil/tests/__init__.py
@@ -900,7 +900,13 @@ class TestMemoryLeak(PsutilTestCase):
It does so by calling a function many times, and checks whether the
process memory usage increased before and after having called the
function repeadetly.
+
+ In addition also call the function onces and make sure num_fds()
+ (POSIX) or num_handles() (Windows) does not increase. This is done
+ in order to discover forgotten close(2) and CloseHandle syscalls.
+
Note that sometimes this may produce false positives.
+
PyPy appears to be completely unstable for this framework, probably
because of how its JIT handles memory, so tests on PYPY are
automatically skipped.
@@ -910,6 +916,7 @@ class TestMemoryLeak(PsutilTestCase):
warmup_times = 10
tolerance = 4096 # memory
retry_for = 3.0 # seconds
+ check_fds = True # whether to check if num_fds() increased
verbose = True
def setUp(self):
@@ -922,6 +929,12 @@ class TestMemoryLeak(PsutilTestCase):
mem = self._thisproc.memory_full_info()
return getattr(mem, "uss", mem.rss)
+ def _get_fds_or_handles(self):
+ if POSIX:
+ return self._thisproc.num_fds()
+ else:
+ return self._thisproc.num_handles()
+
def _call(self, fun):
return fun()
@@ -959,7 +972,7 @@ class TestMemoryLeak(PsutilTestCase):
print_color(msg, color="yellow", file=sys.stderr)
def execute(self, fun, times=times, warmup_times=warmup_times,
- tolerance=tolerance, retry_for=retry_for):
+ tolerance=tolerance, retry_for=retry_for, check_fds=check_fds):
"""Test a callable."""
if times <= 0:
raise ValueError("times must be > 0")
@@ -970,6 +983,15 @@ class TestMemoryLeak(PsutilTestCase):
if retry_for is not None and retry_for < 0:
raise ValueError("retry_for must be >= 0")
+ if check_fds:
+ before = self._get_fds_or_handles()
+ self._call(fun)
+ after = self._get_fds_or_handles()
+ diff = abs(after - before)
+ if diff > 0:
+ msg = "%s unclosed fd(s) or handle(s)" % (diff)
+ raise self.fail(msg)
+
# warm up
self._call_ntimes(fun, warmup_times)
mem1 = self._call_ntimes(fun, times)
diff --git a/psutil/tests/test_memory_leaks.py b/psutil/tests/test_memory_leaks.py
index 2eb2bc65..61049a91 100755
--- a/psutil/tests/test_memory_leaks.py
+++ b/psutil/tests/test_memory_leaks.py
@@ -472,47 +472,6 @@ class TestModuleFunctionsLeaks(TestMemoryLeak):
self.execute(lambda: cext.winservice_query_descr(name))
-# =====================================================================
-# --- File descriptors and handlers
-# =====================================================================
-
-
-class TestUnclosedFdsOrHandles(unittest.TestCase):
- """Call a function N times (twice) and make sure the number of file
- descriptors (POSIX) or handles (Windows) does not increase. Done in
- order to discover forgotten close(2) and CloseHandle syscalls.
- """
- times = 2
-
- def execute(self, iterator):
- p = psutil.Process()
- failures = []
- for fun, fun_name in iterator:
- before = p.num_fds() if POSIX else p.num_handles()
- try:
- for x in range(self.times):
- fun()
- except psutil.Error:
- continue
- else:
- after = p.num_fds() if POSIX else p.num_handles()
- if abs(after - before) > 0:
- fail = "failure while calling %s function " \
- "(before=%s, after=%s)" % (fun, before, after)
- failures.append(fail)
- if failures:
- self.fail('\n' + '\n'.join(failures))
-
- def test_process_apis(self):
- p = psutil.Process()
- ns = process_namespace(p)
- self.execute(ns.iter(ns.getters + ns.setters))
-
- def test_system_apis(self):
- ns = system_namespace
- self.execute(ns.iter(ns.all))
-
-
if __name__ == '__main__':
from psutil.tests.runner import run_from_name
run_from_name(__file__)
diff --git a/psutil/tests/test_testutils.py b/psutil/tests/test_testutils.py
index 56a465d7..383b1470 100644
--- a/psutil/tests/test_testutils.py
+++ b/psutil/tests/test_testutils.py
@@ -352,21 +352,23 @@ class TestNetUtils(PsutilTestCase):
@serialrun
class TestMemLeakClass(TestMemoryLeak):
+ @retry_on_failure()
def test_times(self):
def fun():
cnt['cnt'] += 1
cnt = {'cnt': 0}
self.execute(fun, times=1, warmup_times=10)
- self.assertEqual(cnt['cnt'], 11)
+ self.assertEqual(cnt['cnt'], 12)
self.execute(fun, times=10, warmup_times=10)
- self.assertEqual(cnt['cnt'], 31)
+ self.assertEqual(cnt['cnt'], 33)
+ @retry_on_failure()
def test_warmup_times(self):
def fun():
cnt['cnt'] += 1
cnt = {'cnt': 0}
self.execute(fun, times=1, warmup_times=10)
- self.assertEqual(cnt['cnt'], 11)
+ self.assertEqual(cnt['cnt'], 12)
def test_param_err(self):
self.assertRaises(ValueError, self.execute, lambda: 0, times=0)
@@ -383,7 +385,7 @@ class TestMemLeakClass(TestMemoryLeak):
times = 100
self.assertRaises(AssertionError, self.execute, fun, times=times,
warmup_times=10, retry_for=None)
- self.assertEqual(len(ls), times + 10)
+ self.assertEqual(len(ls), times + 11)
@retry_on_failure(retries=20) # 2 secs
def test_leak_with_retry(self, ls=[]):
@@ -397,15 +399,17 @@ class TestMemLeakClass(TestMemoryLeak):
self.assertIn("try calling fun for another", f.getvalue())
self.assertGreater(len(ls), times)
+ @retry_on_failure()
def test_tolerance(self):
def fun():
ls.append("x" * 24 * 1024)
ls = []
times = 100
self.execute(fun, times=times, warmup_times=0,
- tolerance=200 * 1024 * 1024)
+ tolerance=200 * 1024 * 1024, check_fds=False)
self.assertEqual(len(ls), times)
+ @retry_on_failure()
def test_execute_w_exc(self):
def fun():
1 / 0
@@ -420,6 +424,17 @@ class TestMemLeakClass(TestMemoryLeak):
with self.assertRaises(AssertionError):
self.execute_w_exc(ZeroDivisionError, fun)
+ def test_unclosed_fds(self):
+ def fun():
+ f = open(__file__)
+ self.addCleanup(f.close)
+ box.append(f)
+
+ box = []
+ self.assertRaisesRegex(
+ AssertionError, r"1 unclosed fd\(s\) or handle\(s\)",
+ self.execute, fun, times=5, warmup_times=5)
+
class TestTestingUtils(PsutilTestCase):