Deprecate Config.warn and Node.warn, replaced by standard warnings

This commit is contained in:
Bruno Oliveira 2018-09-01 21:58:48 -03:00
parent 0c8dbdcd92
commit 78ac7d99f5
21 changed files with 197 additions and 75 deletions

View File

@ -209,8 +209,11 @@ class AssertionRewritingHook(object):
self._must_rewrite.update(names)
def _warn_already_imported(self, name):
self.config.warn(
"P1", "Module already imported so cannot be rewritten: %s" % name
import warnings
from _pytest.warning_types import PytestWarning
warnings.warn(
"Module already imported so cannot be rewritten: %s" % name, PytestWarning
)
def load_module(self, name):
@ -746,13 +749,17 @@ class AssertionRewriter(ast.NodeVisitor):
the expression is false.
"""
if isinstance(assert_.test, ast.Tuple) and self.config is not None:
fslocation = (self.module_path, assert_.lineno)
self.config.warn(
"R1",
if isinstance(assert_.test, ast.Tuple):
from _pytest.warning_types import PytestWarning
import warnings
warnings.warn_explicit(
"assertion is always true, perhaps " "remove parentheses?",
fslocation=fslocation,
PytestWarning,
filename=str(self.module_path),
lineno=assert_.lineno,
)
self.statements = []
self.variables = []
self.variable_counter = itertools.count()

View File

@ -33,7 +33,6 @@ See [the docs](https://docs.pytest.org/en/latest/cache.html) for more informatio
@attr.s
class Cache(object):
_cachedir = attr.ib(repr=False)
_warn = attr.ib(repr=False)
@classmethod
def for_config(cls, config):
@ -41,14 +40,19 @@ class Cache(object):
if config.getoption("cacheclear") and cachedir.exists():
shutil.rmtree(str(cachedir))
cachedir.mkdir()
return cls(cachedir, config.warn)
return cls(cachedir)
@staticmethod
def cache_dir_from_config(config):
return paths.resolve_from_str(config.getini("cache_dir"), config.rootdir)
def warn(self, fmt, **args):
self._warn(code="I9", message=fmt.format(**args) if args else fmt)
import warnings
from _pytest.warning_types import PytestWarning
warnings.warn(
message=fmt.format(**args) if args else fmt, category=PytestWarning
)
def makedir(self, name):
""" return a directory path object with the given name. If the

View File

@ -176,7 +176,9 @@ def _prepareconfig(args=None, plugins=None):
else:
pluginmanager.register(plugin)
if warning:
config.warn("C1", warning)
from _pytest.warning_types import PytestUsageWarning
warnings.warn(warning, PytestUsageWarning)
return pluginmanager.hook.pytest_cmdline_parse(
pluginmanager=pluginmanager, args=args
)
@ -609,7 +611,26 @@ class Config(object):
fin()
def warn(self, code, message, fslocation=None, nodeid=None):
""" generate a warning for this test session. """
"""
.. deprecated:: 3.8
Use :py:func:`warnings.warn` or :py:func:`warnings.warn_explicit` directly instead.
Generate a warning for this test session.
"""
from _pytest.warning_types import RemovedInPytest4Warning
if isinstance(fslocation, (tuple, list)) and len(fslocation) > 2:
filename, lineno = fslocation[:2]
else:
filename = "unknown file"
lineno = 0
msg = "config.warn has been deprecated, use warnings.warn instead"
if nodeid:
msg = "{}: {}".format(nodeid, msg)
warnings.warn_explicit(
msg, RemovedInPytest4Warning, filename=filename, lineno=lineno
)
self.hook.pytest_logwarning.call_historic(
kwargs=dict(
code=code, message=message, fslocation=fslocation, nodeid=nodeid
@ -674,7 +695,6 @@ class Config(object):
r = determine_setup(
ns.inifilename,
ns.file_or_dir + unknown_args,
warnfunc=self.warn,
rootdir_cmd_arg=ns.rootdir or None,
)
self.rootdir, self.inifile, self.inicfg = r

View File

@ -10,7 +10,7 @@ def exists(path, ignore=EnvironmentError):
return False
def getcfg(args, warnfunc=None):
def getcfg(args):
"""
Search the list of arguments for a valid ini-file for pytest,
and return a tuple of (rootdir, inifile, cfg-dict).
@ -34,9 +34,13 @@ def getcfg(args, warnfunc=None):
if exists(p):
iniconfig = py.iniconfig.IniConfig(p)
if "pytest" in iniconfig.sections:
if inibasename == "setup.cfg" and warnfunc:
warnfunc(
"C1", CFG_PYTEST_SECTION.format(filename=inibasename)
if inibasename == "setup.cfg":
import warnings
from _pytest.warning_types import RemovedInPytest4Warning
warnings.warn(
CFG_PYTEST_SECTION.format(filename=inibasename),
RemovedInPytest4Warning,
)
return base, p, iniconfig["pytest"]
if (
@ -95,7 +99,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, warnfunc=None, rootdir_cmd_arg=None):
def determine_setup(inifile, args, rootdir_cmd_arg=None):
dirs = get_dirs_from_args(args)
if inifile:
iniconfig = py.iniconfig.IniConfig(inifile)
@ -105,23 +109,28 @@ def determine_setup(inifile, args, warnfunc=None, rootdir_cmd_arg=None):
for section in sections:
try:
inicfg = iniconfig[section]
if is_cfg_file and section == "pytest" and warnfunc:
if is_cfg_file and section == "pytest":
from _pytest.warning_types import RemovedInPytest4Warning
from _pytest.deprecated import CFG_PYTEST_SECTION
import warnings
warnfunc("C1", CFG_PYTEST_SECTION.format(filename=str(inifile)))
warnings.warn(
CFG_PYTEST_SECTION.format(filename=str(inifile)),
RemovedInPytest4Warning,
)
break
except KeyError:
inicfg = None
rootdir = get_common_ancestor(dirs)
else:
ancestor = get_common_ancestor(dirs)
rootdir, inifile, inicfg = getcfg([ancestor], warnfunc=warnfunc)
rootdir, inifile, inicfg = getcfg([ancestor])
if rootdir is None:
for rootdir in ancestor.parts(reverse=True):
if rootdir.join("setup.py").exists():
break
else:
rootdir, inifile, inicfg = getcfg(dirs, warnfunc=warnfunc)
rootdir, inifile, inicfg = getcfg(dirs)
if rootdir is None:
rootdir = get_common_ancestor([py.path.local(), ancestor])
is_fs_root = os.path.splitdrive(str(rootdir))[1] == "/"

View File

@ -1281,8 +1281,12 @@ class FixtureManager(object):
marker = defaultfuncargprefixmarker
from _pytest import deprecated
self.config.warn(
"C1", deprecated.FUNCARG_PREFIX.format(name=name), nodeid=nodeid
filename, lineno = getfslineno(obj)
warnings.warn_explicit(
deprecated.FUNCARG_PREFIX.format(name=name),
RemovedInPytest4Warning,
filename=str(filename),
lineno=lineno + 1,
)
name = name[len(self._argprefix) :]
elif not isinstance(marker, FixtureFunctionMarker):

View File

@ -274,8 +274,11 @@ def record_xml_attribute(request):
The fixture is callable with ``(name, value)``, with value being
automatically xml-encoded
"""
request.node.warn(
code="C3", message="record_xml_attribute is an experimental feature"
from _pytest.warning_types import PytestWarning
request.node.std_warn(
message="record_xml_attribute is an experimental feature",
category=PytestWarning,
)
xml = getattr(request.config, "_xml", None)
if xml is not None:

View File

@ -137,8 +137,20 @@ class Node(object):
return "<%s %r>" % (self.__class__.__name__, getattr(self, "name", None))
def warn(self, code, message):
""" generate a warning with the given code and message for this
item. """
"""
.. deprecated:: 3.8
Use :meth:`Node.std_warn <_pytest.nodes.Node.std_warn>` instead.
Generate a warning with the given code and message for this item.
"""
from _pytest.warning_types import RemovedInPytest4Warning
self.std_warn(
"Node.warn has been deprecated, use Node.std_warn instead",
RemovedInPytest4Warning,
)
assert isinstance(code, str)
fslocation = get_fslocation_from_item(self)
self.ihook.pytest_logwarning.call_historic(
@ -148,12 +160,24 @@ class Node(object):
)
def std_warn(self, message, category=None):
"""Issue a warning for this item.
Warnings will be displayed after the test session, unless explicitly suppressed
:param Union[str,Warning] message: text message of the warning or ``Warning`` instance.
:param Type[Warning] category: warning category.
"""
from _pytest.warning_types import PytestWarning
if category is None:
assert isinstance(message, PytestWarning)
path, lineno = get_fslocation_from_item(self)
warnings.warn_explicit(message, category, filename=str(path), lineno=lineno)
warnings.warn_explicit(
message,
category,
filename=str(path),
lineno=lineno + 1 if lineno is not None else None,
)
# methods for ordering nodes
@property
@ -323,6 +347,8 @@ def get_fslocation_from_item(item):
* "fslocation": a pair (path, lineno)
* "fspath": just a path
:rtype: a tuple of (str|LocalPath, int) with filename and line number.
"""
result = getattr(item, "location", None)
if result is not None:
@ -330,7 +356,7 @@ def get_fslocation_from_item(item):
obj = getattr(item, "obj", None)
if obj is not None:
return getfslineno(obj)
return getattr(item, "fspath", None), None
return getattr(item, "fspath", "unknown location"), -1
class Collector(Node):

View File

@ -44,7 +44,11 @@ from _pytest.mark.structures import (
get_unpacked_marks,
normalize_mark_list,
)
from _pytest.warning_types import PytestUsageWarning, RemovedInPytest4Warning
from _pytest.warning_types import (
PytestUsageWarning,
RemovedInPytest4Warning,
PytestWarning,
)
# relative paths that we use to filter traceback entries from appearing to the user;
# see filter_traceback
@ -239,9 +243,12 @@ def pytest_pycollect_makeitem(collector, name, obj):
# or a funtools.wrapped.
# We musn't if it's been wrapped with mock.patch (python 2 only)
if not (isfunction(obj) or isfunction(get_real_func(obj))):
collector.warn(
code="C2",
filename, lineno = getfslineno(obj)
warnings.warn_explicit(
message="cannot collect %r because it is not a function." % name,
category=PytestWarning,
filename=str(filename),
lineno=lineno + 1,
)
elif getattr(obj, "__test__", True):
if is_generator(obj):
@ -800,7 +807,7 @@ class Generator(FunctionMixin, PyCollector):
)
seen[name] = True
values.append(self.Function(name, self, args=args, callobj=call))
self.warn("C1", deprecated.YIELD_TESTS)
self.std_warn(deprecated.YIELD_TESTS, RemovedInPytest4Warning)
return values
def getcallargs(self, obj):
@ -1107,9 +1114,10 @@ class Metafunc(fixtures.FuncargnamesCompatAttr):
invocation through the ``request.param`` attribute.
"""
if self.config:
self.config.warn(
"C1", message=deprecated.METAFUNC_ADD_CALL, fslocation=None
self.definition.std_warn(
deprecated.METAFUNC_ADD_CALL, RemovedInPytest4Warning
)
assert funcargs is None or isinstance(funcargs, dict)
if funcargs is not None:
for name in funcargs:

View File

@ -31,8 +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
config.warn("C1", RESULT_LOG)
warnings.warn(RESULT_LOG, RemovedInPytest4Warning)
def pytest_unconfigure(config):

View File

@ -526,7 +526,7 @@ class TestInvocationVariants(object):
assert pytest.main == py.test.cmdline.main
def test_invoke_with_string(self, capsys):
retcode = pytest.main("-h")
retcode = pytest.main(["-h"])
assert not retcode
out, err = capsys.readouterr()
assert "--help" in out

View File

@ -5,6 +5,7 @@ import os
import pytest
@pytest.mark.filterwarnings("default")
def test_yield_tests_deprecation(testdir):
testdir.makepyfile(
"""
@ -18,16 +19,18 @@ def test_yield_tests_deprecation(testdir):
yield func1, 1, 1
"""
)
result = testdir.runpytest("-ra")
result = testdir.runpytest()
result.stdout.fnmatch_lines(
[
"*yield tests are deprecated, and scheduled to be removed in pytest 4.0*",
"*test_yield_tests_deprecation.py:3:*yield tests are deprecated*",
"*test_yield_tests_deprecation.py:6:*yield tests are deprecated*",
"*2 passed*",
]
)
assert result.stdout.str().count("yield tests are deprecated") == 2
@pytest.mark.filterwarnings("default")
def test_funcarg_prefix_deprecation(testdir):
testdir.makepyfile(
"""
@ -42,16 +45,18 @@ def test_funcarg_prefix_deprecation(testdir):
result.stdout.fnmatch_lines(
[
(
"*pytest_funcarg__value: "
'declaring fixtures using "pytest_funcarg__" prefix is deprecated '
"and scheduled to be removed in pytest 4.0. "
"Please remove the prefix and use the @pytest.fixture decorator instead."
"*test_funcarg_prefix_deprecation.py:1: *pytest_funcarg__value: "
'declaring fixtures using "pytest_funcarg__" prefix is deprecated*'
),
"*1 passed*",
]
)
@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",
@ -66,6 +71,10 @@ 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",
@ -80,6 +89,9 @@ 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):
"""Deprecate passing strings to pytest.main(). Scheduled for removal in pytest-4.0."""
from _pytest.main import EXIT_NOTESTSCOLLECTED
@ -103,6 +115,10 @@ def test_getfuncargvalue_is_deprecated(request):
pytest.deprecated_call(request.getfuncargvalue, "tmpdir")
@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*"])

View File

@ -462,6 +462,7 @@ class TestFunction(object):
assert isinstance(modcol, pytest.Module)
assert hasattr(modcol.obj, "test_func")
@pytest.mark.filterwarnings("default")
def test_function_as_object_instance_ignored(self, testdir):
testdir.makepyfile(
"""
@ -472,8 +473,14 @@ class TestFunction(object):
test_a = A()
"""
)
reprec = testdir.inline_run()
reprec.assertoutcome()
result = testdir.runpytest()
result.stdout.fnmatch_lines(
[
"collected 0 items",
"*test_function_as_object_instance_ignored.py:2: "
"*cannot collect 'test_a' because it is not a function.",
]
)
def test_function_equality(self, testdir, tmpdir):
from _pytest.fixtures import FixtureManager
@ -1468,6 +1475,7 @@ def test_collect_functools_partial(testdir):
result.assertoutcome(passed=6, failed=2)
@pytest.mark.filterwarnings("default")
def test_dont_collect_non_function_callable(testdir):
"""Test for issue https://github.com/pytest-dev/pytest/issues/331
@ -1490,7 +1498,7 @@ def test_dont_collect_non_function_callable(testdir):
result.stdout.fnmatch_lines(
[
"*collected 1 item*",
"*cannot collect 'test_a' because it is not a function*",
"*test_dont_collect_non_function_callable.py:2: *cannot collect 'test_a' because it is not a function*",
"*1 passed, 1 warnings in *",
]
)

View File

@ -407,8 +407,8 @@ class TestMetafunc(object):
"<Module 'test_parametrize_ids_exception.py'>",
" <Function 'test_foo[a]'>",
" <Function 'test_foo[b]'>",
"*test_parametrize_ids_exception.py:5: *parameter arg at position 0*",
"*test_parametrize_ids_exception.py:5: *parameter arg at position 1*",
"*test_parametrize_ids_exception.py:6: *parameter arg at position 0*",
"*test_parametrize_ids_exception.py:6: *parameter arg at position 1*",
]
)

View File

@ -1075,6 +1075,7 @@ def test_diff_newline_at_end(monkeypatch, testdir):
)
@pytest.mark.filterwarnings("default")
def test_assert_tuple_warning(testdir):
testdir.makepyfile(
"""
@ -1084,7 +1085,7 @@ def test_assert_tuple_warning(testdir):
)
result = testdir.runpytest("-rw")
result.stdout.fnmatch_lines(
["*test_assert_tuple_warning.py:2", "*assertion is always true*"]
["*test_assert_tuple_warning.py:2:*assertion is always true*"]
)

View File

@ -759,16 +759,12 @@ def test_rewritten():
testdir.makepyfile("import a_package_without_init_py.module")
assert testdir.runpytest().ret == EXIT_NOTESTSCOLLECTED
def test_rewrite_warning(self, pytestconfig, monkeypatch):
def test_rewrite_warning(self, pytestconfig):
hook = AssertionRewritingHook(pytestconfig)
warnings = []
from _pytest.warning_types import PytestWarning
def mywarn(code, msg):
warnings.append((code, msg))
monkeypatch.setattr(hook.config, "warn", mywarn)
with pytest.warns(PytestWarning):
hook.mark_rewrite("_pytest")
assert "_pytest" in warnings[0][1]
def test_rewrite_module_imported_from_conftest(self, testdir):
testdir.makeconftest(

View File

@ -31,6 +31,7 @@ class TestNewAPI(object):
val = config.cache.get("key/name", -2)
assert val == -2
@pytest.mark.filterwarnings("default")
def test_cache_writefail_cachfile_silent(self, testdir):
testdir.makeini("[pytest]")
testdir.tmpdir.join(".pytest_cache").write("gone wrong")

View File

@ -135,13 +135,13 @@ class TestConfigCmdlineParsing(object):
"""
)
testdir.makefile(
".cfg",
".ini",
custom="""
[pytest]
custom = 1
""",
)
config = testdir.parseconfig("-c", "custom.cfg")
config = testdir.parseconfig("-c", "custom.ini")
assert config.getini("custom") == "1"
testdir.makefile(
@ -155,8 +155,8 @@ class TestConfigCmdlineParsing(object):
assert config.getini("custom") == "1"
def test_absolute_win32_path(self, testdir):
temp_cfg_file = testdir.makefile(
".cfg",
temp_ini_file = testdir.makefile(
".ini",
custom="""
[pytest]
addopts = --version
@ -164,8 +164,8 @@ class TestConfigCmdlineParsing(object):
)
from os.path import normpath
temp_cfg_file = normpath(str(temp_cfg_file))
ret = pytest.main("-c " + temp_cfg_file)
temp_ini_file = normpath(str(temp_ini_file))
ret = pytest.main(["-c", temp_ini_file])
assert ret == _pytest.main.EXIT_OK
@ -783,13 +783,14 @@ def test_collect_pytest_prefix_bug(pytestconfig):
assert pm.parse_hookimpl_opts(Dummy(), "pytest_something") is None
class TestWarning(object):
class TestLegacyWarning(object):
@pytest.mark.filterwarnings("default")
def test_warn_config(self, testdir):
testdir.makeconftest(
"""
values = []
def pytest_configure(config):
config.warn("C1", "hello")
def pytest_runtest_setup(item):
item.config.warn("C1", "hello")
def pytest_logwarning(code, message):
if message == "hello" and code == "C1":
values.append(1)
@ -802,9 +803,12 @@ class TestWarning(object):
assert conftest.values == [1]
"""
)
reprec = testdir.inline_run()
reprec.assertoutcome(passed=1)
result = testdir.runpytest()
result.stdout.fnmatch_lines(
["*hello", "*config.warn has been deprecated*", "*1 passed*"]
)
@pytest.mark.filterwarnings("default")
def test_warn_on_test_item_from_request(self, testdir, request):
testdir.makepyfile(
"""
@ -819,7 +823,6 @@ class TestWarning(object):
"""
)
result = testdir.runpytest("--disable-pytest-warnings")
assert result.parseoutcomes()["warnings"] > 0
assert "hello" not in result.stdout.str()
result = testdir.runpytest()
@ -828,6 +831,7 @@ class TestWarning(object):
===*warnings summary*===
*test_warn_on_test_item_from_request.py::test_hello*
*hello*
*test_warn_on_test_item_from_request.py:7:*Node.warn has been deprecated, use Node.std_warn instead*
"""
)
@ -847,7 +851,7 @@ class TestRootdir(object):
@pytest.mark.parametrize("name", "setup.cfg tox.ini pytest.ini".split())
def test_with_ini(self, tmpdir, name):
inifile = tmpdir.join(name)
inifile.write("[pytest]\n")
inifile.write("[pytest]\n" if name != "setup.cfg" else "[tool:pytest]\n")
a = tmpdir.mkdir("a")
b = a.mkdir("b")
@ -893,11 +897,14 @@ class TestRootdir(object):
class TestOverrideIniArgs(object):
@pytest.mark.parametrize("name", "setup.cfg tox.ini pytest.ini".split())
def test_override_ini_names(self, testdir, name):
section = "[pytest]" if name != "setup.cfg" else "[tool:pytest]"
testdir.tmpdir.join(name).write(
textwrap.dedent(
"""
[pytest]
custom = 1.0"""
{section}
custom = 1.0""".format(
section=section
)
)
)
testdir.makeconftest(

View File

@ -1005,6 +1005,7 @@ def test_record_property_same_name(testdir):
pnodes[1].assert_attr(name="foo", value="baz")
@pytest.mark.filterwarnings("default")
def test_record_attribute(testdir):
testdir.makepyfile(
"""
@ -1023,7 +1024,10 @@ def test_record_attribute(testdir):
tnode.assert_attr(bar="1")
tnode.assert_attr(foo="<1")
result.stdout.fnmatch_lines(
["test_record_attribute.py::test_record", "*record_xml_attribute*experimental*"]
[
"test_record_attribute.py::test_record",
"*test_record_attribute.py:6:*record_xml_attribute is an experimental feature",
]
)

View File

@ -13,6 +13,9 @@ from _pytest.resultlog import (
)
pytestmark = pytest.mark.filterwarnings("ignore:--result-log is deprecated")
def test_generic_path(testdir):
from _pytest.main import Session

View File

@ -37,7 +37,7 @@ def pyfile_with_warnings(testdir, request):
)
@pytest.mark.filterwarnings("always")
@pytest.mark.filterwarnings("default")
def test_normal_flow(testdir, pyfile_with_warnings):
"""
Check that the warnings section is displayed, containing test node ids followed by

View File

@ -218,6 +218,9 @@ norecursedirs = .tox ja .hg cx_freeze_source testing/example_scripts
xfail_strict=true
filterwarnings =
error
ignore:yield tests are deprecated, and scheduled to be removed in pytest 4.0:
ignore:Metafunc.addcall is deprecated and scheduled to be removed in pytest 4.0:
ignore:Module already imported so cannot be rewritten:
# produced by path.local
ignore:bad escape.*:DeprecationWarning:re
# produced by path.readlines