From a0edbb75a46c95956a6a6d042a60904c077f7c1a Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 29 Dec 2015 20:55:19 -0200 Subject: [PATCH 1/4] Implement ALLOW_BYTES doctest option Fix #1287 --- _pytest/doctest.py | 58 +++++++++++++++++++++++++++-------------- testing/test_doctest.py | 47 ++++++++++++++++++++++++++++++++- 2 files changed, 84 insertions(+), 21 deletions(-) diff --git a/_pytest/doctest.py b/_pytest/doctest.py index fd4a24790..d2215ad18 100644 --- a/_pytest/doctest.py +++ b/_pytest/doctest.py @@ -79,7 +79,7 @@ class DoctestItem(pytest.Item): lineno = test.lineno + example.lineno + 1 message = excinfo.type.__name__ reprlocation = ReprFileLocation(filename, lineno, message) - checker = _get_unicode_checker() + checker = _get_checker() REPORT_UDIFF = doctest.REPORT_UDIFF filelines = py.path.local(filename).readlines(cr=0) lines = [] @@ -118,7 +118,9 @@ def _get_flag_lookup(): ELLIPSIS=doctest.ELLIPSIS, IGNORE_EXCEPTION_DETAIL=doctest.IGNORE_EXCEPTION_DETAIL, COMPARISON_FLAGS=doctest.COMPARISON_FLAGS, - ALLOW_UNICODE=_get_allow_unicode_flag()) + ALLOW_UNICODE=_get_allow_unicode_flag(), + ALLOW_BYTES=_get_allow_bytes_flag(), + ) def get_optionflags(parent): @@ -147,7 +149,7 @@ class DoctestTextfile(DoctestItem, pytest.Module): optionflags = get_optionflags(self) runner = doctest.DebugRunner(verbose=0, optionflags=optionflags, - checker=_get_unicode_checker()) + checker=_get_checker()) parser = doctest.DocTestParser() test = parser.get_doctest(text, globs, name, filename, 0) @@ -182,7 +184,7 @@ class DoctestModule(pytest.Module): finder = doctest.DocTestFinder() optionflags = get_optionflags(self) runner = doctest.DebugRunner(verbose=0, optionflags=optionflags, - checker=_get_unicode_checker()) + checker=_get_checker()) for test in finder.find(module, module.__name__): if test.examples: # skip empty doctests yield DoctestItem(test.name, self, runner, test) @@ -204,28 +206,32 @@ def _setup_fixtures(doctest_item): return fixture_request -def _get_unicode_checker(): +def _get_checker(): """ Returns a doctest.OutputChecker subclass that takes in account the - ALLOW_UNICODE option to ignore u'' prefixes in strings. Useful - when the same doctest should run in Python 2 and Python 3. + ALLOW_UNICODE option to ignore u'' prefixes in strings and ALLOW_BYTES + to strip b'' prefixes. + Useful when the same doctest should run in Python 2 and Python 3. An inner class is used to avoid importing "doctest" at the module level. """ - if hasattr(_get_unicode_checker, 'UnicodeOutputChecker'): - return _get_unicode_checker.UnicodeOutputChecker() + if hasattr(_get_checker, 'LiteralsOutputChecker'): + return _get_checker.LiteralsOutputChecker() import doctest import re - class UnicodeOutputChecker(doctest.OutputChecker): + class LiteralsOutputChecker(doctest.OutputChecker): """ Copied from doctest_nose_plugin.py from the nltk project: https://github.com/nltk/nltk + + Further extended to also support byte literals. """ - _literal_re = re.compile(r"(\W|^)[uU]([rR]?[\'\"])", re.UNICODE) + _unicode_literal_re = re.compile(r"(\W|^)[uU]([rR]?[\'\"])", re.UNICODE) + _bytes_literal_re = re.compile(r"(\W|^)[bB]([rR]?[\'\"])", re.UNICODE) def check_output(self, want, got, optionflags): res = doctest.OutputChecker.check_output(self, want, got, @@ -233,23 +239,27 @@ def _get_unicode_checker(): if res: return True - if not (optionflags & _get_allow_unicode_flag()): + allow_unicode = optionflags & _get_allow_unicode_flag() + allow_bytes = optionflags & _get_allow_bytes_flag() + if not allow_unicode and not allow_bytes: return False else: # pragma: no cover - # the code below will end up executed only in Python 2 in - # our tests, and our coverage check runs in Python 3 only - def remove_u_prefixes(txt): - return re.sub(self._literal_re, r'\1\2', txt) + def remove_prefixes(regex, txt): + return re.sub(regex, r'\1\2', txt) - want = remove_u_prefixes(want) - got = remove_u_prefixes(got) + if allow_unicode: + want = remove_prefixes(self._unicode_literal_re, want) + got = remove_prefixes(self._unicode_literal_re, got) + if allow_bytes: + want = remove_prefixes(self._bytes_literal_re, want) + got = remove_prefixes(self._bytes_literal_re, got) res = doctest.OutputChecker.check_output(self, want, got, optionflags) return res - _get_unicode_checker.UnicodeOutputChecker = UnicodeOutputChecker - return _get_unicode_checker.UnicodeOutputChecker() + _get_checker.LiteralsOutputChecker = LiteralsOutputChecker + return _get_checker.LiteralsOutputChecker() def _get_allow_unicode_flag(): @@ -258,3 +268,11 @@ def _get_allow_unicode_flag(): """ import doctest return doctest.register_optionflag('ALLOW_UNICODE') + + +def _get_allow_bytes_flag(): + """ + Registers and returns the ALLOW_BYTES flag. + """ + import doctest + return doctest.register_optionflag('ALLOW_BYTES') diff --git a/testing/test_doctest.py b/testing/test_doctest.py index 88d90a7bf..377664134 100644 --- a/testing/test_doctest.py +++ b/testing/test_doctest.py @@ -371,6 +371,9 @@ class TestDoctests: "--junit-xml=junit.xml") reprec.assertoutcome(failed=1) + +class TestLiterals: + @pytest.mark.parametrize('config_mode', ['ini', 'comment']) def test_allow_unicode(self, testdir, config_mode): """Test that doctests which output unicode work in all python versions @@ -400,6 +403,35 @@ class TestDoctests: reprec = testdir.inline_run("--doctest-modules") reprec.assertoutcome(passed=2) + @pytest.mark.parametrize('config_mode', ['ini', 'comment']) + def test_allow_bytes(self, testdir, config_mode): + """Test that doctests which output bytes work in all python versions + tested by pytest when the ALLOW_BYTES option is used (either in + the ini file or by an inline comment)(#1287). + """ + if config_mode == 'ini': + testdir.makeini(''' + [pytest] + doctest_optionflags = ALLOW_BYTES + ''') + comment = '' + else: + comment = '#doctest: +ALLOW_BYTES' + + testdir.maketxtfile(test_doc=""" + >>> b'foo' {comment} + 'foo' + """.format(comment=comment)) + testdir.makepyfile(foo=""" + def foo(): + ''' + >>> b'foo' {comment} + 'foo' + ''' + """.format(comment=comment)) + reprec = testdir.inline_run("--doctest-modules") + reprec.assertoutcome(passed=2) + def test_unicode_string(self, testdir): """Test that doctests which output unicode fail in Python 2 when the ALLOW_UNICODE option is not used. The same test should pass @@ -413,6 +445,19 @@ class TestDoctests: passed = int(sys.version_info[0] >= 3) reprec.assertoutcome(passed=passed, failed=int(not passed)) + def test_bytes_literal(self, testdir): + """Test that doctests which output bytes fail in Python 3 when + the ALLOW_BYTES option is not used. The same test should pass + in Python 2 (#1287). + """ + testdir.maketxtfile(test_doc=""" + >>> b'foo' + 'foo' + """) + reprec = testdir.inline_run() + passed = int(sys.version_info[0] == 2) + reprec.assertoutcome(passed=passed, failed=int(not passed)) + class TestDoctestSkips: """ @@ -579,4 +624,4 @@ class TestDoctestAutoUseFixtures: """) result = testdir.runpytest('--doctest-modules') assert 'FAILURES' not in str(result.stdout.str()) - result.stdout.fnmatch_lines(['*=== 1 passed in *']) \ No newline at end of file + result.stdout.fnmatch_lines(['*=== 1 passed in *']) From 5a5b732fe1b09a409c2498551bff044d0ef9c0a8 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 29 Dec 2015 21:05:11 -0200 Subject: [PATCH 2/4] Add docs for ALLOW_BYTES doctest option Fix #1287 --- doc/en/doctest.rst | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/doc/en/doctest.rst b/doc/en/doctest.rst index 0befa6702..73e514782 100644 --- a/doc/en/doctest.rst +++ b/doc/en/doctest.rst @@ -67,19 +67,32 @@ when executing text doctest files. The standard ``doctest`` module provides some setting flags to configure the strictness of doctest tests. In py.test You can enable those flags those flags using the configuration file. To make pytest ignore trailing whitespaces and -ignore lengthy exception stack traces you can just write:: +ignore lengthy exception stack traces you can just write: + +.. code-block:: ini - # content of pytest.ini [pytest] doctest_optionflags= NORMALIZE_WHITESPACE IGNORE_EXCEPTION_DETAIL +py.test also introduces new options to allow doctests to run in Python 2 and +Python 3 unchanged: -py.test also introduces a new ``ALLOW_UNICODE`` option flag: when enabled, the -``u`` prefix is stripped from unicode strings in expected doctest output. This -allows doctests which use unicode to run in Python 2 and 3 unchanged. +* ``ALLOW_UNICODE``: when enabled, the ``u`` prefix is stripped from unicode + strings in expected doctest output. -As with any other option flag, this flag can be enabled in ``pytest.ini`` using -the ``doctest_optionflags`` ini option or by an inline comment in the doc test +* ``ALLOW_BYTES``: when enabled, the ``b`` prefix is stripped from byte strings + in expected doctest output. + +As with any other option flag, these flags can be enabled in ``pytest.ini`` using +the ``doctest_optionflags`` ini option: + +.. code-block:: ini + + [pytest] + doctest_optionflags = ALLOW_UNICODE ALLOW_BYTES + + +Alternatively, it can be enabled by an inline comment in the doc test itself:: # content of example.rst From 719d63085dbd79300030f2ce39b8bff37d4f64ba Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 29 Dec 2015 21:08:25 -0200 Subject: [PATCH 3/4] Add CHANGELOG entry for ALLOW_BYTES doctest option --- CHANGELOG | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 6d9ab3ec8..33ffca92a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -10,6 +10,10 @@ New Features * New `-rp` and `-rP` reporting options give the summary and full output of passing tests, respectively. Thanks to David Vierra for the PR. +* New ``ALLOW_BYTES`` doctest option strips ``b`` prefixes from byte strings + in doctest output (similar to ``ALLOW_UNICODE``). + Thanks Jason R. Coombs for the request and Bruno Oliveira for the PR (#1287). + Changes ------- From 309ecf7ab3c4a437857f9ce75baaf8624ad3df92 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 29 Dec 2015 21:09:15 -0200 Subject: [PATCH 4/4] Rename "BugFixes/Adjustments" to just "Bug Fixes" as commented elsewhere --- CHANGELOG | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 33ffca92a..5c6108cce 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -21,18 +21,18 @@ Changes * `pytest_enter_pdb` now optionally receives the pytest config object. Thanks Bruno Oliveira for the PR. +* fix #1226: Removed code and documentation for Python 2.5 or lower versions, + including removal of the obsolete ``_pytest.assertion.oldinterpret`` module. + Thanks Bruno Oliveira for the PR. -Bug Fixes/Adjustments ---------------------- + +Bug Fixes +--------- * fix issue #680: the -s and -c options should now work under xdist; `Config.fromdictargs` now represents its input much more faithfully. Thanks to Buck Evan for the complete PR. -* fix #1226: Removed code and documentation for Python 2.5 or lower versions, - including removal of the obsolete ``_pytest.assertion.oldinterpret`` module. - Thanks Bruno Oliveira for the PR. - 2.8.6.dev1 ==========