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,416 @@
1
+ """
2
+ Time Testing Fixtures and Utilities.
3
+
4
+ Fixtures for mocking time, freezing time, and testing time-dependent code
5
+ across the provide-io ecosystem.
6
+ """
7
+
8
+ from collections.abc import Callable
9
+ import datetime
10
+ import time
11
+ from typing import Any
12
+ from unittest.mock import Mock, patch
13
+
14
+ import pytest
15
+
16
+
17
+ @pytest.fixture
18
+ def freeze_time():
19
+ """
20
+ Fixture to freeze time at a specific point.
21
+
22
+ Returns:
23
+ Function that freezes time and returns a context manager.
24
+ """
25
+
26
+ class FrozenTime:
27
+ def __init__(self, frozen_time: datetime.datetime | None = None):
28
+ self.frozen_time = frozen_time or datetime.datetime.now()
29
+ self.original_time = time.time
30
+ self.original_datetime = datetime.datetime
31
+ self.patches = []
32
+
33
+ def __enter__(self):
34
+ # Patch time.time()
35
+ time_patch = patch("time.time", return_value=self.frozen_time.timestamp())
36
+ self.patches.append(time_patch)
37
+ time_patch.start()
38
+
39
+ # Patch datetime.datetime.now()
40
+ datetime_patch = patch("datetime.datetime", wraps=datetime.datetime)
41
+ mock_datetime = datetime_patch.start()
42
+ mock_datetime.now.return_value = self.frozen_time
43
+ mock_datetime.utcnow.return_value = self.frozen_time
44
+ self.patches.append(datetime_patch)
45
+
46
+ return self
47
+
48
+ def __exit__(self, *args):
49
+ for p in self.patches:
50
+ p.stop()
51
+
52
+ def tick(self, seconds: float = 1.0):
53
+ """Advance the frozen time by the specified seconds."""
54
+ self.frozen_time += datetime.timedelta(seconds=seconds)
55
+ # Update mocks
56
+ for p in self.patches:
57
+ if hasattr(p, "return_value"):
58
+ p.return_value = self.frozen_time.timestamp()
59
+
60
+ def _freeze(at: datetime.datetime | None = None) -> FrozenTime:
61
+ """
62
+ Freeze time at a specific point.
63
+
64
+ Args:
65
+ at: Optional datetime to freeze at (defaults to now)
66
+
67
+ Returns:
68
+ FrozenTime context manager
69
+ """
70
+ return FrozenTime(at)
71
+
72
+ return _freeze
73
+
74
+
75
+ @pytest.fixture
76
+ def mock_sleep():
77
+ """
78
+ Mock time.sleep to speed up tests.
79
+
80
+ Returns:
81
+ Mock object that replaces time.sleep.
82
+ """
83
+ with patch("time.sleep") as mock:
84
+ # Make sleep instant by default
85
+ mock.return_value = None
86
+ yield mock
87
+
88
+
89
+ @pytest.fixture
90
+ def mock_sleep_with_callback():
91
+ """
92
+ Mock time.sleep with a callback for each sleep call.
93
+
94
+ Returns:
95
+ Function to set up sleep mock with callback.
96
+ """
97
+
98
+ def _mock_sleep(callback: Callable[[float], None] = None):
99
+ """
100
+ Create a mock sleep with optional callback.
101
+
102
+ Args:
103
+ callback: Function called with sleep duration
104
+
105
+ Returns:
106
+ Mock sleep object
107
+ """
108
+
109
+ def sleep_side_effect(seconds):
110
+ if callback:
111
+ callback(seconds)
112
+ return None
113
+
114
+ mock = Mock(side_effect=sleep_side_effect)
115
+ return mock
116
+
117
+ return _mock_sleep
118
+
119
+
120
+ @pytest.fixture
121
+ def time_machine():
122
+ """
123
+ Advanced time manipulation fixture.
124
+
125
+ Provides methods to:
126
+ - Freeze time
127
+ - Speed up/slow down time
128
+ - Jump to specific times
129
+
130
+ Returns:
131
+ TimeMachine instance for time manipulation.
132
+ """
133
+
134
+ class TimeMachine:
135
+ def __init__(self):
136
+ self.current_time = time.time()
137
+ self.speed_multiplier = 1.0
138
+ self.patches = []
139
+ self.is_frozen = False
140
+
141
+ def freeze(self, at: float | None = None):
142
+ """Freeze time at a specific timestamp."""
143
+ self.is_frozen = True
144
+ self.current_time = at or time.time()
145
+
146
+ patcher = patch("time.time", return_value=self.current_time)
147
+ mock = patcher.start()
148
+ self.patches.append(patcher)
149
+ return self
150
+
151
+ def unfreeze(self):
152
+ """Unfreeze time."""
153
+ self.is_frozen = False
154
+ for p in self.patches:
155
+ p.stop()
156
+ self.patches.clear()
157
+
158
+ def jump(self, seconds: float):
159
+ """Jump forward or backward in time."""
160
+ self.current_time += seconds
161
+ if self.is_frozen:
162
+ for p in self.patches:
163
+ if hasattr(p, "return_value"):
164
+ p.return_value = self.current_time
165
+
166
+ def speed_up(self, factor: float):
167
+ """Speed up time by a factor."""
168
+ self.speed_multiplier = factor
169
+
170
+ def slow_down(self, factor: float):
171
+ """Slow down time by a factor."""
172
+ self.speed_multiplier = 1.0 / factor
173
+
174
+ def cleanup(self):
175
+ """Clean up all patches."""
176
+ for p in self.patches:
177
+ p.stop()
178
+
179
+ machine = TimeMachine()
180
+ yield machine
181
+ machine.cleanup()
182
+
183
+
184
+ @pytest.fixture
185
+ def timer():
186
+ """
187
+ Timer fixture for measuring execution time.
188
+
189
+ Returns:
190
+ Timer instance for measuring durations.
191
+ """
192
+
193
+ class Timer:
194
+ def __init__(self):
195
+ self.start_time = None
196
+ self.end_time = None
197
+ self.durations = []
198
+
199
+ def start(self):
200
+ """Start the timer."""
201
+ self.start_time = time.perf_counter()
202
+ return self
203
+
204
+ def stop(self) -> float:
205
+ """Stop the timer and return duration."""
206
+ self.end_time = time.perf_counter()
207
+ if self.start_time is None:
208
+ raise RuntimeError("Timer not started")
209
+ duration = self.end_time - self.start_time
210
+ self.durations.append(duration)
211
+ return duration
212
+
213
+ def __enter__(self):
214
+ """Context manager entry."""
215
+ self.start()
216
+ return self
217
+
218
+ def __exit__(self, *args):
219
+ """Context manager exit."""
220
+ self.stop()
221
+
222
+ @property
223
+ def elapsed(self) -> float:
224
+ """Get elapsed time since start."""
225
+ if self.start_time is None:
226
+ raise RuntimeError("Timer not started")
227
+ if self.end_time is None:
228
+ return time.perf_counter() - self.start_time
229
+ return self.end_time - self.start_time
230
+
231
+ @property
232
+ def average(self) -> float:
233
+ """Get average duration from all measurements."""
234
+ if not self.durations:
235
+ return 0.0
236
+ return sum(self.durations) / len(self.durations)
237
+
238
+ def reset(self):
239
+ """Reset the timer."""
240
+ self.start_time = None
241
+ self.end_time = None
242
+ self.durations.clear()
243
+
244
+ return Timer()
245
+
246
+
247
+ @pytest.fixture
248
+ def mock_datetime():
249
+ """
250
+ Mock datetime module for testing.
251
+
252
+ Returns:
253
+ Mock datetime module with common methods mocked.
254
+ """
255
+ with patch("datetime.datetime") as mock_dt:
256
+ # Set up a fake "now"
257
+ fake_now = datetime.datetime(2024, 1, 1, 12, 0, 0)
258
+ mock_dt.now.return_value = fake_now
259
+ mock_dt.utcnow.return_value = fake_now
260
+ mock_dt.today.return_value = fake_now.date()
261
+
262
+ # Allow normal datetime construction
263
+ mock_dt.side_effect = lambda *args, **kwargs: datetime.datetime(*args, **kwargs)
264
+
265
+ yield mock_dt
266
+
267
+
268
+ @pytest.fixture
269
+ def time_travel():
270
+ """
271
+ Fixture for traveling through time in tests.
272
+
273
+ Returns:
274
+ Function to travel to specific time points.
275
+ """
276
+ original_time = time.time
277
+ current_offset = 0.0
278
+
279
+ def mock_time():
280
+ return original_time() + current_offset
281
+
282
+ def _travel_to(target: datetime.datetime):
283
+ """
284
+ Travel to a specific point in time.
285
+
286
+ Args:
287
+ target: The datetime to travel to
288
+ """
289
+ nonlocal current_offset
290
+ current_offset = target.timestamp() - original_time()
291
+
292
+ with patch("time.time", mock_time):
293
+ yield _travel_to
294
+
295
+
296
+ @pytest.fixture
297
+ def rate_limiter_mock():
298
+ """
299
+ Mock for testing rate-limited code.
300
+
301
+ Returns:
302
+ Mock rate limiter that can be controlled in tests.
303
+ """
304
+
305
+ class MockRateLimiter:
306
+ def __init__(self):
307
+ self.calls = []
308
+ self.should_limit = False
309
+ self.limit_after = None
310
+ self.call_count = 0
311
+
312
+ def check(self) -> bool:
313
+ """Check if rate limit is exceeded."""
314
+ self.call_count += 1
315
+ self.calls.append(time.time())
316
+
317
+ if self.limit_after and self.call_count > self.limit_after:
318
+ return False # Rate limited
319
+
320
+ return not self.should_limit
321
+
322
+ def reset(self):
323
+ """Reset the rate limiter."""
324
+ self.calls.clear()
325
+ self.call_count = 0
326
+ self.should_limit = False
327
+ self.limit_after = None
328
+
329
+ def set_limit(self, after_calls: int):
330
+ """Set to limit after N calls."""
331
+ self.limit_after = after_calls
332
+
333
+ return MockRateLimiter()
334
+
335
+
336
+ @pytest.fixture
337
+ def benchmark_timer():
338
+ """
339
+ Timer specifically for benchmarking code.
340
+
341
+ Returns:
342
+ Benchmark timer with statistics.
343
+ """
344
+
345
+ class BenchmarkTimer:
346
+ def __init__(self):
347
+ self.measurements = []
348
+
349
+ def measure(self, func: Callable, *args, **kwargs) -> tuple[Any, float]:
350
+ """
351
+ Measure execution time of a function.
352
+
353
+ Args:
354
+ func: Function to measure
355
+ *args: Function arguments
356
+ **kwargs: Function keyword arguments
357
+
358
+ Returns:
359
+ Tuple of (result, duration)
360
+ """
361
+ start = time.perf_counter()
362
+ result = func(*args, **kwargs)
363
+ duration = time.perf_counter() - start
364
+ self.measurements.append(duration)
365
+ return result, duration
366
+
367
+ @property
368
+ def min_time(self) -> float:
369
+ """Get minimum execution time."""
370
+ return min(self.measurements) if self.measurements else 0.0
371
+
372
+ @property
373
+ def max_time(self) -> float:
374
+ """Get maximum execution time."""
375
+ return max(self.measurements) if self.measurements else 0.0
376
+
377
+ @property
378
+ def avg_time(self) -> float:
379
+ """Get average execution time."""
380
+ return sum(self.measurements) / len(self.measurements) if self.measurements else 0.0
381
+
382
+ def assert_faster_than(self, seconds: float):
383
+ """Assert all measurements were faster than threshold."""
384
+ if not self.measurements:
385
+ raise AssertionError("No measurements taken")
386
+ if self.max_time > seconds:
387
+ raise AssertionError(f"Maximum time {self.max_time:.3f}s exceeded threshold {seconds:.3f}s")
388
+
389
+ return BenchmarkTimer()
390
+
391
+
392
+ # Utility functions that can be imported directly
393
+ def advance_time(mock_time: Mock, seconds: float):
394
+ """
395
+ Advance a mocked time by specified seconds.
396
+
397
+ Args:
398
+ mock_time: The mock time object
399
+ seconds: Number of seconds to advance
400
+ """
401
+ if hasattr(mock_time, "return_value"):
402
+ mock_time.return_value += seconds
403
+
404
+
405
+ __all__ = [
406
+ "advance_time",
407
+ "benchmark_timer",
408
+ "freeze_time",
409
+ "mock_datetime",
410
+ "mock_sleep",
411
+ "mock_sleep_with_callback",
412
+ "rate_limiter_mock",
413
+ "time_machine",
414
+ "time_travel",
415
+ "timer",
416
+ ]
@@ -0,0 +1,30 @@
1
+ """
2
+ Transport and network testing fixtures for the provide-io ecosystem.
3
+
4
+ Standard fixtures for testing HTTP clients, WebSocket connections, and
5
+ network operations across any project that depends on provide.foundation.
6
+ """
7
+
8
+ from provide.testkit.transport.fixtures import (
9
+ free_port,
10
+ httpx_mock_responses,
11
+ mock_dns_resolver,
12
+ mock_http_headers,
13
+ mock_server,
14
+ mock_ssl_context,
15
+ mock_websocket,
16
+ network_timeout,
17
+ tcp_client_server,
18
+ )
19
+
20
+ __all__ = [
21
+ "free_port",
22
+ "httpx_mock_responses",
23
+ "mock_dns_resolver",
24
+ "mock_http_headers",
25
+ "mock_server",
26
+ "mock_ssl_context",
27
+ "mock_websocket",
28
+ "network_timeout",
29
+ "tcp_client_server",
30
+ ]