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,380 @@
|
|
|
1
|
+
"""Quality decorators for easy integration of quality checks."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Callable
|
|
6
|
+
import functools
|
|
7
|
+
import inspect
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any, TypeVar
|
|
10
|
+
|
|
11
|
+
from .runner import QualityRunner
|
|
12
|
+
|
|
13
|
+
F = TypeVar("F", bound=Callable[..., Any])
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def quality_gate(
|
|
17
|
+
gates: dict[str, Any],
|
|
18
|
+
path: Path | str | None = None,
|
|
19
|
+
artifact_dir: Path | str | None = None,
|
|
20
|
+
fail_fast: bool = True,
|
|
21
|
+
) -> Callable[[F], F]:
|
|
22
|
+
"""Decorator to apply quality gates to a function or test.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
gates: Quality gate requirements
|
|
26
|
+
path: Path to analyze (defaults to function's module file)
|
|
27
|
+
artifact_dir: Directory for artifacts
|
|
28
|
+
fail_fast: Whether to stop on first failure
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
Decorated function
|
|
32
|
+
|
|
33
|
+
Example:
|
|
34
|
+
@quality_gate({"coverage": 80.0, "security": True})
|
|
35
|
+
def test_my_function():
|
|
36
|
+
# Test implementation
|
|
37
|
+
pass
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
def decorator(func: F) -> F:
|
|
41
|
+
@functools.wraps(func)
|
|
42
|
+
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
43
|
+
# Determine path to analyze
|
|
44
|
+
if path is None:
|
|
45
|
+
# Use the module file containing the decorated function
|
|
46
|
+
module = inspect.getmodule(func)
|
|
47
|
+
if module and module.__file__:
|
|
48
|
+
analysis_path = Path(module.__file__).parent
|
|
49
|
+
else:
|
|
50
|
+
raise ValueError("Could not determine path to analyze")
|
|
51
|
+
else:
|
|
52
|
+
analysis_path = Path(path)
|
|
53
|
+
|
|
54
|
+
# Run quality gates
|
|
55
|
+
runner = QualityRunner()
|
|
56
|
+
results = runner.run_with_gates(
|
|
57
|
+
analysis_path,
|
|
58
|
+
gates,
|
|
59
|
+
artifact_dir=Path(artifact_dir) if artifact_dir else None,
|
|
60
|
+
fail_fast=fail_fast,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
# Check if gates passed
|
|
64
|
+
if not results.passed:
|
|
65
|
+
failed_tools = [tool for tool, result in results.results.items() if not result.passed]
|
|
66
|
+
raise AssertionError(f"Quality gates failed for tools: {failed_tools}")
|
|
67
|
+
|
|
68
|
+
# Execute original function
|
|
69
|
+
return func(*args, **kwargs)
|
|
70
|
+
|
|
71
|
+
return wrapper
|
|
72
|
+
|
|
73
|
+
return decorator
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def coverage_gate(
|
|
77
|
+
min_coverage: float, path: Path | str | None = None, artifact_dir: Path | str | None = None
|
|
78
|
+
) -> Callable[[F], F]:
|
|
79
|
+
"""Decorator to enforce minimum coverage requirements.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
min_coverage: Minimum coverage percentage required
|
|
83
|
+
path: Path to analyze
|
|
84
|
+
artifact_dir: Directory for artifacts
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
Decorated function
|
|
88
|
+
|
|
89
|
+
Example:
|
|
90
|
+
@coverage_gate(80.0)
|
|
91
|
+
def test_my_function():
|
|
92
|
+
# Test implementation
|
|
93
|
+
pass
|
|
94
|
+
"""
|
|
95
|
+
return quality_gate({"coverage": min_coverage}, path, artifact_dir)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def security_gate(
|
|
99
|
+
min_score: float = 90.0, path: Path | str | None = None, artifact_dir: Path | str | None = None
|
|
100
|
+
) -> Callable[[F], F]:
|
|
101
|
+
"""Decorator to enforce security scanning requirements.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
min_score: Minimum security score required
|
|
105
|
+
path: Path to analyze
|
|
106
|
+
artifact_dir: Directory for artifacts
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
Decorated function
|
|
110
|
+
|
|
111
|
+
Example:
|
|
112
|
+
@security_gate(95.0)
|
|
113
|
+
def test_my_function():
|
|
114
|
+
# Test implementation
|
|
115
|
+
pass
|
|
116
|
+
"""
|
|
117
|
+
return quality_gate({"security": min_score}, path, artifact_dir)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def complexity_gate(
|
|
121
|
+
max_complexity: int | None = None,
|
|
122
|
+
min_grade: str | None = None,
|
|
123
|
+
min_score: float | None = None,
|
|
124
|
+
path: Path | str | None = None,
|
|
125
|
+
artifact_dir: Path | str | None = None,
|
|
126
|
+
) -> Callable[[F], F]:
|
|
127
|
+
"""Decorator to enforce complexity requirements.
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
max_complexity: Maximum complexity allowed
|
|
131
|
+
min_grade: Minimum complexity grade required
|
|
132
|
+
min_score: Minimum complexity score required
|
|
133
|
+
path: Path to analyze
|
|
134
|
+
artifact_dir: Directory for artifacts
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
Decorated function
|
|
138
|
+
|
|
139
|
+
Example:
|
|
140
|
+
@complexity_gate(max_complexity=10, min_grade="B")
|
|
141
|
+
def test_my_function():
|
|
142
|
+
# Test implementation
|
|
143
|
+
pass
|
|
144
|
+
"""
|
|
145
|
+
gate_config: dict[str, Any] = {}
|
|
146
|
+
|
|
147
|
+
if max_complexity is not None:
|
|
148
|
+
gate_config["max_complexity"] = max_complexity
|
|
149
|
+
if min_grade is not None:
|
|
150
|
+
gate_config["min_grade"] = min_grade
|
|
151
|
+
if min_score is not None:
|
|
152
|
+
gate_config["min_score"] = min_score
|
|
153
|
+
|
|
154
|
+
if not gate_config:
|
|
155
|
+
gate_config = True # Use default requirements
|
|
156
|
+
|
|
157
|
+
return quality_gate({"complexity": gate_config}, path, artifact_dir)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def documentation_gate(
|
|
161
|
+
min_coverage: float | None = None,
|
|
162
|
+
min_grade: str | None = None,
|
|
163
|
+
min_score: float | None = None,
|
|
164
|
+
path: Path | str | None = None,
|
|
165
|
+
artifact_dir: Path | str | None = None,
|
|
166
|
+
) -> Callable[[F], F]:
|
|
167
|
+
"""Decorator to enforce documentation requirements.
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
min_coverage: Minimum documentation coverage percentage
|
|
171
|
+
min_grade: Minimum documentation grade required
|
|
172
|
+
min_score: Minimum documentation score required
|
|
173
|
+
path: Path to analyze
|
|
174
|
+
artifact_dir: Directory for artifacts
|
|
175
|
+
|
|
176
|
+
Returns:
|
|
177
|
+
Decorated function
|
|
178
|
+
|
|
179
|
+
Example:
|
|
180
|
+
@documentation_gate(min_coverage=80.0, min_grade="B")
|
|
181
|
+
def test_my_function():
|
|
182
|
+
# Test implementation
|
|
183
|
+
pass
|
|
184
|
+
"""
|
|
185
|
+
gate_config: dict[str, Any] = {}
|
|
186
|
+
|
|
187
|
+
if min_coverage is not None:
|
|
188
|
+
gate_config["min_coverage"] = min_coverage
|
|
189
|
+
if min_grade is not None:
|
|
190
|
+
gate_config["min_grade"] = min_grade
|
|
191
|
+
if min_score is not None:
|
|
192
|
+
gate_config["min_score"] = min_score
|
|
193
|
+
|
|
194
|
+
if not gate_config:
|
|
195
|
+
gate_config = True # Use default requirements
|
|
196
|
+
|
|
197
|
+
return quality_gate({"documentation": gate_config}, path, artifact_dir)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def performance_gate(
|
|
201
|
+
max_memory_mb: float | None = None, max_execution_time: float | None = None, min_score: float | None = None
|
|
202
|
+
) -> Callable[[F], F]:
|
|
203
|
+
"""Decorator to enforce performance requirements on function execution.
|
|
204
|
+
|
|
205
|
+
Args:
|
|
206
|
+
max_memory_mb: Maximum memory usage in MB
|
|
207
|
+
max_execution_time: Maximum execution time in seconds
|
|
208
|
+
min_score: Minimum performance score
|
|
209
|
+
|
|
210
|
+
Returns:
|
|
211
|
+
Decorated function
|
|
212
|
+
|
|
213
|
+
Example:
|
|
214
|
+
@performance_gate(max_memory_mb=50.0, max_execution_time=1.0)
|
|
215
|
+
def test_my_function():
|
|
216
|
+
# Test implementation
|
|
217
|
+
pass
|
|
218
|
+
"""
|
|
219
|
+
|
|
220
|
+
def decorator(func: F) -> F:
|
|
221
|
+
@functools.wraps(func)
|
|
222
|
+
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
223
|
+
# Configure and run profiler
|
|
224
|
+
profiler = _create_performance_profiler(max_memory_mb, max_execution_time, min_score)
|
|
225
|
+
result = profiler.profile_function(lambda: func(*args, **kwargs))
|
|
226
|
+
|
|
227
|
+
# Check requirements and handle failures
|
|
228
|
+
_validate_performance_requirements(result, max_memory_mb, max_execution_time, min_score)
|
|
229
|
+
|
|
230
|
+
# Extract and return the actual function result
|
|
231
|
+
return _extract_function_result(result)
|
|
232
|
+
|
|
233
|
+
return wrapper
|
|
234
|
+
|
|
235
|
+
return decorator
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def _create_performance_profiler(
|
|
239
|
+
max_memory_mb: float | None, max_execution_time: float | None, min_score: float | None
|
|
240
|
+
) -> Any:
|
|
241
|
+
"""Create and configure a performance profiler."""
|
|
242
|
+
from .profiling.profiler import PerformanceProfiler
|
|
243
|
+
|
|
244
|
+
config = {"profile_memory": True, "profile_cpu": True}
|
|
245
|
+
|
|
246
|
+
if max_memory_mb is not None:
|
|
247
|
+
config["max_memory_mb"] = max_memory_mb
|
|
248
|
+
if max_execution_time is not None:
|
|
249
|
+
config["max_execution_time"] = max_execution_time
|
|
250
|
+
if min_score is not None:
|
|
251
|
+
config["min_score"] = min_score
|
|
252
|
+
|
|
253
|
+
return PerformanceProfiler(config)
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def _validate_performance_requirements(
|
|
257
|
+
result: Any, max_memory_mb: float | None, max_execution_time: float | None, min_score: float | None
|
|
258
|
+
) -> None:
|
|
259
|
+
"""Validate performance requirements and raise error if not met."""
|
|
260
|
+
if result.passed:
|
|
261
|
+
return
|
|
262
|
+
|
|
263
|
+
failure_reasons = []
|
|
264
|
+
memory_data = result.details.get("memory", {})
|
|
265
|
+
cpu_data = result.details.get("cpu", {})
|
|
266
|
+
|
|
267
|
+
# Check memory requirement
|
|
268
|
+
if max_memory_mb and memory_data.get("peak_memory_mb", 0) > max_memory_mb:
|
|
269
|
+
actual_mb = memory_data["peak_memory_mb"]
|
|
270
|
+
failure_reasons.append(f"Memory usage {actual_mb:.2f}MB exceeds limit {max_memory_mb}MB")
|
|
271
|
+
|
|
272
|
+
# Check execution time requirement
|
|
273
|
+
if max_execution_time and cpu_data.get("execution_time", 0) > max_execution_time:
|
|
274
|
+
actual_time = cpu_data["execution_time"]
|
|
275
|
+
failure_reasons.append(f"Execution time {actual_time:.4f}s exceeds limit {max_execution_time}s")
|
|
276
|
+
|
|
277
|
+
# Check score requirement
|
|
278
|
+
if min_score and result.score < min_score:
|
|
279
|
+
failure_reasons.append(f"Performance score {result.score:.1f}% below minimum {min_score}%")
|
|
280
|
+
|
|
281
|
+
raise AssertionError(f"Performance requirements not met: {'; '.join(failure_reasons)}")
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def _extract_function_result(result: Any) -> Any:
|
|
285
|
+
"""Extract the actual function result from profiling data."""
|
|
286
|
+
return result.details.get("memory", {}).get("function_result") or result.details.get("cpu", {}).get(
|
|
287
|
+
"function_result"
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def quality_check(
|
|
292
|
+
coverage: float | bool | None = None,
|
|
293
|
+
security: float | bool | None = None,
|
|
294
|
+
complexity: dict[str, Any] | bool | None = None,
|
|
295
|
+
documentation: dict[str, Any] | bool | None = None,
|
|
296
|
+
performance: dict[str, Any] | None = None,
|
|
297
|
+
path: Path | str | None = None,
|
|
298
|
+
artifact_dir: Path | str | None = None,
|
|
299
|
+
fail_fast: bool = True,
|
|
300
|
+
) -> Callable[[F], F]:
|
|
301
|
+
"""Comprehensive quality check decorator with multiple dimensions.
|
|
302
|
+
|
|
303
|
+
Args:
|
|
304
|
+
coverage: Coverage requirements (percentage or boolean)
|
|
305
|
+
security: Security requirements (score or boolean)
|
|
306
|
+
complexity: Complexity requirements (config dict or boolean)
|
|
307
|
+
documentation: Documentation requirements (config dict or boolean)
|
|
308
|
+
performance: Performance requirements (config dict)
|
|
309
|
+
path: Path to analyze
|
|
310
|
+
artifact_dir: Directory for artifacts
|
|
311
|
+
fail_fast: Whether to stop on first failure
|
|
312
|
+
|
|
313
|
+
Returns:
|
|
314
|
+
Decorated function
|
|
315
|
+
|
|
316
|
+
Example:
|
|
317
|
+
@quality_check(
|
|
318
|
+
coverage=80.0,
|
|
319
|
+
security=True,
|
|
320
|
+
complexity={"max_complexity": 10, "min_grade": "B"},
|
|
321
|
+
documentation={"min_coverage": 80.0},
|
|
322
|
+
performance={"max_memory_mb": 50.0}
|
|
323
|
+
)
|
|
324
|
+
def test_my_function():
|
|
325
|
+
# Test implementation
|
|
326
|
+
pass
|
|
327
|
+
"""
|
|
328
|
+
|
|
329
|
+
def decorator(func: F) -> F:
|
|
330
|
+
@functools.wraps(func)
|
|
331
|
+
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
332
|
+
# Build gates configuration
|
|
333
|
+
gates = {}
|
|
334
|
+
|
|
335
|
+
if coverage is not None:
|
|
336
|
+
gates["coverage"] = coverage
|
|
337
|
+
|
|
338
|
+
if security is not None:
|
|
339
|
+
gates["security"] = security
|
|
340
|
+
|
|
341
|
+
if complexity is not None:
|
|
342
|
+
gates["complexity"] = complexity
|
|
343
|
+
|
|
344
|
+
if documentation is not None:
|
|
345
|
+
gates["documentation"] = documentation
|
|
346
|
+
|
|
347
|
+
# Handle performance separately since it profiles the function execution
|
|
348
|
+
if performance is not None:
|
|
349
|
+
# Apply performance gate to the function
|
|
350
|
+
perf_decorator = performance_gate(
|
|
351
|
+
max_memory_mb=performance.get("max_memory_mb"),
|
|
352
|
+
max_execution_time=performance.get("max_execution_time"),
|
|
353
|
+
min_score=performance.get("min_score"),
|
|
354
|
+
)
|
|
355
|
+
func_with_perf = perf_decorator(func)
|
|
356
|
+
else:
|
|
357
|
+
func_with_perf = func
|
|
358
|
+
|
|
359
|
+
# Apply quality gates if any are specified
|
|
360
|
+
if gates:
|
|
361
|
+
gate_decorator = quality_gate(gates, path, artifact_dir, fail_fast)
|
|
362
|
+
func_with_gates = gate_decorator(func_with_perf)
|
|
363
|
+
else:
|
|
364
|
+
func_with_gates = func_with_perf
|
|
365
|
+
|
|
366
|
+
# Execute the decorated function
|
|
367
|
+
return func_with_gates(*args, **kwargs)
|
|
368
|
+
|
|
369
|
+
return wrapper
|
|
370
|
+
|
|
371
|
+
return decorator
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
# Convenience aliases
|
|
375
|
+
coverage_required = coverage_gate
|
|
376
|
+
security_required = security_gate
|
|
377
|
+
complexity_required = complexity_gate
|
|
378
|
+
documentation_required = documentation_gate
|
|
379
|
+
performance_required = performance_gate
|
|
380
|
+
quality_required = quality_check
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""Documentation coverage analysis for provide-testkit.
|
|
2
|
+
|
|
3
|
+
Provides documentation coverage analysis using interrogate and other tools.
|
|
4
|
+
Integrates with the quality framework for comprehensive docstring analysis.
|
|
5
|
+
|
|
6
|
+
Features:
|
|
7
|
+
- Docstring coverage analysis with interrogate
|
|
8
|
+
- Module, class, and function documentation checking
|
|
9
|
+
- Integration with quality gates
|
|
10
|
+
- Configurable exclusions and requirements
|
|
11
|
+
|
|
12
|
+
Usage:
|
|
13
|
+
# Basic documentation coverage
|
|
14
|
+
def test_with_docs(documentation_checker):
|
|
15
|
+
result = documentation_checker.check(path)
|
|
16
|
+
assert result.passed
|
|
17
|
+
|
|
18
|
+
# Documentation with quality gates
|
|
19
|
+
runner = QualityRunner()
|
|
20
|
+
results = runner.run_with_gates(path, {"documentation": 80.0})
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from .checker import DocumentationChecker
|
|
24
|
+
from .fixture import DocumentationFixture
|
|
25
|
+
|
|
26
|
+
__all__ = [
|
|
27
|
+
"DocumentationChecker",
|
|
28
|
+
"DocumentationFixture",
|
|
29
|
+
]
|