Coverage for lintro / tools / implementations / pytest / pytest_option_validators.py: 64%

78 statements  

« prev     ^ index     » next       coverage.py v7.13.0, created at 2026-04-03 18:53 +0000

1"""Option validation functions for pytest tool. 

2 

3This module contains validation logic extracted from PytestTool.set_options() 

4to improve maintainability and reduce file size. 

5""" 

6 

7from lintro.tools.implementations.pytest.collection import ( 

8 get_parallel_workers_from_preset, 

9) 

10 

11 

12def validate_pytest_options( 

13 verbose: bool | None = None, 

14 tb: str | None = None, 

15 maxfail: int | None = None, 

16 no_header: bool | None = None, 

17 disable_warnings: bool | None = None, 

18 json_report: bool | None = None, 

19 junitxml: str | None = None, 

20 slow_test_threshold: float | None = None, 

21 total_time_warning: float | None = None, 

22 workers: str | None = None, 

23 coverage_threshold: float | None = None, 

24 auto_junitxml: bool | None = None, 

25 detect_flaky: bool | None = None, 

26 flaky_min_runs: int | None = None, 

27 flaky_failure_rate: float | None = None, 

28 html_report: str | None = None, 

29 parallel_preset: str | None = None, 

30 list_plugins: bool | None = None, 

31 check_plugins: bool | None = None, 

32 required_plugins: str | None = None, 

33 coverage_html: str | None = None, 

34 coverage_xml: str | None = None, 

35 coverage_report: bool | None = None, 

36 coverage_term_missing: bool | None = None, 

37 collect_only: bool | None = None, 

38 list_fixtures: bool | None = None, 

39 fixture_info: str | None = None, 

40 list_markers: bool | None = None, 

41 parametrize_help: bool | None = None, 

42 show_progress: bool | None = None, 

43 timeout: int | None = None, 

44 reruns: int | None = None, 

45 reruns_delay: int | None = None, 

46) -> None: 

47 """Validate pytest-specific options. 

48 

49 Args: 

50 verbose: Enable verbose output. 

51 tb: Traceback format (short, long, auto, line, native). 

52 maxfail: Stop after first N failures. 

53 no_header: Disable header. 

54 disable_warnings: Disable warnings. 

55 json_report: Enable JSON report output. 

56 junitxml: Path for JUnit XML output. 

57 slow_test_threshold: Duration threshold in seconds for slow test warning 

58 (default: 1.0). 

59 total_time_warning: Total execution time threshold in seconds for warning 

60 (default: 60.0). 

61 workers: Number of parallel workers for pytest-xdist (auto, N, or None). 

62 coverage_threshold: Minimum coverage percentage to require (0-100). 

63 auto_junitxml: Auto-enable junitxml in CI environments (default: True). 

64 detect_flaky: Enable flaky test detection (default: True). 

65 flaky_min_runs: Minimum runs before detecting flaky tests (default: 3). 

66 flaky_failure_rate: Minimum failure rate to consider flaky (default: 0.3). 

67 html_report: Path for HTML report output (pytest-html plugin). 

68 parallel_preset: Parallel execution preset (auto, small, medium, large). 

69 list_plugins: List all installed pytest plugins. 

70 check_plugins: Check if required plugins are installed. 

71 required_plugins: Comma-separated list of required plugin names. 

72 coverage_html: Path for HTML coverage report (requires pytest-cov). 

73 coverage_xml: Path for XML coverage report (requires pytest-cov). 

74 coverage_report: Generate both HTML and XML coverage reports. 

75 coverage_term_missing: Show coverage report in terminal with missing lines. 

76 collect_only: List tests without executing them. 

77 list_fixtures: List all available fixtures. 

78 fixture_info: Show detailed information about a specific fixture. 

79 list_markers: List all available markers. 

80 parametrize_help: Show help for parametrized tests. 

81 show_progress: Show progress during test execution. 

82 timeout: Timeout in seconds for individual tests (pytest-timeout plugin). 

83 reruns: Number of times to retry failed tests (pytest-rerunfailures plugin). 

84 reruns_delay: Delay in seconds between retries (pytest-rerunfailures plugin). 

85 

86 Raises: 

87 ValueError: If an option value is invalid. 

88 """ 

89 if verbose is not None and not isinstance(verbose, bool): 

90 raise ValueError("verbose must be a boolean") 

91 

92 if tb is not None and tb not in ("short", "long", "auto", "line", "native"): 

93 raise ValueError("tb must be one of: short, long, auto, line, native") 

94 

95 if maxfail is not None and (not isinstance(maxfail, int) or maxfail <= 0): 

96 raise ValueError("maxfail must be a positive integer") 

97 

98 if no_header is not None and not isinstance(no_header, bool): 

99 raise ValueError("no_header must be a boolean") 

100 

101 if disable_warnings is not None and not isinstance(disable_warnings, bool): 

102 raise ValueError("disable_warnings must be a boolean") 

103 

104 if json_report is not None and not isinstance(json_report, bool): 

105 raise ValueError("json_report must be a boolean") 

106 

107 if junitxml is not None and not isinstance(junitxml, str): 

108 raise ValueError("junitxml must be a string") 

109 

110 if slow_test_threshold is not None and ( 

111 not isinstance(slow_test_threshold, (int, float)) or slow_test_threshold < 0 

112 ): 

113 raise ValueError("slow_test_threshold must be a non-negative number") 

114 

115 if total_time_warning is not None and ( 

116 not isinstance(total_time_warning, (int, float)) or total_time_warning < 0 

117 ): 

118 raise ValueError("total_time_warning must be a non-negative number") 

119 

120 if workers is not None and not isinstance(workers, (str, int)): 

121 raise ValueError( 

122 f"workers must be a string or integer (e.g., 'auto', '2', 4), " 

123 f"got {type(workers).__name__}: {workers!r}", 

124 ) 

125 

126 if coverage_threshold is not None and not isinstance( 

127 coverage_threshold, 

128 (int, float), 

129 ): 

130 raise ValueError("coverage_threshold must be a number") 

131 if coverage_threshold is not None and not (0 <= coverage_threshold <= 100): 

132 raise ValueError("coverage_threshold must be between 0 and 100") 

133 

134 if auto_junitxml is not None and not isinstance(auto_junitxml, bool): 

135 raise ValueError("auto_junitxml must be a boolean") 

136 

137 if detect_flaky is not None and not isinstance(detect_flaky, bool): 

138 raise ValueError("detect_flaky must be a boolean") 

139 

140 if flaky_min_runs is not None and ( 

141 not isinstance(flaky_min_runs, int) or flaky_min_runs < 1 

142 ): 

143 raise ValueError("flaky_min_runs must be a positive integer") 

144 

145 if flaky_failure_rate is not None: 

146 if not isinstance(flaky_failure_rate, (int, float)): 

147 raise ValueError("flaky_failure_rate must be a number") 

148 if not (0 <= flaky_failure_rate <= 1): 

149 raise ValueError("flaky_failure_rate must be between 0 and 1") 

150 

151 if html_report is not None and not isinstance(html_report, str): 

152 raise ValueError("html_report must be a string (path to HTML report)") 

153 

154 if parallel_preset is not None and not isinstance(parallel_preset, str): 

155 raise ValueError("parallel_preset must be a string") 

156 # Validate preset value 

157 if parallel_preset is not None: 

158 try: 

159 get_parallel_workers_from_preset(parallel_preset) 

160 except ValueError as e: 

161 raise ValueError(f"Invalid parallel_preset: {e}") from e 

162 

163 # Validate plugin options 

164 if list_plugins is not None and not isinstance(list_plugins, bool): 

165 raise ValueError("list_plugins must be a boolean") 

166 

167 if check_plugins is not None and not isinstance(check_plugins, bool): 

168 raise ValueError("check_plugins must be a boolean") 

169 

170 if required_plugins is not None and not isinstance(required_plugins, str): 

171 raise ValueError("required_plugins must be a string") 

172 

173 # Validate coverage options 

174 if coverage_html is not None and not isinstance(coverage_html, str): 

175 raise ValueError("coverage_html must be a string") 

176 

177 if coverage_xml is not None and not isinstance(coverage_xml, str): 

178 raise ValueError("coverage_xml must be a string") 

179 

180 if coverage_report is not None and not isinstance(coverage_report, bool): 

181 raise ValueError("coverage_report must be a boolean") 

182 

183 if coverage_term_missing is not None and not isinstance( 

184 coverage_term_missing, 

185 bool, 

186 ): 

187 raise ValueError("coverage_term_missing must be a boolean") 

188 

189 # Validate discovery and inspection options 

190 if collect_only is not None and not isinstance(collect_only, bool): 

191 raise ValueError("collect_only must be a boolean") 

192 

193 if list_fixtures is not None and not isinstance(list_fixtures, bool): 

194 raise ValueError("list_fixtures must be a boolean") 

195 

196 if fixture_info is not None and not isinstance(fixture_info, str): 

197 raise ValueError("fixture_info must be a string") 

198 

199 if list_markers is not None and not isinstance(list_markers, bool): 

200 raise ValueError("list_markers must be a boolean") 

201 

202 if parametrize_help is not None and not isinstance(parametrize_help, bool): 

203 raise ValueError("parametrize_help must be a boolean") 

204 

205 if show_progress is not None and not isinstance(show_progress, bool): 

206 raise ValueError("show_progress must be a boolean") 

207 

208 # Validate plugin-specific options 

209 if timeout is not None and (not isinstance(timeout, int) or timeout <= 0): 

210 raise ValueError("timeout must be a positive integer (seconds)") 

211 

212 if reruns is not None and (not isinstance(reruns, int) or reruns < 0): 

213 raise ValueError("reruns must be a non-negative integer") 

214 

215 if reruns_delay is not None and ( 

216 not isinstance(reruns_delay, int) or reruns_delay < 0 

217 ): 

218 raise ValueError("reruns_delay must be a non-negative integer (seconds)")