Skip to content

Commit

Permalink
Implement hack to issue warnings during config
Browse files Browse the repository at this point in the history
Once we can capture warnings during the config stage, we can
then get rid of this function

Related to #2891
  • Loading branch information
nicoddemus committed Sep 4, 2018
1 parent 60499d2 commit 0fffa6b
Show file tree
Hide file tree
Showing 7 changed files with 64 additions and 42 deletions.
2 changes: 2 additions & 0 deletions doc/en/reference.rst
Expand Up @@ -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:
Expand Down
10 changes: 6 additions & 4 deletions src/_pytest/config/__init__.py
Expand Up @@ -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):
Expand All @@ -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:
Expand All @@ -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
)
Expand Down Expand Up @@ -696,6 +697,7 @@ def _initini(self, args):
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
Expand Down
39 changes: 20 additions & 19 deletions src/_pytest/config/findpaths.py
Expand Up @@ -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

Expand All @@ -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 (
Expand Down Expand Up @@ -99,7 +98,7 @@ def get_dir_from_path(path):
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)
Expand All @@ -109,28 +108,30 @@ 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:
inicfg = 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] == "/"
Expand Down
16 changes: 14 additions & 2 deletions src/_pytest/hookspec.py
Expand Up @@ -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).
Expand All @@ -538,14 +548,16 @@ 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
the same attributes as the parameters of :py:func:`warnings.showwarning`.
: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.
Expand Down
4 changes: 2 additions & 2 deletions src/_pytest/resultlog.py
Expand Up @@ -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):
Expand Down
17 changes: 17 additions & 0 deletions src/_pytest/warnings.py
Expand Up @@ -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)
)
18 changes: 3 additions & 15 deletions testing/deprecated_test.py
Expand Up @@ -54,9 +54,6 @@ def test_funcarg_prefix(value):


@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",
Expand All @@ -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",
Expand All @@ -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 = (
Expand All @@ -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*"])
Expand Down

0 comments on commit 0fffa6b

Please sign in to comment.