Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/master' into release-4.3.0
Browse files Browse the repository at this point in the history
  • Loading branch information
nicoddemus committed Feb 16, 2019
2 parents 986dd84 + a36e986 commit 0395996
Show file tree
Hide file tree
Showing 11 changed files with 96 additions and 50 deletions.
20 changes: 11 additions & 9 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
sudo: false
language: python
dist: xenial
stages:
Expand Down Expand Up @@ -26,22 +25,20 @@ env:
matrix:
allow_failures:
- python: '3.8-dev'
env: TOXENV=py38
env: TOXENV=py38-xdist

jobs:
include:
# Coverage tracking is slow with pypy, skip it.
- env: TOXENV=pypy PYTEST_NO_COVERAGE=1
python: 'pypy-5.4'
dist: trusty
- env: TOXENV=py34
- env: TOXENV=py34-xdist
python: '3.4'
- env: TOXENV=py35
- env: TOXENV=py35-xdist
python: '3.5'
- env: TOXENV=py36
- env: TOXENV=py36-xdist
python: '3.6'
- env: TOXENV=py38
python: '3.8-dev'
- env: TOXENV=py37
- &test-macos
language: generic
Expand All @@ -50,15 +47,20 @@ jobs:
sudo: required
install:
- python -m pip install --pre tox
env: TOXENV=py27
env: TOXENV=py27-xdist
- <<: *test-macos
env: TOXENV=py37
env: TOXENV=py37-xdist
before_install:
- brew update
- brew upgrade python
- brew unlink 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
env: TOXENV=py27-pexpect,py27-trial,py27-numpy
- env: TOXENV=py37-xdist
Expand Down
1 change: 1 addition & 0 deletions changelog/4782.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix ``AssertionError`` with collection of broken symlinks with packages.
46 changes: 18 additions & 28 deletions doc/en/assert.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
``.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
``message`` to specify a custom failure message::
import pytest

>>> with raises(ZeroDivisionError, message="Expecting ZeroDivisionError"):
... pass
... Failed: Expecting ZeroDivisionError
def myfunc():
raise ValueError("Exception 123 raised")

If you want to write test code that works on Python 2.4 as well,
you may also use two other ways to test for an expected exception::
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.

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)

which will execute the specified function with args and kwargs and
assert that the given ``ExpectedException`` is raised. The reporter will
provide you with helpful output in case of failures such as *no
The reporter will provide you with helpful output in case of failures such as *no
exception* or *wrong exception*.

Note that it is also possible to specify a "raises" argument to
Expand All @@ -121,23 +128,6 @@ exceptions your own code is deliberately raising, whereas using
like documenting unfixed bugs (where the test describes what "should" happen)
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`:

Expand Down
3 changes: 3 additions & 0 deletions doc/en/talks.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 advanced, Andrew Svetlov (Russian, PyCon Russia, 2016)
<https://www.youtube.com/watch?v=7KgihdKTWY4>`_.

- `Pythonic testing, Igor Starikov (Russian, PyNsk, November 2016)
<https://www.youtube.com/watch?v=_92nfdd5nK8>`_.

Expand Down
7 changes: 6 additions & 1 deletion src/_pytest/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -618,7 +618,12 @@ def _collect(self, arg):
yield y

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)
if not self.isinitpath(path):
if ihook.pytest_ignore_collect(path=path, config=self.config):
Expand Down
3 changes: 2 additions & 1 deletion src/_pytest/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,11 +113,12 @@ def warn(self, warning):
:raise ValueError: if ``warning`` instance is not a subclass of PytestWarning.
Example usage::
Example usage:
.. code-block:: python
node.warn(PytestWarning("some message"))
"""
from _pytest.warning_types import PytestWarning

Expand Down
20 changes: 14 additions & 6 deletions src/_pytest/python.py
Original file line number Diff line number Diff line change
Expand Up @@ -599,7 +599,12 @@ def gethookproxy(self, fspath):
return proxy

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)
if not self.isinitpath(path):
if ihook.pytest_ignore_collect(path=path, config=self.config):
Expand Down Expand Up @@ -632,7 +637,8 @@ def collect(self):
pkg_prefixes = set()
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.
if path.isfile():
is_file = path.isfile()
if is_file:
if path.basename == "__init__.py" and path.dirpath() == this_path:
continue

Expand All @@ -643,12 +649,14 @@ def collect(self):
):
continue

if path.isdir():
if path.join("__init__.py").check(file=1):
pkg_prefixes.add(path)
else:
if is_file:
for x in self._collectfile(path):
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):
Expand Down
4 changes: 3 additions & 1 deletion src/_pytest/terminal.py
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,9 @@ def hasopt(self, char):

def write_fspath_result(self, nodeid, res, **markup):
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:
self._write_progress_information_filling_space()
self.currentfspath = fspath
Expand Down
2 changes: 1 addition & 1 deletion src/_pytest/tmpdir.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class TempPathFactory(object):
# using os.path.abspath() to get absolute path instead of resolve() as it
# does not work the same in all platforms (see #4427)
# 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)))
)
)
Expand Down
13 changes: 10 additions & 3 deletions testing/test_assertrewrite.py
Original file line number Diff line number Diff line change
Expand Up @@ -1308,10 +1308,17 @@ def test_simple_failure():
@pytest.mark.skipif(
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(
**{
"test_bar.py": """
"test_setup_nonexisting_cwd.py": """
import os
import shutil
import tempfile
Expand All @@ -1320,7 +1327,7 @@ def test_cwd_changed(self, testdir):
os.chdir(d)
shutil.rmtree(d)
""",
"test_foo.py": """
"test_test.py": """
def test():
pass
""",
Expand Down
27 changes: 27 additions & 0 deletions testing/test_collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -1206,3 +1206,30 @@ def test_collect_pkg_init_and_file_in_args(testdir):
"*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*",
]
)

0 comments on commit 0395996

Please sign in to comment.