Merge pull request #6640 from bluetech/master-to-features
Merge master to features - one last time
This commit is contained in:
commit
4038d6c773
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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 "$@"
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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`.
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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. """
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
2
tox.ini
2
tox.ini
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue