Merge remote-tracking branch 'upstream/master' into release-4.3.0
This commit is contained in:
commit
0395996756
20
.travis.yml
20
.travis.yml
|
@ -1,4 +1,3 @@
|
||||||
sudo: false
|
|
||||||
language: python
|
language: python
|
||||||
dist: xenial
|
dist: xenial
|
||||||
stages:
|
stages:
|
||||||
|
@ -26,7 +25,7 @@ env:
|
||||||
matrix:
|
matrix:
|
||||||
allow_failures:
|
allow_failures:
|
||||||
- python: '3.8-dev'
|
- python: '3.8-dev'
|
||||||
env: TOXENV=py38
|
env: TOXENV=py38-xdist
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
include:
|
include:
|
||||||
|
@ -34,14 +33,12 @@ jobs:
|
||||||
- env: TOXENV=pypy PYTEST_NO_COVERAGE=1
|
- env: TOXENV=pypy PYTEST_NO_COVERAGE=1
|
||||||
python: 'pypy-5.4'
|
python: 'pypy-5.4'
|
||||||
dist: trusty
|
dist: trusty
|
||||||
- env: TOXENV=py34
|
- env: TOXENV=py34-xdist
|
||||||
python: '3.4'
|
python: '3.4'
|
||||||
- env: TOXENV=py35
|
- env: TOXENV=py35-xdist
|
||||||
python: '3.5'
|
python: '3.5'
|
||||||
- env: TOXENV=py36
|
- env: TOXENV=py36-xdist
|
||||||
python: '3.6'
|
python: '3.6'
|
||||||
- env: TOXENV=py38
|
|
||||||
python: '3.8-dev'
|
|
||||||
- env: TOXENV=py37
|
- env: TOXENV=py37
|
||||||
- &test-macos
|
- &test-macos
|
||||||
language: generic
|
language: generic
|
||||||
|
@ -50,15 +47,20 @@ jobs:
|
||||||
sudo: required
|
sudo: required
|
||||||
install:
|
install:
|
||||||
- python -m pip install --pre tox
|
- python -m pip install --pre tox
|
||||||
env: TOXENV=py27
|
env: TOXENV=py27-xdist
|
||||||
- <<: *test-macos
|
- <<: *test-macos
|
||||||
env: TOXENV=py37
|
env: TOXENV=py37-xdist
|
||||||
before_install:
|
before_install:
|
||||||
- brew update
|
- brew update
|
||||||
- brew upgrade python
|
- brew upgrade python
|
||||||
- brew unlink python
|
- brew unlink python
|
||||||
- brew link python
|
- brew link python
|
||||||
|
|
||||||
|
# Jobs only run via Travis cron jobs (currently daily).
|
||||||
|
- env: TOXENV=py38-xdist
|
||||||
|
python: '3.8-dev'
|
||||||
|
if: type = cron
|
||||||
|
|
||||||
- stage: baseline
|
- stage: baseline
|
||||||
env: TOXENV=py27-pexpect,py27-trial,py27-numpy
|
env: TOXENV=py27-pexpect,py27-trial,py27-numpy
|
||||||
- env: TOXENV=py37-xdist
|
- env: TOXENV=py37-xdist
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Fix ``AssertionError`` with collection of broken symlinks with packages.
|
|
@ -88,23 +88,30 @@ and if you need to have access to the actual exception info you may use::
|
||||||
the actual exception raised. The main attributes of interest are
|
the actual exception raised. The main attributes of interest are
|
||||||
``.type``, ``.value`` and ``.traceback``.
|
``.type``, ``.value`` and ``.traceback``.
|
||||||
|
|
||||||
.. versionchanged:: 3.0
|
You can pass a ``match`` keyword parameter to the context-manager to test
|
||||||
|
that a regular expression matches on the string representation of an exception
|
||||||
|
(similar to the ``TestCase.assertRaisesRegexp`` method from ``unittest``)::
|
||||||
|
|
||||||
In the context manager form you may use the keyword argument
|
import pytest
|
||||||
``message`` to specify a custom failure message::
|
|
||||||
|
|
||||||
>>> with raises(ZeroDivisionError, message="Expecting ZeroDivisionError"):
|
def myfunc():
|
||||||
... pass
|
raise ValueError("Exception 123 raised")
|
||||||
... Failed: Expecting ZeroDivisionError
|
|
||||||
|
|
||||||
If you want to write test code that works on Python 2.4 as well,
|
def test_match():
|
||||||
you may also use two other ways to test for an expected exception::
|
with pytest.raises(ValueError, match=r'.* 123 .*'):
|
||||||
|
myfunc()
|
||||||
|
|
||||||
|
The regexp parameter of the ``match`` method is matched with the ``re.search``
|
||||||
|
function, so in the above example ``match='123'`` would have worked as
|
||||||
|
well.
|
||||||
|
|
||||||
|
There's an alternate form of the ``pytest.raises`` function where you pass
|
||||||
|
a function that will be executed with the given ``*args`` and ``**kwargs`` and
|
||||||
|
assert that the given exception is raised::
|
||||||
|
|
||||||
pytest.raises(ExpectedException, func, *args, **kwargs)
|
pytest.raises(ExpectedException, func, *args, **kwargs)
|
||||||
|
|
||||||
which will execute the specified function with args and kwargs and
|
The reporter will provide you with helpful output in case of failures such as *no
|
||||||
assert that the given ``ExpectedException`` is raised. The reporter will
|
|
||||||
provide you with helpful output in case of failures such as *no
|
|
||||||
exception* or *wrong exception*.
|
exception* or *wrong exception*.
|
||||||
|
|
||||||
Note that it is also possible to specify a "raises" argument to
|
Note that it is also possible to specify a "raises" argument to
|
||||||
|
@ -121,23 +128,6 @@ exceptions your own code is deliberately raising, whereas using
|
||||||
like documenting unfixed bugs (where the test describes what "should" happen)
|
like documenting unfixed bugs (where the test describes what "should" happen)
|
||||||
or bugs in dependencies.
|
or bugs in dependencies.
|
||||||
|
|
||||||
Also, the context manager form accepts a ``match`` keyword parameter to test
|
|
||||||
that a regular expression matches on the string representation of an exception
|
|
||||||
(like the ``TestCase.assertRaisesRegexp`` method from ``unittest``)::
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
def myfunc():
|
|
||||||
raise ValueError("Exception 123 raised")
|
|
||||||
|
|
||||||
def test_match():
|
|
||||||
with pytest.raises(ValueError, match=r'.* 123 .*'):
|
|
||||||
myfunc()
|
|
||||||
|
|
||||||
The regexp parameter of the ``match`` method is matched with the ``re.search``
|
|
||||||
function. So in the above example ``match='123'`` would have worked as
|
|
||||||
well.
|
|
||||||
|
|
||||||
|
|
||||||
.. _`assertwarns`:
|
.. _`assertwarns`:
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,9 @@ Talks and blog postings
|
||||||
|
|
||||||
- pytest: recommendations, basic packages for testing in Python and Django, Andreu Vallbona, PyconES 2017 (`slides in english <http://talks.apsl.io/testing-pycones-2017/>`_, `video in spanish <https://www.youtube.com/watch?v=K20GeR-lXDk>`_)
|
- pytest: recommendations, basic packages for testing in Python and Django, Andreu Vallbona, PyconES 2017 (`slides in english <http://talks.apsl.io/testing-pycones-2017/>`_, `video in spanish <https://www.youtube.com/watch?v=K20GeR-lXDk>`_)
|
||||||
|
|
||||||
|
- `pytest advanced, Andrew Svetlov (Russian, PyCon Russia, 2016)
|
||||||
|
<https://www.youtube.com/watch?v=7KgihdKTWY4>`_.
|
||||||
|
|
||||||
- `Pythonic testing, Igor Starikov (Russian, PyNsk, November 2016)
|
- `Pythonic testing, Igor Starikov (Russian, PyNsk, November 2016)
|
||||||
<https://www.youtube.com/watch?v=_92nfdd5nK8>`_.
|
<https://www.youtube.com/watch?v=_92nfdd5nK8>`_.
|
||||||
|
|
||||||
|
|
|
@ -618,7 +618,12 @@ class Session(nodes.FSCollector):
|
||||||
yield y
|
yield y
|
||||||
|
|
||||||
def _collectfile(self, path, handle_dupes=True):
|
def _collectfile(self, path, handle_dupes=True):
|
||||||
assert path.isfile()
|
assert path.isfile(), "%r is not a file (isdir=%r, exists=%r, islink=%r)" % (
|
||||||
|
path,
|
||||||
|
path.isdir(),
|
||||||
|
path.exists(),
|
||||||
|
path.islink(),
|
||||||
|
)
|
||||||
ihook = self.gethookproxy(path)
|
ihook = self.gethookproxy(path)
|
||||||
if not self.isinitpath(path):
|
if not self.isinitpath(path):
|
||||||
if ihook.pytest_ignore_collect(path=path, config=self.config):
|
if ihook.pytest_ignore_collect(path=path, config=self.config):
|
||||||
|
|
|
@ -113,11 +113,12 @@ class Node(object):
|
||||||
|
|
||||||
:raise ValueError: if ``warning`` instance is not a subclass of PytestWarning.
|
:raise ValueError: if ``warning`` instance is not a subclass of PytestWarning.
|
||||||
|
|
||||||
Example usage::
|
Example usage:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
node.warn(PytestWarning("some message"))
|
node.warn(PytestWarning("some message"))
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from _pytest.warning_types import PytestWarning
|
from _pytest.warning_types import PytestWarning
|
||||||
|
|
||||||
|
|
|
@ -599,7 +599,12 @@ class Package(Module):
|
||||||
return proxy
|
return proxy
|
||||||
|
|
||||||
def _collectfile(self, path, handle_dupes=True):
|
def _collectfile(self, path, handle_dupes=True):
|
||||||
assert path.isfile()
|
assert path.isfile(), "%r is not a file (isdir=%r, exists=%r, islink=%r)" % (
|
||||||
|
path,
|
||||||
|
path.isdir(),
|
||||||
|
path.exists(),
|
||||||
|
path.islink(),
|
||||||
|
)
|
||||||
ihook = self.gethookproxy(path)
|
ihook = self.gethookproxy(path)
|
||||||
if not self.isinitpath(path):
|
if not self.isinitpath(path):
|
||||||
if ihook.pytest_ignore_collect(path=path, config=self.config):
|
if ihook.pytest_ignore_collect(path=path, config=self.config):
|
||||||
|
@ -632,7 +637,8 @@ class Package(Module):
|
||||||
pkg_prefixes = set()
|
pkg_prefixes = set()
|
||||||
for path in this_path.visit(rec=self._recurse, bf=True, sort=True):
|
for path in this_path.visit(rec=self._recurse, bf=True, sort=True):
|
||||||
# We will visit our own __init__.py file, in which case we skip it.
|
# We will visit our own __init__.py file, in which case we skip it.
|
||||||
if path.isfile():
|
is_file = path.isfile()
|
||||||
|
if is_file:
|
||||||
if path.basename == "__init__.py" and path.dirpath() == this_path:
|
if path.basename == "__init__.py" and path.dirpath() == this_path:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
@ -643,12 +649,14 @@ class Package(Module):
|
||||||
):
|
):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if path.isdir():
|
if is_file:
|
||||||
if path.join("__init__.py").check(file=1):
|
|
||||||
pkg_prefixes.add(path)
|
|
||||||
else:
|
|
||||||
for x in self._collectfile(path):
|
for x in self._collectfile(path):
|
||||||
yield x
|
yield x
|
||||||
|
elif not path.isdir():
|
||||||
|
# Broken symlink or invalid/missing file.
|
||||||
|
continue
|
||||||
|
elif path.join("__init__.py").check(file=1):
|
||||||
|
pkg_prefixes.add(path)
|
||||||
|
|
||||||
|
|
||||||
def _get_xunit_setup_teardown(holder, attr_name, param_obj=None):
|
def _get_xunit_setup_teardown(holder, attr_name, param_obj=None):
|
||||||
|
|
|
@ -280,7 +280,9 @@ class TerminalReporter(object):
|
||||||
|
|
||||||
def write_fspath_result(self, nodeid, res, **markup):
|
def write_fspath_result(self, nodeid, res, **markup):
|
||||||
fspath = self.config.rootdir.join(nodeid.split("::")[0])
|
fspath = self.config.rootdir.join(nodeid.split("::")[0])
|
||||||
if fspath != self.currentfspath:
|
# NOTE: explicitly check for None to work around py bug, and for less
|
||||||
|
# overhead in general (https://github.com/pytest-dev/py/pull/207).
|
||||||
|
if self.currentfspath is None or fspath != self.currentfspath:
|
||||||
if self.currentfspath is not None and self._show_progress_info:
|
if self.currentfspath is not None and self._show_progress_info:
|
||||||
self._write_progress_information_filling_space()
|
self._write_progress_information_filling_space()
|
||||||
self.currentfspath = fspath
|
self.currentfspath = fspath
|
||||||
|
|
|
@ -31,7 +31,7 @@ class TempPathFactory(object):
|
||||||
# using os.path.abspath() to get absolute path instead of resolve() as it
|
# using os.path.abspath() to get absolute path instead of resolve() as it
|
||||||
# does not work the same in all platforms (see #4427)
|
# does not work the same in all platforms (see #4427)
|
||||||
# Path.absolute() exists, but it is not public (see https://bugs.python.org/issue25012)
|
# Path.absolute() exists, but it is not public (see https://bugs.python.org/issue25012)
|
||||||
convert=attr.converters.optional(
|
converter=attr.converters.optional(
|
||||||
lambda p: Path(os.path.abspath(six.text_type(p)))
|
lambda p: Path(os.path.abspath(six.text_type(p)))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
|
@ -1308,10 +1308,17 @@ class TestEarlyRewriteBailout(object):
|
||||||
@pytest.mark.skipif(
|
@pytest.mark.skipif(
|
||||||
sys.platform.startswith("win32"), reason="cannot remove cwd on Windows"
|
sys.platform.startswith("win32"), reason="cannot remove cwd on Windows"
|
||||||
)
|
)
|
||||||
def test_cwd_changed(self, testdir):
|
def test_cwd_changed(self, testdir, monkeypatch):
|
||||||
|
# Setup conditions for py's fspath trying to import pathlib on py34
|
||||||
|
# always (previously triggered via xdist only).
|
||||||
|
# Ref: https://github.com/pytest-dev/py/pull/207
|
||||||
|
monkeypatch.setattr(sys, "path", [""] + sys.path)
|
||||||
|
if "pathlib" in sys.modules:
|
||||||
|
del sys.modules["pathlib"]
|
||||||
|
|
||||||
testdir.makepyfile(
|
testdir.makepyfile(
|
||||||
**{
|
**{
|
||||||
"test_bar.py": """
|
"test_setup_nonexisting_cwd.py": """
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import tempfile
|
import tempfile
|
||||||
|
@ -1320,7 +1327,7 @@ class TestEarlyRewriteBailout(object):
|
||||||
os.chdir(d)
|
os.chdir(d)
|
||||||
shutil.rmtree(d)
|
shutil.rmtree(d)
|
||||||
""",
|
""",
|
||||||
"test_foo.py": """
|
"test_test.py": """
|
||||||
def test():
|
def test():
|
||||||
pass
|
pass
|
||||||
""",
|
""",
|
||||||
|
|
|
@ -1206,3 +1206,30 @@ def test_collect_pkg_init_and_file_in_args(testdir):
|
||||||
"*2 passed in*",
|
"*2 passed in*",
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skipif(
|
||||||
|
not hasattr(py.path.local, "mksymlinkto"),
|
||||||
|
reason="symlink not available on this platform",
|
||||||
|
)
|
||||||
|
@pytest.mark.parametrize("use_pkg", (True, False))
|
||||||
|
def test_collect_sub_with_symlinks(use_pkg, testdir):
|
||||||
|
sub = testdir.mkdir("sub")
|
||||||
|
if use_pkg:
|
||||||
|
sub.ensure("__init__.py")
|
||||||
|
sub.ensure("test_file.py").write("def test_file(): pass")
|
||||||
|
|
||||||
|
# Create a broken symlink.
|
||||||
|
sub.join("test_broken.py").mksymlinkto("test_doesnotexist.py")
|
||||||
|
|
||||||
|
# Symlink that gets collected.
|
||||||
|
sub.join("test_symlink.py").mksymlinkto("test_file.py")
|
||||||
|
|
||||||
|
result = testdir.runpytest("-v", str(sub))
|
||||||
|
result.stdout.fnmatch_lines(
|
||||||
|
[
|
||||||
|
"sub/test_file.py::test_file PASSED*",
|
||||||
|
"sub/test_symlink.py::test_file PASSED*",
|
||||||
|
"*2 passed in*",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
Loading…
Reference in New Issue