Merge pull request from ceridwen/features

Escape both bytes and unicode strings for "ids" in Metafunc.parametrize
This commit is contained in:
Bruno Oliveira 2016-04-03 13:48:30 -03:00
commit e3bc6faa2b
6 changed files with 80 additions and 45 deletions

1
.gitignore vendored
View File

@ -32,3 +32,4 @@ env/
.coverage .coverage
.ropeproject .ropeproject
.idea .idea
.hypothesis

View File

@ -27,6 +27,10 @@
**Changes** **Changes**
* Fix (`#1351`_):
explicitly passed parametrize ids do not get escaped to ascii.
Thanks `@ceridwen`_ for the PR.
* parametrize ids can accept None as specific test id. The * parametrize ids can accept None as specific test id. The
automatically generated id for that argument will be used. automatically generated id for that argument will be used.
Thanks `@palaviv`_ for the complete PR (`#1468`_). Thanks `@palaviv`_ for the complete PR (`#1468`_).
@ -41,6 +45,7 @@
.. _@novas0x2a: https://github.com/novas0x2a .. _@novas0x2a: https://github.com/novas0x2a
.. _@kalekundert: https://github.com/kalekundert .. _@kalekundert: https://github.com/kalekundert
.. _@tareqalayan: https://github.com/tareqalayan .. _@tareqalayan: https://github.com/tareqalayan
.. _@ceridwen: https://github.com/ceridwen
.. _@palaviv: https://github.com/palaviv .. _@palaviv: https://github.com/palaviv
.. _@omarkohl: https://github.com/omarkohl .. _@omarkohl: https://github.com/omarkohl
@ -48,12 +53,12 @@
.. _#1444: https://github.com/pytest-dev/pytest/pull/1444 .. _#1444: https://github.com/pytest-dev/pytest/pull/1444
.. _#1441: https://github.com/pytest-dev/pytest/pull/1441 .. _#1441: https://github.com/pytest-dev/pytest/pull/1441
.. _#1454: https://github.com/pytest-dev/pytest/pull/1454 .. _#1454: https://github.com/pytest-dev/pytest/pull/1454
.. _#1351: https://github.com/pytest-dev/pytest/issues/1351
.. _#1468: https://github.com/pytest-dev/pytest/pull/1468 .. _#1468: https://github.com/pytest-dev/pytest/pull/1468
.. _#1474: https://github.com/pytest-dev/pytest/pull/1474 .. _#1474: https://github.com/pytest-dev/pytest/pull/1474
.. _#1502: https://github.com/pytest-dev/pytest/pull/1502 .. _#1502: https://github.com/pytest-dev/pytest/pull/1502
.. _#372: https://github.com/pytest-dev/pytest/issues/372 .. _#372: https://github.com/pytest-dev/pytest/issues/372
2.9.2.dev1 2.9.2.dev1
========== ==========

View File

@ -1079,38 +1079,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.encode('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):
@ -1118,28 +1135,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, str)) 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, bool, NoneType)):
return str(val) return str(val)
elif isinstance(val, REGEX_TYPE): elif isinstance(val, REGEX_TYPE):
return _escape_bytes(val.pattern) if isinstance(val.pattern, bytes) else 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, ids): def _idvalset(idx, valset, argnames, idfn, ids):
@ -1148,7 +1157,7 @@ def _idvalset(idx, valset, argnames, idfn, ids):
for val, argname in zip(valset, argnames)] for val, argname in zip(valset, argnames)]
return "-".join(this_id) return "-".join(this_id)
else: else:
return ids[idx] return _escape_strings(ids[idx])
def idmaker(argnames, argvalues, idfn=None, ids=None): def idmaker(argnames, argvalues, idfn=None, ids=None):
ids = [_idvalset(valindex, valset, argnames, idfn, ids) ids = [_idvalset(valindex, valset, argnames, idfn, ids)

View File

@ -1,11 +1,18 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import re import re
import sys
import _pytest._code import _pytest._code
import py import py
import pytest import pytest
from _pytest import python as funcargs from _pytest import python as funcargs
import hypothesis
from hypothesis import strategies
PY3 = sys.version_info >= (3, 0)
class TestMetafunc: class TestMetafunc:
def Metafunc(self, func): def Metafunc(self, func):
# the unit tests of this class check if things work correctly # the unit tests of this class check if things work correctly
@ -121,20 +128,29 @@ class TestMetafunc:
assert metafunc._calls[2].id == "x1-a" assert metafunc._calls[2].id == "x1-a"
assert metafunc._calls[3].id == "x1-b" assert metafunc._calls[3].id == "x1-b"
@pytest.mark.skipif('sys.version_info[0] >= 3') @hypothesis.given(strategies.text() | strategies.binary())
def test_unicode_idval_python2(self): def test_idval_hypothesis(self, value):
"""unittest for the expected behavior to obtain ids for parametrized from _pytest.python import _idval
unicode values in Python 2: if convertible to ascii, they should appear escaped = _idval(value, 'a', 6, None)
as ascii values, otherwise fallback to hide the value behind the name assert isinstance(escaped, str)
of the parametrized variable name. #1086 if PY3:
escaped.encode('ascii')
else:
escaped.decode('ascii')
def test_unicode_idval(self):
"""This tests that Unicode strings outside the ASCII character set get
escaped, using byte escapes if they're in that range or unicode
escapes if they're not.
""" """
from _pytest.python import _idval from _pytest.python import _idval
values = [ values = [
(u'', ''), (u'', ''),
(u'ascii', 'ascii'), (u'ascii', 'ascii'),
(u'ação', 'a6'), (u'ação', 'a\\xe7\\xe3o'),
(u'josé@blah.com', 'a6'), (u'josé@blah.com', 'jos\\xe9@blah.com'),
(u'δοκ.ιμή@παράδειγμα.δοκιμή', 'a6'), (u'δοκ.ιμή@παράδειγμα.δοκιμή', '\\u03b4\\u03bf\\u03ba.\\u03b9\\u03bc\\u03ae@\\u03c0\\u03b1\\u03c1\\u03ac\\u03b4\\u03b5\\u03b9\\u03b3\\u03bc\\u03b1.\\u03b4\\u03bf\\u03ba\\u03b9\\u03bc\\u03ae'),
] ]
for val, expected in values: for val, expected in values:
assert _idval(val, 'a', 6, None) == expected assert _idval(val, 'a', 6, None) == expected

View File

@ -610,14 +610,14 @@ def test_logxml_makedir(testdir):
def test_escaped_parametrized_names_xml(testdir): def test_escaped_parametrized_names_xml(testdir):
testdir.makepyfile(""" testdir.makepyfile("""
import pytest import pytest
@pytest.mark.parametrize('char', ["\\x00"]) @pytest.mark.parametrize('char', [u"\\x00"])
def test_func(char): def test_func(char):
assert char assert char
""") """)
result, dom = runandparse(testdir) result, dom = runandparse(testdir)
assert result.ret == 0 assert result.ret == 0
node = dom.find_first_by_tag("testcase") node = dom.find_first_by_tag("testcase")
node.assert_attr(name="test_func[#x00]") node.assert_attr(name="test_func[\\x00]")
def test_double_colon_split_function_issue469(testdir): def test_double_colon_split_function_issue469(testdir):

View File

@ -10,6 +10,7 @@ envlist=
commands= py.test --lsof -rfsxX {posargs:testing} commands= py.test --lsof -rfsxX {posargs:testing}
passenv = USER USERNAME passenv = USER USERNAME
deps= deps=
hypothesis
nose nose
mock mock
requests requests
@ -17,6 +18,7 @@ deps=
[testenv:py26] [testenv:py26]
commands= py.test --lsof -rfsxX {posargs:testing} commands= py.test --lsof -rfsxX {posargs:testing}
deps= deps=
hypothesis<3.0
nose nose
mock<1.1 # last supported version for py26 mock<1.1 # last supported version for py26
@ -43,6 +45,7 @@ commands = flake8 pytest.py _pytest testing
deps=pytest-xdist>=1.13 deps=pytest-xdist>=1.13
mock mock
nose nose
hypothesis
commands= commands=
py.test -n1 -rfsxX {posargs:testing} py.test -n1 -rfsxX {posargs:testing}
@ -67,6 +70,7 @@ commands=
[testenv:py27-nobyte] [testenv:py27-nobyte]
deps=pytest-xdist>=1.13 deps=pytest-xdist>=1.13
hypothesis
distribute=true distribute=true
setenv= setenv=
PYTHONDONTWRITEBYTECODE=1 PYTHONDONTWRITEBYTECODE=1