Escape both bytes and unicode strings for "ids" in Metafunc.parametrize

This commit is contained in:
Ceridwen 2016-03-22 01:31:48 -04:00
parent ceacc12b52
commit 4405dd0ffe
2 changed files with 49 additions and 35 deletions

View File

@ -23,7 +23,9 @@
**Changes** **Changes**
* * Fix (`#1351 <https://github.com/pytest-dev/pytest/issues/1351>`_):
explicitly passed parametrize ids do not get escaped to ascii.
Thanks `@ceridwen`_ for the PR.
* *

View File

@ -1025,9 +1025,12 @@ class Metafunc(FuncargnamesCompatAttr):
if callable(ids): if callable(ids):
idfn = ids idfn = ids
ids = None ids = None
if ids and len(ids) != len(argvalues): if ids:
raise ValueError('%d tests specified with %d ids' %( if len(ids) != len(argvalues):
len(argvalues), len(ids))) raise ValueError('%d tests specified with %d ids' %(
len(argvalues), len(ids)))
else:
ids = [_escape_strings(i) for i in ids]
if not ids: if not ids:
ids = idmaker(argnames, argvalues, idfn) ids = idmaker(argnames, argvalues, idfn)
newcalls = [] newcalls = []
@ -1078,38 +1081,55 @@ class Metafunc(FuncargnamesCompatAttr):
self._calls.append(cs) self._calls.append(cs)
if _PY3: if _PY3:
import codecs import codecs
def _escape_bytes(val): def _escape_strings(val):
""" """If val is pure ascii, returns it as a str(). Otherwise, escapes
If val is pure ascii, returns it as a str(), otherwise escapes bytes objects into a sequence of escaped bytes:
into a sequence of escaped bytes:
b'\xc3\xb4\xc5\xd6' -> u'\\xc3\\xb4\\xc5\\xd6' b'\xc3\xb4\xc5\xd6' -> u'\\xc3\\xb4\\xc5\\xd6'
and escapes unicode objects into a sequence of escaped unicode
ids, e.g.:
'4\\nV\\U00043efa\\x0eMXWB\\x1e\\u3028\\u15fd\\xcd\\U0007d944'
note: note:
the obvious "v.decode('unicode-escape')" will return the obvious "v.decode('unicode-escape')" will return
valid utf-8 unicode if it finds them in the string, but we valid utf-8 unicode if it finds them in bytes, but we
want to return escaped bytes for any byte, even if they match want to return escaped bytes for any byte, even if they match
a utf-8 string. a utf-8 string.
""" """
if val: if isinstance(val, bytes):
# source: http://goo.gl/bGsnwC if val:
encoded_bytes, _ = codecs.escape_encode(val) # source: http://goo.gl/bGsnwC
return encoded_bytes.decode('ascii') encoded_bytes, _ = codecs.escape_encode(val)
return encoded_bytes.decode('ascii')
else:
# empty bytes crashes codecs.escape_encode (#1087)
return ''
else: else:
# empty bytes crashes codecs.escape_encode (#1087) return val.encode('unicode_escape').decode('ascii')
return ''
else: else:
def _escape_bytes(val): def _escape_strings(val):
"""In py2 bytes and str are the same type, so return if it's a bytes
object, return it unchanged if it is a full ascii string,
otherwise escape it into its binary form.
If it's a unicode string, change the unicode characters into
unicode escapes.
""" """
In py2 bytes and str are the same type, so return it unchanged if it if isinstance(val, bytes):
is a full ascii string, otherwise escape it into its binary form. try:
""" return val.decode('ascii')
try: except UnicodeDecodeError:
return val.decode('ascii') return val.encode('string-escape')
except UnicodeDecodeError: else:
return val.encode('string-escape') return val.encode('unicode-escape')
def _idval(val, argname, idx, idfn): def _idval(val, argname, idx, idfn):
@ -1117,28 +1137,20 @@ def _idval(val, argname, idx, idfn):
try: try:
s = idfn(val) s = idfn(val)
if s: if s:
return s return _escape_strings(s)
except Exception: except Exception:
pass pass
if isinstance(val, bytes): if isinstance(val, bytes) or (_PY2 and isinstance(val, unicode)):
return _escape_bytes(val) return _escape_strings(val)
elif isinstance(val, (float, int, str, bool, NoneType)): elif isinstance(val, (float, int, str, bool, NoneType)):
return str(val) return str(val)
elif isinstance(val, REGEX_TYPE): elif isinstance(val, REGEX_TYPE):
return val.pattern return _escape_strings(val.pattern)
elif enum is not None and isinstance(val, enum.Enum): elif enum is not None and isinstance(val, enum.Enum):
return str(val) return str(val)
elif isclass(val) and hasattr(val, '__name__'): elif isclass(val) and hasattr(val, '__name__'):
return val.__name__ return val.__name__
elif _PY2 and isinstance(val, unicode):
# special case for python 2: if a unicode string is
# convertible to ascii, return it as an str() object instead
try:
return str(val)
except UnicodeError:
# fallthrough
pass
return str(argname)+str(idx) return str(argname)+str(idx)
def _idvalset(idx, valset, argnames, idfn): def _idvalset(idx, valset, argnames, idfn):