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,332 @@
1
+ """Performance profiling fixture for pytest integration."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Callable
6
+ from pathlib import Path
7
+ from typing import Any
8
+
9
+ import pytest
10
+
11
+ from ..base import BaseQualityFixture
12
+
13
+ try:
14
+ from .profiler import MEMRAY_AVAILABLE, PerformanceProfiler
15
+ except ImportError:
16
+ PerformanceProfiler = None
17
+ MEMRAY_AVAILABLE = False
18
+
19
+
20
+ class ProfilingFixture(BaseQualityFixture):
21
+ """Pytest fixture for performance profiling.
22
+
23
+ Provides easy access to performance profiling with automatic
24
+ setup and teardown. Integrates with the quality framework fixtures.
25
+ """
26
+
27
+ def __init__(self, config: dict[str, Any] | None = None, artifact_dir: Path | None = None):
28
+ """Initialize profiling fixture.
29
+
30
+ Args:
31
+ config: Profiler configuration
32
+ artifact_dir: Directory for artifacts
33
+ """
34
+ super().__init__(config or {}, artifact_dir)
35
+ self.profiler: PerformanceProfiler | None = None
36
+
37
+ def setup(self) -> None:
38
+ """Set up performance profiler."""
39
+ self.profiler = PerformanceProfiler(self.config)
40
+ self._setup_complete = True
41
+
42
+ def profile_function(self, func: Callable[..., Any], *args: Any, **kwargs: Any) -> dict[str, Any]:
43
+ """Profile a function's performance.
44
+
45
+ Args:
46
+ func: Function to profile
47
+ *args: Function arguments
48
+ **kwargs: Function keyword arguments
49
+
50
+ Returns:
51
+ Profiling results as dict
52
+ """
53
+ if not self.profiler:
54
+ return {"error": "Profiler not available"}
55
+
56
+ result = self.profiler.profile_function(func, *args, **kwargs)
57
+ self.add_result(result)
58
+
59
+ return {
60
+ "passed": result.passed,
61
+ "score": result.score,
62
+ "memory": result.details.get("memory", {}),
63
+ "cpu": result.details.get("cpu", {}),
64
+ "scores": result.details.get("scores", {}),
65
+ "thresholds": result.details.get("thresholds", {}),
66
+ "execution_time": result.execution_time,
67
+ "function_result": (
68
+ result.details.get("memory", {}).get("function_result")
69
+ or result.details.get("cpu", {}).get("function_result")
70
+ ),
71
+ }
72
+
73
+ def profile_memory(self, func: Callable[..., Any], *args: Any, **kwargs: Any) -> dict[str, Any]:
74
+ """Profile memory usage only.
75
+
76
+ Args:
77
+ func: Function to profile
78
+ *args: Function arguments
79
+ **kwargs: Function keyword arguments
80
+
81
+ Returns:
82
+ Memory profiling results
83
+ """
84
+ if not self._setup_complete:
85
+ self.setup()
86
+
87
+ # Configure for memory-only profiling
88
+ original_config = self.config.copy()
89
+ self.config.update({"profile_memory": True, "profile_cpu": False})
90
+
91
+ # Recreate profiler with updated config
92
+ self.profiler = PerformanceProfiler(self.config)
93
+
94
+ try:
95
+ return self.profile_function(func, *args, **kwargs)
96
+ finally:
97
+ # Restore original config
98
+ self.config = original_config
99
+ self.profiler = PerformanceProfiler(self.config)
100
+
101
+ def profile_cpu(self, func: Callable[..., Any], *args: Any, **kwargs: Any) -> dict[str, Any]:
102
+ """Profile CPU usage only.
103
+
104
+ Args:
105
+ func: Function to profile
106
+ *args: Function arguments
107
+ **kwargs: Function keyword arguments
108
+
109
+ Returns:
110
+ CPU profiling results
111
+ """
112
+ if not self._setup_complete:
113
+ self.setup()
114
+
115
+ # Configure for CPU-only profiling
116
+ original_config = self.config.copy()
117
+ self.config.update({"profile_memory": False, "profile_cpu": True})
118
+
119
+ # Recreate profiler with updated config
120
+ self.profiler = PerformanceProfiler(self.config)
121
+
122
+ try:
123
+ return self.profile_function(func, *args, **kwargs)
124
+ finally:
125
+ # Restore original config
126
+ self.config = original_config
127
+ self.profiler = PerformanceProfiler(self.config)
128
+
129
+ def benchmark_function(
130
+ self, func: Callable[..., Any], iterations: int = 100, *args: Any, **kwargs: Any
131
+ ) -> dict[str, Any]:
132
+ """Benchmark a function over multiple iterations.
133
+
134
+ Args:
135
+ func: Function to benchmark
136
+ iterations: Number of iterations to run
137
+ *args: Function arguments
138
+ **kwargs: Function keyword arguments
139
+
140
+ Returns:
141
+ Benchmark results with statistics
142
+ """
143
+ if not self._setup_complete:
144
+ self.setup()
145
+
146
+ import statistics
147
+
148
+ execution_times = []
149
+ memory_peaks = []
150
+
151
+ for _ in range(iterations):
152
+ result = self.profile_function(func, *args, **kwargs)
153
+
154
+ if result.get("cpu", {}).get("execution_time"):
155
+ execution_times.append(result["cpu"]["execution_time"])
156
+
157
+ if result.get("memory", {}).get("peak_memory_mb"):
158
+ memory_peaks.append(result["memory"]["peak_memory_mb"])
159
+
160
+ # Calculate statistics
161
+ benchmark_stats = {}
162
+
163
+ if execution_times:
164
+ benchmark_stats["execution_time"] = {
165
+ "mean": statistics.mean(execution_times),
166
+ "median": statistics.median(execution_times),
167
+ "min": min(execution_times),
168
+ "max": max(execution_times),
169
+ "stdev": statistics.stdev(execution_times) if len(execution_times) > 1 else 0,
170
+ "iterations": len(execution_times),
171
+ }
172
+
173
+ if memory_peaks:
174
+ benchmark_stats["memory_usage"] = {
175
+ "mean_mb": statistics.mean(memory_peaks),
176
+ "median_mb": statistics.median(memory_peaks),
177
+ "min_mb": min(memory_peaks),
178
+ "max_mb": max(memory_peaks),
179
+ "stdev_mb": statistics.stdev(memory_peaks) if len(memory_peaks) > 1 else 0,
180
+ "iterations": len(memory_peaks),
181
+ }
182
+
183
+ return {
184
+ "benchmark_stats": benchmark_stats,
185
+ "iterations": iterations,
186
+ "total_profiling_runs": len(self.results),
187
+ }
188
+
189
+ def assert_performance(
190
+ self,
191
+ func: Callable[..., Any],
192
+ max_memory_mb: float | None = None,
193
+ max_execution_time: float | None = None,
194
+ min_score: float | None = None,
195
+ *args: Any,
196
+ **kwargs: Any,
197
+ ) -> None:
198
+ """Assert performance requirements for a function.
199
+
200
+ Args:
201
+ func: Function to test
202
+ max_memory_mb: Maximum memory usage in MB
203
+ max_execution_time: Maximum execution time in seconds
204
+ min_score: Minimum performance score
205
+ *args: Function arguments
206
+ **kwargs: Function keyword arguments
207
+
208
+ Raises:
209
+ AssertionError: If performance requirements are not met
210
+ """
211
+ if not self._setup_complete:
212
+ self.setup()
213
+
214
+ # Update config with assertion requirements
215
+ if max_memory_mb is not None:
216
+ self.config["max_memory_mb"] = max_memory_mb
217
+ if max_execution_time is not None:
218
+ self.config["max_execution_time"] = max_execution_time
219
+ if min_score is not None:
220
+ self.config["min_score"] = min_score
221
+
222
+ # Recreate profiler with updated config
223
+ self.profiler = PerformanceProfiler(self.config)
224
+
225
+ result = self.profile_function(func, *args, **kwargs)
226
+
227
+ # Check assertions
228
+ if not result["passed"]:
229
+ failure_reasons = []
230
+
231
+ if max_memory_mb and result.get("memory", {}).get("peak_memory_mb", 0) > max_memory_mb:
232
+ actual_mb = result["memory"]["peak_memory_mb"]
233
+ failure_reasons.append(f"Memory usage {actual_mb:.2f}MB exceeds limit {max_memory_mb}MB")
234
+
235
+ if max_execution_time and result.get("cpu", {}).get("execution_time", 0) > max_execution_time:
236
+ actual_time = result["cpu"]["execution_time"]
237
+ failure_reasons.append(
238
+ f"Execution time {actual_time:.4f}s exceeds limit {max_execution_time}s"
239
+ )
240
+
241
+ if min_score and result.get("score", 0) < min_score:
242
+ actual_score = result["score"]
243
+ failure_reasons.append(f"Performance score {actual_score:.1f}% below minimum {min_score}%")
244
+
245
+ raise AssertionError(f"Performance requirements not met: {'; '.join(failure_reasons)}")
246
+
247
+ def generate_report(self, format: str = "terminal") -> str:
248
+ """Generate profiling report.
249
+
250
+ Args:
251
+ format: Report format (terminal, json)
252
+
253
+ Returns:
254
+ Formatted report
255
+ """
256
+ if not self.profiler:
257
+ return "No performance profiler available"
258
+
259
+ if not self.results:
260
+ return "No profiling results available"
261
+
262
+ # Use the most recent result
263
+ latest_result = self.results[-1]
264
+ return self.profiler.report(latest_result, format)
265
+
266
+
267
+ @pytest.fixture
268
+ def profiling_fixture() -> ProfilingFixture:
269
+ """Provide performance profiling fixture.
270
+
271
+ Returns:
272
+ ProfilingFixture instance
273
+ """
274
+ fixture = ProfilingFixture()
275
+ fixture.setup()
276
+ yield fixture
277
+ fixture.teardown()
278
+
279
+
280
+ @pytest.fixture
281
+ def profiling_config() -> dict[str, Any]:
282
+ """Provide default profiling configuration.
283
+
284
+ Returns:
285
+ Default configuration for profiling
286
+ """
287
+ return {
288
+ "profile_memory": True,
289
+ "profile_cpu": True,
290
+ "use_memray": MEMRAY_AVAILABLE,
291
+ "min_score": 70.0,
292
+ "max_memory_mb": 100.0,
293
+ "max_execution_time": 1.0,
294
+ }
295
+
296
+
297
+ @pytest.fixture
298
+ def memory_profiler(profiling_config: dict[str, Any]) -> ProfilingFixture:
299
+ """Provide memory-only profiling fixture.
300
+
301
+ Args:
302
+ profiling_config: Base configuration
303
+
304
+ Returns:
305
+ ProfilingFixture configured for memory profiling only
306
+ """
307
+ config = profiling_config.copy()
308
+ config.update({"profile_memory": True, "profile_cpu": False})
309
+
310
+ fixture = ProfilingFixture(config)
311
+ fixture.setup()
312
+ yield fixture
313
+ fixture.teardown()
314
+
315
+
316
+ @pytest.fixture
317
+ def cpu_profiler(profiling_config: dict[str, Any]) -> ProfilingFixture:
318
+ """Provide CPU-only profiling fixture.
319
+
320
+ Args:
321
+ profiling_config: Base configuration
322
+
323
+ Returns:
324
+ ProfilingFixture configured for CPU profiling only
325
+ """
326
+ config = profiling_config.copy()
327
+ config.update({"profile_memory": False, "profile_cpu": True})
328
+
329
+ fixture = ProfilingFixture(config)
330
+ fixture.setup()
331
+ yield fixture
332
+ fixture.teardown()