Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into raisesgroup
Browse files Browse the repository at this point in the history
  • Loading branch information
jakkdl committed Feb 18, 2025
2 parents 9714dc0 + b0caf3d commit ad6542e
Show file tree
Hide file tree
Showing 26 changed files with 437 additions and 263 deletions.
6 changes: 3 additions & 3 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: "v0.9.4"
rev: "v0.9.6"
hooks:
- id: ruff
args: ["--fix"]
Expand All @@ -12,7 +12,7 @@ repos:
- id: end-of-file-fixer
- id: check-yaml
- repo: https://github.com/woodruffw/zizmor-pre-commit
rev: v1.3.0
rev: v1.3.1
hooks:
- id: zizmor
- repo: https://github.com/adamchainz/blacken-docs
Expand All @@ -32,7 +32,7 @@ repos:
hooks:
- id: python-use-type-annotations
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.14.1
rev: v1.15.0
hooks:
- id: mypy
files: ^(src/|testing/|scripts/)
Expand Down
17 changes: 17 additions & 0 deletions changelog/11381.improvement.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
The ``type`` parameter of the ``parser.addini`` method now accepts `"int"` and ``"float"`` parameters, facilitating the parsing of configuration values in the configuration file.

Example:

.. code-block:: python
def pytest_addoption(parser):
parser.addini("int_value", type="int", default=2, help="my int value")
parser.addini("float_value", type="float", default=4.2, help="my float value")
The `pytest.ini` file:

.. code-block:: ini
[pytest]
int_value = 3
float_value = 5.4
1 change: 1 addition & 0 deletions changelog/13175.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The diff is now also highlighted correctly when comparing two strings.
1 change: 1 addition & 0 deletions changelog/13221.doc.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Improved grouping of CLI options in the ``--help`` output.
1 change: 1 addition & 0 deletions changelog/7683.improvement.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The formerly optional ``pygments`` dependency is now required, causing output always to be source-highlighted (unless disabled via the ``--code-highlight=no`` CLI option).
2 changes: 1 addition & 1 deletion doc/en/how-to/capture-stdout-stderr.rst
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ test from having to care about setting/resetting
output streams and also interacts well with pytest's
own per-test capturing.

The return value from ``readouterr`` changed to a ``namedtuple`` with two attributes, ``out`` and ``err``.
The return value of ``readouterr()`` is a ``namedtuple`` with two attributes, ``out`` and ``err``.

If the code under test writes non-textual data (``bytes``), you can capture this using
the :fixture:`capsysbinary` fixture which instead returns ``bytes`` from
Expand Down
2 changes: 1 addition & 1 deletion doc/en/how-to/capture-warnings.rst
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ See :ref:`@pytest.mark.filterwarnings <filterwarnings>` and
the :envvar:`python:PYTHONWARNINGS` environment variable or the
``-W`` command-line option, pytest will not configure any filters by default.

Also pytest doesn't follow :pep:`506` suggestion of resetting all warning filters because
Also pytest doesn't follow :pep:`565` suggestion of resetting all warning filters because
it might break test suites that configure warning filters themselves
by calling :func:`warnings.simplefilter` (see :issue:`2430` for an example of that).

Expand Down
4 changes: 0 additions & 4 deletions doc/en/how-to/parametrize.rst
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,6 @@ pytest enables test parametrization at several levels:

.. regendoc: wipe
Several improvements.
The builtin :ref:`pytest.mark.parametrize ref` decorator enables
parametrization of arguments for a test function. Here is a typical example
of a test function that implements checking that a certain input leads
Expand Down
224 changes: 128 additions & 96 deletions doc/en/reference/plugin_list.rst

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,14 @@ dependencies = [
"iniconfig",
"packaging",
"pluggy>=1.5,<2",
"pygments>=2.7.2",
"tomli>=1; python_version<'3.11'",
]
optional-dependencies.dev = [
"argcomplete",
"attrs>=19.2",
"hypothesis>=3.56",
"mock",
"pygments>=2.7.2",
"requests",
"setuptools",
"xmlschema",
Expand Down
54 changes: 17 additions & 37 deletions src/_pytest/_io/terminalwriter.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,17 @@
from typing import final
from typing import Literal
from typing import TextIO
from typing import TYPE_CHECKING

import pygments
from pygments.formatters.terminal import TerminalFormatter
from pygments.lexer import Lexer
from pygments.lexers.diff import DiffLexer
from pygments.lexers.python import PythonLexer

from ..compat import assert_never
from .wcwidth import wcswidth


if TYPE_CHECKING:
from pygments.formatter import Formatter
from pygments.lexer import Lexer


# This code was initially copied from py 1.8.1, file _io/terminalwriter.py.


Expand Down Expand Up @@ -201,37 +201,22 @@ def _write_source(self, lines: Sequence[str], indents: Sequence[str] = ()) -> No
for indent, new_line in zip(indents, new_lines):
self.line(indent + new_line)

def _get_pygments_lexer(self, lexer: Literal["python", "diff"]) -> Lexer | None:
try:
if lexer == "python":
from pygments.lexers.python import PythonLexer

return PythonLexer()
elif lexer == "diff":
from pygments.lexers.diff import DiffLexer

return DiffLexer()
else:
assert_never(lexer)
except ModuleNotFoundError:
return None

def _get_pygments_formatter(self) -> Formatter | None:
try:
import pygments.util
except ModuleNotFoundError:
return None
def _get_pygments_lexer(self, lexer: Literal["python", "diff"]) -> Lexer:
if lexer == "python":
return PythonLexer()
elif lexer == "diff":
return DiffLexer()
else:
assert_never(lexer)

def _get_pygments_formatter(self) -> TerminalFormatter:
from _pytest.config.exceptions import UsageError

theme = os.getenv("PYTEST_THEME")
theme_mode = os.getenv("PYTEST_THEME_MODE", "dark")

try:
from pygments.formatters.terminal import TerminalFormatter

return TerminalFormatter(bg=theme_mode, style=theme)

except pygments.util.ClassNotFound as e:
raise UsageError(
f"PYTEST_THEME environment variable has an invalid value: '{theme}'. "
Expand All @@ -251,16 +236,11 @@ def _highlight(
return source

pygments_lexer = self._get_pygments_lexer(lexer)
if pygments_lexer is None:
return source

pygments_formatter = self._get_pygments_formatter()
if pygments_formatter is None:
return source

from pygments import highlight

highlighted: str = highlight(source, pygments_lexer, pygments_formatter)
highlighted: str = pygments.highlight(
source, pygments_lexer, pygments_formatter
)
# pygments terminal formatter may add a newline when there wasn't one.
# We don't want this, remove.
if highlighted[-1] == "\n" and source[-1] != "\n":
Expand Down
29 changes: 22 additions & 7 deletions src/_pytest/assertion/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,14 @@ def __call__(self, source: str, lexer: Literal["diff", "python"] = "python") ->
"""Apply highlighting to the given source."""


def dummy_highlighter(source: str, lexer: Literal["diff", "python"] = "python") -> str:
"""Dummy highlighter that returns the text unprocessed.
Needed for _notin_text, as the diff gets post-processed to only show the "+" part.
"""
return source


def format_explanation(explanation: str) -> str:
r"""Format an explanation.
Expand Down Expand Up @@ -242,7 +250,7 @@ def _compare_eq_any(
) -> list[str]:
explanation = []
if istext(left) and istext(right):
explanation = _diff_text(left, right, verbose)
explanation = _diff_text(left, right, highlighter, verbose)
else:
from _pytest.python_api import ApproxBase

Expand Down Expand Up @@ -274,7 +282,9 @@ def _compare_eq_any(
return explanation


def _diff_text(left: str, right: str, verbose: int = 0) -> list[str]:
def _diff_text(
left: str, right: str, highlighter: _HighlightFunc, verbose: int = 0
) -> list[str]:
"""Return the explanation for the diff between text.
Unless --verbose is used this will skip leading and trailing
Expand Down Expand Up @@ -315,10 +325,15 @@ def _diff_text(left: str, right: str, verbose: int = 0) -> list[str]:
explanation += ["Strings contain only whitespace, escaping them using repr()"]
# "right" is the expected base against which we compare "left",
# see https://github.com/pytest-dev/pytest/issues/3333
explanation += [
line.strip("\n")
for line in ndiff(right.splitlines(keepends), left.splitlines(keepends))
]
explanation.extend(
highlighter(
"\n".join(
line.strip("\n")
for line in ndiff(right.splitlines(keepends), left.splitlines(keepends))
),
lexer="diff",
).splitlines()
)
return explanation


Expand Down Expand Up @@ -586,7 +601,7 @@ def _notin_text(term: str, text: str, verbose: int = 0) -> list[str]:
head = text[:index]
tail = text[index + len(term) :]
correct_text = head + tail
diff = _diff_text(text, correct_text, verbose)
diff = _diff_text(text, correct_text, dummy_highlighter, verbose)
newdiff = [f"{saferepr(term, maxsize=42)} is contained here:"]
for line in diff:
if line.startswith("Skipping"):
Expand Down
4 changes: 2 additions & 2 deletions src/_pytest/capture.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,15 @@

def pytest_addoption(parser: Parser) -> None:
group = parser.getgroup("general")
group._addoption(
group.addoption(
"--capture",
action="store",
default="fd",
metavar="method",
choices=["fd", "sys", "no", "tee-sys"],
help="Per-test capturing method: one of fd|sys|no|tee-sys",
)
group._addoption(
group._addoption( # private to use reserved lower-case short option
"-s",
action="store_const",
const="no",
Expand Down
22 changes: 19 additions & 3 deletions src/_pytest/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1587,6 +1587,8 @@ def getini(self, name: str):
``paths``, ``pathlist``, ``args`` and ``linelist`` : empty list ``[]``
``bool`` : ``False``
``string`` : empty string ``""``
``int`` : ``0``
``float`` : ``0.0``
If neither the ``default`` nor the ``type`` parameter is passed
while registering the configuration through
Expand All @@ -1605,9 +1607,11 @@ def getini(self, name: str):

# Meant for easy monkeypatching by legacypath plugin.
# Can be inlined back (with no cover removed) once legacypath is gone.
def _getini_unknown_type(self, name: str, type: str, value: str | list[str]):
msg = f"unknown configuration type: {type}"
raise ValueError(msg, value) # pragma: no cover
def _getini_unknown_type(self, name: str, type: str, value: object):
msg = (
f"Option {name} has unknown configuration type {type} with value {value!r}"
)
raise ValueError(msg) # pragma: no cover

def _getini(self, name: str):
try:
Expand Down Expand Up @@ -1656,6 +1660,18 @@ def _getini(self, name: str):
return _strtobool(str(value).strip())
elif type == "string":
return value
elif type == "int":
if not isinstance(value, str):
raise TypeError(
f"Expected an int string for option {name} of type integer, but got: {value!r}"
) from None
return int(value)
elif type == "float":
if not isinstance(value, str):
raise TypeError(
f"Expected a float string for option {name} of type float, but got: {value!r}"
) from None
return float(value)
elif type is None:
return value
else:
Expand Down
27 changes: 25 additions & 2 deletions src/_pytest/config/argparsing.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,12 @@ def addini(
* ``linelist``: a list of strings, separated by line breaks
* ``paths``: a list of :class:`pathlib.Path`, separated as in a shell
* ``pathlist``: a list of ``py.path``, separated as in a shell
* ``int``: an integer
* ``float``: a floating-point number
.. versionadded:: 8.4
The ``float`` and ``int`` types.
For ``paths`` and ``pathlist`` types, they are considered relative to the ini-file.
In case the execution is happening without an ini-file defined,
Expand All @@ -209,7 +215,17 @@ def addini(
The value of ini-variables can be retrieved via a call to
:py:func:`config.getini(name) <pytest.Config.getini>`.
"""
assert type in (None, "string", "paths", "pathlist", "args", "linelist", "bool")
assert type in (
None,
"string",
"paths",
"pathlist",
"args",
"linelist",
"bool",
"int",
"float",
)
if default is NOT_SET:
default = get_ini_default_for_type(type)

Expand All @@ -218,7 +234,10 @@ def addini(


def get_ini_default_for_type(
type: Literal["string", "paths", "pathlist", "args", "linelist", "bool"] | None,
type: Literal[
"string", "paths", "pathlist", "args", "linelist", "bool", "int", "float"
]
| None,
) -> Any:
"""
Used by addini to get the default value for a given ini-option type, when
Expand All @@ -230,6 +249,10 @@ def get_ini_default_for_type(
return []
elif type == "bool":
return False
elif type == "int":
return 0
elif type == "float":
return 0.0
else:
return ""

Expand Down
Loading

0 comments on commit ad6542e

Please sign in to comment.