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 
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*"])