Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,7 @@ Simon Holesch
Simon Kerr
Skylar Downes
Srinivas Reddy Thatiparthy
starx710
Stefaan Lippens
Stefan Farmbauer
Stefan Scherfke
Expand Down
3 changes: 3 additions & 0 deletions changelog/0.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Live logging no longer increases progress verbosity when the current process has
no local tests to run, such as a pytest-xdist controller. Serial live logging
continues to show the current test and its outcome.
7 changes: 6 additions & 1 deletion src/_pytest/logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -820,8 +820,13 @@ def pytest_runtestloop(self, session: Session) -> Generator[None, object, object
if session.config.option.collectonly:
return (yield)

if self._log_cli_enabled() and self._config.get_verbosity() < 1:
if (
session.items
and self._log_cli_enabled()
and self._config.get_verbosity() < 1
):
# The verbose flag is needed to avoid messy test progress output.
# A distributed controller has no local items whose logs need it.
self._config.option.verbose = 1

with catching_logs(self.log_cli_handler, level=self.log_cli_level):
Expand Down
110 changes: 110 additions & 0 deletions testing/logging/test_reporting.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,116 @@ def test_log_cli():
assert msg not in result.stdout.str()


@pytest.mark.parametrize("log_args", [["-o", "log_cli=true"], ["--log-cli-level=INFO"]])
@pytest.mark.parametrize("verbosity", [[], ["-q"], ["-v"]])
def test_log_cli_without_local_tests(
pytester: Pytester, log_args: list[str], verbosity: list[str]
) -> None:
pytester.makeconftest(
"""
import logging
from pathlib import Path

def pytest_runtestloop(session):
logging.warning("message without local tests")

def pytest_sessionfinish(session):
Path("verbosity").write_text(str(session.config.option.verbose), encoding="utf-8")
"""
)
result = pytester.runpytest(
*log_args, *verbosity, "--log-file=hooks.log", "--log-file-level=WARNING"
)
assert result.ret == ExitCode.NO_TESTS_COLLECTED
assert (pytester.path / "verbosity").read_text(encoding="utf-8") == str(
verbosity.count("-v") - verbosity.count("-q")
)
result.stdout.fnmatch_lines(["*message without local tests*"])
assert "message without local tests" in (pytester.path / "hooks.log").read_text(
encoding="utf-8"
)


@pytest.mark.parametrize("log_args", [["-o", "log_cli=true"], ["--log-cli-level=INFO"]])
@pytest.mark.parametrize("verbosity", [[], ["-q"], ["-qq"], ["-v"], ["-vv"]])
def test_log_cli_xdist_verbosity(
pytester: Pytester, log_args: list[str], verbosity: list[str]
) -> None:
pytest.importorskip("xdist")
pytester.makeconftest(
"""
from pathlib import Path

def pytest_sessionfinish(session):
if not hasattr(session.config, "workerinput"):
Path("verbosity").write_text(str(session.config.option.verbose), encoding="utf-8")
"""
)
pytester.makepyfile(
"""
import logging

def test_one(caplog):
logging.warning("worker log remains captured")
assert "worker log remains captured" in caplog.text

def test_two():
pass
"""
)
result = pytester.runpytest_subprocess(
"-p", "xdist.plugin", "-n2", *log_args, *verbosity
)
assert result.ret == ExitCode.OK
if verbosity != ["-qq"]:
result.assert_outcomes(passed=2)
expected_verbosity = {"-q": -1, "-qq": -2, "-v": 1, "-vv": 2}
assert (pytester.path / "verbosity").read_text(encoding="utf-8") == str(
expected_verbosity.get(verbosity[0], 0) if verbosity else 0
)
if verbosity in (["-v"], ["-vv"]):
result.stdout.fnmatch_lines(["*PASSED*test_log_cli_xdist_verbosity.py::test_*"])
else:
result.stdout.re_match_lines([r"\.\.\s+\[100%\]"])
result.stdout.no_fnmatch_line("*PASSED*test_log_cli_xdist_verbosity.py::test_*")
if verbosity == ["-qq"]:
result.stdout.no_fnmatch_line("*2 passed*")


@pytest.mark.parametrize("log_args", [["-o", "log_cli=true"], ["--log-cli-level=INFO"]])
@pytest.mark.parametrize(
("dist_args", "distributed"),
[
(["-n1"], True),
(["--dist=load", "--tx=popen"], True),
(["-n0"], False),
(["--dist=no"], False),
],
)
def test_log_cli_xdist_execution_modes(
pytester: Pytester, log_args: list[str], dist_args: list[str], distributed: bool
) -> None:
pytest.importorskip("xdist")
pytester.makepyfile(
"""
import logging

def test_log():
logging.warning("message from test")
"""
)
result = pytester.runpytest_subprocess("-p", "xdist.plugin", *dist_args, *log_args)
result.assert_outcomes(passed=1)
assert result.ret == ExitCode.OK
if distributed:
result.stdout.re_match_lines([r"\.\s+\[100%\]"])
result.stdout.no_fnmatch_line(
"*PASSED*test_log_cli_xdist_execution_modes.py::test_log*"
)
else:
result.stdout.fnmatch_lines(["*message from test*", "PASSED*"])


def test_log_cli_default_level(pytester: Pytester) -> None:
# Default log file level
pytester.makepyfile(
Expand Down