diff --git a/CHANGELOG b/CHANGELOG index 4d62b18f5..3dba965eb 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -19,6 +19,9 @@ properly displayed. Thanks Ionel Maries Cristian for the report and Bruno Oliveira for the PR. +- fix #628: fixed internal UnicodeDecodeError when doctests contain unicode. + Thanks Jason R. Coombs for the report and Bruno Oliveira for the PR. + 2.8.5 ----- diff --git a/_pytest/doctest.py b/_pytest/doctest.py index fd4a24790..3052d0590 100644 --- a/_pytest/doctest.py +++ b/_pytest/doctest.py @@ -81,15 +81,15 @@ class DoctestItem(pytest.Item): reprlocation = ReprFileLocation(filename, lineno, message) checker = _get_unicode_checker() REPORT_UDIFF = doctest.REPORT_UDIFF - filelines = py.path.local(filename).readlines(cr=0) - lines = [] if lineno is not None: - i = max(test.lineno, max(0, lineno - 10)) # XXX? - for line in filelines[i:lineno]: - lines.append("%03d %s" % (i+1, line)) - i += 1 + lines = doctestfailure.test.docstring.splitlines(False) + # add line numbers to the left of the error message + lines = ["%03d %s" % (i + test.lineno + 1, x) + for (i, x) in enumerate(lines)] + # trim docstring error lines to 10 + lines = lines[example.lineno - 9:example.lineno + 1] else: - lines.append('EXAMPLE LOCATION UNKNOWN, not showing all tests of that example') + lines = ['EXAMPLE LOCATION UNKNOWN, not showing all tests of that example'] indent = '>>>' for line in example.source.splitlines(): lines.append('??? %s %s' % (indent, line)) diff --git a/testing/test_doctest.py b/testing/test_doctest.py index 88d90a7bf..0ac0b5f7e 100644 --- a/testing/test_doctest.py +++ b/testing/test_doctest.py @@ -1,3 +1,4 @@ +# encoding: utf-8 import sys from _pytest.doctest import DoctestItem, DoctestModule, DoctestTextfile import py @@ -109,6 +110,46 @@ class TestDoctests: "*UNEXPECTED*ZeroDivision*", ]) + def test_docstring_context_around_error(self, testdir): + """Test that we show some context before the actual line of a failing + doctest. + """ + testdir.makepyfile(''' + def foo(): + """ + text-line-1 + text-line-2 + text-line-3 + text-line-4 + text-line-5 + text-line-6 + text-line-7 + text-line-8 + text-line-9 + text-line-10 + text-line-11 + >>> 1 + 1 + 3 + + text-line-after + """ + ''') + result = testdir.runpytest('--doctest-modules') + result.stdout.fnmatch_lines([ + '*docstring_context_around_error*', + '005*text-line-3', + '006*text-line-4', + '013*text-line-11', + '014*>>> 1 + 1', + 'Expected:', + ' 3', + 'Got:', + ' 2', + ]) + # lines below should be trimmed out + assert 'text-line-2' not in result.stdout.str() + assert 'text-line-after' not in result.stdout.str() + def test_doctest_linedata_missing(self, testdir): testdir.tmpdir.join('hello.py').write(py.code.Source(""" class Fun(object): @@ -339,6 +380,23 @@ class TestDoctests: reprec = testdir.inline_run(p, "--doctest-glob=x*.txt") reprec.assertoutcome(failed=1, passed=0) + def test_contains_unicode(self, testdir): + """Fix internal error with docstrings containing non-ascii characters. + """ + testdir.makepyfile(u''' + # encoding: utf-8 + def foo(): + """ + >>> name = 'с' # not letter 'c' but instead Cyrillic 's'. + 'anything' + """ + ''') + result = testdir.runpytest('--doctest-modules') + result.stdout.fnmatch_lines([ + 'Got nothing', + '* 1 failed in*', + ]) + def test_ignore_import_errors_on_doctest(self, testdir): p = testdir.makepyfile(""" import asdf @@ -579,4 +637,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 *'])