Merge branch 'master' into features

This commit is contained in:
holger krekel 2015-09-23 16:42:42 +02:00
commit 4867554eec
7 changed files with 141 additions and 18 deletions

View File

@ -12,13 +12,21 @@
- Fix issue #766 by removing documentation references to distutils. - Fix issue #766 by removing documentation references to distutils.
Thanks Russel Winder. Thanks Russel Winder.
- Fix issue #1030: now byte-strings are escaped to produce item node ids
to make them always serializable.
Thanks Andy Freeland for the report and Bruno Oliveira for the PR.
- Python 2: if unicode parametrized values are convertible to ascii, their
ascii representation is used for the node id.
- Fix issue #411: Add __eq__ method to assertion comparison example. - Fix issue #411: Add __eq__ method to assertion comparison example.
Thanks Ben Webb. Thanks Ben Webb.
- Fix issue #653: deprecated_call can be used as context manager. - Fix issue #653: deprecated_call can be used as context manager.
- fix issue 877: propperly handle assertion explanations with non-ascii repr - fix issue 877: properly handle assertion explanations with non-ascii repr
Thanks Mathieu Agopian for the report and Ronny Pfannschmidt for the PR. Thanks Mathieu Agopian for the report and Ronny Pfannschmidt for the PR.
- fix issue 1029: transform errors when writing cache values into pytest-warnings
2.8.0 2.8.0
----------------------------- -----------------------------
@ -196,8 +204,6 @@
- fix issue714: add ability to apply indirect=True parameter on particular argnames. - fix issue714: add ability to apply indirect=True parameter on particular argnames.
Thanks Elizaveta239. Thanks Elizaveta239.
- fix issue714: add ability to apply indirect=True parameter on particular argnames.
- fix issue890: changed extension of all documentation files from ``txt`` to - fix issue890: changed extension of all documentation files from ``txt`` to
``rst``. Thanks to Abhijeet for the PR. ``rst``. Thanks to Abhijeet for the PR.

View File

@ -153,11 +153,11 @@ but here is a simple overview:
$ cd pytest $ cd pytest
# now, to fix a bug create your own branch off "master": # now, to fix a bug create your own branch off "master":
$ git checkout master -b your-bugfix-branch-name $ git checkout -b your-bugfix-branch-name master
# or to instead add a feature create your own branch off "features": # or to instead add a feature create your own branch off "features":
$ git checkout features -b your-feature-branch-name $ git checkout -b your-feature-branch-name features
Given we have "major.minor.micro" version numbers, bugfixes will usually Given we have "major.minor.micro" version numbers, bugfixes will usually
be released in micro releases whereas features will be released in be released in micro releases whereas features will be released in

View File

@ -69,10 +69,22 @@ class Cache(object):
like e. g. lists of dictionaries. like e. g. lists of dictionaries.
""" """
path = self._getvaluepath(key) path = self._getvaluepath(key)
path.dirpath().ensure_dir() try:
with path.open("w") as f: path.dirpath().ensure_dir()
self.trace("cache-write %s: %r" % (key, value,)) except (py.error.EEXIST, py.error.EACCES):
json.dump(value, f, indent=2, sort_keys=True) self.config.warn(
code='I9', message='could not create cache path %s' % (path,)
)
return
try:
f = path.open('w')
except py.error.ENOTDIR:
self.config.warn(
code='I9', message='cache could not write path %s' % (path,))
else:
with f:
self.trace("cache-write %s: %r" % (key, value,))
json.dump(value, f, indent=2, sort_keys=True)
class LFPlugin: class LFPlugin:
@ -174,8 +186,20 @@ def pytest_configure(config):
@pytest.fixture @pytest.fixture
def cache(request): def cache(request):
"""
Return a cache object that can persist state between testing sessions.
cache.get(key, default)
cache.set(key, value)
Keys must be strings not containing a "/" separator. Add a unique identifier
(such as plugin/app name) to avoid clashes with other cache users.
Values can be any object handled by the json stdlib module.
"""
return request.config.cache return request.config.cache
def pytest_report_header(config): def pytest_report_header(config):
if config.option.verbose: if config.option.verbose:
relpath = py.path.local().bestrelpath(config.cache._cachedir) relpath = py.path.local().bestrelpath(config.cache._cachedir)

View File

@ -32,6 +32,9 @@ exc_clear = getattr(sys, 'exc_clear', lambda: None)
# The type of re.compile objects is not exposed in Python. # The type of re.compile objects is not exposed in Python.
REGEX_TYPE = type(re.compile('')) REGEX_TYPE = type(re.compile(''))
_PY3 = sys.version_info > (3, 0)
_PY2 = not _PY3
if hasattr(inspect, 'signature'): if hasattr(inspect, 'signature'):
def _format_args(func): def _format_args(func):
@ -912,7 +915,7 @@ class Metafunc(FuncargnamesCompatAttr):
:arg argvalues: The list of argvalues determines how often a :arg argvalues: The list of argvalues determines how often a
test is invoked with different argument values. If only one test is invoked with different argument values. If only one
argname was specified argvalues is a list of simple values. If N argname was specified argvalues is a list of values. If N
argnames were specified, argvalues must be a list of N-tuples, argnames were specified, argvalues must be a list of N-tuples,
where each tuple-element specifies a value for its respective where each tuple-element specifies a value for its respective
argname. argname.
@ -1037,6 +1040,35 @@ class Metafunc(FuncargnamesCompatAttr):
self._calls.append(cs) self._calls.append(cs)
if _PY3:
def _escape_bytes(val):
"""
If val is pure ascii, returns it as a str(), otherwise escapes
into a sequence of escaped bytes:
b'\xc3\xb4\xc5\xd6' -> u'\\xc3\\xb4\\xc5\\xd6'
note:
the obvious "v.decode('unicode-escape')" will return
valid utf-8 unicode if it finds them in the string, but we
want to return escaped bytes for any byte, even if they match
a utf-8 string.
"""
# source: http://goo.gl/bGsnwC
import codecs
encoded_bytes, _ = codecs.escape_encode(val)
return encoded_bytes.decode('ascii')
else:
def _escape_bytes(val):
"""
In py2 bytes and str are the same, so return it unchanged if it
is a full ascii string, otherwise escape it into its binary form.
"""
try:
return val.encode('ascii')
except UnicodeDecodeError:
return val.encode('string-escape')
def _idval(val, argname, idx, idfn): def _idval(val, argname, idx, idfn):
if idfn: if idfn:
try: try:
@ -1046,7 +1078,9 @@ def _idval(val, argname, idx, idfn):
except Exception: except Exception:
pass pass
if isinstance(val, (float, int, str, bool, NoneType)): if isinstance(val, bytes):
return _escape_bytes(val)
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 val.pattern
@ -1054,6 +1088,14 @@ def _idval(val, argname, idx, idfn):
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 UnicodeDecodeError:
# fallthrough
pass
return str(argname)+str(idx) return str(argname)+str(idx)
def _idvalset(idx, valset, argnames, idfn): def _idvalset(idx, valset, argnames, idfn):

View File

@ -22,7 +22,7 @@ def pytest_addoption(parser):
group._addoption('-r', group._addoption('-r',
action="store", dest="reportchars", default=None, metavar="chars", action="store", dest="reportchars", default=None, metavar="chars",
help="show extra test summary info as specified by chars (f)ailed, " help="show extra test summary info as specified by chars (f)ailed, "
"(E)error, (s)skipped, (x)failed, (X)passed (w)warnings (a)all.") "(E)error, (s)skipped, (x)failed, (X)passed (w)pytest-warnings (a)all.")
group._addoption('-l', '--showlocals', group._addoption('-l', '--showlocals',
action="store_true", dest="showlocals", default=False, action="store_true", dest="showlocals", default=False,
help="show locals in tracebacks (disabled by default).") help="show locals in tracebacks (disabled by default).")

View File

@ -129,11 +129,12 @@ class TestMetafunc:
(object(), object())]) (object(), object())])
assert result == ["a0-1.0", "a1-b1"] assert result == ["a0-1.0", "a1-b1"]
# unicode mixing, issue250 # unicode mixing, issue250
result = idmaker((py.builtin._totext("a"), "b"), [({}, '\xc3\xb4')]) result = idmaker((py.builtin._totext("a"), "b"), [({}, b'\xc3\xb4')])
assert result == ['a0-\xc3\xb4'] assert result == ['a0-\\xc3\\xb4']
def test_idmaker_native_strings(self): def test_idmaker_native_strings(self):
from _pytest.python import idmaker from _pytest.python import idmaker
totext = py.builtin._totext
result = idmaker(("a", "b"), [(1.0, -1.1), result = idmaker(("a", "b"), [(1.0, -1.1),
(2, -202), (2, -202),
("three", "three hundred"), ("three", "three hundred"),
@ -143,7 +144,9 @@ class TestMetafunc:
(str, int), (str, int),
(list("six"), [66, 66]), (list("six"), [66, 66]),
(set([7]), set("seven")), (set([7]), set("seven")),
(tuple("eight"), (8, -8, 8)) (tuple("eight"), (8, -8, 8)),
(b'\xc3\xb4', b"name"),
(b'\xc3\xb4', totext("other")),
]) ])
assert result == ["1.0--1.1", assert result == ["1.0--1.1",
"2--202", "2--202",
@ -154,7 +157,10 @@ class TestMetafunc:
"str-int", "str-int",
"a7-b7", "a7-b7",
"a8-b8", "a8-b8",
"a9-b9"] "a9-b9",
"\\xc3\\xb4-name",
"\\xc3\\xb4-other",
]
def test_idmaker_enum(self): def test_idmaker_enum(self):
from _pytest.python import idmaker from _pytest.python import idmaker
@ -312,7 +318,6 @@ class TestMetafunc:
"*uses no fixture 'y'*", "*uses no fixture 'y'*",
]) ])
@pytest.mark.xfail
@pytest.mark.issue714 @pytest.mark.issue714
def test_parametrize_uses_no_fixture_error_indirect_true(self, testdir): def test_parametrize_uses_no_fixture_error_indirect_true(self, testdir):
testdir.makepyfile(""" testdir.makepyfile("""
@ -333,7 +338,6 @@ class TestMetafunc:
"*uses no fixture 'y'*", "*uses no fixture 'y'*",
]) ])
@pytest.mark.xfail
@pytest.mark.issue714 @pytest.mark.issue714
def test_parametrize_indirect_uses_no_fixture_error_indirect_list(self, testdir): def test_parametrize_indirect_uses_no_fixture_error_indirect_list(self, testdir):
testdir.makepyfile(""" testdir.makepyfile("""

View File

@ -1,3 +1,4 @@
import sys
import pytest import pytest
import os import os
import shutil import shutil
@ -25,6 +26,36 @@ class TestNewAPI:
val = config.cache.get("key/name", -2) val = config.cache.get("key/name", -2)
assert val == -2 assert val == -2
def test_cache_writefail_cachfile_silent(self, testdir):
testdir.makeini("[pytest]")
testdir.tmpdir.join('.cache').write('gone wrong')
config = testdir.parseconfigure()
cache = config.cache
cache.set('test/broken', [])
@pytest.mark.skipif(sys.platform.startswith('win'), reason='no chmod on windows')
def test_cache_writefail_permissions(self, testdir):
testdir.makeini("[pytest]")
testdir.tmpdir.ensure_dir('.cache').chmod(0)
config = testdir.parseconfigure()
cache = config.cache
cache.set('test/broken', [])
@pytest.mark.skipif(sys.platform.startswith('win'), reason='no chmod on windows')
def test_cache_failure_warns(self, testdir):
testdir.tmpdir.ensure_dir('.cache').chmod(0)
testdir.makepyfile("""
def test_pass():
pass
""")
result = testdir.runpytest('-rw')
assert result.ret == 0
result.stdout.fnmatch_lines([
"*could not create cache path*",
"*1 pytest-warnings*",
])
def test_config_cache(self, testdir): def test_config_cache(self, testdir):
testdir.makeconftest(""" testdir.makeconftest("""
def pytest_configure(config): def pytest_configure(config):
@ -238,6 +269,22 @@ class TestLastFailed:
lastfailed = config.cache.get("cache/lastfailed", -1) lastfailed = config.cache.get("cache/lastfailed", -1)
assert not lastfailed assert not lastfailed
def test_non_serializable_parametrize(self, testdir):
"""Test that failed parametrized tests with unmarshable parameters
don't break pytest-cache.
"""
testdir.makepyfile(r"""
import pytest
@pytest.mark.parametrize('val', [
b'\xac\x10\x02G',
])
def test_fail(val):
assert False
""")
result = testdir.runpytest()
result.stdout.fnmatch_lines('*1 failed in*')
def test_lastfailed_collectfailure(self, testdir, monkeypatch): def test_lastfailed_collectfailure(self, testdir, monkeypatch):
testdir.makepyfile(test_maybe=""" testdir.makepyfile(test_maybe="""