Remove resultlog plugin

This commit is contained in:
Bruno Oliveira 2020-08-17 18:10:27 -03:00
parent b32c48ee05
commit ef946d557c
9 changed files with 10 additions and 441 deletions

View File

@ -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.

View File

@ -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
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -239,7 +239,6 @@ default_plugins = essential_plugins + (
"nose",
"assertion",
"junitxml",
"resultlog",
"doctest",
"cacheprovider",
"freeze_support",

View File

@ -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"

View File

@ -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))

View File

@ -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

View File

@ -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(

View File

@ -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

View File

@ -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.