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

1"""Pytest configuration management. 

2 

3This module contains the PytestConfiguration dataclass that encapsulates 

4all pytest-specific option management and validation logic. 

5""" 

6 

7from dataclasses import dataclass, field 

8from typing import Any 

9 

10from lintro.enums.pytest_enums import PytestSpecialMode 

11from lintro.tools.implementations.pytest.pytest_option_validators import ( 

12 validate_pytest_options, 

13) 

14 

15 

16@dataclass 

17class PytestConfiguration: 

18 """Configuration class for pytest-specific options. 

19 

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. 

23 

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 """ 

61 

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 ] 

71 

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) 

105 

106 def set_options(self, **kwargs: Any) -> None: 

107 """Set pytest-specific options with validation. 

108 

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()} 

114 

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"]) 

119 

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 ) 

124 

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 

133 

134 # Update the dataclass fields 

135 for key, value in kwargs.items(): 

136 if key in config_fields: 

137 setattr(self, key, value) 

138 

139 def get_options_dict(self) -> dict[str, Any]: 

140 """Get a dictionary of all non-None options. 

141 

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 

151 

152 def is_special_mode(self) -> bool: 

153 """Check if any special mode is enabled. 

154 

155 Special modes are modes that don't run tests but perform other operations 

156 like listing plugins, fixtures, etc. 

157 

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 

164 

165 # Check fixture_info (string value, not boolean) 

166 return bool(getattr(self, "fixture_info", None)) 

167 

168 def get_special_mode(self) -> str | None: 

169 """Get the active special mode, if any. 

170 

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 

177 

178 # Check for fixture_info (string value, not boolean) 

179 if getattr(self, PytestSpecialMode.FIXTURE_INFO.value, None): 

180 return PytestSpecialMode.FIXTURE_INFO.value 

181 

182 return None 

183 

184 def get_special_mode_value(self, mode: str) -> Any: 

185 """Get the value for a special mode. 

186 

187 Args: 

188 mode: The special mode name. 

189 

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)