Implement hack to issue warnings during config

Once we can capture warnings during the config stage, we can
then get rid of this function

Related to #2891
This commit is contained in:
Bruno Oliveira 2018-09-03 19:27:46 -03:00
parent 60499d221e
commit 0fffa6ba2f
7 changed files with 64 additions and 42 deletions

View File

@ -611,6 +611,8 @@ Session related reporting hooks:
.. autofunction:: pytest_terminal_summary
.. autofunction:: pytest_fixture_setup
.. autofunction:: pytest_fixture_post_finalizer
.. autofunction:: pytest_logwarning
.. autofunction:: pytest_warning_captured
And here is the central hook for reporting about
test execution:

View File

@ -154,7 +154,7 @@ def get_plugin_manager():
def _prepareconfig(args=None, plugins=None):
warning = None
warning_msg = None
if args is None:
args = sys.argv[1:]
elif isinstance(args, py.path.local):
@ -165,7 +165,7 @@ def _prepareconfig(args=None, plugins=None):
args = shlex.split(args, posix=sys.platform != "win32")
from _pytest import deprecated
warning = deprecated.MAIN_STR_ARGS
warning_msg = deprecated.MAIN_STR_ARGS
config = get_config()
pluginmanager = config.pluginmanager
try:
@ -175,10 +175,11 @@ def _prepareconfig(args=None, plugins=None):
pluginmanager.consider_pluginarg(plugin)
else:
pluginmanager.register(plugin)
if warning:
if warning_msg:
from _pytest.warning_types import PytestWarning
from _pytest.warnings import _issue_config_warning
warnings.warn(warning, PytestWarning)
_issue_config_warning(PytestWarning(warning_msg), config=config)
return pluginmanager.hook.pytest_cmdline_parse(
pluginmanager=pluginmanager, args=args
)
@ -696,6 +697,7 @@ class Config(object):
ns.inifilename,
ns.file_or_dir + unknown_args,
rootdir_cmd_arg=ns.rootdir or None,
config=self,
)
self.rootdir, self.inifile, self.inicfg = r
self._parser.extra_info["rootdir"] = self.rootdir

View File

@ -10,15 +10,12 @@ def exists(path, ignore=EnvironmentError):
return False
def getcfg(args):
def getcfg(args, config=None):
"""
Search the list of arguments for a valid ini-file for pytest,
and return a tuple of (rootdir, inifile, cfg-dict).
note: warnfunc is an optional function used to warn
about ini-files that use deprecated features.
This parameter should be removed when pytest
adopts standard deprecation warnings (#1804).
note: config is optional and used only to issue warnings explicitly (#2891).
"""
from _pytest.deprecated import CFG_PYTEST_SECTION
@ -34,13 +31,15 @@ def getcfg(args):
if exists(p):
iniconfig = py.iniconfig.IniConfig(p)
if "pytest" in iniconfig.sections:
if inibasename == "setup.cfg":
import warnings
if inibasename == "setup.cfg" and config is not None:
from _pytest.warnings import _issue_config_warning
from _pytest.warning_types import RemovedInPytest4Warning
warnings.warn(
CFG_PYTEST_SECTION.format(filename=inibasename),
RemovedInPytest4Warning,
_issue_config_warning(
RemovedInPytest4Warning(
CFG_PYTEST_SECTION.format(filename=inibasename)
),
config=config,
)
return base, p, iniconfig["pytest"]
if (
@ -99,7 +98,7 @@ def get_dirs_from_args(args):
return [get_dir_from_path(path) for path in possible_paths if path.exists()]
def determine_setup(inifile, args, rootdir_cmd_arg=None):
def determine_setup(inifile, args, rootdir_cmd_arg=None, config=None):
dirs = get_dirs_from_args(args)
if inifile:
iniconfig = py.iniconfig.IniConfig(inifile)
@ -109,14 +108,16 @@ def determine_setup(inifile, args, rootdir_cmd_arg=None):
for section in sections:
try:
inicfg = iniconfig[section]
if is_cfg_file and section == "pytest":
from _pytest.warning_types import RemovedInPytest4Warning
if is_cfg_file and section == "pytest" and config is not None:
from _pytest.deprecated import CFG_PYTEST_SECTION
import warnings
from _pytest.warning_types import RemovedInPytest4Warning
from _pytest.warnings import _issue_config_warning
warnings.warn(
CFG_PYTEST_SECTION.format(filename=str(inifile)),
RemovedInPytest4Warning,
_issue_config_warning(
RemovedInPytest4Warning(
CFG_PYTEST_SECTION.format(filename=str(inifile))
),
config,
)
break
except KeyError:
@ -124,13 +125,13 @@ def determine_setup(inifile, args, rootdir_cmd_arg=None):
rootdir = get_common_ancestor(dirs)
else:
ancestor = get_common_ancestor(dirs)
rootdir, inifile, inicfg = getcfg([ancestor])
rootdir, inifile, inicfg = getcfg([ancestor], config=config)
if rootdir is None:
for rootdir in ancestor.parts(reverse=True):
if rootdir.join("setup.py").exists():
break
else:
rootdir, inifile, inicfg = getcfg(dirs)
rootdir, inifile, inicfg = getcfg(dirs, config=config)
if rootdir is None:
rootdir = get_common_ancestor([py.path.local(), ancestor])
is_fs_root = os.path.splitdrive(str(rootdir))[1] == "/"

View File

@ -526,7 +526,17 @@ def pytest_terminal_summary(terminalreporter, exitstatus):
@hookspec(historic=True)
def pytest_logwarning(message, code, nodeid, fslocation):
""" process a warning specified by a message, a code string,
"""
.. deprecated:: 3.8
This hook is will stop working in a future release.
pytest no longer triggers this hook, but the
terminal writer still implements it to display warnings issued by
:meth:`_pytest.config.Config.warn` and :meth:`_pytest.nodes.Node.warn`. Calling those functions will be
an error in future releases.
process a warning specified by a message, a code string,
a nodeid and fslocation (both of which may be None
if the warning is not tied to a particular node/location).
@ -538,7 +548,7 @@ def pytest_logwarning(message, code, nodeid, fslocation):
@hookspec(historic=True)
def pytest_warning_captured(warning_message, when, item):
"""
Process a warning captured by the internal pytest plugin.
Process a warning captured by the internal pytest warnings plugin.
:param warnings.WarningMessage warning_message:
The captured warning. This is the same object produced by :py:func:`warnings.catch_warnings`, and contains
@ -546,6 +556,8 @@ def pytest_warning_captured(warning_message, when, item):
:param str when:
Indicates when the warning was captured. Possible values:
* ``"config"``: during pytest configuration/initialization stage.
* ``"collect"``: during test collection.
* ``"runtest"``: during test execution.

View File

@ -31,10 +31,10 @@ def pytest_configure(config):
config.pluginmanager.register(config._resultlog)
from _pytest.deprecated import RESULT_LOG
import warnings
from _pytest.warning_types import RemovedInPytest4Warning
from _pytest.warnings import _issue_config_warning
warnings.warn(RESULT_LOG, RemovedInPytest4Warning)
_issue_config_warning(RemovedInPytest4Warning(RESULT_LOG), config)
def pytest_unconfigure(config):

View File

@ -146,3 +146,20 @@ def pytest_terminal_summary(terminalreporter):
config = terminalreporter.config
with catch_warnings_for_item(config=config, ihook=config.hook, item=None):
yield
def _issue_config_warning(warning, config):
"""
This function should be used instead of calling ``warnings.warn`` directly when we are in the "configure" stage:
at this point the actual options might not have been set, so we manually trigger the pytest_warning_captured
hook so we can display this warnings in the terminal. This is a hack until we can sort out #2891.
:param warning: the warning instance.
:param config:
"""
with warnings.catch_warnings(record=True) as records:
warnings.simplefilter("always", type(warning))
warnings.warn(warning, stacklevel=2)
config.hook.pytest_warning_captured.call_historic(
kwargs=dict(warning_message=records[0], when="config", item=None)
)

View File

@ -54,9 +54,6 @@ def test_funcarg_prefix_deprecation(testdir):
@pytest.mark.filterwarnings("default")
@pytest.mark.xfail(
reason="#2891 need to handle warnings during pre-config", strict=True
)
def test_pytest_setup_cfg_deprecated(testdir):
testdir.makefile(
".cfg",
@ -72,9 +69,6 @@ def test_pytest_setup_cfg_deprecated(testdir):
@pytest.mark.filterwarnings("default")
@pytest.mark.xfail(
reason="#2891 need to handle warnings during pre-config", strict=True
)
def test_pytest_custom_cfg_deprecated(testdir):
testdir.makefile(
".cfg",
@ -89,18 +83,15 @@ def test_pytest_custom_cfg_deprecated(testdir):
)
@pytest.mark.xfail(
reason="#2891 need to handle warnings during pre-config", strict=True
)
def test_str_args_deprecated(tmpdir, testdir):
def test_str_args_deprecated(tmpdir):
"""Deprecate passing strings to pytest.main(). Scheduled for removal in pytest-4.0."""
from _pytest.main import EXIT_NOTESTSCOLLECTED
warnings = []
class Collect(object):
def pytest_logwarning(self, message):
warnings.append(message)
def pytest_warning_captured(self, warning_message):
warnings.append(str(warning_message.message))
ret = pytest.main("%s -x" % tmpdir, plugins=[Collect()])
msg = (
@ -116,9 +107,6 @@ def test_getfuncargvalue_is_deprecated(request):
@pytest.mark.filterwarnings("default")
@pytest.mark.xfail(
reason="#2891 need to handle warnings during pre-config", strict=True
)
def test_resultlog_is_deprecated(testdir):
result = testdir.runpytest("--help")
result.stdout.fnmatch_lines(["*DEPRECATED path for machine-readable result log*"])