Coverage for lintro / tools / implementations / pytest / pytest_config.py: 92%
79 statements
« prev ^ index » next coverage.py v7.13.0, created at 2026-04-03 18:53 +0000
« prev ^ index » next coverage.py v7.13.0, created at 2026-04-03 18:53 +0000
1"""Pytest configuration management.
3This module contains the PytestConfiguration dataclass that encapsulates
4all pytest-specific option management and validation logic.
5"""
7from dataclasses import dataclass, field
8from typing import Any
10from lintro.enums.pytest_enums import PytestSpecialMode
11from lintro.tools.implementations.pytest.pytest_option_validators import (
12 validate_pytest_options,
13)
16@dataclass
17class PytestConfiguration:
18 """Configuration class for pytest-specific options.
20 This dataclass encapsulates all pytest configuration options and provides
21 validation and management methods. It follows the project's preference for
22 dataclasses and proper data modeling.
24 Attributes:
25 verbose: Enable verbose output.
26 tb: Traceback format (short, long, auto, line, native).
27 maxfail: Stop after first N failures.
28 no_header: Disable header.
29 disable_warnings: Disable warnings.
30 json_report: Enable JSON report output.
31 junitxml: Path for JUnit XML output.
32 slow_test_threshold: Duration threshold in seconds for slow test warning
33 (default: 1.0).
34 total_time_warning: Total execution time threshold in seconds for warning
35 (default: 60.0).
36 workers: Number of parallel workers for pytest-xdist (auto, N, or None).
37 coverage_threshold: Minimum coverage percentage to require (0-100).
38 auto_junitxml: Auto-enable junitxml in CI environments (default: True).
39 detect_flaky: Enable flaky test detection (default: True).
40 flaky_min_runs: Minimum runs before detecting flaky tests (default: 3).
41 flaky_failure_rate: Minimum failure rate to consider flaky (default: 0.3).
42 html_report: Path for HTML report output (pytest-html plugin).
43 parallel_preset: Parallel execution preset (auto, small, medium, large).
44 list_plugins: List all installed pytest plugins.
45 check_plugins: Check if required plugins are installed.
46 required_plugins: Comma-separated list of required plugin names.
47 coverage_html: Path for HTML coverage report (requires pytest-cov).
48 coverage_xml: Path for XML coverage report (requires pytest-cov).
49 coverage_report: Generate both HTML and XML coverage reports.
50 coverage_term_missing: Show coverage report in terminal with missing lines.
51 collect_only: List tests without executing them.
52 list_fixtures: List all available fixtures.
53 fixture_info: Show detailed information about a specific fixture.
54 list_markers: List all available markers.
55 parametrize_help: Show help for parametrized tests.
56 show_progress: Show progress during test execution (default: True).
57 timeout: Timeout in seconds for individual tests (pytest-timeout plugin).
58 reruns: Number of times to retry failed tests (pytest-rerunfailures plugin).
59 reruns_delay: Delay in seconds between retries (pytest-rerunfailures plugin).
60 """
62 # Constants for special modes
63 _SPECIAL_MODES = [
64 PytestSpecialMode.LIST_PLUGINS,
65 PytestSpecialMode.CHECK_PLUGINS,
66 PytestSpecialMode.COLLECT_ONLY,
67 PytestSpecialMode.LIST_FIXTURES,
68 PytestSpecialMode.LIST_MARKERS,
69 PytestSpecialMode.PARAMETRIZE_HELP,
70 ]
72 verbose: bool | None = field(default=None)
73 tb: str | None = field(default=None)
74 maxfail: int | None = field(default=None)
75 no_header: bool | None = field(default=None)
76 disable_warnings: bool | None = field(default=None)
77 json_report: bool | None = field(default=None)
78 junitxml: str | None = field(default=None)
79 slow_test_threshold: float | None = field(default=None)
80 total_time_warning: float | None = field(default=None)
81 workers: str | None = field(default=None)
82 coverage_threshold: float | None = field(default=None)
83 auto_junitxml: bool | None = field(default=None)
84 detect_flaky: bool | None = field(default=None)
85 flaky_min_runs: int | None = field(default=None)
86 flaky_failure_rate: float | None = field(default=None)
87 html_report: str | None = field(default=None)
88 parallel_preset: str | None = field(default=None)
89 list_plugins: bool | None = field(default=None)
90 check_plugins: bool | None = field(default=None)
91 required_plugins: str | None = field(default=None)
92 coverage_html: str | None = field(default=None)
93 coverage_xml: str | None = field(default=None)
94 coverage_report: bool | None = field(default=None)
95 coverage_term_missing: bool | None = field(default=None)
96 collect_only: bool | None = field(default=None)
97 list_fixtures: bool | None = field(default=None)
98 fixture_info: str | None = field(default=None)
99 list_markers: bool | None = field(default=None)
100 parametrize_help: bool | None = field(default=None)
101 show_progress: bool | None = field(default=None)
102 timeout: int | None = field(default=None)
103 reruns: int | None = field(default=None)
104 reruns_delay: int | None = field(default=None)
106 def set_options(self, **kwargs: Any) -> None:
107 """Set pytest-specific options with validation.
109 Args:
110 **kwargs: Option key-value pairs to set.
111 """
112 # Extract only the options that belong to this configuration
113 config_fields = {field.name for field in self.__dataclass_fields__.values()}
115 # Coerce workers to string when passed as int via CLI parsing
116 if "workers" in kwargs and isinstance(kwargs.get("workers"), int):
117 kwargs = kwargs.copy()
118 kwargs["workers"] = str(kwargs["workers"])
120 # Validate all options using extracted validator
121 validate_pytest_options(
122 **{k: v for k, v in kwargs.items() if k in config_fields},
123 )
125 # Set default junitxml if auto_junitxml is enabled and junitxml not
126 # explicitly set
127 junitxml = kwargs.get("junitxml")
128 auto_junitxml = kwargs.get("auto_junitxml")
129 if junitxml is None and (auto_junitxml is None or auto_junitxml):
130 junitxml = "report.xml"
131 kwargs = kwargs.copy()
132 kwargs["junitxml"] = junitxml
134 # Update the dataclass fields
135 for key, value in kwargs.items():
136 if key in config_fields:
137 setattr(self, key, value)
139 def get_options_dict(self) -> dict[str, Any]:
140 """Get a dictionary of all non-None options.
142 Returns:
143 Dict[str, Any]: Dictionary of option key-value pairs, excluding None values.
144 """
145 options = {}
146 for field_name, _field_info in self.__dataclass_fields__.items():
147 value = getattr(self, field_name)
148 if value is not None:
149 options[field_name] = value
150 return options
152 def is_special_mode(self) -> bool:
153 """Check if any special mode is enabled.
155 Special modes are modes that don't run tests but perform other operations
156 like listing plugins, fixtures, etc.
158 Returns:
159 bool: True if any special mode is enabled.
160 """
161 # Check boolean special modes
162 if any(getattr(self, mode.value, False) for mode in self._SPECIAL_MODES):
163 return True
165 # Check fixture_info (string value, not boolean)
166 return bool(getattr(self, "fixture_info", None))
168 def get_special_mode(self) -> str | None:
169 """Get the active special mode, if any.
171 Returns:
172 str | None: Name of the active special mode, or None if no special mode.
173 """
174 for mode in self._SPECIAL_MODES:
175 if getattr(self, mode.value, False):
176 return mode.value
178 # Check for fixture_info (string value, not boolean)
179 if getattr(self, PytestSpecialMode.FIXTURE_INFO.value, None):
180 return PytestSpecialMode.FIXTURE_INFO.value
182 return None
184 def get_special_mode_value(self, mode: str) -> Any:
185 """Get the value for a special mode.
187 Args:
188 mode: The special mode name.
190 Returns:
191 Any: The value associated with the special mode.
192 """
193 if mode == PytestSpecialMode.FIXTURE_INFO.value:
194 return self.fixture_info
195 elif mode == PytestSpecialMode.CHECK_PLUGINS.value:
196 return self.required_plugins
197 else:
198 return getattr(self, mode, False)