diff --git a/CHANGES.rst b/CHANGES.rst index e887351..daae4de 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,8 +1,8 @@ 0.7.0 (unreleased) ================== -- No changes yet - +- Added a new ini option, ``doctest_subpackage_requires``, that can be used to skip + specific subpackages based on required packages. [#112] 0.6.1 (2020-05-04) ================== diff --git a/README.rst b/README.rst index 4f624d6..76b52e7 100644 --- a/README.rst +++ b/README.rst @@ -225,6 +225,17 @@ conditionally skipped if a dependency is not available. >>> import asdf >>> asdf.open('file.asdf') +Finally, it is possible to skip collecting doctests in entire subpackages by +using the ``doctest_subpackage_requires`` in the ``[tool:pytest]`` section of +the package's ``setup.cfg`` file. The syntax for this option is a list of +``path = requirements``, e.g.:: + + doctest_subpackage_requires = + astropy/wcs/* = scipy>2.0;numpy>1.14 + astropy/cosmology/* = scipy>1.0 + +Multiple requirements can be specified if separated by semicolons. + Remote Data ~~~~~~~~~~~ diff --git a/pytest_doctestplus/plugin.py b/pytest_doctestplus/plugin.py index d3d2870..e6556a2 100644 --- a/pytest_doctestplus/plugin.py +++ b/pytest_doctestplus/plugin.py @@ -115,6 +115,12 @@ def pytest_addoption(parser): type='linelist', default=[]) + parser.addini("doctest_subpackage_requires", + "A list of paths to skip if requirements are not satisfied. Each item in the list " + "should have the syntax path=req1;req2", + type='linelist', + default=[]) + def get_optionflags(parent): optionflags_str = parent.config.getini('doctest_optionflags') @@ -168,12 +174,10 @@ def collect(self): try: module = self.fspath.pyimport() except ImportError: - pytest.skip("unable to import module %r" % self.fspath) - # NOT USED: While correct, this breaks existing behavior. - # if self.config.getvalue("doctest_ignore_import_errors"): - # pytest.skip("unable to import module %r" % self.fspath) - # else: - # raise + if self.config.getvalue("doctest_ignore_import_errors"): + pytest.skip("unable to import module %r" % self.fspath) + else: + raise options = get_optionflags(self) | FIX @@ -410,6 +414,14 @@ def get_list_opt(name): self._ignore_paths.append(path) break + for option in config.getini("doctest_subpackage_requires"): + subpackage_pattern, required = option.split('=', 1) + if path.check(fnmatch=subpackage_pattern.strip()): + required = required.strip().split(';') + if not DocTestFinderPlus.check_required_modules(required): + self._ignore_paths.append(path) + break + return False def pytest_collect_file(self, path, parent): diff --git a/pytest_doctestplus/utils.py b/pytest_doctestplus/utils.py index 236f611..4463cea 100644 --- a/pytest_doctestplus/utils.py +++ b/pytest_doctestplus/utils.py @@ -78,7 +78,6 @@ def find_distribution(self, dist): try: return self._find_distribution(dist) except Exception as e: - logger.warning(e) return None def check(self, module): diff --git a/tests/test_doctestplus.py b/tests/test_doctestplus.py index 249d5f6..5a1e8f7 100644 --- a/tests/test_doctestplus.py +++ b/tests/test_doctestplus.py @@ -1,3 +1,4 @@ +import os from distutils.version import LooseVersion from textwrap import dedent @@ -651,3 +652,41 @@ def test_doctest_float_replacement(tmpdir): doctest.testfile(str(test2_rst), module_relative=False, raise_on_error=True, verbose=False, encoding='utf-8') + + +def test_doctest_subpackage_requires(testdir, caplog): + + # Note that each entry below has different whitespace around the = to + # make sure that all cases work properly. + + testdir.makeini( + """ + [pytest] + doctest_subpackage_requires = + test/a/* = pytest>1 + test/b/*= pytest>1;averyfakepackage>99999.9 + test/c/*=anotherfakepackage>=22000.1.2 + """ + ) + test = testdir.mkdir('test') + a = test.mkdir('a') + b = test.mkdir('b') + c = test.mkdir('c') + + pyfile = dedent(""" + def f(): + ''' + >>> 1 + 1 + ''' + pass + """) + + a.join('testcode.py').write(pyfile) + b.join('testcode.py').write(pyfile) + c.join('testcode.py').write(pyfile) + + reprec = testdir.inline_run(test, "--doctest-plus") + reprec.assertoutcome(passed=1) + assert reprec.listoutcomes()[0][0].location[0] == os.path.join('test', 'a', 'testcode.py') + assert caplog.text == ''