add changelog: fix issue319 - correctly show unicode in assertion errors. Many
thanks to Floris Bruynooghe for the complete PR. Also means we depend on py>=1.4.19 now.
This commit is contained in:
commit
fa80b8ad17
|
@ -13,6 +13,10 @@ Unreleased
|
||||||
- PR90: add --color=yes|no|auto option to force terminal coloring
|
- PR90: add --color=yes|no|auto option to force terminal coloring
|
||||||
mode ("auto" is default). Thanks Marc Abramowitz.
|
mode ("auto" is default). Thanks Marc Abramowitz.
|
||||||
|
|
||||||
|
- fix issue319 - correctly show unicode in assertion errors. Many
|
||||||
|
thanks to Floris Bruynooghe for the complete PR. Also means
|
||||||
|
we depend on py>=1.4.19 now.
|
||||||
|
|
||||||
- fix issue396 - correctly sort and finalize class-scoped parametrized
|
- fix issue396 - correctly sort and finalize class-scoped parametrized
|
||||||
tests independently from number of methods on the class.
|
tests independently from number of methods on the class.
|
||||||
|
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
#
|
#
|
||||||
__version__ = '2.4.3.dev2'
|
__version__ = '2.5.0.dev1'
|
||||||
|
|
|
@ -78,10 +78,13 @@ def pytest_runtest_setup(item):
|
||||||
|
|
||||||
for new_expl in hook_result:
|
for new_expl in hook_result:
|
||||||
if new_expl:
|
if new_expl:
|
||||||
# Don't include pageloads of data unless we are very verbose (-vv)
|
# Don't include pageloads of data unless we are very
|
||||||
if len(''.join(new_expl[1:])) > 80*8 and item.config.option.verbose < 2:
|
# verbose (-vv)
|
||||||
new_expl[1:] = ['Detailed information truncated, use "-vv" to see']
|
if (len(py.builtin._totext('').join(new_expl[1:])) > 80*8
|
||||||
res = '\n~'.join(new_expl)
|
and item.config.option.verbose < 2):
|
||||||
|
new_expl[1:] = [py.builtin._totext(
|
||||||
|
'Detailed information truncated, use "-vv" to see')]
|
||||||
|
res = py.builtin._totext('\n~').join(new_expl)
|
||||||
if item.config.getvalue("assertmode") == "rewrite":
|
if item.config.getvalue("assertmode") == "rewrite":
|
||||||
# The result will be fed back a python % formatting
|
# The result will be fed back a python % formatting
|
||||||
# operation, which will fail if there are extraneous
|
# operation, which will fail if there are extraneous
|
||||||
|
|
|
@ -2,17 +2,17 @@ import sys
|
||||||
import py
|
import py
|
||||||
from _pytest.assertion.util import BuiltinAssertionError
|
from _pytest.assertion.util import BuiltinAssertionError
|
||||||
|
|
||||||
|
|
||||||
class AssertionError(BuiltinAssertionError):
|
class AssertionError(BuiltinAssertionError):
|
||||||
def __init__(self, *args):
|
def __init__(self, *args):
|
||||||
BuiltinAssertionError.__init__(self, *args)
|
BuiltinAssertionError.__init__(self, *args)
|
||||||
if args:
|
if args:
|
||||||
try:
|
try:
|
||||||
self.msg = str(args[0])
|
self.msg = py.builtin._totext(args[0])
|
||||||
except py.builtin._sysex:
|
except Exception:
|
||||||
raise
|
self.msg = py.builtin._totext(
|
||||||
except:
|
"<[broken __repr__] %s at %0xd>"
|
||||||
self.msg = "<[broken __repr__] %s at %0xd>" %(
|
% (args[0].__class__, id(args[0])))
|
||||||
args[0].__class__, id(args[0]))
|
|
||||||
else:
|
else:
|
||||||
f = py.code.Frame(sys._getframe(1))
|
f = py.code.Frame(sys._getframe(1))
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -655,7 +655,7 @@ class AssertionRewriter(ast.NodeVisitor):
|
||||||
res_expr = ast.Compare(left_res, [op], [next_res])
|
res_expr = ast.Compare(left_res, [op], [next_res])
|
||||||
self.statements.append(ast.Assign([store_names[i]], res_expr))
|
self.statements.append(ast.Assign([store_names[i]], res_expr))
|
||||||
left_res, left_expl = next_res, next_expl
|
left_res, left_expl = next_res, next_expl
|
||||||
# Use py.code._reprcompare if that's available.
|
# Use pytest.assertion.util._reprcompare if that's available.
|
||||||
expl_call = self.helper("call_reprcompare",
|
expl_call = self.helper("call_reprcompare",
|
||||||
ast.Tuple(syms, ast.Load()),
|
ast.Tuple(syms, ast.Load()),
|
||||||
ast.Tuple(load_names, ast.Load()),
|
ast.Tuple(load_names, ast.Load()),
|
||||||
|
|
|
@ -11,6 +11,7 @@ except ImportError:
|
||||||
|
|
||||||
|
|
||||||
BuiltinAssertionError = py.builtin.builtins.AssertionError
|
BuiltinAssertionError = py.builtin.builtins.AssertionError
|
||||||
|
u = py.builtin._totext
|
||||||
|
|
||||||
# The _reprcompare attribute on the util module is used by the new assertion
|
# The _reprcompare attribute on the util module is used by the new assertion
|
||||||
# interpretation code and assertion rewriter to detect this plugin was
|
# interpretation code and assertion rewriter to detect this plugin was
|
||||||
|
@ -29,7 +30,18 @@ def format_explanation(explanation):
|
||||||
for when one explanation needs to span multiple lines, e.g. when
|
for when one explanation needs to span multiple lines, e.g. when
|
||||||
displaying diffs.
|
displaying diffs.
|
||||||
"""
|
"""
|
||||||
# simplify 'assert False where False = ...'
|
explanation = _collapse_false(explanation)
|
||||||
|
lines = _split_explanation(explanation)
|
||||||
|
result = _format_lines(lines)
|
||||||
|
return u('\n').join(result)
|
||||||
|
|
||||||
|
|
||||||
|
def _collapse_false(explanation):
|
||||||
|
"""Collapse expansions of False
|
||||||
|
|
||||||
|
So this strips out any "assert False\n{where False = ...\n}"
|
||||||
|
blocks.
|
||||||
|
"""
|
||||||
where = 0
|
where = 0
|
||||||
while True:
|
while True:
|
||||||
start = where = explanation.find("False\n{False = ", where)
|
start = where = explanation.find("False\n{False = ", where)
|
||||||
|
@ -51,28 +63,48 @@ def format_explanation(explanation):
|
||||||
explanation = (explanation[:start] + explanation[start+15:end-1] +
|
explanation = (explanation[:start] + explanation[start+15:end-1] +
|
||||||
explanation[end+1:])
|
explanation[end+1:])
|
||||||
where -= 17
|
where -= 17
|
||||||
raw_lines = (explanation or '').split('\n')
|
return explanation
|
||||||
# escape newlines not followed by {, } and ~
|
|
||||||
|
|
||||||
|
def _split_explanation(explanation):
|
||||||
|
"""Return a list of individual lines in the explanation
|
||||||
|
|
||||||
|
This will return a list of lines split on '\n{', '\n}' and '\n~'.
|
||||||
|
Any other newlines will be escaped and appear in the line as the
|
||||||
|
literal '\n' characters.
|
||||||
|
"""
|
||||||
|
raw_lines = (explanation or u('')).split('\n')
|
||||||
lines = [raw_lines[0]]
|
lines = [raw_lines[0]]
|
||||||
for l in raw_lines[1:]:
|
for l in raw_lines[1:]:
|
||||||
if l.startswith('{') or l.startswith('}') or l.startswith('~'):
|
if l.startswith('{') or l.startswith('}') or l.startswith('~'):
|
||||||
lines.append(l)
|
lines.append(l)
|
||||||
else:
|
else:
|
||||||
lines[-1] += '\\n' + l
|
lines[-1] += '\\n' + l
|
||||||
|
return lines
|
||||||
|
|
||||||
|
|
||||||
|
def _format_lines(lines):
|
||||||
|
"""Format the individual lines
|
||||||
|
|
||||||
|
This will replace the '{', '}' and '~' characters of our mini
|
||||||
|
formatting language with the proper 'where ...', 'and ...' and ' +
|
||||||
|
...' text, taking care of indentation along the way.
|
||||||
|
|
||||||
|
Return a list of formatted lines.
|
||||||
|
"""
|
||||||
result = lines[:1]
|
result = lines[:1]
|
||||||
stack = [0]
|
stack = [0]
|
||||||
stackcnt = [0]
|
stackcnt = [0]
|
||||||
for line in lines[1:]:
|
for line in lines[1:]:
|
||||||
if line.startswith('{'):
|
if line.startswith('{'):
|
||||||
if stackcnt[-1]:
|
if stackcnt[-1]:
|
||||||
s = 'and '
|
s = u('and ')
|
||||||
else:
|
else:
|
||||||
s = 'where '
|
s = u('where ')
|
||||||
stack.append(len(result))
|
stack.append(len(result))
|
||||||
stackcnt[-1] += 1
|
stackcnt[-1] += 1
|
||||||
stackcnt.append(0)
|
stackcnt.append(0)
|
||||||
result.append(' +' + ' '*(len(stack)-1) + s + line[1:])
|
result.append(u(' +') + u(' ')*(len(stack)-1) + s + line[1:])
|
||||||
elif line.startswith('}'):
|
elif line.startswith('}'):
|
||||||
assert line.startswith('}')
|
assert line.startswith('}')
|
||||||
stack.pop()
|
stack.pop()
|
||||||
|
@ -80,9 +112,9 @@ def format_explanation(explanation):
|
||||||
result[stack[-1]] += line[1:]
|
result[stack[-1]] += line[1:]
|
||||||
else:
|
else:
|
||||||
assert line.startswith('~')
|
assert line.startswith('~')
|
||||||
result.append(' '*len(stack) + line[1:])
|
result.append(u(' ')*len(stack) + line[1:])
|
||||||
assert len(stack) == 1
|
assert len(stack) == 1
|
||||||
return '\n'.join(result)
|
return result
|
||||||
|
|
||||||
|
|
||||||
# Provide basestring in python3
|
# Provide basestring in python3
|
||||||
|
@ -97,7 +129,7 @@ def assertrepr_compare(config, op, left, right):
|
||||||
width = 80 - 15 - len(op) - 2 # 15 chars indentation, 1 space around op
|
width = 80 - 15 - len(op) - 2 # 15 chars indentation, 1 space around op
|
||||||
left_repr = py.io.saferepr(left, maxsize=int(width/2))
|
left_repr = py.io.saferepr(left, maxsize=int(width/2))
|
||||||
right_repr = py.io.saferepr(right, maxsize=width-len(left_repr))
|
right_repr = py.io.saferepr(right, maxsize=width-len(left_repr))
|
||||||
summary = '%s %s %s' % (left_repr, op, right_repr)
|
summary = u('%s %s %s') % (left_repr, op, right_repr)
|
||||||
|
|
||||||
issequence = lambda x: (isinstance(x, (list, tuple, Sequence))
|
issequence = lambda x: (isinstance(x, (list, tuple, Sequence))
|
||||||
and not isinstance(x, basestring))
|
and not isinstance(x, basestring))
|
||||||
|
@ -120,13 +152,12 @@ def assertrepr_compare(config, op, left, right):
|
||||||
elif op == 'not in':
|
elif op == 'not in':
|
||||||
if istext(left) and istext(right):
|
if istext(left) and istext(right):
|
||||||
explanation = _notin_text(left, right, verbose)
|
explanation = _notin_text(left, right, verbose)
|
||||||
except py.builtin._sysex:
|
except Exception:
|
||||||
raise
|
|
||||||
except:
|
|
||||||
excinfo = py.code.ExceptionInfo()
|
excinfo = py.code.ExceptionInfo()
|
||||||
explanation = [
|
explanation = [
|
||||||
'(pytest_assertion plugin: representation of details failed. '
|
u('(pytest_assertion plugin: representation of details failed. '
|
||||||
'Probably an object has a faulty __repr__.)', str(excinfo)]
|
'Probably an object has a faulty __repr__.)'),
|
||||||
|
u(excinfo)]
|
||||||
|
|
||||||
if not explanation:
|
if not explanation:
|
||||||
return None
|
return None
|
||||||
|
@ -148,8 +179,8 @@ def _diff_text(left, right, verbose=False):
|
||||||
break
|
break
|
||||||
if i > 42:
|
if i > 42:
|
||||||
i -= 10 # Provide some context
|
i -= 10 # Provide some context
|
||||||
explanation = ['Skipping %s identical leading '
|
explanation = [u('Skipping %s identical leading '
|
||||||
'characters in diff, use -v to show' % i]
|
'characters in diff, use -v to show') % i]
|
||||||
left = left[i:]
|
left = left[i:]
|
||||||
right = right[i:]
|
right = right[i:]
|
||||||
if len(left) == len(right):
|
if len(left) == len(right):
|
||||||
|
@ -158,8 +189,8 @@ def _diff_text(left, right, verbose=False):
|
||||||
break
|
break
|
||||||
if i > 42:
|
if i > 42:
|
||||||
i -= 10 # Provide some context
|
i -= 10 # Provide some context
|
||||||
explanation += ['Skipping %s identical trailing '
|
explanation += [u('Skipping %s identical trailing '
|
||||||
'characters in diff, use -v to show' % i]
|
'characters in diff, use -v to show') % i]
|
||||||
left = left[:-i]
|
left = left[:-i]
|
||||||
right = right[:-i]
|
right = right[:-i]
|
||||||
explanation += [line.strip('\n')
|
explanation += [line.strip('\n')
|
||||||
|
@ -172,16 +203,15 @@ def _compare_eq_sequence(left, right, verbose=False):
|
||||||
explanation = []
|
explanation = []
|
||||||
for i in range(min(len(left), len(right))):
|
for i in range(min(len(left), len(right))):
|
||||||
if left[i] != right[i]:
|
if left[i] != right[i]:
|
||||||
explanation += ['At index %s diff: %r != %r' %
|
explanation += [u('At index %s diff: %r != %r')
|
||||||
(i, left[i], right[i])]
|
% (i, left[i], right[i])]
|
||||||
break
|
break
|
||||||
if len(left) > len(right):
|
if len(left) > len(right):
|
||||||
explanation += [
|
explanation += [u('Left contains more items, first extra item: %s')
|
||||||
'Left contains more items, first extra item: %s' %
|
% py.io.saferepr(left[len(right)],)]
|
||||||
py.io.saferepr(left[len(right)],)]
|
|
||||||
elif len(left) < len(right):
|
elif len(left) < len(right):
|
||||||
explanation += [
|
explanation += [
|
||||||
'Right contains more items, first extra item: %s' %
|
u('Right contains more items, first extra item: %s') %
|
||||||
py.io.saferepr(right[len(left)],)]
|
py.io.saferepr(right[len(left)],)]
|
||||||
return explanation # + _diff_text(py.std.pprint.pformat(left),
|
return explanation # + _diff_text(py.std.pprint.pformat(left),
|
||||||
# py.std.pprint.pformat(right))
|
# py.std.pprint.pformat(right))
|
||||||
|
@ -192,11 +222,11 @@ def _compare_eq_set(left, right, verbose=False):
|
||||||
diff_left = left - right
|
diff_left = left - right
|
||||||
diff_right = right - left
|
diff_right = right - left
|
||||||
if diff_left:
|
if diff_left:
|
||||||
explanation.append('Extra items in the left set:')
|
explanation.append(u('Extra items in the left set:'))
|
||||||
for item in diff_left:
|
for item in diff_left:
|
||||||
explanation.append(py.io.saferepr(item))
|
explanation.append(py.io.saferepr(item))
|
||||||
if diff_right:
|
if diff_right:
|
||||||
explanation.append('Extra items in the right set:')
|
explanation.append(u('Extra items in the right set:'))
|
||||||
for item in diff_right:
|
for item in diff_right:
|
||||||
explanation.append(py.io.saferepr(item))
|
explanation.append(py.io.saferepr(item))
|
||||||
return explanation
|
return explanation
|
||||||
|
@ -207,25 +237,25 @@ def _compare_eq_dict(left, right, verbose=False):
|
||||||
common = set(left).intersection(set(right))
|
common = set(left).intersection(set(right))
|
||||||
same = dict((k, left[k]) for k in common if left[k] == right[k])
|
same = dict((k, left[k]) for k in common if left[k] == right[k])
|
||||||
if same and not verbose:
|
if same and not verbose:
|
||||||
explanation += ['Omitting %s identical items, use -v to show' %
|
explanation += [u('Omitting %s identical items, use -v to show') %
|
||||||
len(same)]
|
len(same)]
|
||||||
elif same:
|
elif same:
|
||||||
explanation += ['Common items:']
|
explanation += [u('Common items:')]
|
||||||
explanation += py.std.pprint.pformat(same).splitlines()
|
explanation += py.std.pprint.pformat(same).splitlines()
|
||||||
diff = set(k for k in common if left[k] != right[k])
|
diff = set(k for k in common if left[k] != right[k])
|
||||||
if diff:
|
if diff:
|
||||||
explanation += ['Differing items:']
|
explanation += [u('Differing items:')]
|
||||||
for k in diff:
|
for k in diff:
|
||||||
explanation += [py.io.saferepr({k: left[k]}) + ' != ' +
|
explanation += [py.io.saferepr({k: left[k]}) + ' != ' +
|
||||||
py.io.saferepr({k: right[k]})]
|
py.io.saferepr({k: right[k]})]
|
||||||
extra_left = set(left) - set(right)
|
extra_left = set(left) - set(right)
|
||||||
if extra_left:
|
if extra_left:
|
||||||
explanation.append('Left contains more items:')
|
explanation.append(u('Left contains more items:'))
|
||||||
explanation.extend(py.std.pprint.pformat(
|
explanation.extend(py.std.pprint.pformat(
|
||||||
dict((k, left[k]) for k in extra_left)).splitlines())
|
dict((k, left[k]) for k in extra_left)).splitlines())
|
||||||
extra_right = set(right) - set(left)
|
extra_right = set(right) - set(left)
|
||||||
if extra_right:
|
if extra_right:
|
||||||
explanation.append('Right contains more items:')
|
explanation.append(u('Right contains more items:'))
|
||||||
explanation.extend(py.std.pprint.pformat(
|
explanation.extend(py.std.pprint.pformat(
|
||||||
dict((k, right[k]) for k in extra_right)).splitlines())
|
dict((k, right[k]) for k in extra_right)).splitlines())
|
||||||
return explanation
|
return explanation
|
||||||
|
@ -237,14 +267,14 @@ def _notin_text(term, text, verbose=False):
|
||||||
tail = text[index+len(term):]
|
tail = text[index+len(term):]
|
||||||
correct_text = head + tail
|
correct_text = head + tail
|
||||||
diff = _diff_text(correct_text, text, verbose)
|
diff = _diff_text(correct_text, text, verbose)
|
||||||
newdiff = ['%s is contained here:' % py.io.saferepr(term, maxsize=42)]
|
newdiff = [u('%s is contained here:') % py.io.saferepr(term, maxsize=42)]
|
||||||
for line in diff:
|
for line in diff:
|
||||||
if line.startswith('Skipping'):
|
if line.startswith(u('Skipping')):
|
||||||
continue
|
continue
|
||||||
if line.startswith('- '):
|
if line.startswith(u('- ')):
|
||||||
continue
|
continue
|
||||||
if line.startswith('+ '):
|
if line.startswith(u('+ ')):
|
||||||
newdiff.append(' ' + line[2:])
|
newdiff.append(u(' ') + line[2:])
|
||||||
else:
|
else:
|
||||||
newdiff.append(line)
|
newdiff.append(line)
|
||||||
return newdiff
|
return newdiff
|
||||||
|
|
4
setup.py
4
setup.py
|
@ -17,7 +17,7 @@ classifiers=['Development Status :: 6 - Mature',
|
||||||
|
|
||||||
long_description = open("README.rst").read()
|
long_description = open("README.rst").read()
|
||||||
def main():
|
def main():
|
||||||
install_requires = ["py>=1.4.17"]
|
install_requires = ["py>=1.4.19"]
|
||||||
if sys.version_info < (2,7):
|
if sys.version_info < (2,7):
|
||||||
install_requires.append("argparse")
|
install_requires.append("argparse")
|
||||||
if sys.platform == "win32":
|
if sys.platform == "win32":
|
||||||
|
@ -27,7 +27,7 @@ def main():
|
||||||
name='pytest',
|
name='pytest',
|
||||||
description='py.test: simple powerful testing with Python',
|
description='py.test: simple powerful testing with Python',
|
||||||
long_description = long_description,
|
long_description = long_description,
|
||||||
version='2.4.3.dev2',
|
version='2.5.0.dev1',
|
||||||
url='http://pytest.org',
|
url='http://pytest.org',
|
||||||
license='MIT license',
|
license='MIT license',
|
||||||
platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'],
|
platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'],
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
import py, pytest
|
import py, pytest
|
||||||
|
@ -176,6 +177,15 @@ class TestAssert_reprcompare:
|
||||||
expl = ' '.join(callequal('foo', 'bar'))
|
expl = ' '.join(callequal('foo', 'bar'))
|
||||||
assert 'raised in repr()' not in expl
|
assert 'raised in repr()' not in expl
|
||||||
|
|
||||||
|
def test_unicode(self):
|
||||||
|
left = py.builtin._totext('£€', 'utf-8')
|
||||||
|
right = py.builtin._totext('£', 'utf-8')
|
||||||
|
expl = callequal(left, right)
|
||||||
|
assert expl[0] == py.builtin._totext("'£€' == '£'", 'utf-8')
|
||||||
|
assert expl[1] == py.builtin._totext('- £€', 'utf-8')
|
||||||
|
assert expl[2] == py.builtin._totext('+ £', 'utf-8')
|
||||||
|
|
||||||
|
|
||||||
def test_python25_compile_issue257(testdir):
|
def test_python25_compile_issue257(testdir):
|
||||||
testdir.makepyfile("""
|
testdir.makepyfile("""
|
||||||
def test_rewritten():
|
def test_rewritten():
|
||||||
|
|
Loading…
Reference in New Issue