From fc5c9603041251225a2ddc7caf77e9fb3d013e68 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Sun, 28 Apr 2024 01:39:36 +0300 Subject: [PATCH 1/8] runner: avoid adding uninteresting entry to tracebacks In these two cases which re-raise an immediately-caught exception, do the re-raising with `raise` instead of `raise exc`, because the `raise exc` adds an uninteresting entry to the exception's traceback (that of itself), while `raise` doesn't. --- src/_pytest/runner.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/_pytest/runner.py b/src/_pytest/runner.py index 3f706b927..6883a1a58 100644 --- a/src/_pytest/runner.py +++ b/src/_pytest/runner.py @@ -180,7 +180,7 @@ def pytest_runtest_call(item: Item) -> None: assert e.__traceback__ is not None # Skip *this* frame sys.last_traceback = e.__traceback__.tb_next - raise e + raise def pytest_runtest_teardown(item: Item, nextitem: Optional[Item]) -> None: @@ -512,7 +512,7 @@ class SetupState: col.setup() except TEST_OUTCOME as exc: self.stack[col] = (self.stack[col][0], exc) - raise exc + raise def addfinalizer(self, finalizer: Callable[[], object], node: Node) -> None: """Attach a finalizer to the given node. From 1a84d233f3a64cab72795b236523bdff95781c81 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Sat, 27 Apr 2024 01:41:45 +0300 Subject: [PATCH 2/8] terminal: some minor code cleanups No logical changed intended. --- src/_pytest/terminal.py | 41 ++++++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/src/_pytest/terminal.py b/src/_pytest/terminal.py index 724d5c54d..c0ca97df6 100644 --- a/src/_pytest/terminal.py +++ b/src/_pytest/terminal.py @@ -432,7 +432,7 @@ class TerminalReporter: char = {"xfailed": "x", "skipped": "s"}.get(char, char) return char in self.reportchars - def write_fspath_result(self, nodeid: str, res, **markup: bool) -> None: + def write_fspath_result(self, nodeid: str, res: str, **markup: bool) -> None: fspath = self.config.rootpath / nodeid.split("::")[0] if self.currentfspath is None or fspath != self.currentfspath: if self.currentfspath is not None and self._show_progress_info: @@ -565,10 +565,11 @@ class TerminalReporter: def pytest_runtest_logstart( self, nodeid: str, location: Tuple[str, Optional[int], str] ) -> None: + fspath, lineno, domain = location # Ensure that the path is printed before the # 1st test of a module starts running. if self.showlongtestinfo: - line = self._locationline(nodeid, *location) + line = self._locationline(nodeid, fspath, lineno, domain) self.write_ensure_prefix(line, "") self.flush() elif self.showfspath: @@ -591,7 +592,6 @@ class TerminalReporter: if not letter and not word: # Probably passed setup/teardown. return - running_xdist = hasattr(rep, "node") if markup is None: was_xfail = hasattr(report, "wasxfail") if rep.passed and not was_xfail: @@ -609,6 +609,7 @@ class TerminalReporter: else: self._progress_nodeids_reported.add(rep.nodeid) line = self._locationline(rep.nodeid, *rep.location) + running_xdist = hasattr(rep, "node") if not running_xdist: self.write_ensure_prefix(line, word, **markup) if rep.skipped or hasattr(report, "wasxfail"): @@ -649,38 +650,26 @@ class TerminalReporter: return len(self._progress_nodeids_reported) == self._session.testscollected def pytest_runtest_logfinish(self, nodeid: str) -> None: - assert self._session if ( self.config.get_verbosity(Config.VERBOSITY_TEST_CASES) <= 0 and self._show_progress_info ): - if self._show_progress_info == "count": - num_tests = self._session.testscollected - progress_length = len(f" [{num_tests}/{num_tests}]") - else: - progress_length = len(" [100%]") - self._progress_nodeids_reported.add(nodeid) if self._is_last_item: self._write_progress_information_filling_space() else: - main_color, _ = self._get_main_color() - w = self._width_of_current_line - past_edge = w + progress_length + 1 >= self._screen_width - if past_edge: - msg = self._get_progress_information_message() - self._tw.write(msg + "\n", **{main_color: True}) + self._write_progress_information_if_past_edge() def _get_progress_information_message(self) -> str: assert self._session collected = self._session.testscollected if self._show_progress_info == "count": if collected: - progress = self._progress_nodeids_reported + progress = len(self._progress_nodeids_reported) counter_format = f"{{:{len(str(collected))}d}}" format_string = f" [{counter_format}/{{}}]" - return format_string.format(len(progress), collected) + return format_string.format(progress, collected) return f" [ {collected} / {collected} ]" else: if collected: @@ -689,6 +678,20 @@ class TerminalReporter: ) return " [100%]" + def _write_progress_information_if_past_edge(self) -> None: + w = self._width_of_current_line + if self._show_progress_info == "count": + assert self._session + num_tests = self._session.testscollected + progress_length = len(f" [{num_tests}/{num_tests}]") + else: + progress_length = len(" [100%]") + past_edge = w + progress_length + 1 >= self._screen_width + if past_edge: + main_color, _ = self._get_main_color() + msg = self._get_progress_information_message() + self._tw.write(msg + "\n", **{main_color: True}) + def _write_progress_information_filling_space(self) -> None: color, _ = self._get_main_color() msg = self._get_progress_information_message() @@ -937,7 +940,7 @@ class TerminalReporter: line += "[".join(values) return line - # collect_fspath comes from testid which has a "/"-normalized path. + # fspath comes from testid which has a "/"-normalized path. if fspath: res = mkrel(nodeid) if self.verbosity >= 2 and nodeid.split("::")[0] != fspath.replace( From 50d1e81713af75ab6d3608d2920ddf21bf53d765 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Sat, 27 Apr 2024 13:50:31 +0300 Subject: [PATCH 3/8] terminal: fix progress percentages not aligning correctly in xdist Fix #7166 --- changelog/7166.bugfix.rst | 1 + src/_pytest/terminal.py | 24 +++++++++++++++++------- 2 files changed, 18 insertions(+), 7 deletions(-) create mode 100644 changelog/7166.bugfix.rst diff --git a/changelog/7166.bugfix.rst b/changelog/7166.bugfix.rst new file mode 100644 index 000000000..98e6821f2 --- /dev/null +++ b/changelog/7166.bugfix.rst @@ -0,0 +1 @@ +Fixed progress percentages (the ``[ 87%]`` at the edge of the screen) sometimes not aligning correctly when running with pytest-xdist ``-n``. diff --git a/src/_pytest/terminal.py b/src/_pytest/terminal.py index c0ca97df6..f4b6e9b40 100644 --- a/src/_pytest/terminal.py +++ b/src/_pytest/terminal.py @@ -604,10 +604,18 @@ class TerminalReporter: markup = {"yellow": True} else: markup = {} + self._progress_nodeids_reported.add(rep.nodeid) if self.config.get_verbosity(Config.VERBOSITY_TEST_CASES) <= 0: self._tw.write(letter, **markup) + # When running in xdist, the logreport and logfinish of multiple + # items are interspersed, e.g. `logreport`, `logreport`, + # `logfinish`, `logfinish`. To avoid the "past edge" calculation + # from getting confused and overflowing (#7166), do the past edge + # printing here and not in logfinish, except for the 100% which + # should only be printed after all teardowns are finished. + if self._show_progress_info and not self._is_last_item: + self._write_progress_information_if_past_edge() else: - self._progress_nodeids_reported.add(rep.nodeid) line = self._locationline(rep.nodeid, *rep.location) running_xdist = hasattr(rep, "node") if not running_xdist: @@ -649,17 +657,19 @@ class TerminalReporter: assert self._session is not None return len(self._progress_nodeids_reported) == self._session.testscollected - def pytest_runtest_logfinish(self, nodeid: str) -> None: + @hookimpl(wrapper=True) + def pytest_runtestloop(self) -> Generator[None, object, object]: + result = yield + + # Write the final/100% progress -- deferred until the loop is complete. if ( self.config.get_verbosity(Config.VERBOSITY_TEST_CASES) <= 0 and self._show_progress_info + and self._progress_nodeids_reported ): - self._progress_nodeids_reported.add(nodeid) + self._write_progress_information_filling_space() - if self._is_last_item: - self._write_progress_information_filling_space() - else: - self._write_progress_information_if_past_edge() + return result def _get_progress_information_message(self) -> str: assert self._session From 85bc9ca9541e83ba6f171edc60daec9503054633 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Sun, 28 Apr 2024 17:03:52 +0300 Subject: [PATCH 4/8] pre-commit: remove deprecated `fix-encoding-pragma` hook Deprecated by pre-commit. --- .pre-commit-config.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a80edd28c..d54c08136 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,8 +10,6 @@ repos: hooks: - id: trailing-whitespace - id: end-of-file-fixer - - id: fix-encoding-pragma - args: [--remove] - id: check-yaml - id: debug-statements exclude: _pytest/(debugging|hookspec).py From e847b2a5a99d8d9af77c23c625cdb6121c04ad31 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Sun, 28 Apr 2024 17:04:27 +0300 Subject: [PATCH 5/8] pre-commit: replace `debug-statements` hook with ruff Ruff supports this functionality so we can use it. --- .pre-commit-config.yaml | 3 --- pyproject.toml | 1 + src/_pytest/debugging.py | 1 + src/_pytest/hookspec.py | 1 + 4 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d54c08136..0225a6a22 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,9 +11,6 @@ repos: - id: trailing-whitespace - id: end-of-file-fixer - id: check-yaml - - id: debug-statements - exclude: _pytest/(debugging|hookspec).py - language_version: python3 - repo: https://github.com/adamchainz/blacken-docs rev: 1.16.0 hooks: diff --git a/pyproject.toml b/pyproject.toml index 43efacf09..2be02ee7e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -107,6 +107,7 @@ select = [ "PLE", # pylint error "PLW", # pylint warning "PLR1714", # Consider merging multiple comparisons + "T100", # flake8-debugger ] ignore = [ # bugbear ignore diff --git a/src/_pytest/debugging.py b/src/_pytest/debugging.py index 6ed0c5c7a..7beab563e 100644 --- a/src/_pytest/debugging.py +++ b/src/_pytest/debugging.py @@ -1,4 +1,5 @@ # mypy: allow-untyped-defs +# ruff: noqa: T100 """Interactive debugging with PDB, the Python Debugger.""" import argparse diff --git a/src/_pytest/hookspec.py b/src/_pytest/hookspec.py index acfe7eb95..01d3b6640 100644 --- a/src/_pytest/hookspec.py +++ b/src/_pytest/hookspec.py @@ -1,4 +1,5 @@ # mypy: allow-untyped-defs +# ruff: noqa: T100 """Hook specifications for pytest plugins which are invoked by pytest itself and by builtin plugins.""" From 940b78232e48c34501cfe6e0bfd0ea6d64f4521b Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Sun, 28 Apr 2024 17:11:11 +0300 Subject: [PATCH 6/8] pre-commit: fix misleading indentation --- .pre-commit-config.yaml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0225a6a22..6cebeeb46 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -45,13 +45,13 @@ repos: additional_dependencies: ["tox>=4.9"] - repo: local hooks: - - id: pylint - name: pylint - entry: pylint - language: system - types: [python] - args: ["-rn", "-sn", "--fail-on=I"] - stages: [manual] + - id: pylint + name: pylint + entry: pylint + language: system + types: [python] + args: ["-rn", "-sn", "--fail-on=I"] + stages: [manual] - id: rst name: rst entry: rst-lint --encoding utf-8 From 2ede8778d063756663c508f0a39c949d4943e03d Mon Sep 17 00:00:00 2001 From: dj <112573278+dheerajck@users.noreply.github.com> Date: Mon, 29 Apr 2024 00:22:29 +0530 Subject: [PATCH 7/8] Document using PYTEST_VERSION to detect if a code is running inside pytest (#12153) Related to #9502 --- changelog/12153.doc.rst | 1 + doc/en/example/simple.rst | 25 +++++-------------------- 2 files changed, 6 insertions(+), 20 deletions(-) create mode 100644 changelog/12153.doc.rst diff --git a/changelog/12153.doc.rst b/changelog/12153.doc.rst new file mode 100644 index 000000000..ac36becf9 --- /dev/null +++ b/changelog/12153.doc.rst @@ -0,0 +1 @@ +Documented using :envvar:`PYTEST_VERSION` to detect if code is running from within a pytest run. diff --git a/doc/en/example/simple.rst b/doc/en/example/simple.rst index 7064f61f0..dec1bed5f 100644 --- a/doc/en/example/simple.rst +++ b/doc/en/example/simple.rst @@ -405,35 +405,20 @@ Detect if running from within a pytest run Usually it is a bad idea to make application code behave differently if called from a test. But if you absolutely must find out if your application code is -running from a test you can do something like this: +running from a test you can do this: .. code-block:: python - # content of your_module.py + import os - _called_from_test = False - -.. code-block:: python - - # content of conftest.py - - - def pytest_configure(config): - your_module._called_from_test = True - -and then check for the ``your_module._called_from_test`` flag: - -.. code-block:: python - - if your_module._called_from_test: - # called from within a test run + if os.environ.get("PYTEST_VERSION") is not None: + # Things you want to to do if your code is called by pytest. ... else: - # called "normally" + # Things you want to to do if your code is not called by pytest. ... -accordingly in your application. Adding info to test report header -------------------------------------------------------------- From f138f1d288033c12dec0c0b932e8c112489dc751 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 28 Apr 2024 19:56:47 +0000 Subject: [PATCH 8/8] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- changelog/12231.feature.rst | 10 ++++------ testing/test_terminal.py | 2 +- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/changelog/12231.feature.rst b/changelog/12231.feature.rst index 6369c92f7..cddb1e2a8 100644 --- a/changelog/12231.feature.rst +++ b/changelog/12231.feature.rst @@ -1,16 +1,14 @@ -Add `--xfail-tb` flag, which acts a lot like `--tb`, but controls the traceback output for XFAIL results. +Add `--xfail-tb` flag, which acts a lot like `--tb`, but controls the traceback output for XFAIL results. -* The `--xfail-tb` flag takes one argument, `auto|long|short|line|native|no`, and defaults to `no`. +* The `--xfail-tb` flag takes one argument, `auto|long|short|line|native|no`, and defaults to `no`. * To turn on xfail tracebacks, pass in `-rx` or `-ra` along with `--xfail-tb=short` (or any of `auto`, `long`, `short`, `line`, or `native`). -The `--tb` flag, that controls normal test failure tracebacks, defaults to `auto`. +The `--tb` flag, that controls normal test failure tracebacks, defaults to `auto`. This change really only separates the traceback behavior of normal vs xfail failures. -Some history: +Some history: * This is a behavior break, but brings default behavior back to pre-8.0.0 behavior. * With this change, default `-rx`/ `-ra` behavior is identical to pre-8.0 with respect to xfail tracebacks. * With pytest 8.0, `-rx` or `-ra` would not only turn on summary reports for xfail, but also report the tracebacks for xfail results. * This caused issues with some projects that utilize xfail, but don't want to see all of the xfail tracebacks. - - diff --git a/testing/test_terminal.py b/testing/test_terminal.py index 4957c0e02..f58605be9 100644 --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -2933,7 +2933,7 @@ def test_xfail_tb_default(pytester: Pytester) -> None: "*= 1 xfailed in * =*", ] ) - + def test_xfail_tb_short(pytester: Pytester) -> None: pytester.makepyfile(