Merge pull request #4420 from blueyed/merge-master

Merge master into features
This commit is contained in:
Daniel Hahler 2018-11-19 14:53:01 +01:00 committed by GitHub
commit 6e85febf20
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 69 additions and 24 deletions

View File

@ -0,0 +1 @@
Rearrange warning handling for the yield test errors so the opt-out in 4.0.x correctly works.

View File

@ -0,0 +1 @@
Fix collection of testpaths with ``--pyargs``.

View File

@ -0,0 +1 @@
Fix assertion rewriting involving ``Starred`` + side-effects.

View File

@ -29,6 +29,16 @@ def main():
use_scm_version={"write_to": "src/_pytest/_version.py"}, use_scm_version={"write_to": "src/_pytest/_version.py"},
setup_requires=["setuptools-scm", "setuptools>=40.0"], setup_requires=["setuptools-scm", "setuptools>=40.0"],
package_dir={"": "src"}, package_dir={"": "src"},
# fmt: off
extras_require={
"testing": [
"hypothesis>=3.56",
"nose",
"requests",
"mock;python_version=='2.7'",
],
},
# fmt: on
install_requires=INSTALL_REQUIRES, install_requires=INSTALL_REQUIRES,
) )

View File

@ -946,7 +946,8 @@ class AssertionRewriter(ast.NodeVisitor):
def visit_Starred(self, starred): def visit_Starred(self, starred):
# From Python 3.5, a Starred node can appear in a function call # From Python 3.5, a Starred node can appear in a function call
res, expl = self.visit(starred.value) 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): def visit_Call_legacy(self, call):
""" """

View File

@ -852,10 +852,7 @@ class Config(object):
) )
if not args: if not args:
if self.invocation_dir == self.rootdir: if self.invocation_dir == self.rootdir:
args = [ args = self.getini("testpaths")
str(self.invocation_dir.join(x, abs=True))
for x in self.getini("testpaths")
]
if not args: if not args:
args = [str(self.invocation_dir)] args = [str(self.invocation_dir)]
self.args = args self.args = args

View File

@ -518,6 +518,9 @@ class Testdir(object):
def __repr__(self): def __repr__(self):
return "<Testdir %r>" % (self.tmpdir,) return "<Testdir %r>" % (self.tmpdir,)
def __str__(self):
return str(self.tmpdir)
def finalize(self): def finalize(self):
"""Clean up global state artifacts. """Clean up global state artifacts.

View File

@ -741,16 +741,20 @@ class FunctionMixin(PyobjMixin):
class Generator(FunctionMixin, PyCollector): class Generator(FunctionMixin, PyCollector):
def collect(self): def collect(self):
# test generators are seen as collectors but they also # test generators are seen as collectors but they also
# invoke setup/teardown on popular request # invoke setup/teardown on popular request
# (induced by the common "test_*" naming shared with normal tests) # (induced by the common "test_*" naming shared with normal tests)
from _pytest import deprecated from _pytest import deprecated
self.warn(deprecated.YIELD_TESTS)
self.session._setupstate.prepare(self) self.session._setupstate.prepare(self)
# see FunctionMixin.setup and test_setupstate_is_preserved_134 # see FunctionMixin.setup and test_setupstate_is_preserved_134
self._preservedparent = self.parent.obj self._preservedparent = self.parent.obj
values = [] values = []
seen = {} seen = {}
_Function = self._getcustomclass("Function")
for i, x in enumerate(self.obj()): for i, x in enumerate(self.obj()):
name, call, args = self.getcallargs(x) name, call, args = self.getcallargs(x)
if not callable(call): if not callable(call):
@ -764,11 +768,7 @@ class Generator(FunctionMixin, PyCollector):
"%r generated tests with non-unique name %r" % (self, name) "%r generated tests with non-unique name %r" % (self, name)
) )
seen[name] = True seen[name] = True
with warnings.catch_warnings(): values.append(_Function(name, self, args=args, callobj=call))
# ignore our own deprecation warning
function_class = self.Function
values.append(function_class(name, self, args=args, callobj=call))
self.warn(deprecated.YIELD_TESTS)
return values return values
def getcallargs(self, obj): def getcallargs(self, obj):

View File

@ -413,6 +413,19 @@ class TestAssertionRewrite(object):
) )
testdir.runpytest().assert_outcomes(passed=1) 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 test_call(self):
def g(a=42, *args, **kwargs): def g(a=42, *args, **kwargs):
return False return False

View File

@ -1086,6 +1086,28 @@ def test_collect_with_chdir_during_import(testdir):
result.stdout.fnmatch_lines(["collected 1 item"]) 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( @pytest.mark.skipif(
not hasattr(py.path.local, "mksymlinkto"), not hasattr(py.path.local, "mksymlinkto"),
reason="symlink not available on this platform", reason="symlink not available on this platform",

View File

@ -4,6 +4,7 @@ from __future__ import print_function
import pytest import pytest
from _pytest.main import EXIT_NOTESTSCOLLECTED from _pytest.main import EXIT_NOTESTSCOLLECTED
from _pytest.warnings import SHOW_PYTEST_WARNINGS_ARG
class SessionTests(object): class SessionTests(object):
@ -77,7 +78,8 @@ class SessionTests(object):
""" """
def test_1(): def test_1():
yield None yield None
""" """,
SHOW_PYTEST_WARNINGS_ARG,
) )
failures = reprec.getfailedcollections() failures = reprec.getfailedcollections()
out = failures[0].longrepr.reprcrash.message out = failures[0].longrepr.reprcrash.message

20
tox.ini
View File

@ -28,11 +28,8 @@ setenv =
coverage: _PYTEST_TOX_EXTRA_DEP=coverage-enable-subprocess coverage: _PYTEST_TOX_EXTRA_DEP=coverage-enable-subprocess
coverage: COVERAGE_FILE={toxinidir}/.coverage coverage: COVERAGE_FILE={toxinidir}/.coverage
coverage: COVERAGE_PROCESS_START={toxinidir}/.coveragerc coverage: COVERAGE_PROCESS_START={toxinidir}/.coveragerc
extras = testing
deps = deps =
hypothesis>=3.56
nose
{py27,pypy}: mock
requests
{env:_PYTEST_TOX_EXTRA_DEP:} {env:_PYTEST_TOX_EXTRA_DEP:}
[testenv:py27-subprocess] [testenv:py27-subprocess]
@ -51,22 +48,18 @@ deps = pre-commit>=1.11.0
commands = pre-commit run --all-files --show-diff-on-failure commands = pre-commit run --all-files --show-diff-on-failure
[testenv:py27-xdist] [testenv:py27-xdist]
extras = testing
deps = deps =
pytest-xdist>=1.13 pytest-xdist>=1.13
{py27,pypy}: mock
nose
hypothesis>=3.56
{env:_PYTEST_TOX_EXTRA_DEP:} {env:_PYTEST_TOX_EXTRA_DEP:}
commands = commands =
{env:_PYTEST_TOX_COVERAGE_RUN:} pytest -n auto {posargs} {env:_PYTEST_TOX_COVERAGE_RUN:} pytest -n auto {posargs}
[testenv:py37-xdist] [testenv:py37-xdist]
# NOTE: copied from above due to https://github.com/tox-dev/tox/issues/706. # NOTE: copied from above due to https://github.com/tox-dev/tox/issues/706.
extras = testing
deps = deps =
pytest-xdist>=1.13 pytest-xdist>=1.13
{py27,pypy}: mock
nose
hypothesis>=3.56
{env:_PYTEST_TOX_EXTRA_DEP:} {env:_PYTEST_TOX_EXTRA_DEP:}
commands = {[testenv:py27-xdist]commands} commands = {[testenv:py27-xdist]commands}
@ -76,7 +69,7 @@ deps =
pexpect pexpect
{env:_PYTEST_TOX_EXTRA_DEP:} {env:_PYTEST_TOX_EXTRA_DEP:}
commands = 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] [testenv:py37-pexpect]
platform = {[testenv:py27-pexpect]platform} platform = {[testenv:py27-pexpect]platform}
@ -84,10 +77,9 @@ deps = {[testenv:py27-pexpect]deps}
commands = {[testenv:py27-pexpect]commands} commands = {[testenv:py27-pexpect]commands}
[testenv:py27-nobyte] [testenv:py27-nobyte]
extras = testing
deps = deps =
pytest-xdist>=1.13 pytest-xdist>=1.13
hypothesis>=3.56
py27: mock
{env:_PYTEST_TOX_EXTRA_DEP:} {env:_PYTEST_TOX_EXTRA_DEP:}
distribute = true distribute = true
setenv = setenv =
@ -219,6 +211,8 @@ filterwarnings =
ignore:.*inspect.getargspec.*deprecated, use inspect.signature.*:DeprecationWarning ignore:.*inspect.getargspec.*deprecated, use inspect.signature.*:DeprecationWarning
# pytest's own futurewarnings # pytest's own futurewarnings
ignore::pytest.PytestExperimentalApiWarning ignore::pytest.PytestExperimentalApiWarning
# Do not cause SyntaxError for invalid escape sequences in py37.
default:invalid escape sequence:DeprecationWarning
pytester_example_dir = testing/example_scripts pytester_example_dir = testing/example_scripts
[flake8] [flake8]
max-line-length = 120 max-line-length = 120