Remove resultlog plugin
This commit is contained in:
parent
b32c48ee05
commit
ef946d557c
|
@ -11,6 +11,8 @@ removed:
|
|||
|
||||
* The ``TerminalReporter`` no longer has a ``writer`` attribute. Plugin authors may use the public functions of the ``TerminalReporter`` instead of accessing the ``TerminalWriter`` object directly.
|
||||
|
||||
* The ``--result-log`` option has been removed. Users are recommended to use the `pytest-reportlog <https://github.com/pytest-dev/pytest-reportlog>`__ plugin instead.
|
||||
|
||||
|
||||
For more information consult
|
||||
`Deprecations and Removals <https://docs.pytest.org/en/stable/deprecations.html>`__ in the docs.
|
||||
|
|
|
@ -66,10 +66,17 @@ display captured output when tests fail: ``no``, ``stdout``, ``stderr``, ``log``
|
|||
|
||||
|
||||
|
||||
Removed Features
|
||||
----------------
|
||||
|
||||
As stated in our :ref:`backwards-compatibility` policy, deprecated features are removed only in major releases after
|
||||
an appropriate period of deprecation has passed.
|
||||
|
||||
Result log (``--result-log``)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. deprecated:: 4.0
|
||||
.. versionremoved:: 6.0
|
||||
|
||||
The ``--result-log`` option produces a stream of test reports which can be
|
||||
analysed at runtime, but it uses a custom format which requires users to implement their own
|
||||
|
@ -78,18 +85,9 @@ parser.
|
|||
The `pytest-reportlog <https://github.com/pytest-dev/pytest-reportlog>`__ plugin provides a ``--report-log`` option, a more standard and extensible alternative, producing
|
||||
one JSON object per-line, and should cover the same use cases. Please try it out and provide feedback.
|
||||
|
||||
The plan is remove the ``--result-log`` option in pytest 6.0 if ``pytest-reportlog`` proves satisfactory
|
||||
to all users and is deemed stable. The ``pytest-reportlog`` plugin might even be merged into the core
|
||||
The ``pytest-reportlog`` plugin might even be merged into the core
|
||||
at some point, depending on the plans for the plugins and number of users using it.
|
||||
|
||||
|
||||
Removed Features
|
||||
----------------
|
||||
|
||||
As stated in our :ref:`backwards-compatibility` policy, deprecated features are removed only in major releases after
|
||||
an appropriate period of deprecation has passed.
|
||||
|
||||
|
||||
``pytest_collect_directory`` hook
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
|
|
@ -239,7 +239,6 @@ default_plugins = essential_plugins + (
|
|||
"nose",
|
||||
"assertion",
|
||||
"junitxml",
|
||||
"resultlog",
|
||||
"doctest",
|
||||
"cacheprovider",
|
||||
"freeze_support",
|
||||
|
|
|
@ -25,12 +25,6 @@ FILLFUNCARGS = PytestDeprecationWarning(
|
|||
"function._request._fillfixtures() instead if you cannot avoid reaching into internals."
|
||||
)
|
||||
|
||||
RESULT_LOG = PytestDeprecationWarning(
|
||||
"--result-log is deprecated, please try the new pytest-reportlog plugin.\n"
|
||||
"See https://docs.pytest.org/en/stable/deprecations.html#result-log-result-log for more information."
|
||||
)
|
||||
|
||||
|
||||
PYTEST_COLLECT_MODULE = UnformattedWarning(
|
||||
PytestDeprecationWarning,
|
||||
"pytest.collect.{name} was moved to pytest.{name}\n"
|
||||
|
|
|
@ -1,108 +0,0 @@
|
|||
"""log machine-parseable test session result information to a plain text file."""
|
||||
import os
|
||||
from typing import IO
|
||||
from typing import Union
|
||||
|
||||
from _pytest._code.code import ExceptionRepr
|
||||
from _pytest.config import Config
|
||||
from _pytest.config.argparsing import Parser
|
||||
from _pytest.reports import CollectReport
|
||||
from _pytest.reports import TestReport
|
||||
from _pytest.store import StoreKey
|
||||
|
||||
|
||||
resultlog_key = StoreKey["ResultLog"]()
|
||||
|
||||
|
||||
def pytest_addoption(parser: Parser) -> None:
|
||||
group = parser.getgroup("terminal reporting", "resultlog plugin options")
|
||||
group.addoption(
|
||||
"--resultlog",
|
||||
"--result-log",
|
||||
action="store",
|
||||
metavar="path",
|
||||
default=None,
|
||||
help="DEPRECATED path for machine-readable result log.",
|
||||
)
|
||||
|
||||
|
||||
def pytest_configure(config: Config) -> None:
|
||||
resultlog = config.option.resultlog
|
||||
# Prevent opening resultlog on worker nodes (xdist).
|
||||
if resultlog and not hasattr(config, "workerinput"):
|
||||
dirname = os.path.dirname(os.path.abspath(resultlog))
|
||||
if not os.path.isdir(dirname):
|
||||
os.makedirs(dirname)
|
||||
logfile = open(resultlog, "w", 1) # line buffered
|
||||
config._store[resultlog_key] = ResultLog(config, logfile)
|
||||
config.pluginmanager.register(config._store[resultlog_key])
|
||||
|
||||
from _pytest.deprecated import RESULT_LOG
|
||||
from _pytest.warnings import _issue_warning_captured
|
||||
|
||||
_issue_warning_captured(RESULT_LOG, config.hook, stacklevel=2)
|
||||
|
||||
|
||||
def pytest_unconfigure(config: Config) -> None:
|
||||
resultlog = config._store.get(resultlog_key, None)
|
||||
if resultlog:
|
||||
resultlog.logfile.close()
|
||||
del config._store[resultlog_key]
|
||||
config.pluginmanager.unregister(resultlog)
|
||||
|
||||
|
||||
class ResultLog:
|
||||
def __init__(self, config: Config, logfile: IO[str]) -> None:
|
||||
self.config = config
|
||||
self.logfile = logfile # preferably line buffered
|
||||
|
||||
def write_log_entry(self, testpath: str, lettercode: str, longrepr: str) -> None:
|
||||
print("{} {}".format(lettercode, testpath), file=self.logfile)
|
||||
for line in longrepr.splitlines():
|
||||
print(" %s" % line, file=self.logfile)
|
||||
|
||||
def log_outcome(
|
||||
self, report: Union[TestReport, CollectReport], lettercode: str, longrepr: str
|
||||
) -> None:
|
||||
testpath = getattr(report, "nodeid", None)
|
||||
if testpath is None:
|
||||
testpath = report.fspath
|
||||
self.write_log_entry(testpath, lettercode, longrepr)
|
||||
|
||||
def pytest_runtest_logreport(self, report: TestReport) -> None:
|
||||
if report.when != "call" and report.passed:
|
||||
return
|
||||
res = self.config.hook.pytest_report_teststatus(
|
||||
report=report, config=self.config
|
||||
)
|
||||
code = res[1] # type: str
|
||||
if code == "x":
|
||||
longrepr = str(report.longrepr)
|
||||
elif code == "X":
|
||||
longrepr = ""
|
||||
elif report.passed:
|
||||
longrepr = ""
|
||||
elif report.skipped:
|
||||
assert isinstance(report.longrepr, tuple)
|
||||
longrepr = str(report.longrepr[2])
|
||||
else:
|
||||
longrepr = str(report.longrepr)
|
||||
self.log_outcome(report, code, longrepr)
|
||||
|
||||
def pytest_collectreport(self, report: CollectReport) -> None:
|
||||
if not report.passed:
|
||||
if report.failed:
|
||||
code = "F"
|
||||
longrepr = str(report.longrepr)
|
||||
else:
|
||||
assert report.skipped
|
||||
code = "S"
|
||||
longrepr = "%s:%d: %s" % report.longrepr # type: ignore
|
||||
self.log_outcome(report, code, longrepr)
|
||||
|
||||
def pytest_internalerror(self, excrepr: ExceptionRepr) -> None:
|
||||
if excrepr.reprcrash is not None:
|
||||
path = excrepr.reprcrash.path
|
||||
else:
|
||||
path = "cwd:%s" % os.getcwd()
|
||||
self.write_log_entry(path, "!", str(excrepr))
|
|
@ -6,26 +6,6 @@ from _pytest import deprecated
|
|||
from _pytest.pytester import Testdir
|
||||
|
||||
|
||||
@pytest.mark.filterwarnings("default")
|
||||
def test_resultlog_is_deprecated(testdir):
|
||||
result = testdir.runpytest("--help")
|
||||
result.stdout.fnmatch_lines(["*DEPRECATED path for machine-readable result log*"])
|
||||
|
||||
testdir.makepyfile(
|
||||
"""
|
||||
def test():
|
||||
pass
|
||||
"""
|
||||
)
|
||||
result = testdir.runpytest("--result-log=%s" % testdir.tmpdir.join("result.log"))
|
||||
result.stdout.fnmatch_lines(
|
||||
[
|
||||
"*--result-log is deprecated, please try the new pytest-reportlog plugin.",
|
||||
"*See https://docs.pytest.org/en/stable/deprecations.html#result-log-result-log for more information*",
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="should be reintroduced in 6.1: #7361")
|
||||
@pytest.mark.parametrize("attribute", pytest.collect.__all__) # type: ignore
|
||||
# false positive due to dynamic attribute
|
||||
|
|
|
@ -306,21 +306,6 @@ def test_no_conftest(testdir):
|
|||
assert result.ret == ExitCode.USAGE_ERROR
|
||||
|
||||
|
||||
def test_conftest_existing_resultlog(testdir):
|
||||
x = testdir.mkdir("tests")
|
||||
x.join("conftest.py").write(
|
||||
textwrap.dedent(
|
||||
"""\
|
||||
def pytest_addoption(parser):
|
||||
parser.addoption("--xyz", action="store_true")
|
||||
"""
|
||||
)
|
||||
)
|
||||
testdir.makefile(ext=".log", result="") # Writes result.log
|
||||
result = testdir.runpytest("-h", "--resultlog", "result.log")
|
||||
result.stdout.fnmatch_lines(["*--xyz*"])
|
||||
|
||||
|
||||
def test_conftest_existing_junitxml(testdir):
|
||||
x = testdir.mkdir("tests")
|
||||
x.join("conftest.py").write(
|
||||
|
|
|
@ -1,256 +0,0 @@
|
|||
import os
|
||||
from io import StringIO
|
||||
from typing import List
|
||||
|
||||
import _pytest._code
|
||||
import pytest
|
||||
from _pytest.pytester import Testdir
|
||||
from _pytest.resultlog import pytest_configure
|
||||
from _pytest.resultlog import pytest_unconfigure
|
||||
from _pytest.resultlog import ResultLog
|
||||
from _pytest.resultlog import resultlog_key
|
||||
|
||||
|
||||
pytestmark = pytest.mark.filterwarnings("ignore:--result-log is deprecated")
|
||||
|
||||
|
||||
def test_write_log_entry() -> None:
|
||||
reslog = ResultLog(None, None) # type: ignore[arg-type]
|
||||
reslog.logfile = StringIO()
|
||||
reslog.write_log_entry("name", ".", "")
|
||||
entry = reslog.logfile.getvalue()
|
||||
assert entry[-1] == "\n"
|
||||
entry_lines = entry.splitlines()
|
||||
assert len(entry_lines) == 1
|
||||
assert entry_lines[0] == ". name"
|
||||
|
||||
reslog.logfile = StringIO()
|
||||
reslog.write_log_entry("name", "s", "Skipped")
|
||||
entry = reslog.logfile.getvalue()
|
||||
assert entry[-1] == "\n"
|
||||
entry_lines = entry.splitlines()
|
||||
assert len(entry_lines) == 2
|
||||
assert entry_lines[0] == "s name"
|
||||
assert entry_lines[1] == " Skipped"
|
||||
|
||||
reslog.logfile = StringIO()
|
||||
reslog.write_log_entry("name", "s", "Skipped\n")
|
||||
entry = reslog.logfile.getvalue()
|
||||
assert entry[-1] == "\n"
|
||||
entry_lines = entry.splitlines()
|
||||
assert len(entry_lines) == 2
|
||||
assert entry_lines[0] == "s name"
|
||||
assert entry_lines[1] == " Skipped"
|
||||
|
||||
reslog.logfile = StringIO()
|
||||
longrepr = " tb1\n tb 2\nE tb3\nSome Error"
|
||||
reslog.write_log_entry("name", "F", longrepr)
|
||||
entry = reslog.logfile.getvalue()
|
||||
assert entry[-1] == "\n"
|
||||
entry_lines = entry.splitlines()
|
||||
assert len(entry_lines) == 5
|
||||
assert entry_lines[0] == "F name"
|
||||
assert entry_lines[1:] == [" " + line for line in longrepr.splitlines()]
|
||||
|
||||
|
||||
class TestWithFunctionIntegration:
|
||||
# XXX (hpk) i think that the resultlog plugin should
|
||||
# provide a Parser object so that one can remain
|
||||
# ignorant regarding formatting details.
|
||||
def getresultlog(self, testdir: Testdir, arg: str) -> List[str]:
|
||||
resultlog = testdir.tmpdir.join("resultlog")
|
||||
testdir.plugins.append("resultlog")
|
||||
args = ["--resultlog=%s" % resultlog] + [arg]
|
||||
testdir.runpytest(*args)
|
||||
return [x for x in resultlog.readlines(cr=0) if x]
|
||||
|
||||
def test_collection_report(self, testdir: Testdir) -> None:
|
||||
ok = testdir.makepyfile(test_collection_ok="")
|
||||
fail = testdir.makepyfile(test_collection_fail="XXX")
|
||||
lines = self.getresultlog(testdir, ok)
|
||||
assert not lines
|
||||
|
||||
lines = self.getresultlog(testdir, fail)
|
||||
assert lines
|
||||
assert lines[0].startswith("F ")
|
||||
assert lines[0].endswith("test_collection_fail.py"), lines[0]
|
||||
for x in lines[1:]:
|
||||
assert x.startswith(" ")
|
||||
assert "XXX" in "".join(lines[1:])
|
||||
|
||||
def test_log_test_outcomes(self, testdir: Testdir) -> None:
|
||||
mod = testdir.makepyfile(
|
||||
test_mod="""
|
||||
import pytest
|
||||
def test_pass(): pass
|
||||
def test_skip(): pytest.skip("hello")
|
||||
def test_fail(): raise ValueError("FAIL")
|
||||
|
||||
@pytest.mark.xfail
|
||||
def test_xfail(): raise ValueError("XFAIL")
|
||||
@pytest.mark.xfail
|
||||
def test_xpass(): pass
|
||||
|
||||
"""
|
||||
)
|
||||
lines = self.getresultlog(testdir, mod)
|
||||
assert len(lines) >= 3
|
||||
assert lines[0].startswith(". ")
|
||||
assert lines[0].endswith("test_pass")
|
||||
assert lines[1].startswith("s "), lines[1]
|
||||
assert lines[1].endswith("test_skip")
|
||||
assert lines[2].find("hello") != -1
|
||||
|
||||
assert lines[3].startswith("F ")
|
||||
assert lines[3].endswith("test_fail")
|
||||
tb = "".join(lines[4:8])
|
||||
assert tb.find('raise ValueError("FAIL")') != -1
|
||||
|
||||
assert lines[8].startswith("x ")
|
||||
tb = "".join(lines[8:14])
|
||||
assert tb.find('raise ValueError("XFAIL")') != -1
|
||||
|
||||
assert lines[14].startswith("X ")
|
||||
assert len(lines) == 15
|
||||
|
||||
@pytest.mark.parametrize("style", ("native", "long", "short"))
|
||||
def test_internal_exception(self, style) -> None:
|
||||
# they are produced for example by a teardown failing
|
||||
# at the end of the run or a failing hook invocation
|
||||
try:
|
||||
raise ValueError
|
||||
except ValueError:
|
||||
excinfo = _pytest._code.ExceptionInfo.from_current()
|
||||
file = StringIO()
|
||||
reslog = ResultLog(None, file) # type: ignore[arg-type]
|
||||
reslog.pytest_internalerror(excinfo.getrepr(style=style))
|
||||
entry = file.getvalue()
|
||||
entry_lines = entry.splitlines()
|
||||
|
||||
assert entry_lines[0].startswith("! ")
|
||||
if style != "native":
|
||||
assert os.path.basename(__file__)[:-9] in entry_lines[0] # .pyc/class
|
||||
assert entry_lines[-1][0] == " "
|
||||
assert "ValueError" in entry
|
||||
|
||||
|
||||
def test_generic(testdir: Testdir, LineMatcher) -> None:
|
||||
testdir.plugins.append("resultlog")
|
||||
testdir.makepyfile(
|
||||
"""
|
||||
import pytest
|
||||
def test_pass():
|
||||
pass
|
||||
def test_fail():
|
||||
assert 0
|
||||
def test_skip():
|
||||
pytest.skip("")
|
||||
@pytest.mark.xfail
|
||||
def test_xfail():
|
||||
assert 0
|
||||
@pytest.mark.xfail(run=False)
|
||||
def test_xfail_norun():
|
||||
assert 0
|
||||
"""
|
||||
)
|
||||
testdir.runpytest("--resultlog=result.log")
|
||||
lines = testdir.tmpdir.join("result.log").readlines(cr=0)
|
||||
LineMatcher(lines).fnmatch_lines(
|
||||
[
|
||||
". *:test_pass",
|
||||
"F *:test_fail",
|
||||
"s *:test_skip",
|
||||
"x *:test_xfail",
|
||||
"x *:test_xfail_norun",
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def test_makedir_for_resultlog(testdir: Testdir, LineMatcher) -> None:
|
||||
"""--resultlog should automatically create directories for the log file"""
|
||||
testdir.plugins.append("resultlog")
|
||||
testdir.makepyfile(
|
||||
"""
|
||||
import pytest
|
||||
def test_pass():
|
||||
pass
|
||||
"""
|
||||
)
|
||||
testdir.runpytest("--resultlog=path/to/result.log")
|
||||
lines = testdir.tmpdir.join("path/to/result.log").readlines(cr=0)
|
||||
LineMatcher(lines).fnmatch_lines([". *:test_pass"])
|
||||
|
||||
|
||||
def test_no_resultlog_on_workers(testdir: Testdir) -> None:
|
||||
config = testdir.parseconfig("-p", "resultlog", "--resultlog=resultlog")
|
||||
|
||||
assert resultlog_key not in config._store
|
||||
pytest_configure(config)
|
||||
assert resultlog_key in config._store
|
||||
pytest_unconfigure(config)
|
||||
assert resultlog_key not in config._store
|
||||
|
||||
config.workerinput = {} # type: ignore[attr-defined]
|
||||
pytest_configure(config)
|
||||
assert resultlog_key not in config._store
|
||||
pytest_unconfigure(config)
|
||||
assert resultlog_key not in config._store
|
||||
|
||||
|
||||
def test_unknown_teststatus(testdir: Testdir) -> None:
|
||||
"""Ensure resultlog correctly handles unknown status from pytest_report_teststatus
|
||||
|
||||
Inspired on pytest-rerunfailures.
|
||||
"""
|
||||
testdir.makepyfile(
|
||||
"""
|
||||
def test():
|
||||
assert 0
|
||||
"""
|
||||
)
|
||||
testdir.makeconftest(
|
||||
"""
|
||||
import pytest
|
||||
|
||||
def pytest_report_teststatus(report):
|
||||
if report.outcome == 'rerun':
|
||||
return "rerun", "r", "RERUN"
|
||||
|
||||
@pytest.hookimpl(hookwrapper=True)
|
||||
def pytest_runtest_makereport():
|
||||
res = yield
|
||||
report = res.get_result()
|
||||
if report.when == "call":
|
||||
report.outcome = 'rerun'
|
||||
"""
|
||||
)
|
||||
result = testdir.runpytest("--resultlog=result.log")
|
||||
result.stdout.fnmatch_lines(
|
||||
["test_unknown_teststatus.py r *[[]100%[]]", "* 1 rerun *"]
|
||||
)
|
||||
|
||||
lines = testdir.tmpdir.join("result.log").readlines(cr=0)
|
||||
assert lines[0] == "r test_unknown_teststatus.py::test"
|
||||
|
||||
|
||||
def test_failure_issue380(testdir: Testdir) -> None:
|
||||
testdir.makeconftest(
|
||||
"""
|
||||
import pytest
|
||||
class MyCollector(pytest.File):
|
||||
def collect(self):
|
||||
raise ValueError()
|
||||
def repr_failure(self, excinfo):
|
||||
return "somestring"
|
||||
def pytest_collect_file(path, parent):
|
||||
return MyCollector(parent=parent, fspath=path)
|
||||
"""
|
||||
)
|
||||
testdir.makepyfile(
|
||||
"""
|
||||
def test_func():
|
||||
pass
|
||||
"""
|
||||
)
|
||||
result = testdir.runpytest("--resultlog=log")
|
||||
assert result.ret == 2
|
|
@ -737,31 +737,6 @@ class TestStackLevel:
|
|||
assert "config{sep}__init__.py".format(sep=os.sep) in file
|
||||
assert func == "import_plugin"
|
||||
|
||||
def test_issue4445_resultlog(self, testdir, capwarn):
|
||||
"""#4445: Make sure the warning points to a reasonable location
|
||||
See origin of _issue_warning_captured at: _pytest.resultlog.py:35
|
||||
"""
|
||||
testdir.makepyfile(
|
||||
"""
|
||||
def test_dummy():
|
||||
pass
|
||||
"""
|
||||
)
|
||||
# Use parseconfigure() because the warning in resultlog.py is triggered in
|
||||
# the pytest_configure hook
|
||||
testdir.parseconfigure(
|
||||
"--result-log={dir}".format(dir=testdir.tmpdir.join("result.log"))
|
||||
)
|
||||
|
||||
# with stacklevel=2 the warning originates from resultlog.pytest_configure
|
||||
# and is thrown when --result-log is used
|
||||
warning, location = capwarn.captured.pop()
|
||||
file, _, func = location
|
||||
|
||||
assert "--result-log is deprecated" in str(warning.message)
|
||||
assert "resultlog.py" in file
|
||||
assert func == "pytest_configure"
|
||||
|
||||
def test_issue4445_issue5928_mark_generator(self, testdir):
|
||||
"""#4445 and #5928: Make sure the warning from an unknown mark points to
|
||||
the test file where this mark is used.
|
||||
|
|
Loading…
Reference in New Issue