diff --git a/src/_pytest/assertion/__init__.py b/src/_pytest/assertion/__init__.py index 8ffb32d9d..8b442463c 100644 --- a/src/_pytest/assertion/__init__.py +++ b/src/_pytest/assertion/__init__.py @@ -13,6 +13,7 @@ from _pytest.assertion.rewrite import assertstate_key from _pytest.config import Config from _pytest.config import hookimpl from _pytest.config import OutputVerbosity +from _pytest.config import VerbosityType from _pytest.config.argparsing import Parser from _pytest.nodes import Item @@ -43,9 +44,9 @@ def pytest_addoption(parser: Parser) -> None: help="Enables the pytest_assertion_pass hook. " "Make sure to delete any previously generated pyc cache files.", ) - OutputVerbosity.add_ini( + OutputVerbosity._add_ini( parser, - "assertions", + VerbosityType.Assertions, help=( "Specify a verbosity level for assertions, overriding the main level. " "Higher levels will provide more detailed explanation when an assertion fails." diff --git a/src/_pytest/assertion/truncate.py b/src/_pytest/assertion/truncate.py index e6d870196..d472e239a 100644 --- a/src/_pytest/assertion/truncate.py +++ b/src/_pytest/assertion/truncate.py @@ -7,9 +7,9 @@ from typing import List from typing import Optional from _pytest.assertion import util +from _pytest.config import VerbosityType from _pytest.nodes import Item - DEFAULT_MAX_LINES = 8 DEFAULT_MAX_CHARS = 8 * 80 USAGE_MSG = "use '-vv' to show" @@ -26,7 +26,7 @@ def truncate_if_required( def _should_truncate_item(item: Item) -> bool: """Whether or not this test item is eligible for truncation.""" - verbose = item.config.output_verbosity.verbosity_for("assertions") + verbose = item.config.output_verbosity.get(VerbosityType.Assertions) return verbose < 2 and not util.running_on_ci() diff --git a/src/_pytest/assertion/util.py b/src/_pytest/assertion/util.py index b0ffd462c..266a4b6ec 100644 --- a/src/_pytest/assertion/util.py +++ b/src/_pytest/assertion/util.py @@ -18,6 +18,7 @@ from _pytest._io.saferepr import _pformat_dispatch from _pytest._io.saferepr import saferepr from _pytest._io.saferepr import saferepr_unlimited from _pytest.config import Config +from _pytest.config import VerbosityType # The _reprcompare attribute on the util module is used by the new assertion # interpretation code and assertion rewriter to detect this plugin was @@ -161,7 +162,7 @@ def assertrepr_compare( config, op: str, left: Any, right: Any, use_ascii: bool = False ) -> Optional[List[str]]: """Return specialised explanations for some operators/operands.""" - verbose = config.output_verbosity.verbosity_for("assertions") + verbose = config.output_verbosity.get(VerbosityType.Assertions) # Strings which normalize equal are often hard to distinguish when printed; use ascii() to make this easier. # See issue #3246. diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index 7ff1dace6..d222d4afe 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -13,6 +13,7 @@ import shlex import sys import types import warnings +from enum import Enum from functools import lru_cache from pathlib import Path from textwrap import dedent @@ -1663,6 +1664,13 @@ class Config: ) +class VerbosityType(Enum): + """Fine-grained verbosity categories.""" + + Global = "global" + Assertions = "assertions" + + class OutputVerbosity: DEFAULT = "auto" _option_name_fmt = "verbosity_{}" @@ -1675,36 +1683,36 @@ class OutputVerbosity: """Application wide verbosity level.""" return cast(int, self._config.option.verbose) - def verbosity_for(self, output_type: str) -> int: + def get(self, verbosity_type: VerbosityType = VerbosityType.Global) -> int: """Return verbosity level for the given output type. - :param output_type: Name of the 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(output_type)) + level = self._config.getini(OutputVerbosity._ini_name(verbosity_type)) if level == OutputVerbosity.DEFAULT: return self.verbose return int(level) @staticmethod - def _ini_name(output_type: str) -> str: - return f"verbosity_{output_type}" + def _ini_name(verbosity_type: VerbosityType) -> str: + return f"verbosity_{verbosity_type.value}" @staticmethod - def add_ini(parser: "Parser", output_type: str, help: str) -> None: + 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 output_type: Name of the output type. + :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.verbosity_for(name) `. + :py:func:`config.output_verbosity.get(type) `. """ parser.addini( - OutputVerbosity._ini_name(output_type), + OutputVerbosity._ini_name(verbosity_type), help=help, type="string", default=OutputVerbosity.DEFAULT, diff --git a/testing/test_assertion.py b/testing/test_assertion.py index afb47db05..b9828893e 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -13,6 +13,7 @@ import pytest from _pytest import outcomes from _pytest.assertion import truncate from _pytest.assertion import util +from _pytest.config import VerbosityType from _pytest.monkeypatch import MonkeyPatch from _pytest.pytester import Pytester @@ -23,13 +24,13 @@ def mock_config(verbose: int = 0, assertion_override: Optional[int] = None): def verbose(self) -> int: return verbose - def verbosity_for(self, output_type: str) -> int: - if output_type == "assertions": + def get(self, verbosity_type: VerbosityType = VerbosityType.Global) -> int: + if verbosity_type == VerbosityType.Assertions: if assertion_override is not None: return assertion_override return verbose - raise KeyError("Not mocked out: %s" % output_type) + raise KeyError(f"Not mocked out: {verbosity_type}") class Config: def __init__(self) -> None: @@ -47,30 +48,30 @@ class TestMockConfig: assert config.output_verbosity.verbose == TestMockConfig.SOME_VERBOSITY_LEVEL - def test_verbosity_for_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) assert ( - config.output_verbosity.verbosity_for("assertions") + config.output_verbosity.get(VerbosityType.Assertions) == TestMockConfig.SOME_VERBOSITY_LEVEL ) - def test_verbosity_for_assertion_override_set_custom_value(self): + def test_get_assertion_override_set_custom_value(self): config = mock_config( verbose=TestMockConfig.SOME_VERBOSITY_LEVEL, assertion_override=TestMockConfig.SOME_OTHER_VERBOSITY_LEVEL, ) assert ( - config.output_verbosity.verbosity_for("assertions") + config.output_verbosity.get(VerbosityType.Assertions) == TestMockConfig.SOME_OTHER_VERBOSITY_LEVEL ) - def test_verbosity_for_unsupported_type_error(self): + def test_get_unsupported_type_error(self): config = mock_config(verbose=TestMockConfig.SOME_VERBOSITY_LEVEL) with pytest.raises(KeyError): - config.output_verbosity.verbosity_for("NOT CONFIGURED NAME") + config.output_verbosity.get(VerbosityType.Global) class TestImportHookInstallation: diff --git a/testing/test_config.py b/testing/test_config.py index 744983ed5..5735958c6 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -22,6 +22,7 @@ from _pytest.config import ConftestImportFailure from _pytest.config import ExitCode from _pytest.config import OutputVerbosity from _pytest.config import parse_warning_filter +from _pytest.config import VerbosityType from _pytest.config.argparsing import Parser from _pytest.config.exceptions import UsageError from _pytest.config.findpaths import determine_setup @@ -2186,12 +2187,12 @@ class TestDebugOptions: class TestOutputVerbosity: - SOME_OUTPUT_TYPE = "foo" + SOME_OUTPUT_TYPE = VerbosityType.Assertions SOME_OUTPUT_VERBOSITY_LEVEL = 5 class VerbosityIni: def pytest_addoption(self, parser: Parser) -> None: - OutputVerbosity.add_ini( + OutputVerbosity._add_ini( parser, TestOutputVerbosity.SOME_OUTPUT_TYPE, help="some help text" ) @@ -2229,19 +2230,20 @@ class TestOutputVerbosity: config = pytester.parseconfig(tmp_path) assert ( - config.output_verbosity.verbosity_for(TestOutputVerbosity.SOME_OUTPUT_TYPE) + config.output_verbosity.get(TestOutputVerbosity.SOME_OUTPUT_TYPE) == config.output_verbosity.verbose ) def test_level_matches_specified_override( self, pytester: Pytester, tmp_path: Path ) -> None: + setting_name = f"verbosity_{TestOutputVerbosity.SOME_OUTPUT_TYPE.value}" tmp_path.joinpath("pytest.ini").write_text( textwrap.dedent( f"""\ [pytest] addopts = --verbose - verbosity_{TestOutputVerbosity.SOME_OUTPUT_TYPE} = {TestOutputVerbosity.SOME_OUTPUT_VERBOSITY_LEVEL} + {setting_name} = {TestOutputVerbosity.SOME_OUTPUT_VERBOSITY_LEVEL} """ ), encoding="utf-8", @@ -2251,6 +2253,6 @@ class TestOutputVerbosity: config = pytester.parseconfig(tmp_path) assert ( - config.output_verbosity.verbosity_for(TestOutputVerbosity.SOME_OUTPUT_TYPE) + config.output_verbosity.get(TestOutputVerbosity.SOME_OUTPUT_TYPE) == TestOutputVerbosity.SOME_OUTPUT_VERBOSITY_LEVEL )