provide-testkit 0.0.0.dev0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- provide/__init__.py +3 -0
- provide/testkit/__init__.py +248 -0
- provide/testkit/archive/__init__.py +24 -0
- provide/testkit/archive/fixtures.py +217 -0
- provide/testkit/cli.py +229 -0
- provide/testkit/common/__init__.py +32 -0
- provide/testkit/common/fixtures.py +234 -0
- provide/testkit/crypto.py +163 -0
- provide/testkit/environment.py +79 -0
- provide/testkit/file/__init__.py +40 -0
- provide/testkit/file/content_fixtures.py +275 -0
- provide/testkit/file/directory_fixtures.py +105 -0
- provide/testkit/file/fixtures.py +49 -0
- provide/testkit/file/special_fixtures.py +141 -0
- provide/testkit/fixtures.py +52 -0
- provide/testkit/harness.py +122 -0
- provide/testkit/hub.py +22 -0
- provide/testkit/logger/__init__.py +39 -0
- provide/testkit/logger/hooks.py +100 -0
- provide/testkit/logger/reset.py +230 -0
- provide/testkit/main.py +22 -0
- provide/testkit/mocking/__init__.py +46 -0
- provide/testkit/mocking/fixtures.py +340 -0
- provide/testkit/process/__init__.py +48 -0
- provide/testkit/process/async_fixtures.py +410 -0
- provide/testkit/process/fixtures.py +54 -0
- provide/testkit/process/subprocess_fixtures.py +208 -0
- provide/testkit/quality/__init__.py +101 -0
- provide/testkit/quality/artifacts.py +360 -0
- provide/testkit/quality/base.py +158 -0
- provide/testkit/quality/cli.py +394 -0
- provide/testkit/quality/complexity/__init__.py +30 -0
- provide/testkit/quality/complexity/analyzer.py +392 -0
- provide/testkit/quality/complexity/fixture.py +196 -0
- provide/testkit/quality/coverage/__init__.py +36 -0
- provide/testkit/quality/coverage/fixture.py +236 -0
- provide/testkit/quality/coverage/reporter.py +150 -0
- provide/testkit/quality/coverage/tracker.py +313 -0
- provide/testkit/quality/decorators.py +380 -0
- provide/testkit/quality/documentation/__init__.py +29 -0
- provide/testkit/quality/documentation/checker.py +361 -0
- provide/testkit/quality/documentation/fixture.py +187 -0
- provide/testkit/quality/profiling/__init__.py +30 -0
- provide/testkit/quality/profiling/fixture.py +332 -0
- provide/testkit/quality/profiling/profiler.py +428 -0
- provide/testkit/quality/report.py +266 -0
- provide/testkit/quality/runner.py +319 -0
- provide/testkit/quality/security/__init__.py +29 -0
- provide/testkit/quality/security/fixture.py +196 -0
- provide/testkit/quality/security/scanner.py +338 -0
- provide/testkit/streams.py +54 -0
- provide/testkit/threading/__init__.py +38 -0
- provide/testkit/threading/basic_fixtures.py +103 -0
- provide/testkit/threading/data_fixtures.py +101 -0
- provide/testkit/threading/execution_fixtures.py +268 -0
- provide/testkit/threading/fixtures.py +50 -0
- provide/testkit/threading/sync_fixtures.py +98 -0
- provide/testkit/time/__init__.py +32 -0
- provide/testkit/time/fixtures.py +416 -0
- provide/testkit/transport/__init__.py +30 -0
- provide/testkit/transport/fixtures.py +278 -0
- provide_testkit-0.0.0.dev0.dist-info/METADATA +145 -0
- provide_testkit-0.0.0.dev0.dist-info/RECORD +66 -0
- provide_testkit-0.0.0.dev0.dist-info/WHEEL +5 -0
- provide_testkit-0.0.0.dev0.dist-info/entry_points.txt +2 -0
- provide_testkit-0.0.0.dev0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
"""CLI commands for quality analysis."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
import sys
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
import click
|
|
11
|
+
|
|
12
|
+
from .runner import QualityRunner
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@click.group(name="quality")
|
|
16
|
+
@click.pass_context
|
|
17
|
+
def quality_cli(ctx: click.Context) -> None:
|
|
18
|
+
"""Quality analysis commands for provide-testkit."""
|
|
19
|
+
ctx.ensure_object(dict)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@quality_cli.command("analyze")
|
|
23
|
+
@click.argument("path", type=click.Path(exists=True, path_type=Path))
|
|
24
|
+
@click.option(
|
|
25
|
+
"--tool",
|
|
26
|
+
multiple=True,
|
|
27
|
+
type=click.Choice(["coverage", "security", "complexity", "documentation", "profiling"]),
|
|
28
|
+
help="Specific tools to run (default: all available)",
|
|
29
|
+
)
|
|
30
|
+
@click.option(
|
|
31
|
+
"--artifact-dir",
|
|
32
|
+
type=click.Path(path_type=Path),
|
|
33
|
+
default=".quality-artifacts",
|
|
34
|
+
help="Directory for output artifacts",
|
|
35
|
+
)
|
|
36
|
+
@click.option(
|
|
37
|
+
"--format", type=click.Choice(["terminal", "json", "summary"]), default="terminal", help="Output format"
|
|
38
|
+
)
|
|
39
|
+
@click.option("--fail-fast", is_flag=True, help="Stop on first failure")
|
|
40
|
+
@click.option("--config", type=click.Path(exists=True, path_type=Path), help="Configuration file (JSON)")
|
|
41
|
+
@click.option("--verbose", "-v", is_flag=True, help="Verbose output")
|
|
42
|
+
def analyze_command(
|
|
43
|
+
path: Path,
|
|
44
|
+
tool: tuple[str, ...],
|
|
45
|
+
artifact_dir: Path,
|
|
46
|
+
format: str,
|
|
47
|
+
fail_fast: bool,
|
|
48
|
+
config: Path | None,
|
|
49
|
+
verbose: bool,
|
|
50
|
+
) -> None:
|
|
51
|
+
"""Analyze code quality for the given path."""
|
|
52
|
+
try:
|
|
53
|
+
# Load configuration
|
|
54
|
+
config_data = {}
|
|
55
|
+
if config:
|
|
56
|
+
config_data = json.loads(config.read_text())
|
|
57
|
+
|
|
58
|
+
# Determine which tools to run
|
|
59
|
+
if tool:
|
|
60
|
+
tools_to_run = list(tool)
|
|
61
|
+
else:
|
|
62
|
+
tools_to_run = ["coverage", "security", "complexity", "documentation"]
|
|
63
|
+
|
|
64
|
+
if verbose:
|
|
65
|
+
click.echo(f"Analyzing {path} with tools: {', '.join(tools_to_run)}")
|
|
66
|
+
click.echo(f"Artifacts will be saved to: {artifact_dir}")
|
|
67
|
+
|
|
68
|
+
# Run quality analysis
|
|
69
|
+
runner = QualityRunner()
|
|
70
|
+
results = runner.run_tools(path, tools_to_run, artifact_dir=artifact_dir, tool_configs=config_data)
|
|
71
|
+
|
|
72
|
+
# Output results
|
|
73
|
+
if format == "json":
|
|
74
|
+
output = {
|
|
75
|
+
"path": str(path),
|
|
76
|
+
"tools": tools_to_run,
|
|
77
|
+
"results": {
|
|
78
|
+
tool: {
|
|
79
|
+
"passed": result.passed,
|
|
80
|
+
"score": result.score,
|
|
81
|
+
"details": result.details,
|
|
82
|
+
"execution_time": result.execution_time,
|
|
83
|
+
}
|
|
84
|
+
for tool, result in results.items()
|
|
85
|
+
},
|
|
86
|
+
}
|
|
87
|
+
click.echo(json.dumps(output, indent=2))
|
|
88
|
+
elif format == "summary":
|
|
89
|
+
_print_summary(results, verbose)
|
|
90
|
+
else:
|
|
91
|
+
_print_terminal_results(results, verbose)
|
|
92
|
+
|
|
93
|
+
# Exit with error code if any tool failed
|
|
94
|
+
if any(not result.passed for result in results.values()):
|
|
95
|
+
sys.exit(1)
|
|
96
|
+
|
|
97
|
+
except Exception as e:
|
|
98
|
+
click.echo(f"Error: {e}", err=True)
|
|
99
|
+
sys.exit(1)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@quality_cli.command("gates")
|
|
103
|
+
@click.argument("path", type=click.Path(exists=True, path_type=Path))
|
|
104
|
+
@click.option("--coverage", type=float, help="Minimum coverage percentage required")
|
|
105
|
+
@click.option("--security", type=float, help="Minimum security score required")
|
|
106
|
+
@click.option("--complexity", type=int, help="Maximum complexity allowed")
|
|
107
|
+
@click.option("--documentation", type=float, help="Minimum documentation coverage required")
|
|
108
|
+
@click.option("--config", type=click.Path(exists=True, path_type=Path), help="Gates configuration file (JSON)")
|
|
109
|
+
@click.option(
|
|
110
|
+
"--artifact-dir",
|
|
111
|
+
type=click.Path(path_type=Path),
|
|
112
|
+
default=".quality-artifacts",
|
|
113
|
+
help="Directory for output artifacts",
|
|
114
|
+
)
|
|
115
|
+
@click.option("--verbose", "-v", is_flag=True, help="Verbose output")
|
|
116
|
+
def gates_command(
|
|
117
|
+
path: Path,
|
|
118
|
+
coverage: float | None,
|
|
119
|
+
security: float | None,
|
|
120
|
+
complexity: int | None,
|
|
121
|
+
documentation: float | None,
|
|
122
|
+
config: Path | None,
|
|
123
|
+
artifact_dir: Path,
|
|
124
|
+
verbose: bool,
|
|
125
|
+
) -> None:
|
|
126
|
+
"""Run quality gates on the given path."""
|
|
127
|
+
try:
|
|
128
|
+
# Build and validate gates configuration
|
|
129
|
+
gates = _build_gates_config(coverage, security, complexity, documentation, config)
|
|
130
|
+
|
|
131
|
+
if verbose:
|
|
132
|
+
_print_gate_info(path, gates)
|
|
133
|
+
|
|
134
|
+
# Run quality gates and handle results
|
|
135
|
+
results = _execute_quality_gates(path, gates, artifact_dir)
|
|
136
|
+
_handle_gate_results(results, verbose)
|
|
137
|
+
|
|
138
|
+
except Exception as e:
|
|
139
|
+
click.echo(f"Error: {e}", err=True)
|
|
140
|
+
sys.exit(1)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def _build_gates_config(
|
|
144
|
+
coverage: float | None,
|
|
145
|
+
security: float | None,
|
|
146
|
+
complexity: int | None,
|
|
147
|
+
documentation: float | None,
|
|
148
|
+
config: Path | None,
|
|
149
|
+
) -> dict[str, Any]:
|
|
150
|
+
"""Build gates configuration from CLI arguments and config file."""
|
|
151
|
+
gates = {}
|
|
152
|
+
|
|
153
|
+
# Add CLI-specified gates
|
|
154
|
+
if coverage is not None:
|
|
155
|
+
gates["coverage"] = coverage
|
|
156
|
+
if security is not None:
|
|
157
|
+
gates["security"] = security
|
|
158
|
+
if complexity is not None:
|
|
159
|
+
gates["complexity"] = {"max_complexity": complexity}
|
|
160
|
+
if documentation is not None:
|
|
161
|
+
gates["documentation"] = documentation
|
|
162
|
+
|
|
163
|
+
# Load from config file if provided
|
|
164
|
+
if config:
|
|
165
|
+
config_gates = json.loads(config.read_text())
|
|
166
|
+
gates.update(config_gates)
|
|
167
|
+
|
|
168
|
+
if not gates:
|
|
169
|
+
click.echo("Error: No quality gates specified", err=True)
|
|
170
|
+
sys.exit(1)
|
|
171
|
+
|
|
172
|
+
return gates
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def _print_gate_info(path: Path, gates: dict[str, Any]) -> None:
|
|
176
|
+
"""Print verbose information about gates being run."""
|
|
177
|
+
click.echo(f"Running quality gates on {path}")
|
|
178
|
+
click.echo(f"Gates: {gates}")
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def _execute_quality_gates(path: Path, gates: dict[str, Any], artifact_dir: Path) -> Any:
|
|
182
|
+
"""Execute quality gates and return results."""
|
|
183
|
+
runner = QualityRunner()
|
|
184
|
+
return runner.run_with_gates(path, gates, artifact_dir=artifact_dir)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def _handle_gate_results(results: Any, verbose: bool) -> None:
|
|
188
|
+
"""Handle and display gate results, exit on failure."""
|
|
189
|
+
# Print summary
|
|
190
|
+
if results.passed:
|
|
191
|
+
click.echo("✅ All quality gates passed!", fg="green")
|
|
192
|
+
else:
|
|
193
|
+
click.echo("❌ Quality gates failed!", fg="red")
|
|
194
|
+
|
|
195
|
+
# Print detailed results if verbose
|
|
196
|
+
if verbose:
|
|
197
|
+
_print_detailed_results(results)
|
|
198
|
+
|
|
199
|
+
# Exit with error code if gates failed
|
|
200
|
+
if not results.passed:
|
|
201
|
+
sys.exit(1)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def _print_detailed_results(results: Any) -> None:
|
|
205
|
+
"""Print detailed results for each tool."""
|
|
206
|
+
click.echo("\nDetailed Results:")
|
|
207
|
+
for tool, result in results.results.items():
|
|
208
|
+
status = "✅ PASS" if result.passed else "❌ FAIL"
|
|
209
|
+
score_text = f" (Score: {result.score:.1f}%)" if result.score is not None else ""
|
|
210
|
+
click.echo(f" {tool}: {status}{score_text}")
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
@quality_cli.command("coverage")
|
|
214
|
+
@click.argument("path", type=click.Path(exists=True, path_type=Path))
|
|
215
|
+
@click.option("--min-coverage", type=float, default=80.0, help="Minimum coverage percentage")
|
|
216
|
+
@click.option(
|
|
217
|
+
"--artifact-dir",
|
|
218
|
+
type=click.Path(path_type=Path),
|
|
219
|
+
default=".coverage-artifacts",
|
|
220
|
+
help="Directory for coverage artifacts",
|
|
221
|
+
)
|
|
222
|
+
@click.option("--html", is_flag=True, help="Generate HTML report")
|
|
223
|
+
@click.option("--xml", is_flag=True, help="Generate XML report")
|
|
224
|
+
@click.option("--verbose", "-v", is_flag=True, help="Verbose output")
|
|
225
|
+
def coverage_command(
|
|
226
|
+
path: Path, min_coverage: float, artifact_dir: Path, html: bool, xml: bool, verbose: bool
|
|
227
|
+
) -> None:
|
|
228
|
+
"""Run coverage analysis on the given path."""
|
|
229
|
+
try:
|
|
230
|
+
from .coverage.tracker import CoverageTracker
|
|
231
|
+
|
|
232
|
+
config = {"min_coverage": min_coverage, "generate_html": html, "generate_xml": xml}
|
|
233
|
+
|
|
234
|
+
tracker = CoverageTracker(config)
|
|
235
|
+
result = tracker.analyze(path, artifact_dir=artifact_dir)
|
|
236
|
+
|
|
237
|
+
status = "✅ PASSED" if result.passed else "❌ FAILED"
|
|
238
|
+
coverage_pct = result.details.get("coverage_percentage", 0)
|
|
239
|
+
|
|
240
|
+
click.echo(f"Coverage Analysis: {status}")
|
|
241
|
+
click.echo(f"Coverage: {coverage_pct:.1f}% (required: {min_coverage}%)")
|
|
242
|
+
|
|
243
|
+
if verbose and result.details.get("missing_files"):
|
|
244
|
+
click.echo("\nFiles with missing coverage:")
|
|
245
|
+
for file_info in result.details["missing_files"][:10]: # Top 10
|
|
246
|
+
click.echo(f" {file_info['filename']}: {file_info['coverage']:.1f}%")
|
|
247
|
+
|
|
248
|
+
if not result.passed:
|
|
249
|
+
sys.exit(1)
|
|
250
|
+
|
|
251
|
+
except Exception as e:
|
|
252
|
+
click.echo(f"Error: {e}", err=True)
|
|
253
|
+
sys.exit(1)
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
@quality_cli.command("security")
|
|
257
|
+
@click.argument("path", type=click.Path(exists=True, path_type=Path))
|
|
258
|
+
@click.option("--min-score", type=float, default=90.0, help="Minimum security score")
|
|
259
|
+
@click.option(
|
|
260
|
+
"--artifact-dir",
|
|
261
|
+
type=click.Path(path_type=Path),
|
|
262
|
+
default=".security-artifacts",
|
|
263
|
+
help="Directory for security artifacts",
|
|
264
|
+
)
|
|
265
|
+
@click.option("--verbose", "-v", is_flag=True, help="Verbose output")
|
|
266
|
+
def security_command(path: Path, min_score: float, artifact_dir: Path, verbose: bool) -> None:
|
|
267
|
+
"""Run security analysis on the given path."""
|
|
268
|
+
try:
|
|
269
|
+
from .security.scanner import SecurityScanner
|
|
270
|
+
|
|
271
|
+
config = {"min_score": min_score}
|
|
272
|
+
scanner = SecurityScanner(config)
|
|
273
|
+
result = scanner.analyze(path, artifact_dir=artifact_dir)
|
|
274
|
+
|
|
275
|
+
status = "✅ PASSED" if result.passed else "❌ FAILED"
|
|
276
|
+
score = result.score or 0
|
|
277
|
+
|
|
278
|
+
click.echo(f"Security Analysis: {status}")
|
|
279
|
+
click.echo(f"Score: {score:.1f}% (required: {min_score}%)")
|
|
280
|
+
|
|
281
|
+
if verbose and result.details.get("issues"):
|
|
282
|
+
issues = result.details["issues"]
|
|
283
|
+
click.echo(f"\nFound {len(issues)} security issues:")
|
|
284
|
+
for issue in issues[:5]: # Top 5
|
|
285
|
+
severity = issue.get("severity", "unknown")
|
|
286
|
+
test_id = issue.get("test_id", "unknown")
|
|
287
|
+
filename = issue.get("filename", "unknown")
|
|
288
|
+
click.echo(f" [{severity.upper()}] {test_id} in {filename}")
|
|
289
|
+
|
|
290
|
+
if not result.passed:
|
|
291
|
+
sys.exit(1)
|
|
292
|
+
|
|
293
|
+
except Exception as e:
|
|
294
|
+
click.echo(f"Error: {e}", err=True)
|
|
295
|
+
sys.exit(1)
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
@quality_cli.command("complexity")
|
|
299
|
+
@click.argument("path", type=click.Path(exists=True, path_type=Path))
|
|
300
|
+
@click.option("--max-complexity", type=int, default=10, help="Maximum complexity allowed")
|
|
301
|
+
@click.option(
|
|
302
|
+
"--min-grade",
|
|
303
|
+
type=click.Choice(["A", "B", "C", "D", "F"]),
|
|
304
|
+
default="C",
|
|
305
|
+
help="Minimum complexity grade required",
|
|
306
|
+
)
|
|
307
|
+
@click.option(
|
|
308
|
+
"--artifact-dir",
|
|
309
|
+
type=click.Path(path_type=Path),
|
|
310
|
+
default=".complexity-artifacts",
|
|
311
|
+
help="Directory for complexity artifacts",
|
|
312
|
+
)
|
|
313
|
+
@click.option("--verbose", "-v", is_flag=True, help="Verbose output")
|
|
314
|
+
def complexity_command(
|
|
315
|
+
path: Path, max_complexity: int, min_grade: str, artifact_dir: Path, verbose: bool
|
|
316
|
+
) -> None:
|
|
317
|
+
"""Run complexity analysis on the given path."""
|
|
318
|
+
try:
|
|
319
|
+
from .complexity.analyzer import ComplexityAnalyzer
|
|
320
|
+
|
|
321
|
+
config = {"max_complexity": max_complexity, "min_grade": min_grade}
|
|
322
|
+
|
|
323
|
+
analyzer = ComplexityAnalyzer(config)
|
|
324
|
+
result = analyzer.analyze(path, artifact_dir=artifact_dir)
|
|
325
|
+
|
|
326
|
+
status = "✅ PASSED" if result.passed else "❌ FAILED"
|
|
327
|
+
grade = result.details.get("overall_grade", "N/A")
|
|
328
|
+
avg_complexity = result.details.get("average_complexity", 0)
|
|
329
|
+
|
|
330
|
+
click.echo(f"Complexity Analysis: {status}")
|
|
331
|
+
click.echo(f"Grade: {grade} (Average complexity: {avg_complexity:.1f})")
|
|
332
|
+
|
|
333
|
+
if verbose and result.details.get("most_complex_functions"):
|
|
334
|
+
click.echo("\nMost complex functions:")
|
|
335
|
+
for func in result.details["most_complex_functions"][:5]:
|
|
336
|
+
click.echo(f" {func['name']}: {func['complexity']} (Grade {func['rank']})")
|
|
337
|
+
|
|
338
|
+
if not result.passed:
|
|
339
|
+
sys.exit(1)
|
|
340
|
+
|
|
341
|
+
except Exception as e:
|
|
342
|
+
click.echo(f"Error: {e}", err=True)
|
|
343
|
+
sys.exit(1)
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def _print_terminal_results(results: dict[str, Any], verbose: bool) -> None:
|
|
347
|
+
"""Print results in terminal format."""
|
|
348
|
+
click.echo("Quality Analysis Results")
|
|
349
|
+
click.echo("=" * 50)
|
|
350
|
+
|
|
351
|
+
for tool, result in results.items():
|
|
352
|
+
status = "✅ PASSED" if result.passed else "❌ FAILED"
|
|
353
|
+
score_text = f" ({result.score:.1f}%)" if result.score is not None else ""
|
|
354
|
+
|
|
355
|
+
click.echo(f"{tool.title()}: {status}{score_text}")
|
|
356
|
+
|
|
357
|
+
if verbose:
|
|
358
|
+
if hasattr(result, "execution_time") and result.execution_time:
|
|
359
|
+
click.echo(f" Execution time: {result.execution_time:.2f}s")
|
|
360
|
+
|
|
361
|
+
# Show key details
|
|
362
|
+
details = result.details
|
|
363
|
+
if tool == "coverage" and "coverage_percentage" in details:
|
|
364
|
+
click.echo(f" Coverage: {details['coverage_percentage']:.1f}%")
|
|
365
|
+
elif tool == "security" and "total_issues" in details:
|
|
366
|
+
click.echo(f" Issues found: {details['total_issues']}")
|
|
367
|
+
elif tool == "complexity" and "average_complexity" in details:
|
|
368
|
+
click.echo(f" Average complexity: {details['average_complexity']:.1f}")
|
|
369
|
+
click.echo(f" Grade: {details.get('overall_grade', 'N/A')}")
|
|
370
|
+
elif tool == "documentation" and "total_coverage" in details:
|
|
371
|
+
click.echo(f" Documentation: {details['total_coverage']:.1f}%")
|
|
372
|
+
|
|
373
|
+
click.echo()
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
def _print_summary(results: dict[str, Any], verbose: bool) -> None:
|
|
377
|
+
"""Print summary of results."""
|
|
378
|
+
passed = sum(1 for result in results.values() if result.passed)
|
|
379
|
+
total = len(results)
|
|
380
|
+
|
|
381
|
+
if passed == total:
|
|
382
|
+
click.echo(f"✅ All {total} quality checks passed!", fg="green")
|
|
383
|
+
else:
|
|
384
|
+
failed = total - passed
|
|
385
|
+
click.echo(f"❌ {failed}/{total} quality checks failed!", fg="red")
|
|
386
|
+
|
|
387
|
+
if verbose:
|
|
388
|
+
for tool, result in results.items():
|
|
389
|
+
status_icon = "✅" if result.passed else "❌"
|
|
390
|
+
click.echo(f" {status_icon} {tool}")
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
if __name__ == "__main__":
|
|
394
|
+
quality_cli()
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""Complexity analysis integration for provide-testkit.
|
|
2
|
+
|
|
3
|
+
Provides code complexity analysis using radon and other complexity tools.
|
|
4
|
+
Integrates with the quality framework for comprehensive complexity analysis.
|
|
5
|
+
|
|
6
|
+
Features:
|
|
7
|
+
- Cyclomatic complexity analysis with radon
|
|
8
|
+
- Maintainability index calculation
|
|
9
|
+
- Raw metrics (lines of code, etc.)
|
|
10
|
+
- Integration with quality gates
|
|
11
|
+
- Grade-based reporting (A, B, C, D, F)
|
|
12
|
+
|
|
13
|
+
Usage:
|
|
14
|
+
# Basic complexity analysis
|
|
15
|
+
def test_with_complexity(complexity_analyzer):
|
|
16
|
+
result = complexity_analyzer.analyze(path)
|
|
17
|
+
assert result.passed
|
|
18
|
+
|
|
19
|
+
# Complexity with quality gates
|
|
20
|
+
runner = QualityRunner()
|
|
21
|
+
results = runner.run_with_gates(path, {"complexity": "B"})
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from .analyzer import ComplexityAnalyzer
|
|
25
|
+
from .fixture import ComplexityFixture
|
|
26
|
+
|
|
27
|
+
__all__ = [
|
|
28
|
+
"ComplexityAnalyzer",
|
|
29
|
+
"ComplexityFixture",
|
|
30
|
+
]
|