diff options
author | Giampaolo Rodola <g.rodola@gmail.com> | 2020-05-04 20:01:36 +0200 |
---|---|---|
committer | Giampaolo Rodola <g.rodola@gmail.com> | 2020-05-04 20:01:36 +0200 |
commit | 15b35646d95b617b04a2e556f1b0902d3f64c3cb (patch) | |
tree | 730f7f789628ddd841e961649bd412d60f700a6f | |
parent | 240b15c172538e34a3b073a5c68b232658248049 (diff) | |
download | psutil-15b35646d95b617b04a2e556f1b0902d3f64c3cb.tar.gz |
have mem leak test class check num of fds/handles
-rw-r--r-- | psutil/tests/__init__.py | 24 | ||||
-rwxr-xr-x | psutil/tests/test_memory_leaks.py | 41 | ||||
-rw-r--r-- | psutil/tests/test_testutils.py | 25 |
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): |