Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog/4400.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Rearrange warning handling for the yield test errors so the opt-out in 4.0.x correctly works.
1 change: 1 addition & 0 deletions changelog/4405.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix collection of testpaths with ``--pyargs``.
1 change: 1 addition & 0 deletions changelog/4412.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix assertion rewriting involving ``Starred`` + side-effects.
10 changes: 10 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,16 @@ def main():
use_scm_version={"write_to": "src/_pytest/_version.py"},
setup_requires=["setuptools-scm", "setuptools>=40.0"],
package_dir={"": "src"},
# fmt: off
extras_require={
"testing": [
"hypothesis>=3.56",
"nose",
"requests",
"mock;python_version=='2.7'",
],
},
# fmt: on
install_requires=INSTALL_REQUIRES,
)

Expand Down
3 changes: 2 additions & 1 deletion src/_pytest/assertion/rewrite.py
Original file line number Diff line number Diff line change
Expand Up @@ -946,7 +946,8 @@ def visit_Call_35(self, call):
def visit_Starred(self, starred):
# From Python 3.5, a Starred node can appear in a function call
res, expl = self.visit(starred.value)
return starred, "*" + expl
new_starred = ast.Starred(res, starred.ctx)
return new_starred, "*" + expl

def visit_Call_legacy(self, call):
"""
Expand Down
5 changes: 1 addition & 4 deletions src/_pytest/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -852,10 +852,7 @@ def parse(self, args, addopts=True):
)
if not args:
if self.invocation_dir == self.rootdir:
args = [
str(self.invocation_dir.join(x, abs=True))
for x in self.getini("testpaths")
]
args = self.getini("testpaths")
if not args:
args = [str(self.invocation_dir)]
self.args = args
Expand Down
3 changes: 3 additions & 0 deletions src/_pytest/pytester.py
Original file line number Diff line number Diff line change
Expand Up @@ -518,6 +518,9 @@ def __init__(self, request, tmpdir_factory):
def __repr__(self):
return "<Testdir %r>" % (self.tmpdir,)

def __str__(self):
return str(self.tmpdir)

def finalize(self):
"""Clean up global state artifacts.

Expand Down
10 changes: 5 additions & 5 deletions src/_pytest/python.py
Original file line number Diff line number Diff line change
Expand Up @@ -741,16 +741,20 @@ def repr_failure(self, excinfo, outerr=None):

class Generator(FunctionMixin, PyCollector):
def collect(self):

# test generators are seen as collectors but they also
# invoke setup/teardown on popular request
# (induced by the common "test_*" naming shared with normal tests)
from _pytest import deprecated

self.warn(deprecated.YIELD_TESTS)

self.session._setupstate.prepare(self)
# see FunctionMixin.setup and test_setupstate_is_preserved_134
self._preservedparent = self.parent.obj
values = []
seen = {}
_Function = self._getcustomclass("Function")
for i, x in enumerate(self.obj()):
name, call, args = self.getcallargs(x)
if not callable(call):
Expand All @@ -764,11 +768,7 @@ def collect(self):
"%r generated tests with non-unique name %r" % (self, name)
)
seen[name] = True
with warnings.catch_warnings():
# ignore our own deprecation warning
function_class = self.Function
values.append(function_class(name, self, args=args, callobj=call))
self.warn(deprecated.YIELD_TESTS)
values.append(_Function(name, self, args=args, callobj=call))
return values

def getcallargs(self, obj):
Expand Down
13 changes: 13 additions & 0 deletions testing/test_assertrewrite.py
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,19 @@ def test_multmat_operator():
)
testdir.runpytest().assert_outcomes(passed=1)

@pytest.mark.skipif("sys.version_info < (3,5)")
def test_starred_with_side_effect(self, testdir):
"""See #4412"""
testdir.makepyfile(
"""\
def test():
f = lambda x: x
x = iter([1, 2, 3])
assert 2 * next(x) == f(*[next(x)])
"""
)
testdir.runpytest().assert_outcomes(passed=1)

def test_call(self):
def g(a=42, *args, **kwargs):
return False
Expand Down
22 changes: 22 additions & 0 deletions testing/test_collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -1086,6 +1086,28 @@ def test_1():
result.stdout.fnmatch_lines(["collected 1 item"])


def test_collect_pyargs_with_testpaths(testdir, monkeypatch):
testmod = testdir.mkdir("testmod")
# NOTE: __init__.py is not collected since it does not match python_files.
testmod.ensure("__init__.py").write("def test_func(): pass")
testmod.ensure("test_file.py").write("def test_func(): pass")

root = testdir.mkdir("root")
root.ensure("pytest.ini").write(
textwrap.dedent(
"""
[pytest]
addopts = --pyargs
testpaths = testmod
"""
)
)
monkeypatch.setenv("PYTHONPATH", str(testdir.tmpdir))
with root.as_cwd():
result = testdir.runpytest_subprocess()
result.stdout.fnmatch_lines(["*1 passed in*"])


@pytest.mark.skipif(
not hasattr(py.path.local, "mksymlinkto"),
reason="symlink not available on this platform",
Expand Down
4 changes: 3 additions & 1 deletion testing/test_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import pytest
from _pytest.main import EXIT_NOTESTSCOLLECTED
from _pytest.warnings import SHOW_PYTEST_WARNINGS_ARG


class SessionTests(object):
Expand Down Expand Up @@ -77,7 +78,8 @@ def test_generator_yields_None(self, testdir):
"""
def test_1():
yield None
"""
""",
SHOW_PYTEST_WARNINGS_ARG,
)
failures = reprec.getfailedcollections()
out = failures[0].longrepr.reprcrash.message
Expand Down
20 changes: 7 additions & 13 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,8 @@ setenv =
coverage: _PYTEST_TOX_EXTRA_DEP=coverage-enable-subprocess
coverage: COVERAGE_FILE={toxinidir}/.coverage
coverage: COVERAGE_PROCESS_START={toxinidir}/.coveragerc
extras = testing
deps =
hypothesis>=3.56
nose
{py27,pypy}: mock
requests
{env:_PYTEST_TOX_EXTRA_DEP:}

[testenv:py27-subprocess]
Expand All @@ -51,22 +48,18 @@ deps = pre-commit>=1.11.0
commands = pre-commit run --all-files --show-diff-on-failure

[testenv:py27-xdist]
extras = testing
deps =
pytest-xdist>=1.13
{py27,pypy}: mock
nose
hypothesis>=3.56
{env:_PYTEST_TOX_EXTRA_DEP:}
commands =
{env:_PYTEST_TOX_COVERAGE_RUN:} pytest -n auto {posargs}

[testenv:py37-xdist]
# NOTE: copied from above due to https://github.com/tox-dev/tox/issues/706.
extras = testing
deps =
pytest-xdist>=1.13
{py27,pypy}: mock
nose
hypothesis>=3.56
{env:_PYTEST_TOX_EXTRA_DEP:}
commands = {[testenv:py27-xdist]commands}

Expand All @@ -76,18 +69,17 @@ deps =
pexpect
{env:_PYTEST_TOX_EXTRA_DEP:}
commands =
{env:_PYTEST_TOX_COVERAGE_RUN:} pytest testing/test_pdb.py testing/test_terminal.py testing/test_unittest.py {posargs}
{env:_PYTEST_TOX_COVERAGE_RUN:} pytest {posargs:testing/test_pdb.py testing/test_terminal.py testing/test_unittest.py}

[testenv:py37-pexpect]
platform = {[testenv:py27-pexpect]platform}
deps = {[testenv:py27-pexpect]deps}
commands = {[testenv:py27-pexpect]commands}

[testenv:py27-nobyte]
extras = testing
deps =
pytest-xdist>=1.13
hypothesis>=3.56
py27: mock
{env:_PYTEST_TOX_EXTRA_DEP:}
distribute = true
setenv =
Expand Down Expand Up @@ -219,6 +211,8 @@ filterwarnings =
ignore:.*inspect.getargspec.*deprecated, use inspect.signature.*:DeprecationWarning
# pytest's own futurewarnings
ignore::pytest.PytestExperimentalApiWarning
# Do not cause SyntaxError for invalid escape sequences in py37.
default:invalid escape sequence:DeprecationWarning
pytester_example_dir = testing/example_scripts
[flake8]
max-line-length = 120
Expand Down