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.
Files changed (66) hide show
  1. provide/__init__.py +3 -0
  2. provide/testkit/__init__.py +248 -0
  3. provide/testkit/archive/__init__.py +24 -0
  4. provide/testkit/archive/fixtures.py +217 -0
  5. provide/testkit/cli.py +229 -0
  6. provide/testkit/common/__init__.py +32 -0
  7. provide/testkit/common/fixtures.py +234 -0
  8. provide/testkit/crypto.py +163 -0
  9. provide/testkit/environment.py +79 -0
  10. provide/testkit/file/__init__.py +40 -0
  11. provide/testkit/file/content_fixtures.py +275 -0
  12. provide/testkit/file/directory_fixtures.py +105 -0
  13. provide/testkit/file/fixtures.py +49 -0
  14. provide/testkit/file/special_fixtures.py +141 -0
  15. provide/testkit/fixtures.py +52 -0
  16. provide/testkit/harness.py +122 -0
  17. provide/testkit/hub.py +22 -0
  18. provide/testkit/logger/__init__.py +39 -0
  19. provide/testkit/logger/hooks.py +100 -0
  20. provide/testkit/logger/reset.py +230 -0
  21. provide/testkit/main.py +22 -0
  22. provide/testkit/mocking/__init__.py +46 -0
  23. provide/testkit/mocking/fixtures.py +340 -0
  24. provide/testkit/process/__init__.py +48 -0
  25. provide/testkit/process/async_fixtures.py +410 -0
  26. provide/testkit/process/fixtures.py +54 -0
  27. provide/testkit/process/subprocess_fixtures.py +208 -0
  28. provide/testkit/quality/__init__.py +101 -0
  29. provide/testkit/quality/artifacts.py +360 -0
  30. provide/testkit/quality/base.py +158 -0
  31. provide/testkit/quality/cli.py +394 -0
  32. provide/testkit/quality/complexity/__init__.py +30 -0
  33. provide/testkit/quality/complexity/analyzer.py +392 -0
  34. provide/testkit/quality/complexity/fixture.py +196 -0
  35. provide/testkit/quality/coverage/__init__.py +36 -0
  36. provide/testkit/quality/coverage/fixture.py +236 -0
  37. provide/testkit/quality/coverage/reporter.py +150 -0
  38. provide/testkit/quality/coverage/tracker.py +313 -0
  39. provide/testkit/quality/decorators.py +380 -0
  40. provide/testkit/quality/documentation/__init__.py +29 -0
  41. provide/testkit/quality/documentation/checker.py +361 -0
  42. provide/testkit/quality/documentation/fixture.py +187 -0
  43. provide/testkit/quality/profiling/__init__.py +30 -0
  44. provide/testkit/quality/profiling/fixture.py +332 -0
  45. provide/testkit/quality/profiling/profiler.py +428 -0
  46. provide/testkit/quality/report.py +266 -0
  47. provide/testkit/quality/runner.py +319 -0
  48. provide/testkit/quality/security/__init__.py +29 -0
  49. provide/testkit/quality/security/fixture.py +196 -0
  50. provide/testkit/quality/security/scanner.py +338 -0
  51. provide/testkit/streams.py +54 -0
  52. provide/testkit/threading/__init__.py +38 -0
  53. provide/testkit/threading/basic_fixtures.py +103 -0
  54. provide/testkit/threading/data_fixtures.py +101 -0
  55. provide/testkit/threading/execution_fixtures.py +268 -0
  56. provide/testkit/threading/fixtures.py +50 -0
  57. provide/testkit/threading/sync_fixtures.py +98 -0
  58. provide/testkit/time/__init__.py +32 -0
  59. provide/testkit/time/fixtures.py +416 -0
  60. provide/testkit/transport/__init__.py +30 -0
  61. provide/testkit/transport/fixtures.py +278 -0
  62. provide_testkit-0.0.0.dev0.dist-info/METADATA +145 -0
  63. provide_testkit-0.0.0.dev0.dist-info/RECORD +66 -0
  64. provide_testkit-0.0.0.dev0.dist-info/WHEEL +5 -0
  65. provide_testkit-0.0.0.dev0.dist-info/entry_points.txt +2 -0
  66. 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
+ ]