5 new commits in pytest: https://bitbucket.org/hpk42/pytest/commits/edc9d6f6e76a/ Changeset: edc9d6f6e76a Branch: refactor_LsofFdLeakChecker User: Marc Abramowitz Date: 2014-04-01 19:15:27 Summary: testing/conftest.py: Refactor lsof fd leak checking
Isolate the logic into one class to make easier to understand, more maintainable. This may aid in later plugging in an alternative implementation, such as one that uses psutil (https://bitbucket.org/hpk42/pytest/pull-request/137/use-psutil-to-detect-open-files-in-tests/diff) Affected #: 1 file diff -r d4b093bc36df977c017b43286953286542c788cf -r edc9d6f6e76ac45d9d1adadb710a239c030af28e testing/conftest.py --- a/testing/conftest.py +++ b/testing/conftest.py @@ -4,7 +4,23 @@ pytest_plugins = "pytester", import os, py -pid = os.getpid() + +class LsofFdLeakChecker(object): + def get_open_files(self): + out = self._exec_lsof() + open_files = self._parse_lsof_output(out) + return open_files + + def _exec_lsof(self): + pid = os.getpid() + return py.process.cmdexec("lsof -p %d" % pid) + + def _parse_lsof_output(self, out): + def isopen(line): + return ("REG" in line or "CHR" in line) and ( + "deleted" not in line and 'mem' not in line and "txt" not in line) + return [x for x in out.split("\n") if isopen(x)] + def pytest_addoption(parser): parser.addoption('--lsof', @@ -15,11 +31,12 @@ config._basedir = py.path.local() if config.getvalue("lsof"): try: - out = py.process.cmdexec("lsof -p %d" % pid) + config._fd_leak_checker = LsofFdLeakChecker() + config._openfiles = config._fd_leak_checker.get_open_files() except py.process.cmdexec.Error: pass else: - config._numfiles = len(getopenfiles(out)) + config._numfiles = len(config._openfiles) #def pytest_report_header(): # return "pid: %s" % os.getpid() @@ -31,8 +48,7 @@ return [x for x in out.split("\n") if isopen(x)] def check_open_files(config): - out2 = py.process.cmdexec("lsof -p %d" % pid) - lines2 = getopenfiles(out2) + lines2 = config._fd_leak_checker.get_open_files() if len(lines2) > config._numfiles + 3: error = [] error.append("***** %s FD leackage detected" % https://bitbucket.org/hpk42/pytest/commits/7a00688fef66/ Changeset: 7a00688fef66 Branch: refactor_LsofFdLeakChecker User: Marc Abramowitz Date: 2014-04-01 22:41:35 Summary: Improve LsofFdLeakChecker; more reliable and useful leak checking * Make it invoke lsof with options for machine-readable output * Parse out file descriptor and filename from lsof output * Draw attention to file descriptors now open that weren't open before Affected #: 1 file diff -r edc9d6f6e76ac45d9d1adadb710a239c030af28e -r 7a00688fef6649283f9316d1c0d0872c0af7bd71 testing/conftest.py --- a/testing/conftest.py +++ b/testing/conftest.py @@ -13,13 +13,24 @@ def _exec_lsof(self): pid = os.getpid() - return py.process.cmdexec("lsof -p %d" % pid) + return py.process.cmdexec("lsof -Ffn0 -p %d" % pid) def _parse_lsof_output(self, out): def isopen(line): - return ("REG" in line or "CHR" in line) and ( - "deleted" not in line and 'mem' not in line and "txt" not in line) - return [x for x in out.split("\n") if isopen(x)] + return line.startswith('f') and ( + "deleted" not in line and 'mem' not in line and "txt" not in line and 'cwd' not in line) + + open_files = [] + + for line in out.split("\n"): + if isopen(line): + fields = line.split('\0') + fd = int(fields[0][1:]) + filename = fields[1][1:] + if filename.startswith('/'): + open_files.append((fd, filename)) + + return open_files def pytest_addoption(parser): @@ -49,11 +60,16 @@ def check_open_files(config): lines2 = config._fd_leak_checker.get_open_files() - if len(lines2) > config._numfiles + 3: + new_fds = sorted(set([t[0] for t in lines2]) - set([t[0] for t in config._openfiles])) + open_files = [t for t in lines2 if t[0] in new_fds] + if open_files: error = [] - error.append("***** %s FD leackage detected" % - (len(lines2)-config._numfiles)) - error.extend(lines2) + error.append("***** %s FD leackage detected" % len(open_files)) + error.extend([str(f) for f in open_files]) + error.append("*** Before:") + error.extend([str(f) for f in config._openfiles]) + error.append("*** After:") + error.extend([str(f) for f in lines2]) error.append(error[0]) # update numfile so that the overall test run continuess config._numfiles = len(lines2) https://bitbucket.org/hpk42/pytest/commits/6751cb39a91f/ Changeset: 6751cb39a91f Branch: refactor_LsofFdLeakChecker User: Marc Abramowitz Date: 2014-04-01 23:13:11 Summary: testing/conftest.py: Reintialize config._openfiles for each test And no longer need getopenfiles or config._numfiles Affected #: 1 file diff -r 7a00688fef6649283f9316d1c0d0872c0af7bd71 -r 6751cb39a91f3fc7ed0c023db7fb9d4571ff1b6a testing/conftest.py --- a/testing/conftest.py +++ b/testing/conftest.py @@ -38,7 +38,8 @@ action="store_true", dest="lsof", default=False, help=("run FD checks if lsof is available")) -def pytest_configure(config): +def pytest_runtest_setup(item): + config = item.config config._basedir = py.path.local() if config.getvalue("lsof"): try: @@ -46,18 +47,10 @@ config._openfiles = config._fd_leak_checker.get_open_files() except py.process.cmdexec.Error: pass - else: - config._numfiles = len(config._openfiles) #def pytest_report_header(): # return "pid: %s" % os.getpid() -def getopenfiles(out): - def isopen(line): - return ("REG" in line or "CHR" in line) and ( - "deleted" not in line and 'mem' not in line and "txt" not in line) - return [x for x in out.split("\n") if isopen(x)] - def check_open_files(config): lines2 = config._fd_leak_checker.get_open_files() new_fds = sorted(set([t[0] for t in lines2]) - set([t[0] for t in config._openfiles])) @@ -71,13 +64,11 @@ error.append("*** After:") error.extend([str(f) for f in lines2]) error.append(error[0]) - # update numfile so that the overall test run continuess - config._numfiles = len(lines2) raise AssertionError("\n".join(error)) def pytest_runtest_teardown(item, __multicall__): item.config._basedir.chdir() - if hasattr(item.config, '_numfiles'): + if hasattr(item.config, '_openfiles'): x = __multicall__.execute() check_open_files(item.config) return x https://bitbucket.org/hpk42/pytest/commits/373fcb6b914d/ Changeset: 373fcb6b914d Branch: refactor_LsofFdLeakChecker User: Marc Abramowitz Date: 2014-04-02 00:36:54 Summary: Remove cast of fd to int and sorting Casting of fd can break for non-numeric fd (e.g.: "rtd" on Linux) and isn't necessary since we don't need to sort. Affected #: 1 file diff -r 6751cb39a91f3fc7ed0c023db7fb9d4571ff1b6a -r 373fcb6b914d6295c7b34ce33d464ab2aae27a9e testing/conftest.py --- a/testing/conftest.py +++ b/testing/conftest.py @@ -25,7 +25,7 @@ for line in out.split("\n"): if isopen(line): fields = line.split('\0') - fd = int(fields[0][1:]) + fd = fields[0][1:] filename = fields[1][1:] if filename.startswith('/'): open_files.append((fd, filename)) @@ -53,7 +53,7 @@ def check_open_files(config): lines2 = config._fd_leak_checker.get_open_files() - new_fds = sorted(set([t[0] for t in lines2]) - set([t[0] for t in config._openfiles])) + new_fds = set([t[0] for t in lines2]) - set([t[0] for t in config._openfiles]) open_files = [t for t in lines2 if t[0] in new_fds] if open_files: error = [] https://bitbucket.org/hpk42/pytest/commits/1cf062005b1c/ Changeset: 1cf062005b1c User: hpk42 Date: 2014-04-02 09:24:16 Summary: Merged in msabramo/pytest/refactor_LsofFdLeakChecker (pull request #138) testing/conftest.py: Refactor lsof fd leak checking Affected #: 1 file diff -r d966b6d87ac51aa38c68d73291fdfeb467dec4b9 -r 1cf062005b1c2743006cde11ee746b8dd53ea40f testing/conftest.py --- a/testing/conftest.py +++ b/testing/conftest.py @@ -4,48 +4,71 @@ pytest_plugins = "pytester", import os, py -pid = os.getpid() + +class LsofFdLeakChecker(object): + def get_open_files(self): + out = self._exec_lsof() + open_files = self._parse_lsof_output(out) + return open_files + + def _exec_lsof(self): + pid = os.getpid() + return py.process.cmdexec("lsof -Ffn0 -p %d" % pid) + + def _parse_lsof_output(self, out): + def isopen(line): + return line.startswith('f') and ( + "deleted" not in line and 'mem' not in line and "txt" not in line and 'cwd' not in line) + + open_files = [] + + for line in out.split("\n"): + if isopen(line): + fields = line.split('\0') + fd = fields[0][1:] + filename = fields[1][1:] + if filename.startswith('/'): + open_files.append((fd, filename)) + + return open_files + def pytest_addoption(parser): parser.addoption('--lsof', action="store_true", dest="lsof", default=False, help=("run FD checks if lsof is available")) -def pytest_configure(config): +def pytest_runtest_setup(item): + config = item.config config._basedir = py.path.local() if config.getvalue("lsof"): try: - out = py.process.cmdexec("lsof -p %d" % pid) + config._fd_leak_checker = LsofFdLeakChecker() + config._openfiles = config._fd_leak_checker.get_open_files() except py.process.cmdexec.Error: pass - else: - config._numfiles = len(getopenfiles(out)) #def pytest_report_header(): # return "pid: %s" % os.getpid() -def getopenfiles(out): - def isopen(line): - return ("REG" in line or "CHR" in line) and ( - "deleted" not in line and 'mem' not in line and "txt" not in line) - return [x for x in out.split("\n") if isopen(x)] - def check_open_files(config): - out2 = py.process.cmdexec("lsof -p %d" % pid) - lines2 = getopenfiles(out2) - if len(lines2) > config._numfiles + 3: + lines2 = config._fd_leak_checker.get_open_files() + new_fds = set([t[0] for t in lines2]) - set([t[0] for t in config._openfiles]) + open_files = [t for t in lines2 if t[0] in new_fds] + if open_files: error = [] - error.append("***** %s FD leackage detected" % - (len(lines2)-config._numfiles)) - error.extend(lines2) + error.append("***** %s FD leackage detected" % len(open_files)) + error.extend([str(f) for f in open_files]) + error.append("*** Before:") + error.extend([str(f) for f in config._openfiles]) + error.append("*** After:") + error.extend([str(f) for f in lines2]) error.append(error[0]) - # update numfile so that the overall test run continuess - config._numfiles = len(lines2) raise AssertionError("\n".join(error)) def pytest_runtest_teardown(item, __multicall__): item.config._basedir.chdir() - if hasattr(item.config, '_numfiles'): + if hasattr(item.config, '_openfiles'): x = __multicall__.execute() check_open_files(item.config) return x Repository URL: https://bitbucket.org/hpk42/pytest/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. _______________________________________________ pytest-commit mailing list pytest-commit@python.org https://mail.python.org/mailman/listinfo/pytest-commit