Merge branch 'master' into features
This commit is contained in:
commit
4867554eec
12
CHANGELOG
12
CHANGELOG
|
@ -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.
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -69,8 +69,20 @@ class Cache(object):
|
||||||
like e. g. lists of dictionaries.
|
like e. g. lists of dictionaries.
|
||||||
"""
|
"""
|
||||||
path = self._getvaluepath(key)
|
path = self._getvaluepath(key)
|
||||||
|
try:
|
||||||
path.dirpath().ensure_dir()
|
path.dirpath().ensure_dir()
|
||||||
with path.open("w") as f:
|
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,))
|
self.trace("cache-write %s: %r" % (key, value,))
|
||||||
json.dump(value, f, indent=2, sort_keys=True)
|
json.dump(value, f, indent=2, sort_keys=True)
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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).")
|
||||||
|
|
|
@ -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("""
|
||||||
|
|
|
@ -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="""
|
||||||
|
|
Loading…
Reference in New Issue