From ef946d557cc7b7f030805b94c1dbae51f39fcce4 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Mon, 17 Aug 2020 18:10:27 -0300 Subject: [PATCH] Remove resultlog plugin --- changelog/5585.breaking.rst | 2 + doc/en/deprecations.rst | 18 ++- src/_pytest/config/__init__.py | 1 - src/_pytest/deprecated.py | 6 - src/_pytest/resultlog.py | 108 -------------- testing/deprecated_test.py | 20 --- testing/test_conftest.py | 15 -- testing/test_resultlog.py | 256 --------------------------------- testing/test_warnings.py | 25 ---- 9 files changed, 10 insertions(+), 441 deletions(-) delete mode 100644 src/_pytest/resultlog.py delete mode 100644 testing/test_resultlog.py diff --git a/changelog/5585.breaking.rst b/changelog/5585.breaking.rst index 34e264d0b..0ecba32df 100644 --- a/changelog/5585.breaking.rst +++ b/changelog/5585.breaking.rst @@ -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 `__ plugin instead. + For more information consult `Deprecations and Removals `__ in the docs. diff --git a/doc/en/deprecations.rst b/doc/en/deprecations.rst index 12844265d..bec321e69 100644 --- a/doc/en/deprecations.rst +++ b/doc/en/deprecations.rst @@ -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 `__ 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 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index 453dd8345..1c6ad3288 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -239,7 +239,6 @@ default_plugins = essential_plugins + ( "nose", "assertion", "junitxml", - "resultlog", "doctest", "cacheprovider", "freeze_support", diff --git a/src/_pytest/deprecated.py b/src/_pytest/deprecated.py index 500fbe2f8..ecdb60d37 100644 --- a/src/_pytest/deprecated.py +++ b/src/_pytest/deprecated.py @@ -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" diff --git a/src/_pytest/resultlog.py b/src/_pytest/resultlog.py deleted file mode 100644 index 686f7f3b0..000000000 --- a/src/_pytest/resultlog.py +++ /dev/null @@ -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)) diff --git a/testing/deprecated_test.py b/testing/deprecated_test.py index 5660b312a..d3db792ab 100644 --- a/testing/deprecated_test.py +++ b/testing/deprecated_test.py @@ -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 diff --git a/testing/test_conftest.py b/testing/test_conftest.py index d1a69f4ba..5a4764080 100644 --- a/testing/test_conftest.py +++ b/testing/test_conftest.py @@ -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( diff --git a/testing/test_resultlog.py b/testing/test_resultlog.py deleted file mode 100644 index f2eb612c1..000000000 --- a/testing/test_resultlog.py +++ /dev/null @@ -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 diff --git a/testing/test_warnings.py b/testing/test_warnings.py index d26c71ca3..685e1365d 100644 --- a/testing/test_warnings.py +++ b/testing/test_warnings.py @@ -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.