From 891e0295183d6cd8ca8b6e58af65c397cb7e2197 Mon Sep 17 00:00:00 2001 From: Matt Williams Date: Wed, 2 Mar 2016 12:43:57 +0000 Subject: [PATCH] Add a new doctest_namespace fixture This fixture can be used to inject names into the namespace in which your doctests run. --- AUTHORS | 1 + CHANGELOG.rst | 3 ++- _pytest/doctest.py | 13 +++++++++++ doc/en/doctest.rst | 25 +++++++++++++++++++++ testing/test_doctest.py | 50 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 91 insertions(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index b9868ef2a..47f137892 100644 --- a/AUTHORS +++ b/AUTHORS @@ -59,6 +59,7 @@ Marc Schlaich Mark Abramowitz Markus Unterwaditzer Martijn Faassen +Matt Williams Michael Aquilina Michael Birtwell Michael Droettboom diff --git a/CHANGELOG.rst b/CHANGELOG.rst index b5b400f9c..a203ae2c8 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -3,7 +3,8 @@ **New Features** -* +* New ``doctest_namespace`` fixture for injecting names into the + namespace in which your doctests run. * diff --git a/_pytest/doctest.py b/_pytest/doctest.py index a57f7a494..4050d1ba7 100644 --- a/_pytest/doctest.py +++ b/_pytest/doctest.py @@ -71,6 +71,8 @@ class DoctestItem(pytest.Item): if self.dtest is not None: self.fixture_request = _setup_fixtures(self) globs = dict(getfixture=self.fixture_request.getfuncargvalue) + for name, value in self.fixture_request.getfuncargvalue('doctest_namespace').items(): + globs[name] = value self.dtest.globs.update(globs) def runtest(self): @@ -159,6 +161,9 @@ class DoctestTextfile(DoctestItem, pytest.Module): if '__name__' not in globs: globs['__name__'] = '__main__' + for name, value in fixture_request.getfuncargvalue('doctest_namespace').items(): + globs[name] = value + optionflags = get_optionflags(self) runner = doctest.DebugRunner(verbose=0, optionflags=optionflags, checker=_get_checker()) @@ -288,3 +293,11 @@ def _get_allow_bytes_flag(): """ import doctest return doctest.register_optionflag('ALLOW_BYTES') + + +@pytest.fixture(scope='session') +def doctest_namespace(): + """ + Inject names into the doctest namespace. + """ + return dict() diff --git a/doc/en/doctest.rst b/doc/en/doctest.rst index f13752e66..4798d9714 100644 --- a/doc/en/doctest.rst +++ b/doc/en/doctest.rst @@ -102,4 +102,29 @@ itself:: >>> get_unicode_greeting() # doctest: +ALLOW_UNICODE 'Hello' +The 'doctest_namespace' fixture +------------------------------- +The ``doctest_namespace`` fixture can be used to inject items into the +namespace in which your doctests run. It is intended to be used within +your own fixtures to provide the tests that use them with context. + +``doctest_namespace`` is a standard ``dict`` object into which you +place the objects you want to appear in the doctest namespace:: + + # content of conftest.py + import numpy + @pytest.fixture(autouse=True) + def add_np(doctest_namespace): + doctest_namespace['np'] = numpy + +which can then be used in your doctests directly:: + + # content of numpy.py + def arange(): + """ + >>> a = np.arange(10) + >>> len(a) + 10 + """ + pass diff --git a/testing/test_doctest.py b/testing/test_doctest.py index a4821ee4c..d104d98d3 100644 --- a/testing/test_doctest.py +++ b/testing/test_doctest.py @@ -713,3 +713,53 @@ class TestDoctestAutoUseFixtures: result = testdir.runpytest('--doctest-modules') assert 'FAILURES' not in str(result.stdout.str()) result.stdout.fnmatch_lines(['*=== 1 passed in *']) + + +class TestDoctestNamespaceFixture: + + SCOPES = ['module', 'session', 'class', 'function'] + + @pytest.mark.parametrize('scope', SCOPES) + def test_namespace_doctestfile(self, testdir, scope): + """ + Check that inserting something into the namespace works in a + simple text file doctest + """ + testdir.makeconftest(""" + import pytest + import contextlib + + @pytest.fixture(autouse=True, scope="{scope}") + def add_contextlib(doctest_namespace): + doctest_namespace['cl'] = contextlib + """.format(scope=scope)) + p = testdir.maketxtfile(""" + >>> print(cl.__name__) + contextlib + """) + reprec = testdir.inline_run(p) + reprec.assertoutcome(passed=1) + + @pytest.mark.parametrize('scope', SCOPES) + def test_namespace_pyfile(self, testdir, scope): + """ + Check that inserting something into the namespace works in a + simple Python file docstring doctest + """ + testdir.makeconftest(""" + import pytest + import contextlib + + @pytest.fixture(autouse=True, scope="{scope}") + def add_contextlib(doctest_namespace): + doctest_namespace['cl'] = contextlib + """.format(scope=scope)) + p = testdir.makepyfile(""" + def foo(): + ''' + >>> print(cl.__name__) + contextlib + ''' + """) + reprec = testdir.inline_run(p, "--doctest-modules") + reprec.assertoutcome(passed=1)