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.
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.
Thanks Ben Webb.
- 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.
- fix issue 1029: transform errors when writing cache values into pytest-warnings
2.8.0
-----------------------------
@ -196,8 +204,6 @@
- fix issue714: add ability to apply indirect=True parameter on particular argnames.
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
``rst``. Thanks to Abhijeet for the PR.

View File

@ -153,11 +153,11 @@ but here is a simple overview:
$ cd pytest
# 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":
$ 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
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.
"""
path = self._getvaluepath(key)
path.dirpath().ensure_dir()
with path.open("w") as f:
self.trace("cache-write %s: %r" % (key, value,))
json.dump(value, f, indent=2, sort_keys=True)
try:
path.dirpath().ensure_dir()
except (py.error.EEXIST, py.error.EACCES):
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:
@ -174,8 +186,20 @@ def pytest_configure(config):
@pytest.fixture
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
def pytest_report_header(config):
if config.option.verbose:
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.
REGEX_TYPE = type(re.compile(''))
_PY3 = sys.version_info > (3, 0)
_PY2 = not _PY3
if hasattr(inspect, 'signature'):
def _format_args(func):
@ -912,7 +915,7 @@ class Metafunc(FuncargnamesCompatAttr):
:arg argvalues: The list of argvalues determines how often a
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,
where each tuple-element specifies a value for its respective
argname.
@ -1037,6 +1040,35 @@ class Metafunc(FuncargnamesCompatAttr):
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):
if idfn:
try:
@ -1046,7 +1078,9 @@ def _idval(val, argname, idx, idfn):
except Exception:
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)
elif isinstance(val, REGEX_TYPE):
return val.pattern
@ -1054,6 +1088,14 @@ def _idval(val, argname, idx, idfn):
return str(val)
elif isclass(val) and hasattr(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)
def _idvalset(idx, valset, argnames, idfn):

View File

@ -22,7 +22,7 @@ def pytest_addoption(parser):
group._addoption('-r',
action="store", dest="reportchars", default=None, metavar="chars",
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',
action="store_true", dest="showlocals", default=False,
help="show locals in tracebacks (disabled by default).")

View File

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

View File

@ -1,3 +1,4 @@
import sys
import pytest
import os
import shutil
@ -25,6 +26,36 @@ class TestNewAPI:
val = config.cache.get("key/name", -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):
testdir.makeconftest("""
def pytest_configure(config):
@ -238,6 +269,22 @@ class TestLastFailed:
lastfailed = config.cache.get("cache/lastfailed", -1)
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):
testdir.makepyfile(test_maybe="""