Skip to content

Commit 58720a2

Browse files
committed
Fix decode error in Python 2.7 when docstrings contain a non-ascii character
Fix pytest-dev#628
1 parent 29b05c8 commit 58720a2

File tree

3 files changed

+66
-8
lines changed

3 files changed

+66
-8
lines changed

CHANGELOG

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@
1919
properly displayed.
2020
Thanks Ionel Maries Cristian for the report and Bruno Oliveira for the PR.
2121

22+
- fix #628: fixed internal UnicodeDecodeError when doctests contain unicode.
23+
Thanks Jason R. Coombs for the report and Bruno Oliveira for the PR.
24+
2225

2326
2.8.5
2427
-----

_pytest/doctest.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -81,15 +81,15 @@ def repr_failure(self, excinfo):
8181
reprlocation = ReprFileLocation(filename, lineno, message)
8282
checker = _get_unicode_checker()
8383
REPORT_UDIFF = doctest.REPORT_UDIFF
84-
filelines = py.path.local(filename).readlines(cr=0)
85-
lines = []
8684
if lineno is not None:
87-
i = max(test.lineno, max(0, lineno - 10)) # XXX?
88-
for line in filelines[i:lineno]:
89-
lines.append("%03d %s" % (i+1, line))
90-
i += 1
85+
lines = doctestfailure.test.docstring.splitlines(False)
86+
# add line numbers to the left of the error message
87+
lines = ["%03d %s" % (i + test.lineno + 1, x)
88+
for (i, x) in enumerate(lines)]
89+
# trim docstring error lines to 10
90+
lines = lines[example.lineno - 9:example.lineno + 1]
9191
else:
92-
lines.append('EXAMPLE LOCATION UNKNOWN, not showing all tests of that example')
92+
lines = ['EXAMPLE LOCATION UNKNOWN, not showing all tests of that example']
9393
indent = '>>>'
9494
for line in example.source.splitlines():
9595
lines.append('??? %s %s' % (indent, line))

testing/test_doctest.py

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
# encoding: utf-8
12
import sys
23
from _pytest.doctest import DoctestItem, DoctestModule, DoctestTextfile
34
import py
@@ -109,6 +110,43 @@ def test_doctest_unexpected_exception(self, testdir):
109110
"*UNEXPECTED*ZeroDivision*",
110111
])
111112

113+
def test_docstring_context_around_error(self, testdir):
114+
"""Test that we show some context before the actual line of a failing
115+
doctest.
116+
"""
117+
testdir.makepyfile('''
118+
def foo():
119+
"""
120+
text-line-1
121+
text-line-2
122+
text-line-3
123+
text-line-4
124+
text-line-5
125+
text-line-6
126+
text-line-7
127+
text-line-8
128+
text-line-9
129+
text-line-10
130+
text-line-11
131+
>>> 1 + 1
132+
3
133+
"""
134+
''')
135+
result = testdir.runpytest('--doctest-modules')
136+
result.stdout.fnmatch_lines([
137+
'*docstring_context_around_error*',
138+
'005*text-line-3',
139+
'006*text-line-4',
140+
'013*text-line-11',
141+
'014*>>> 1 + 1',
142+
'Expected:',
143+
' 3',
144+
'Got:',
145+
' 2',
146+
])
147+
# line containing "text-line-2" should have been trimmed out
148+
assert 'text-line-2' not in result.stdout.str()
149+
112150
def test_doctest_linedata_missing(self, testdir):
113151
testdir.tmpdir.join('hello.py').write(py.code.Source("""
114152
class Fun(object):
@@ -339,6 +377,23 @@ def test_non_ignored_whitespace_glob(self, testdir):
339377
reprec = testdir.inline_run(p, "--doctest-glob=x*.txt")
340378
reprec.assertoutcome(failed=1, passed=0)
341379

380+
def test_contains_unicode(self, testdir):
381+
"""Fix internal error with docstrings containing non-ascii characters.
382+
"""
383+
testdir.makepyfile(u'''
384+
# encoding: utf-8
385+
def foo():
386+
"""
387+
>>> name = 'с' # not letter 'c' but instead Cyrillic 's'.
388+
'anything'
389+
"""
390+
''')
391+
result = testdir.runpytest('--doctest-modules')
392+
result.stdout.fnmatch_lines([
393+
'Got nothing',
394+
'* 1 failed in*',
395+
])
396+
342397
def test_ignore_import_errors_on_doctest(self, testdir):
343398
p = testdir.makepyfile("""
344399
import asdf
@@ -579,4 +634,4 @@ def auto(request):
579634
""")
580635
result = testdir.runpytest('--doctest-modules')
581636
assert 'FAILURES' not in str(result.stdout.str())
582-
result.stdout.fnmatch_lines(['*=== 1 passed in *'])
637+
result.stdout.fnmatch_lines(['*=== 1 passed in *'])

0 commit comments

Comments
 (0)