diff --git a/CHANGELOG b/CHANGELOG index 76af068e2..229ae5bdb 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -8,6 +8,12 @@ Changes between 1.2.1 and 1.2.2 (release pending) - fixes for handling of unicode exception values and unprintable objects - (issue87) fix unboundlocal error in assertionold code - (issue86) improve documentation for looponfailing +- add a new pytest_ignore_collect_path(path, config) hook to allow projects and + plugins to define exclusion behaviour for their directory structure - + for example you may define in a conftest.py this method: + def pytest_ignore_collect_path(path): + return path.check(link=1) + to prevent even a collection try of any tests in symlinked dirs. - expose previously internal commonly useful methods: py.io.get_terminal_with() -> return terminal width py.io.ansi_print(...) -> print colored/bold text on linux/win32 diff --git a/py/_plugin/hookspec.py b/py/_plugin/hookspec.py index b43b46783..a1d7ef7de 100644 --- a/py/_plugin/hookspec.py +++ b/py/_plugin/hookspec.py @@ -27,6 +27,13 @@ def pytest_unconfigure(config): # collection hooks # ------------------------------------------------------------------------- +def pytest_ignore_collect_path(path, config): + """ return true value to prevent considering this path for collection. + This hook is consulted for all files and directories prior to considering + collection hooks. + """ +pytest_ignore_collect_path.firstresult = True + def pytest_collect_directory(path, parent): """ return Collection node or None for the given path. """ pytest_collect_directory.firstresult = True diff --git a/py/_plugin/pytest_default.py b/py/_plugin/pytest_default.py index 9bc2dce5b..93ddbf37b 100644 --- a/py/_plugin/pytest_default.py +++ b/py/_plugin/pytest_default.py @@ -24,6 +24,20 @@ def pytest_funcarg__pytestconfig(request): """ the pytest config object with access to command line opts.""" return request.config +def pytest_ignore_collect_path(path, config): + ignore_paths = config.getconftest_pathlist("collect_ignore", path=path) + ignore_paths = ignore_paths or [] + excludeopt = config.getvalue("ignore") + if excludeopt: + ignore_paths.extend([py.path.local(x) for x in excludeopt]) + return path in ignore_paths + # XXX more refined would be: + if ignore_paths: + for p in ignore_paths: + if path == p or path.relto(p): + return True + + def pytest_collect_directory(path, parent): # XXX reconsider the following comment # not use parent.Directory here as we generally diff --git a/py/_test/collect.py b/py/_test/collect.py index 45e0e992f..a1fb4e509 100644 --- a/py/_test/collect.py +++ b/py/_test/collect.py @@ -296,28 +296,15 @@ class Directory(FSCollector): l.append(res) return l - def _ignore(self, path): - ignore_paths = self.config.getconftest_pathlist("collect_ignore", - path=path) or [] - excludeopt = self.config.getvalue("ignore") - if excludeopt: - ignore_paths.extend([py.path.local(x) for x in excludeopt]) - return path in ignore_paths - # XXX more refined would be: - if ignore_paths: - for p in ignore_paths: - if path == p or path.relto(p): - return True - def consider(self, path): - if self._ignore(path): - return + if self.ihook.pytest_ignore_collect_path(path=path, config=self.config): + return if path.check(file=1): res = self.consider_file(path) elif path.check(dir=1): res = self.consider_dir(path) else: - res = None + res = None if isinstance(res, list): # throw out identical results l = [] diff --git a/testing/test_collect.py b/testing/test_collect.py index 6c0aebf12..6295418e8 100644 --- a/testing/test_collect.py +++ b/testing/test_collect.py @@ -157,6 +157,21 @@ class TestPrunetraceback: ]) class TestCustomConftests: + def test_ignore_collect_path(self, testdir): + testdir.makeconftest(""" + def pytest_ignore_collect_path(path, config): + return path.basename.startswith("x") or \ + path.basename == "test_one.py" + """) + testdir.mkdir("xy123").ensure("test_hello.py").write( + "syntax error" + ) + testdir.makepyfile("def test_hello(): pass") + testdir.makepyfile(test_one="syntax error") + result = testdir.runpytest() + assert result.ret == 0 + result.stdout.fnmatch_lines(["*1 passed*"]) + def test_collectignore_exclude_on_option(self, testdir): testdir.makeconftest(""" collect_ignore = ['hello', 'test_world.py']