Additional design changes from review

* Move OutputVerbosity functionality directly in Config
* Use strings instead of an enum for verbosity types
This commit is contained in:
Patrick Lannigan 2023-11-10 09:56:20 -05:00
parent b8714de594
commit 50c0b93770
No known key found for this signature in database
GPG Key ID: BBF5D9DED1E4AAF9
9 changed files with 90 additions and 134 deletions

View File

@ -2,4 +2,4 @@ Added the new :confval:`verbosity_assertions` configuration option for fine-grai
See :ref:`Fine-grained verbosity <pytest.fine_grained_verbosity>` for more details. See :ref:`Fine-grained verbosity <pytest.fine_grained_verbosity>` for more details.
For plugin authors, :attr:`config.output_verbosity <pytest.Config.output_verbosity>` can be used to retrieve the verbosity level for a specific :class:`pytest.VerbosityType`. For plugin authors, :attr:`config.output_verbosity <pytest.Config.get_verbosity>` can be used to retrieve the verbosity level for a specific verbosity type.

View File

@ -961,15 +961,6 @@ OptionGroup
.. autoclass:: pytest.OptionGroup() .. autoclass:: pytest.OptionGroup()
:members: :members:
OutputVerbosity
~~~~~~~~~~~~~~~
.. autoclass:: pytest.OutputVerbosity()
:members:
.. autoclass:: pytest.VerbosityType()
:members:
PytestPluginManager PytestPluginManager
~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~

View File

@ -12,8 +12,6 @@ from _pytest.assertion import util
from _pytest.assertion.rewrite import assertstate_key from _pytest.assertion.rewrite import assertstate_key
from _pytest.config import Config from _pytest.config import Config
from _pytest.config import hookimpl from _pytest.config import hookimpl
from _pytest.config import OutputVerbosity
from _pytest.config import VerbosityType
from _pytest.config.argparsing import Parser from _pytest.config.argparsing import Parser
from _pytest.nodes import Item from _pytest.nodes import Item
@ -44,9 +42,9 @@ def pytest_addoption(parser: Parser) -> None:
help="Enables the pytest_assertion_pass hook. " help="Enables the pytest_assertion_pass hook. "
"Make sure to delete any previously generated pyc cache files.", "Make sure to delete any previously generated pyc cache files.",
) )
OutputVerbosity._add_ini( Config._add_ini(
parser, parser,
VerbosityType.Assertions, Config.VERBOSITY_ASSERTIONS,
help=( help=(
"Specify a verbosity level for assertions, overriding the main level. " "Specify a verbosity level for assertions, overriding the main level. "
"Higher levels will provide more detailed explanation when an assertion fails." "Higher levels will provide more detailed explanation when an assertion fails."

View File

@ -7,7 +7,7 @@ from typing import List
from typing import Optional from typing import Optional
from _pytest.assertion import util from _pytest.assertion import util
from _pytest.config import VerbosityType from _pytest.config import Config
from _pytest.nodes import Item from _pytest.nodes import Item
DEFAULT_MAX_LINES = 8 DEFAULT_MAX_LINES = 8
@ -26,7 +26,7 @@ def truncate_if_required(
def _should_truncate_item(item: Item) -> bool: def _should_truncate_item(item: Item) -> bool:
"""Whether or not this test item is eligible for truncation.""" """Whether or not this test item is eligible for truncation."""
verbose = item.config.output_verbosity.get(VerbosityType.Assertions) verbose = item.config.get_verbosity(Config.VERBOSITY_ASSERTIONS)
return verbose < 2 and not util.running_on_ci() return verbose < 2 and not util.running_on_ci()

View File

@ -20,7 +20,6 @@ from _pytest._io.saferepr import _pformat_dispatch
from _pytest._io.saferepr import saferepr from _pytest._io.saferepr import saferepr
from _pytest._io.saferepr import saferepr_unlimited from _pytest._io.saferepr import saferepr_unlimited
from _pytest.config import Config from _pytest.config import Config
from _pytest.config import VerbosityType
# The _reprcompare attribute on the util module is used by the new assertion # The _reprcompare attribute on the util module is used by the new assertion
# interpretation code and assertion rewriter to detect this plugin was # interpretation code and assertion rewriter to detect this plugin was
@ -169,7 +168,7 @@ def assertrepr_compare(
config, op: str, left: Any, right: Any, use_ascii: bool = False config, op: str, left: Any, right: Any, use_ascii: bool = False
) -> Optional[List[str]]: ) -> Optional[List[str]]:
"""Return specialised explanations for some operators/operands.""" """Return specialised explanations for some operators/operands."""
verbose = config.output_verbosity.get(VerbosityType.Assertions) verbose = config.get_verbosity(Config.VERBOSITY_ASSERTIONS)
# Strings which normalize equal are often hard to distinguish when printed; use ascii() to make this easier. # Strings which normalize equal are often hard to distinguish when printed; use ascii() to make this easier.
# See issue #3246. # See issue #3246.

View File

@ -13,7 +13,6 @@ import shlex
import sys import sys
import types import types
import warnings import warnings
from enum import Enum
from functools import lru_cache from functools import lru_cache
from pathlib import Path from pathlib import Path
from textwrap import dedent from textwrap import dedent
@ -23,6 +22,7 @@ from typing import Any
from typing import Callable from typing import Callable
from typing import cast from typing import cast
from typing import Dict from typing import Dict
from typing import Final
from typing import final from typing import final
from typing import Generator from typing import Generator
from typing import IO from typing import IO
@ -1022,12 +1022,6 @@ class Config:
self.args_source = Config.ArgsSource.ARGS self.args_source = Config.ArgsSource.ARGS
self.args: List[str] = [] self.args: List[str] = []
self.output_verbosity = OutputVerbosity(self)
"""Access to output verbosity configuration.
:type: OutputVerbosity
"""
if TYPE_CHECKING: if TYPE_CHECKING:
from _pytest.cacheprovider import Cache from _pytest.cacheprovider import Cache
@ -1640,6 +1634,66 @@ class Config:
"""Deprecated, use getoption(skip=True) instead.""" """Deprecated, use getoption(skip=True) instead."""
return self.getoption(name, skip=True) return self.getoption(name, skip=True)
#: Verbosity for failed assertions (see :confval:`verbosity_assertions`).
VERBOSITY_ASSERTIONS: Final = "assertions"
_KNOWN_VERBOSITY_TYPES: Final = {VERBOSITY_ASSERTIONS}
_VERBOSITY_INI_DEFAULT = "auto"
def get_verbosity(self, verbosity_type: Optional[str] = None) -> int:
r"""Access to fine-grained verbosity levels.
.. code-block:: ini
# content of pytest.ini
[pytest]
verbosity_assertions = 2
.. code-block:: console
pytest -v
.. code-block:: python
print(config.get_verbosity()) # 1
print(config.get_verbosity(Config.VERBOSITY_ASSERTIONS)) # 2
"""
global_level = self.option.verbose
assert isinstance(global_level, int)
if (
verbosity_type is None
or verbosity_type not in Config._KNOWN_VERBOSITY_TYPES
):
return global_level
level = self.getini(Config._ini_name(verbosity_type))
if level == Config._VERBOSITY_INI_DEFAULT:
return global_level
return int(level)
@staticmethod
def _ini_name(verbosity_type: str) -> str:
return f"verbosity_{verbosity_type}"
@staticmethod
def _add_ini(parser: "Parser", verbosity_type: str, help: str) -> None:
"""Add a output verbosity configuration option for the given output type.
:param parser: Parser for command line arguments and ini-file values.
:param verbosity_type: Fine-grained verbosity category.
:param help: Description of the output this type controls.
The value should be retrieved via a call to
:py:func:`config.get_verbosity(type) <pytest.Config.get_verbosity>`.
"""
parser.addini(
Config._ini_name(verbosity_type),
help=help,
type="string",
default=Config._VERBOSITY_INI_DEFAULT,
)
def _warn_about_missing_assertion(self, mode: str) -> None: def _warn_about_missing_assertion(self, mode: str) -> None:
if not _assertion_supported(): if not _assertion_supported():
if mode == "plain": if mode == "plain":
@ -1669,80 +1723,6 @@ class Config:
) )
class VerbosityType(Enum):
"""Fine-grained verbosity categories."""
#: Application wide, controlled by ``-v``/``-q``.
Global = "global"
#: Verbosity for failed assertions (see :confval:`verbosity_assertions`).
Assertions = "assertions"
class OutputVerbosity:
r"""Access to fine-grained verbosity levels.
Access via :attr:`config.output_verbosity <pytest.Config.output_verbosity>`.
.. code-block:: ini
# content of pytest.ini
[pytest]
verbosity_assertions = 2
.. code-block:: console
pytest -v
.. code-block:: python
print(config.output_verbosity.get()) # 1
print(config.output_verbosity.get(VerbosityType.Assertions)) # 2
"""
DEFAULT = "auto"
def __init__(self, config: Config) -> None:
self._config = config
def get(self, verbosity_type: VerbosityType = VerbosityType.Global) -> int:
"""Return verbosity level for the given output type.
:param verbosity_type: Fine-grained verbosity category.
If the level is not configured, the value of ``config.option.verbose``.
"""
level = self._config.getini(OutputVerbosity._ini_name(verbosity_type))
if level == OutputVerbosity.DEFAULT:
assert isinstance(self._config.option.verbose, int)
return self._config.option.verbose
return int(level)
@staticmethod
def _ini_name(verbosity_type: VerbosityType) -> str:
return f"verbosity_{verbosity_type.value}"
@staticmethod
def _add_ini(parser: "Parser", verbosity_type: VerbosityType, help: str) -> None:
"""Add a output verbosity configuration option for the given output type.
:param parser: Parser for command line arguments and ini-file values.
:param verbosity_type: Fine-grained verbosity category.
:param help: Description of the output this type controls.
The value should be retrieved via a call to
:py:func:`config.output_verbosity.get(type) <pytest.OutputVerbosity.get>`.
"""
parser.addini(
OutputVerbosity._ini_name(verbosity_type),
help=help,
type="string",
default=OutputVerbosity.DEFAULT,
)
def _assertion_supported() -> bool: def _assertion_supported() -> bool:
try: try:
assert False assert False

View File

@ -15,10 +15,8 @@ from _pytest.config import ExitCode
from _pytest.config import hookimpl from _pytest.config import hookimpl
from _pytest.config import hookspec from _pytest.config import hookspec
from _pytest.config import main from _pytest.config import main
from _pytest.config import OutputVerbosity
from _pytest.config import PytestPluginManager from _pytest.config import PytestPluginManager
from _pytest.config import UsageError from _pytest.config import UsageError
from _pytest.config import VerbosityType
from _pytest.config.argparsing import OptionGroup from _pytest.config.argparsing import OptionGroup
from _pytest.config.argparsing import Parser from _pytest.config.argparsing import Parser
from _pytest.debugging import pytestPDB as __pytestPDB from _pytest.debugging import pytestPDB as __pytestPDB
@ -128,7 +126,6 @@ __all__ = [
"Module", "Module",
"MonkeyPatch", "MonkeyPatch",
"OptionGroup", "OptionGroup",
"OutputVerbosity",
"Package", "Package",
"param", "param",
"Parser", "Parser",
@ -164,7 +161,6 @@ __all__ = [
"TestReport", "TestReport",
"TestShortLogReport", "TestShortLogReport",
"UsageError", "UsageError",
"VerbosityType",
"WarningsRecorder", "WarningsRecorder",
"warns", "warns",
"xfail", "xfail",

View File

@ -13,7 +13,7 @@ import pytest
from _pytest import outcomes from _pytest import outcomes
from _pytest.assertion import truncate from _pytest.assertion import truncate
from _pytest.assertion import util from _pytest.assertion import util
from _pytest.config import VerbosityType from _pytest.config import Config as _Config
from _pytest.monkeypatch import MonkeyPatch from _pytest.monkeypatch import MonkeyPatch
from _pytest.pytester import Pytester from _pytest.pytester import Pytester
@ -23,26 +23,20 @@ def mock_config(verbose: int = 0, assertion_override: Optional[int] = None):
def _highlight(self, source, lexer): def _highlight(self, source, lexer):
return source return source
class OutputVerbosity: class Config:
@property def get_terminal_writer(self):
def verbose(self) -> int: return TerminalWriter()
return verbose
def get(self, verbosity_type: VerbosityType = VerbosityType.Global) -> int: def get_verbosity(self, verbosity_type: Optional[str] = None) -> int:
if verbosity_type == VerbosityType.Assertions: if verbosity_type is None:
return verbose
if verbosity_type == _Config.VERBOSITY_ASSERTIONS:
if assertion_override is not None: if assertion_override is not None:
return assertion_override return assertion_override
return verbose return verbose
raise KeyError(f"Not mocked out: {verbosity_type}") raise KeyError(f"Not mocked out: {verbosity_type}")
class Config:
def __init__(self) -> None:
self.output_verbosity = OutputVerbosity()
def get_terminal_writer(self):
return TerminalWriter()
return Config() return Config()
@ -53,13 +47,13 @@ class TestMockConfig:
def test_verbose_exposes_value(self): def test_verbose_exposes_value(self):
config = mock_config(verbose=TestMockConfig.SOME_VERBOSITY_LEVEL) config = mock_config(verbose=TestMockConfig.SOME_VERBOSITY_LEVEL)
assert config.output_verbosity.verbose == TestMockConfig.SOME_VERBOSITY_LEVEL assert config.get_verbosity() == TestMockConfig.SOME_VERBOSITY_LEVEL
def test_get_assertion_override_not_set_verbose_value(self): def test_get_assertion_override_not_set_verbose_value(self):
config = mock_config(verbose=TestMockConfig.SOME_VERBOSITY_LEVEL) config = mock_config(verbose=TestMockConfig.SOME_VERBOSITY_LEVEL)
assert ( assert (
config.output_verbosity.get(VerbosityType.Assertions) config.get_verbosity(_Config.VERBOSITY_ASSERTIONS)
== TestMockConfig.SOME_VERBOSITY_LEVEL == TestMockConfig.SOME_VERBOSITY_LEVEL
) )
@ -70,7 +64,7 @@ class TestMockConfig:
) )
assert ( assert (
config.output_verbosity.get(VerbosityType.Assertions) config.get_verbosity(_Config.VERBOSITY_ASSERTIONS)
== TestMockConfig.SOME_OTHER_VERBOSITY_LEVEL == TestMockConfig.SOME_OTHER_VERBOSITY_LEVEL
) )
@ -78,7 +72,7 @@ class TestMockConfig:
config = mock_config(verbose=TestMockConfig.SOME_VERBOSITY_LEVEL) config = mock_config(verbose=TestMockConfig.SOME_VERBOSITY_LEVEL)
with pytest.raises(KeyError): with pytest.raises(KeyError):
config.output_verbosity.get(VerbosityType.Global) config.get_verbosity("--- NOT A VERBOSITY LEVEL ---")
class TestImportHookInstallation: class TestImportHookInstallation:

View File

@ -20,9 +20,7 @@ from _pytest.config import _strtobool
from _pytest.config import Config from _pytest.config import Config
from _pytest.config import ConftestImportFailure from _pytest.config import ConftestImportFailure
from _pytest.config import ExitCode from _pytest.config import ExitCode
from _pytest.config import OutputVerbosity
from _pytest.config import parse_warning_filter from _pytest.config import parse_warning_filter
from _pytest.config import VerbosityType
from _pytest.config.argparsing import Parser from _pytest.config.argparsing import Parser
from _pytest.config.exceptions import UsageError from _pytest.config.exceptions import UsageError
from _pytest.config.findpaths import determine_setup from _pytest.config.findpaths import determine_setup
@ -2186,14 +2184,14 @@ class TestDebugOptions:
) )
class TestOutputVerbosity: class TestVerbosity:
SOME_OUTPUT_TYPE = VerbosityType.Assertions SOME_OUTPUT_TYPE = Config.VERBOSITY_ASSERTIONS
SOME_OUTPUT_VERBOSITY_LEVEL = 5 SOME_OUTPUT_VERBOSITY_LEVEL = 5
class VerbosityIni: class VerbosityIni:
def pytest_addoption(self, parser: Parser) -> None: def pytest_addoption(self, parser: Parser) -> None:
OutputVerbosity._add_ini( Config._add_ini(
parser, TestOutputVerbosity.SOME_OUTPUT_TYPE, help="some help text" parser, TestVerbosity.SOME_OUTPUT_TYPE, help="some help text"
) )
def test_level_matches_verbose_when_not_specified( def test_level_matches_verbose_when_not_specified(
@ -2208,34 +2206,34 @@ class TestOutputVerbosity:
), ),
encoding="utf-8", encoding="utf-8",
) )
pytester.plugins = [TestOutputVerbosity.VerbosityIni()] pytester.plugins = [TestVerbosity.VerbosityIni()]
config = pytester.parseconfig(tmp_path) config = pytester.parseconfig(tmp_path)
assert ( assert (
config.output_verbosity.get(TestOutputVerbosity.SOME_OUTPUT_TYPE) config.get_verbosity(TestVerbosity.SOME_OUTPUT_TYPE)
== config.option.verbose == config.option.verbose
) )
def test_level_matches_specified_override( def test_level_matches_specified_override(
self, pytester: Pytester, tmp_path: Path self, pytester: Pytester, tmp_path: Path
) -> None: ) -> None:
setting_name = f"verbosity_{TestOutputVerbosity.SOME_OUTPUT_TYPE.value}" setting_name = f"verbosity_{TestVerbosity.SOME_OUTPUT_TYPE}"
tmp_path.joinpath("pytest.ini").write_text( tmp_path.joinpath("pytest.ini").write_text(
textwrap.dedent( textwrap.dedent(
f"""\ f"""\
[pytest] [pytest]
addopts = --verbose addopts = --verbose
{setting_name} = {TestOutputVerbosity.SOME_OUTPUT_VERBOSITY_LEVEL} {setting_name} = {TestVerbosity.SOME_OUTPUT_VERBOSITY_LEVEL}
""" """
), ),
encoding="utf-8", encoding="utf-8",
) )
pytester.plugins = [TestOutputVerbosity.VerbosityIni()] pytester.plugins = [TestVerbosity.VerbosityIni()]
config = pytester.parseconfig(tmp_path) config = pytester.parseconfig(tmp_path)
assert ( assert (
config.output_verbosity.get(TestOutputVerbosity.SOME_OUTPUT_TYPE) config.get_verbosity(TestVerbosity.SOME_OUTPUT_TYPE)
== TestOutputVerbosity.SOME_OUTPUT_VERBOSITY_LEVEL == TestVerbosity.SOME_OUTPUT_VERBOSITY_LEVEL
) )