Skip to content

Commit 0fffa6b

Browse files
committed
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
1 parent 60499d2 commit 0fffa6b

File tree

7 files changed

+64
-42
lines changed

7 files changed

+64
-42
lines changed

doc/en/reference.rst

+2
Original file line numberDiff line numberDiff line change
@@ -611,6 +611,8 @@ Session related reporting hooks:
611611
.. autofunction:: pytest_terminal_summary
612612
.. autofunction:: pytest_fixture_setup
613613
.. autofunction:: pytest_fixture_post_finalizer
614+
.. autofunction:: pytest_logwarning
615+
.. autofunction:: pytest_warning_captured
614616

615617
And here is the central hook for reporting about
616618
test execution:

src/_pytest/config/__init__.py

+6-4
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ def get_plugin_manager():
154154

155155

156156
def _prepareconfig(args=None, plugins=None):
157-
warning = None
157+
warning_msg = None
158158
if args is None:
159159
args = sys.argv[1:]
160160
elif isinstance(args, py.path.local):
@@ -165,7 +165,7 @@ def _prepareconfig(args=None, plugins=None):
165165
args = shlex.split(args, posix=sys.platform != "win32")
166166
from _pytest import deprecated
167167

168-
warning = deprecated.MAIN_STR_ARGS
168+
warning_msg = deprecated.MAIN_STR_ARGS
169169
config = get_config()
170170
pluginmanager = config.pluginmanager
171171
try:
@@ -175,10 +175,11 @@ def _prepareconfig(args=None, plugins=None):
175175
pluginmanager.consider_pluginarg(plugin)
176176
else:
177177
pluginmanager.register(plugin)
178-
if warning:
178+
if warning_msg:
179179
from _pytest.warning_types import PytestWarning
180+
from _pytest.warnings import _issue_config_warning
180181

181-
warnings.warn(warning, PytestWarning)
182+
_issue_config_warning(PytestWarning(warning_msg), config=config)
182183
return pluginmanager.hook.pytest_cmdline_parse(
183184
pluginmanager=pluginmanager, args=args
184185
)
@@ -696,6 +697,7 @@ def _initini(self, args):
696697
ns.inifilename,
697698
ns.file_or_dir + unknown_args,
698699
rootdir_cmd_arg=ns.rootdir or None,
700+
config=self,
699701
)
700702
self.rootdir, self.inifile, self.inicfg = r
701703
self._parser.extra_info["rootdir"] = self.rootdir

src/_pytest/config/findpaths.py

+20-19
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,12 @@ def exists(path, ignore=EnvironmentError):
1010
return False
1111

1212

13-
def getcfg(args):
13+
def getcfg(args, config=None):
1414
"""
1515
Search the list of arguments for a valid ini-file for pytest,
1616
and return a tuple of (rootdir, inifile, cfg-dict).
1717
18-
note: warnfunc is an optional function used to warn
19-
about ini-files that use deprecated features.
20-
This parameter should be removed when pytest
21-
adopts standard deprecation warnings (#1804).
18+
note: config is optional and used only to issue warnings explicitly (#2891).
2219
"""
2320
from _pytest.deprecated import CFG_PYTEST_SECTION
2421

@@ -34,13 +31,15 @@ def getcfg(args):
3431
if exists(p):
3532
iniconfig = py.iniconfig.IniConfig(p)
3633
if "pytest" in iniconfig.sections:
37-
if inibasename == "setup.cfg":
38-
import warnings
34+
if inibasename == "setup.cfg" and config is not None:
35+
from _pytest.warnings import _issue_config_warning
3936
from _pytest.warning_types import RemovedInPytest4Warning
4037

41-
warnings.warn(
42-
CFG_PYTEST_SECTION.format(filename=inibasename),
43-
RemovedInPytest4Warning,
38+
_issue_config_warning(
39+
RemovedInPytest4Warning(
40+
CFG_PYTEST_SECTION.format(filename=inibasename)
41+
),
42+
config=config,
4443
)
4544
return base, p, iniconfig["pytest"]
4645
if (
@@ -99,7 +98,7 @@ def get_dir_from_path(path):
9998
return [get_dir_from_path(path) for path in possible_paths if path.exists()]
10099

101100

102-
def determine_setup(inifile, args, rootdir_cmd_arg=None):
101+
def determine_setup(inifile, args, rootdir_cmd_arg=None, config=None):
103102
dirs = get_dirs_from_args(args)
104103
if inifile:
105104
iniconfig = py.iniconfig.IniConfig(inifile)
@@ -109,28 +108,30 @@ def determine_setup(inifile, args, rootdir_cmd_arg=None):
109108
for section in sections:
110109
try:
111110
inicfg = iniconfig[section]
112-
if is_cfg_file and section == "pytest":
113-
from _pytest.warning_types import RemovedInPytest4Warning
111+
if is_cfg_file and section == "pytest" and config is not None:
114112
from _pytest.deprecated import CFG_PYTEST_SECTION
115-
import warnings
113+
from _pytest.warning_types import RemovedInPytest4Warning
114+
from _pytest.warnings import _issue_config_warning
116115

117-
warnings.warn(
118-
CFG_PYTEST_SECTION.format(filename=str(inifile)),
119-
RemovedInPytest4Warning,
116+
_issue_config_warning(
117+
RemovedInPytest4Warning(
118+
CFG_PYTEST_SECTION.format(filename=str(inifile))
119+
),
120+
config,
120121
)
121122
break
122123
except KeyError:
123124
inicfg = None
124125
rootdir = get_common_ancestor(dirs)
125126
else:
126127
ancestor = get_common_ancestor(dirs)
127-
rootdir, inifile, inicfg = getcfg([ancestor])
128+
rootdir, inifile, inicfg = getcfg([ancestor], config=config)
128129
if rootdir is None:
129130
for rootdir in ancestor.parts(reverse=True):
130131
if rootdir.join("setup.py").exists():
131132
break
132133
else:
133-
rootdir, inifile, inicfg = getcfg(dirs)
134+
rootdir, inifile, inicfg = getcfg(dirs, config=config)
134135
if rootdir is None:
135136
rootdir = get_common_ancestor([py.path.local(), ancestor])
136137
is_fs_root = os.path.splitdrive(str(rootdir))[1] == "/"

src/_pytest/hookspec.py

+14-2
Original file line numberDiff line numberDiff line change
@@ -526,7 +526,17 @@ def pytest_terminal_summary(terminalreporter, exitstatus):
526526

527527
@hookspec(historic=True)
528528
def pytest_logwarning(message, code, nodeid, fslocation):
529-
""" process a warning specified by a message, a code string,
529+
"""
530+
.. deprecated:: 3.8
531+
532+
This hook is will stop working in a future release.
533+
534+
pytest no longer triggers this hook, but the
535+
terminal writer still implements it to display warnings issued by
536+
:meth:`_pytest.config.Config.warn` and :meth:`_pytest.nodes.Node.warn`. Calling those functions will be
537+
an error in future releases.
538+
539+
process a warning specified by a message, a code string,
530540
a nodeid and fslocation (both of which may be None
531541
if the warning is not tied to a particular node/location).
532542
@@ -538,14 +548,16 @@ def pytest_logwarning(message, code, nodeid, fslocation):
538548
@hookspec(historic=True)
539549
def pytest_warning_captured(warning_message, when, item):
540550
"""
541-
Process a warning captured by the internal pytest plugin.
551+
Process a warning captured by the internal pytest warnings plugin.
542552
543553
:param warnings.WarningMessage warning_message:
544554
The captured warning. This is the same object produced by :py:func:`warnings.catch_warnings`, and contains
545555
the same attributes as the parameters of :py:func:`warnings.showwarning`.
546556
547557
:param str when:
548558
Indicates when the warning was captured. Possible values:
559+
560+
* ``"config"``: during pytest configuration/initialization stage.
549561
* ``"collect"``: during test collection.
550562
* ``"runtest"``: during test execution.
551563

src/_pytest/resultlog.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,10 @@ def pytest_configure(config):
3131
config.pluginmanager.register(config._resultlog)
3232

3333
from _pytest.deprecated import RESULT_LOG
34-
import warnings
3534
from _pytest.warning_types import RemovedInPytest4Warning
35+
from _pytest.warnings import _issue_config_warning
3636

37-
warnings.warn(RESULT_LOG, RemovedInPytest4Warning)
37+
_issue_config_warning(RemovedInPytest4Warning(RESULT_LOG), config)
3838

3939

4040
def pytest_unconfigure(config):

src/_pytest/warnings.py

+17
Original file line numberDiff line numberDiff line change
@@ -146,3 +146,20 @@ def pytest_terminal_summary(terminalreporter):
146146
config = terminalreporter.config
147147
with catch_warnings_for_item(config=config, ihook=config.hook, item=None):
148148
yield
149+
150+
151+
def _issue_config_warning(warning, config):
152+
"""
153+
This function should be used instead of calling ``warnings.warn`` directly when we are in the "configure" stage:
154+
at this point the actual options might not have been set, so we manually trigger the pytest_warning_captured
155+
hook so we can display this warnings in the terminal. This is a hack until we can sort out #2891.
156+
157+
:param warning: the warning instance.
158+
:param config:
159+
"""
160+
with warnings.catch_warnings(record=True) as records:
161+
warnings.simplefilter("always", type(warning))
162+
warnings.warn(warning, stacklevel=2)
163+
config.hook.pytest_warning_captured.call_historic(
164+
kwargs=dict(warning_message=records[0], when="config", item=None)
165+
)

testing/deprecated_test.py

+3-15
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,6 @@ def test_funcarg_prefix(value):
5454

5555

5656
@pytest.mark.filterwarnings("default")
57-
@pytest.mark.xfail(
58-
reason="#2891 need to handle warnings during pre-config", strict=True
59-
)
6057
def test_pytest_setup_cfg_deprecated(testdir):
6158
testdir.makefile(
6259
".cfg",
@@ -72,9 +69,6 @@ def test_pytest_setup_cfg_deprecated(testdir):
7269

7370

7471
@pytest.mark.filterwarnings("default")
75-
@pytest.mark.xfail(
76-
reason="#2891 need to handle warnings during pre-config", strict=True
77-
)
7872
def test_pytest_custom_cfg_deprecated(testdir):
7973
testdir.makefile(
8074
".cfg",
@@ -89,18 +83,15 @@ def test_pytest_custom_cfg_deprecated(testdir):
8983
)
9084

9185

92-
@pytest.mark.xfail(
93-
reason="#2891 need to handle warnings during pre-config", strict=True
94-
)
95-
def test_str_args_deprecated(tmpdir, testdir):
86+
def test_str_args_deprecated(tmpdir):
9687
"""Deprecate passing strings to pytest.main(). Scheduled for removal in pytest-4.0."""
9788
from _pytest.main import EXIT_NOTESTSCOLLECTED
9889

9990
warnings = []
10091

10192
class Collect(object):
102-
def pytest_logwarning(self, message):
103-
warnings.append(message)
93+
def pytest_warning_captured(self, warning_message):
94+
warnings.append(str(warning_message.message))
10495

10596
ret = pytest.main("%s -x" % tmpdir, plugins=[Collect()])
10697
msg = (
@@ -116,9 +107,6 @@ def test_getfuncargvalue_is_deprecated(request):
116107

117108

118109
@pytest.mark.filterwarnings("default")
119-
@pytest.mark.xfail(
120-
reason="#2891 need to handle warnings during pre-config", strict=True
121-
)
122110
def test_resultlog_is_deprecated(testdir):
123111
result = testdir.runpytest("--help")
124112
result.stdout.fnmatch_lines(["*DEPRECATED path for machine-readable result log*"])

0 commit comments

Comments
 (0)