Merge pull request #6640 from bluetech/master-to-features

Merge master to features - one last time
This commit is contained in:
Ran Benita 2020-01-31 12:11:14 +02:00 committed by GitHub
commit 4038d6c773
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 111 additions and 99 deletions

View File

@ -849,7 +849,7 @@ Running this test will *skip* the invocation of ``data_set`` with value ``2``:
Modularity: using fixtures from a fixture function
----------------------------------------------------------
You can not only use fixtures in test functions but fixture functions
In addition to using fixtures in test functions, fixture functions
can use other fixtures themselves. This contributes to a modular design
of your fixtures and allows re-use of framework-specific fixtures across
many projects. As a simple example, we can extend the previous example

View File

@ -61,7 +61,9 @@ def parse_changelog(tag_name):
def convert_rst_to_md(text):
return pypandoc.convert_text(text, "md", format="rst", extra_args=["--wrap=none"])
return pypandoc.convert_text(
text, "md", format="rst", extra_args=["--wrap=preserve"]
)
def main(argv):

View File

@ -14,5 +14,5 @@ python -m coverage combine
python -m coverage xml
python -m coverage report -m
# Set --connect-timeout to work around https://github.com/curl/curl/issues/4461
curl -S -L --connect-timeout 5 --retry 6 --retry-connrefused -s https://codecov.io/bash -o codecov-upload.sh
curl -S -L --connect-timeout 5 --retry 6 -s https://codecov.io/bash -o codecov-upload.sh
bash codecov-upload.sh -Z -X fix -f coverage.xml "$@"

View File

@ -7,6 +7,10 @@ from typing import Optional
from _pytest.assertion import rewrite
from _pytest.assertion import truncate
from _pytest.assertion import util
from _pytest.compat import TYPE_CHECKING
if TYPE_CHECKING:
from _pytest.main import Session
def pytest_addoption(parser):
@ -91,7 +95,7 @@ def install_importhook(config):
return hook
def pytest_collection(session):
def pytest_collection(session: "Session") -> None:
# this hook is only called when test modules are collected
# so for example not in the master process of pytest-xdist
# (which does not collect test modules)

View File

@ -144,12 +144,12 @@ def getfuncargnames(
the case of cls, the function is a static method.
The name parameter should be the original name in which the function was collected.
@RonnyPfannschmidt: This function should be refactored when we
revisit fixtures. The fixture mechanism should ask the node for
the fixture names, and not try to obtain directly from the
function object well after collection has occurred.
"""
# TODO(RonnyPfannschmidt): This function should be refactored when we
# revisit fixtures. The fixture mechanism should ask the node for
# the fixture names, and not try to obtain directly from the
# function object well after collection has occurred.
# The parameters attribute of a Signature object contains an
# ordered mapping of parameter names to Parameter instances. This
# creates a tuple of the names of the parameters that don't have

View File

@ -31,6 +31,7 @@ from _pytest.compat import safe_getattr
from _pytest.compat import TYPE_CHECKING
from _pytest.deprecated import FIXTURE_POSITIONAL_ARGUMENTS
from _pytest.deprecated import FUNCARGNAMES
from _pytest.mark import ParameterSet
from _pytest.outcomes import fail
from _pytest.outcomes import TEST_OUTCOME
@ -1262,8 +1263,6 @@ class FixtureManager:
This things are done later as well when dealing with parametrization
so this could be improved
"""
from _pytest.mark import ParameterSet
parametrize_argnames = []
for marker in node.iter_markers(name="parametrize"):
if not marker.kwargs.get("indirect", False):

View File

@ -1,6 +1,14 @@
""" hook specifications for pytest plugins, invoked from main.py and builtin plugins. """
from typing import Any
from typing import Optional
from pluggy import HookspecMarker
from _pytest.compat import TYPE_CHECKING
if TYPE_CHECKING:
from _pytest.main import Session
hookspec = HookspecMarker("pytest")
@ -158,7 +166,7 @@ def pytest_load_initial_conftests(early_config, parser, args):
@hookspec(firstresult=True)
def pytest_collection(session):
def pytest_collection(session: "Session") -> Optional[Any]:
"""Perform the collection protocol for the given session.
Stops at first non-None result, see :ref:`firstresult`.

View File

@ -5,6 +5,7 @@ from contextlib import contextmanager
from io import StringIO
from typing import AbstractSet
from typing import Dict
from typing import Generator
from typing import List
from typing import Mapping
@ -597,7 +598,7 @@ class LoggingPlugin:
) is not None or self._config.getini("log_cli")
@pytest.hookimpl(hookwrapper=True, tryfirst=True)
def pytest_collection(self):
def pytest_collection(self) -> Generator[None, None, None]:
with self.live_logs_context():
if self.log_cli_handler:
self.log_cli_handler.set_when("collection")

View File

@ -8,8 +8,10 @@ import sys
from typing import Dict
from typing import FrozenSet
from typing import List
from typing import Optional
from typing import Sequence
from typing import Tuple
from typing import Union
import attr
import py
@ -253,7 +255,7 @@ def pytest_cmdline_main(config):
return wrap_session(config, _main)
def _main(config, session):
def _main(config: Config, session: "Session") -> Optional[Union[int, ExitCode]]:
""" default command line protocol for initialization, session,
running tests and reporting. """
config.hook.pytest_collection(session=session)
@ -263,6 +265,7 @@ def _main(config, session):
return ExitCode.TESTS_FAILED
elif session.testscollected == 0:
return ExitCode.NO_TESTS_COLLECTED
return None
def pytest_collection(session):
@ -352,18 +355,6 @@ def pytest_collection_modifyitems(items, config):
items[:] = remaining
class FSHookProxy:
def __init__(self, fspath, pm, remove_mods):
self.fspath = fspath
self.pm = pm
self.remove_mods = remove_mods
def __getattr__(self, name):
x = self.pm.subset_hook_caller(name, remove_plugins=self.remove_mods)
self.__dict__[name] = x
return x
class NoMatch(Exception):
""" raised if matching cannot locate a matching names. """
@ -405,7 +396,6 @@ class Session(nodes.FSCollector):
self.shouldstop = False
self.shouldfail = False
self.trace = config.trace.root.get("collection")
self._norecursepatterns = config.getini("norecursedirs")
self.startdir = config.invocation_dir
self._initialpaths = frozenset() # type: FrozenSet[py.path.local]
@ -466,19 +456,8 @@ class Session(nodes.FSCollector):
def isinitpath(self, path):
return path in self._initialpaths
def gethookproxy(self, fspath):
# check if we have the common case of running
# hooks with all conftest.py files
pm = self.config.pluginmanager
my_conftestmodules = pm._getconftestmodules(fspath)
remove_mods = pm._conftest_plugins.difference(my_conftestmodules)
if remove_mods:
# one or more conftests are not in use at this fspath
proxy = FSHookProxy(fspath, pm, remove_mods)
else:
# all plugins are active for this fspath
proxy = self.config.hook
return proxy
def gethookproxy(self, fspath: py.path.local):
return super()._gethookproxy(fspath)
def perform_collect(self, args=None, genitems=True):
hook = self.config.hook
@ -643,19 +622,6 @@ class Session(nodes.FSCollector):
return ihook.pytest_collect_file(path=path, parent=self)
def _recurse(self, dirpath):
if dirpath.basename == "__pycache__":
return False
ihook = self.gethookproxy(dirpath.dirpath())
if ihook.pytest_ignore_collect(path=dirpath, config=self.config):
return False
for pat in self._norecursepatterns:
if dirpath.check(fnmatch=pat):
return False
ihook = self.gethookproxy(dirpath)
ihook.pytest_collect_directory(path=dirpath, parent=self)
return True
@staticmethod
def _visit_filter(f):
return f.check(file=1)

View File

@ -19,6 +19,7 @@ from _pytest.compat import cached_property
from _pytest.compat import getfslineno
from _pytest.compat import TYPE_CHECKING
from _pytest.config import Config
from _pytest.config import PytestPluginManager
from _pytest.deprecated import NODE_USE_FROM_PARENT
from _pytest.fixtures import FixtureDef
from _pytest.fixtures import FixtureLookupError
@ -423,6 +424,20 @@ def _check_initialpaths_for_relpath(session, fspath):
return fspath.relto(initial_path)
class FSHookProxy:
def __init__(
self, fspath: py.path.local, pm: PytestPluginManager, remove_mods
) -> None:
self.fspath = fspath
self.pm = pm
self.remove_mods = remove_mods
def __getattr__(self, name: str):
x = self.pm.subset_hook_caller(name, remove_plugins=self.remove_mods)
self.__dict__[name] = x
return x
class FSCollector(Collector):
def __init__(
self, fspath: py.path.local, parent=None, config=None, session=None, nodeid=None
@ -447,6 +462,8 @@ class FSCollector(Collector):
super().__init__(name, parent, config, session, nodeid=nodeid, fspath=fspath)
self._norecursepatterns = self.config.getini("norecursedirs")
@classmethod
def from_parent(cls, parent, *, fspath):
"""
@ -454,6 +471,33 @@ class FSCollector(Collector):
"""
return super().from_parent(parent=parent, fspath=fspath)
def _gethookproxy(self, fspath: py.path.local):
# check if we have the common case of running
# hooks with all conftest.py files
pm = self.config.pluginmanager
my_conftestmodules = pm._getconftestmodules(fspath)
remove_mods = pm._conftest_plugins.difference(my_conftestmodules)
if remove_mods:
# one or more conftests are not in use at this fspath
proxy = FSHookProxy(fspath, pm, remove_mods)
else:
# all plugins are active for this fspath
proxy = self.config.hook
return proxy
def _recurse(self, dirpath: py.path.local) -> bool:
if dirpath.basename == "__pycache__":
return False
ihook = self._gethookproxy(dirpath.dirpath())
if ihook.pytest_ignore_collect(path=dirpath, config=self.config):
return False
for pat in self._norecursepatterns:
if dirpath.check(fnmatch=pat):
return False
ihook = self._gethookproxy(dirpath)
ihook.pytest_collect_directory(path=dirpath, parent=self)
return True
class File(FSCollector):
""" base class for collecting tests from a file. """

View File

@ -36,7 +36,6 @@ from _pytest.compat import safe_isclass
from _pytest.compat import STRING_TYPES
from _pytest.config import hookimpl
from _pytest.deprecated import FUNCARGNAMES
from _pytest.main import FSHookProxy
from _pytest.mark import MARK_GEN
from _pytest.mark.structures import get_unpacked_marks
from _pytest.mark.structures import Mark
@ -149,27 +148,30 @@ def pytest_configure(config):
)
@hookimpl(trylast=True)
def pytest_pyfunc_call(pyfuncitem):
def async_warn():
msg = "async def functions are not natively supported and have been skipped.\n"
msg += "You need to install a suitable plugin for your async framework, for example:\n"
msg += " - pytest-asyncio\n"
msg += " - pytest-trio\n"
msg += " - pytest-tornasync"
warnings.warn(PytestUnhandledCoroutineWarning(msg.format(pyfuncitem.nodeid)))
skip(msg="async def function and no async plugin installed (see warnings)")
def async_warn(nodeid: str) -> None:
msg = "async def functions are not natively supported and have been skipped.\n"
msg += (
"You need to install a suitable plugin for your async framework, for example:\n"
)
msg += " - pytest-asyncio\n"
msg += " - pytest-trio\n"
msg += " - pytest-tornasync"
warnings.warn(PytestUnhandledCoroutineWarning(msg.format(nodeid)))
skip(msg="async def function and no async plugin installed (see warnings)")
@hookimpl(trylast=True)
def pytest_pyfunc_call(pyfuncitem: "Function"):
testfunction = pyfuncitem.obj
if iscoroutinefunction(testfunction) or (
sys.version_info >= (3, 6) and inspect.isasyncgenfunction(testfunction)
):
async_warn()
async_warn(pyfuncitem.nodeid)
funcargs = pyfuncitem.funcargs
testargs = {arg: funcargs[arg] for arg in pyfuncitem._fixtureinfo.argnames}
result = testfunction(**testargs)
if hasattr(result, "__await__") or hasattr(result, "__aiter__"):
async_warn()
async_warn(pyfuncitem.nodeid)
return True
@ -547,15 +549,23 @@ class Module(nodes.File, PyCollector):
class Package(Module):
def __init__(self, fspath, parent=None, config=None, session=None, nodeid=None):
def __init__(
self,
fspath: py.path.local,
parent: nodes.Collector,
# NOTE: following args are unused:
config=None,
session=None,
nodeid=None,
) -> None:
# NOTE: could be just the following, but kept as-is for compat.
# nodes.FSCollector.__init__(self, fspath, parent=parent)
session = parent.session
nodes.FSCollector.__init__(
self, fspath, parent=parent, config=config, session=session, nodeid=nodeid
)
self.name = fspath.dirname
self.trace = session.trace
self._norecursepatterns = session._norecursepatterns
self.fspath = fspath
def setup(self):
# not using fixtures to call setup_module here because autouse fixtures
@ -573,32 +583,8 @@ class Package(Module):
func = partial(_call_with_optional_argument, teardown_module, self.obj)
self.addfinalizer(func)
def _recurse(self, dirpath):
if dirpath.basename == "__pycache__":
return False
ihook = self.gethookproxy(dirpath.dirpath())
if ihook.pytest_ignore_collect(path=dirpath, config=self.config):
return
for pat in self._norecursepatterns:
if dirpath.check(fnmatch=pat):
return False
ihook = self.gethookproxy(dirpath)
ihook.pytest_collect_directory(path=dirpath, parent=self)
return True
def gethookproxy(self, fspath):
# check if we have the common case of running
# hooks with all conftest.py filesall conftest.py
pm = self.config.pluginmanager
my_conftestmodules = pm._getconftestmodules(fspath)
remove_mods = pm._conftest_plugins.difference(my_conftestmodules)
if remove_mods:
# one or more conftests are not in use at this fspath
proxy = FSHookProxy(fspath, pm, remove_mods)
else:
# all plugins are active for this fspath
proxy = self.config.hook
return proxy
def gethookproxy(self, fspath: py.path.local):
return super()._gethookproxy(fspath)
def _collectfile(self, path, handle_dupes=True):
assert (

View File

@ -521,7 +521,7 @@ class TerminalReporter:
# py < 1.6.0
return self._tw.chars_on_current_line
def pytest_collection(self):
def pytest_collection(self) -> None:
if self.isatty:
if self.config.option.verbose >= 0:
self.write("collecting ... ", bold=True)

View File

@ -1,8 +1,10 @@
import sys
import warnings
from contextlib import contextmanager
from typing import Generator
import pytest
from _pytest.main import Session
def _setoption(wmod, arg):
@ -117,7 +119,7 @@ def pytest_runtest_protocol(item):
@pytest.hookimpl(hookwrapper=True, tryfirst=True)
def pytest_collection(session):
def pytest_collection(session: Session) -> Generator[None, None, None]:
config = session.config
with catch_warnings_for_item(
config=config, ihook=config.hook, when="collect", item=None

View File

@ -147,7 +147,7 @@ commands = python scripts/publish-gh-release-notes.py {posargs}
[pytest]
minversion = 2.0
addopts = -ra -p pytester --strict-markers
addopts = -rfEX -p pytester --strict-markers
rsyncdirs = tox.ini doc src testing
python_files = test_*.py *_test.py testing/*/*.py
python_classes = Test Acceptance