provide-foundation 0.0.0.dev0__py3-none-any.whl → 0.0.0.dev1__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/foundation/__init__.py +12 -20
- provide/foundation/archive/__init__.py +23 -0
- provide/foundation/archive/base.py +70 -0
- provide/foundation/archive/bzip2.py +157 -0
- provide/foundation/archive/gzip.py +159 -0
- provide/foundation/archive/operations.py +336 -0
- provide/foundation/archive/tar.py +164 -0
- provide/foundation/archive/zip.py +203 -0
- provide/foundation/config/base.py +2 -2
- provide/foundation/config/sync.py +19 -4
- provide/foundation/core.py +1 -2
- provide/foundation/crypto/__init__.py +2 -0
- provide/foundation/crypto/certificates/__init__.py +34 -0
- provide/foundation/crypto/certificates/base.py +173 -0
- provide/foundation/crypto/certificates/certificate.py +290 -0
- provide/foundation/crypto/certificates/factory.py +213 -0
- provide/foundation/crypto/certificates/generator.py +138 -0
- provide/foundation/crypto/certificates/loader.py +130 -0
- provide/foundation/crypto/certificates/operations.py +198 -0
- provide/foundation/crypto/certificates/trust.py +107 -0
- provide/foundation/eventsets/__init__.py +0 -0
- provide/foundation/eventsets/display.py +84 -0
- provide/foundation/eventsets/registry.py +160 -0
- provide/foundation/eventsets/resolver.py +192 -0
- provide/foundation/eventsets/sets/das.py +128 -0
- provide/foundation/eventsets/sets/database.py +125 -0
- provide/foundation/eventsets/sets/http.py +153 -0
- provide/foundation/eventsets/sets/llm.py +139 -0
- provide/foundation/eventsets/sets/task_queue.py +107 -0
- provide/foundation/eventsets/types.py +70 -0
- provide/foundation/hub/components.py +7 -133
- provide/foundation/logger/__init__.py +3 -10
- provide/foundation/logger/config/logging.py +6 -6
- provide/foundation/logger/core.py +0 -2
- provide/foundation/logger/custom_processors.py +1 -0
- provide/foundation/logger/factories.py +11 -2
- provide/foundation/logger/processors/main.py +20 -84
- provide/foundation/logger/setup/__init__.py +5 -1
- provide/foundation/logger/setup/coordinator.py +75 -23
- provide/foundation/logger/setup/processors.py +2 -9
- provide/foundation/logger/trace.py +27 -0
- provide/foundation/metrics/otel.py +10 -10
- provide/foundation/process/lifecycle.py +82 -26
- provide/foundation/testing/__init__.py +77 -0
- provide/foundation/testing/archive/__init__.py +24 -0
- provide/foundation/testing/archive/fixtures.py +217 -0
- provide/foundation/testing/common/__init__.py +34 -0
- provide/foundation/testing/common/fixtures.py +263 -0
- provide/foundation/testing/file/__init__.py +40 -0
- provide/foundation/testing/file/fixtures.py +523 -0
- provide/foundation/testing/logger.py +41 -11
- provide/foundation/testing/mocking/__init__.py +46 -0
- provide/foundation/testing/mocking/fixtures.py +331 -0
- provide/foundation/testing/process/__init__.py +48 -0
- provide/foundation/testing/process/fixtures.py +577 -0
- provide/foundation/testing/threading/__init__.py +38 -0
- provide/foundation/testing/threading/fixtures.py +520 -0
- provide/foundation/testing/time/__init__.py +32 -0
- provide/foundation/testing/time/fixtures.py +409 -0
- provide/foundation/testing/transport/__init__.py +30 -0
- provide/foundation/testing/transport/fixtures.py +280 -0
- provide/foundation/tools/__init__.py +58 -0
- provide/foundation/tools/base.py +348 -0
- provide/foundation/tools/cache.py +266 -0
- provide/foundation/tools/downloader.py +213 -0
- provide/foundation/tools/installer.py +254 -0
- provide/foundation/tools/registry.py +223 -0
- provide/foundation/tools/resolver.py +321 -0
- provide/foundation/tools/verifier.py +186 -0
- provide/foundation/tracer/otel.py +7 -11
- provide/foundation/transport/__init__.py +155 -0
- provide/foundation/transport/base.py +171 -0
- provide/foundation/transport/client.py +266 -0
- provide/foundation/transport/config.py +209 -0
- provide/foundation/transport/errors.py +79 -0
- provide/foundation/transport/http.py +232 -0
- provide/foundation/transport/middleware.py +366 -0
- provide/foundation/transport/registry.py +167 -0
- provide/foundation/transport/types.py +45 -0
- {provide_foundation-0.0.0.dev0.dist-info → provide_foundation-0.0.0.dev1.dist-info}/METADATA +5 -28
- {provide_foundation-0.0.0.dev0.dist-info → provide_foundation-0.0.0.dev1.dist-info}/RECORD +85 -34
- provide/foundation/cli/commands/logs/generate_old.py +0 -569
- provide/foundation/crypto/certificates.py +0 -896
- provide/foundation/logger/emoji/__init__.py +0 -44
- provide/foundation/logger/emoji/matrix.py +0 -209
- provide/foundation/logger/emoji/sets.py +0 -458
- provide/foundation/logger/emoji/types.py +0 -56
- provide/foundation/logger/setup/emoji_resolver.py +0 -64
- {provide_foundation-0.0.0.dev0.dist-info → provide_foundation-0.0.0.dev1.dist-info}/WHEEL +0 -0
- {provide_foundation-0.0.0.dev0.dist-info → provide_foundation-0.0.0.dev1.dist-info}/entry_points.txt +0 -0
- {provide_foundation-0.0.0.dev0.dist-info → provide_foundation-0.0.0.dev1.dist-info}/licenses/LICENSE +0 -0
- {provide_foundation-0.0.0.dev0.dist-info → provide_foundation-0.0.0.dev1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,520 @@
|
|
1
|
+
"""
|
2
|
+
Threading Test Fixtures and Utilities.
|
3
|
+
|
4
|
+
Fixtures for testing multi-threaded code, thread synchronization,
|
5
|
+
and concurrent operations across the provide-io ecosystem.
|
6
|
+
"""
|
7
|
+
|
8
|
+
import threading
|
9
|
+
import time
|
10
|
+
import queue
|
11
|
+
from typing import Any, Callable, Optional
|
12
|
+
from collections.abc import Generator
|
13
|
+
from concurrent.futures import ThreadPoolExecutor
|
14
|
+
from unittest.mock import Mock
|
15
|
+
|
16
|
+
import pytest
|
17
|
+
|
18
|
+
|
19
|
+
@pytest.fixture
|
20
|
+
def test_thread():
|
21
|
+
"""
|
22
|
+
Create a test thread with automatic cleanup.
|
23
|
+
|
24
|
+
Returns:
|
25
|
+
Function to create and manage test threads.
|
26
|
+
"""
|
27
|
+
threads = []
|
28
|
+
|
29
|
+
def _create_thread(target: Callable, args: tuple = (), kwargs: dict = None, daemon: bool = True) -> threading.Thread:
|
30
|
+
"""
|
31
|
+
Create a test thread.
|
32
|
+
|
33
|
+
Args:
|
34
|
+
target: Function to run in thread
|
35
|
+
args: Positional arguments for target
|
36
|
+
kwargs: Keyword arguments for target
|
37
|
+
daemon: Whether thread should be daemon
|
38
|
+
|
39
|
+
Returns:
|
40
|
+
Started thread instance
|
41
|
+
"""
|
42
|
+
kwargs = kwargs or {}
|
43
|
+
thread = threading.Thread(target=target, args=args, kwargs=kwargs, daemon=daemon)
|
44
|
+
threads.append(thread)
|
45
|
+
thread.start()
|
46
|
+
return thread
|
47
|
+
|
48
|
+
yield _create_thread
|
49
|
+
|
50
|
+
# Cleanup: wait for all threads to complete
|
51
|
+
for thread in threads:
|
52
|
+
if thread.is_alive():
|
53
|
+
thread.join(timeout=1.0)
|
54
|
+
|
55
|
+
|
56
|
+
@pytest.fixture
|
57
|
+
def thread_pool():
|
58
|
+
"""
|
59
|
+
Create a thread pool executor for testing.
|
60
|
+
|
61
|
+
Returns:
|
62
|
+
ThreadPoolExecutor instance with automatic cleanup.
|
63
|
+
"""
|
64
|
+
executor = ThreadPoolExecutor(max_workers=4)
|
65
|
+
yield executor
|
66
|
+
executor.shutdown(wait=True, cancel_futures=True)
|
67
|
+
|
68
|
+
|
69
|
+
@pytest.fixture
|
70
|
+
def thread_barrier():
|
71
|
+
"""
|
72
|
+
Create a barrier for thread synchronization.
|
73
|
+
|
74
|
+
Returns:
|
75
|
+
Function to create barriers for N threads.
|
76
|
+
"""
|
77
|
+
barriers = []
|
78
|
+
|
79
|
+
def _create_barrier(n_threads: int, timeout: Optional[float] = None) -> threading.Barrier:
|
80
|
+
"""
|
81
|
+
Create a barrier for synchronizing threads.
|
82
|
+
|
83
|
+
Args:
|
84
|
+
n_threads: Number of threads to synchronize
|
85
|
+
timeout: Optional timeout for barrier
|
86
|
+
|
87
|
+
Returns:
|
88
|
+
Barrier instance
|
89
|
+
"""
|
90
|
+
barrier = threading.Barrier(n_threads, timeout=timeout)
|
91
|
+
barriers.append(barrier)
|
92
|
+
return barrier
|
93
|
+
|
94
|
+
yield _create_barrier
|
95
|
+
|
96
|
+
# Cleanup: abort all barriers
|
97
|
+
for barrier in barriers:
|
98
|
+
try:
|
99
|
+
barrier.abort()
|
100
|
+
except threading.BrokenBarrierError:
|
101
|
+
pass
|
102
|
+
|
103
|
+
|
104
|
+
@pytest.fixture
|
105
|
+
def thread_safe_list():
|
106
|
+
"""
|
107
|
+
Create a thread-safe list for collecting results.
|
108
|
+
|
109
|
+
Returns:
|
110
|
+
Thread-safe list implementation.
|
111
|
+
"""
|
112
|
+
class ThreadSafeList:
|
113
|
+
def __init__(self):
|
114
|
+
self._list = []
|
115
|
+
self._lock = threading.Lock()
|
116
|
+
|
117
|
+
def append(self, item: Any):
|
118
|
+
"""Thread-safe append."""
|
119
|
+
with self._lock:
|
120
|
+
self._list.append(item)
|
121
|
+
|
122
|
+
def extend(self, items):
|
123
|
+
"""Thread-safe extend."""
|
124
|
+
with self._lock:
|
125
|
+
self._list.extend(items)
|
126
|
+
|
127
|
+
def get_all(self) -> list:
|
128
|
+
"""Get copy of all items."""
|
129
|
+
with self._lock:
|
130
|
+
return self._list.copy()
|
131
|
+
|
132
|
+
def clear(self):
|
133
|
+
"""Clear the list."""
|
134
|
+
with self._lock:
|
135
|
+
self._list.clear()
|
136
|
+
|
137
|
+
def __len__(self) -> int:
|
138
|
+
with self._lock:
|
139
|
+
return len(self._list)
|
140
|
+
|
141
|
+
def __getitem__(self, index):
|
142
|
+
with self._lock:
|
143
|
+
return self._list[index]
|
144
|
+
|
145
|
+
return ThreadSafeList()
|
146
|
+
|
147
|
+
|
148
|
+
@pytest.fixture
|
149
|
+
def thread_safe_counter():
|
150
|
+
"""
|
151
|
+
Create a thread-safe counter.
|
152
|
+
|
153
|
+
Returns:
|
154
|
+
Thread-safe counter implementation.
|
155
|
+
"""
|
156
|
+
class ThreadSafeCounter:
|
157
|
+
def __init__(self, initial: int = 0):
|
158
|
+
self._value = initial
|
159
|
+
self._lock = threading.Lock()
|
160
|
+
|
161
|
+
def increment(self, amount: int = 1) -> int:
|
162
|
+
"""Thread-safe increment."""
|
163
|
+
with self._lock:
|
164
|
+
self._value += amount
|
165
|
+
return self._value
|
166
|
+
|
167
|
+
def decrement(self, amount: int = 1) -> int:
|
168
|
+
"""Thread-safe decrement."""
|
169
|
+
with self._lock:
|
170
|
+
self._value -= amount
|
171
|
+
return self._value
|
172
|
+
|
173
|
+
@property
|
174
|
+
def value(self) -> int:
|
175
|
+
"""Get current value."""
|
176
|
+
with self._lock:
|
177
|
+
return self._value
|
178
|
+
|
179
|
+
def reset(self, value: int = 0):
|
180
|
+
"""Reset counter."""
|
181
|
+
with self._lock:
|
182
|
+
self._value = value
|
183
|
+
|
184
|
+
return ThreadSafeCounter()
|
185
|
+
|
186
|
+
|
187
|
+
@pytest.fixture
|
188
|
+
def thread_event():
|
189
|
+
"""
|
190
|
+
Create thread events for signaling.
|
191
|
+
|
192
|
+
Returns:
|
193
|
+
Function to create thread events.
|
194
|
+
"""
|
195
|
+
events = []
|
196
|
+
|
197
|
+
def _create_event() -> threading.Event:
|
198
|
+
"""Create a thread event."""
|
199
|
+
event = threading.Event()
|
200
|
+
events.append(event)
|
201
|
+
return event
|
202
|
+
|
203
|
+
yield _create_event
|
204
|
+
|
205
|
+
# Cleanup: set all events to release waiting threads
|
206
|
+
for event in events:
|
207
|
+
event.set()
|
208
|
+
|
209
|
+
|
210
|
+
@pytest.fixture
|
211
|
+
def thread_condition():
|
212
|
+
"""
|
213
|
+
Create condition variables for thread coordination.
|
214
|
+
|
215
|
+
Returns:
|
216
|
+
Function to create condition variables.
|
217
|
+
"""
|
218
|
+
def _create_condition(lock: Optional[threading.Lock] = None) -> threading.Condition:
|
219
|
+
"""
|
220
|
+
Create a condition variable.
|
221
|
+
|
222
|
+
Args:
|
223
|
+
lock: Optional lock to use (creates new if None)
|
224
|
+
|
225
|
+
Returns:
|
226
|
+
Condition variable
|
227
|
+
"""
|
228
|
+
return threading.Condition(lock)
|
229
|
+
|
230
|
+
return _create_condition
|
231
|
+
|
232
|
+
|
233
|
+
@pytest.fixture
|
234
|
+
def mock_thread():
|
235
|
+
"""
|
236
|
+
Create a mock thread for testing without actual threading.
|
237
|
+
|
238
|
+
Returns:
|
239
|
+
Mock thread object.
|
240
|
+
"""
|
241
|
+
mock = Mock(spec=threading.Thread)
|
242
|
+
mock.is_alive.return_value = False
|
243
|
+
mock.daemon = False
|
244
|
+
mock.name = "MockThread"
|
245
|
+
mock.ident = 12345
|
246
|
+
mock.start = Mock()
|
247
|
+
mock.join = Mock()
|
248
|
+
mock.run = Mock()
|
249
|
+
|
250
|
+
return mock
|
251
|
+
|
252
|
+
|
253
|
+
@pytest.fixture
|
254
|
+
def thread_local_storage():
|
255
|
+
"""
|
256
|
+
Create thread-local storage for testing.
|
257
|
+
|
258
|
+
Returns:
|
259
|
+
Thread-local storage object.
|
260
|
+
"""
|
261
|
+
return threading.local()
|
262
|
+
|
263
|
+
|
264
|
+
@pytest.fixture
|
265
|
+
def concurrent_executor():
|
266
|
+
"""
|
267
|
+
Helper for executing functions concurrently in tests.
|
268
|
+
|
269
|
+
Returns:
|
270
|
+
Concurrent execution helper.
|
271
|
+
"""
|
272
|
+
class ConcurrentExecutor:
|
273
|
+
def __init__(self):
|
274
|
+
self.results = []
|
275
|
+
self.exceptions = []
|
276
|
+
|
277
|
+
def run_concurrent(self, func: Callable, args_list: list[tuple], max_workers: int = 4) -> list[Any]:
|
278
|
+
"""
|
279
|
+
Run function concurrently with different arguments.
|
280
|
+
|
281
|
+
Args:
|
282
|
+
func: Function to execute
|
283
|
+
args_list: List of argument tuples
|
284
|
+
max_workers: Maximum concurrent workers
|
285
|
+
|
286
|
+
Returns:
|
287
|
+
List of results in order
|
288
|
+
"""
|
289
|
+
with ThreadPoolExecutor(max_workers=max_workers) as executor:
|
290
|
+
futures = []
|
291
|
+
for args in args_list:
|
292
|
+
if isinstance(args, tuple):
|
293
|
+
future = executor.submit(func, *args)
|
294
|
+
else:
|
295
|
+
future = executor.submit(func, args)
|
296
|
+
futures.append(future)
|
297
|
+
|
298
|
+
results = []
|
299
|
+
for future in futures:
|
300
|
+
try:
|
301
|
+
result = future.result(timeout=10)
|
302
|
+
results.append(result)
|
303
|
+
self.results.append(result)
|
304
|
+
except Exception as e:
|
305
|
+
self.exceptions.append(e)
|
306
|
+
results.append(None)
|
307
|
+
|
308
|
+
return results
|
309
|
+
|
310
|
+
def run_parallel(self, funcs: list[Callable], timeout: float = 10) -> list[Any]:
|
311
|
+
"""
|
312
|
+
Run different functions in parallel.
|
313
|
+
|
314
|
+
Args:
|
315
|
+
funcs: List of functions to execute
|
316
|
+
timeout: Timeout for each function
|
317
|
+
|
318
|
+
Returns:
|
319
|
+
List of results
|
320
|
+
"""
|
321
|
+
with ThreadPoolExecutor(max_workers=len(funcs)) as executor:
|
322
|
+
futures = [executor.submit(func) for func in funcs]
|
323
|
+
results = []
|
324
|
+
|
325
|
+
for future in futures:
|
326
|
+
try:
|
327
|
+
result = future.result(timeout=timeout)
|
328
|
+
results.append(result)
|
329
|
+
except Exception as e:
|
330
|
+
self.exceptions.append(e)
|
331
|
+
results.append(None)
|
332
|
+
|
333
|
+
return results
|
334
|
+
|
335
|
+
return ConcurrentExecutor()
|
336
|
+
|
337
|
+
|
338
|
+
@pytest.fixture
|
339
|
+
def thread_synchronizer():
|
340
|
+
"""
|
341
|
+
Helper for synchronizing test threads.
|
342
|
+
|
343
|
+
Returns:
|
344
|
+
Thread synchronization helper.
|
345
|
+
"""
|
346
|
+
class ThreadSynchronizer:
|
347
|
+
def __init__(self):
|
348
|
+
self.checkpoints = {}
|
349
|
+
|
350
|
+
def checkpoint(self, name: str, thread_id: Optional[int] = None):
|
351
|
+
"""
|
352
|
+
Record that a thread reached a checkpoint.
|
353
|
+
|
354
|
+
Args:
|
355
|
+
name: Checkpoint name
|
356
|
+
thread_id: Optional thread ID (uses current if None)
|
357
|
+
"""
|
358
|
+
thread_id = thread_id or threading.get_ident()
|
359
|
+
if name not in self.checkpoints:
|
360
|
+
self.checkpoints[name] = []
|
361
|
+
self.checkpoints[name].append((thread_id, time.time()))
|
362
|
+
|
363
|
+
def wait_for_checkpoint(self, name: str, count: int, timeout: float = 5.0) -> bool:
|
364
|
+
"""
|
365
|
+
Wait for N threads to reach a checkpoint.
|
366
|
+
|
367
|
+
Args:
|
368
|
+
name: Checkpoint name
|
369
|
+
count: Number of threads to wait for
|
370
|
+
timeout: Maximum wait time
|
371
|
+
|
372
|
+
Returns:
|
373
|
+
True if checkpoint reached, False if timeout
|
374
|
+
"""
|
375
|
+
start = time.time()
|
376
|
+
while time.time() - start < timeout:
|
377
|
+
if name in self.checkpoints and len(self.checkpoints[name]) >= count:
|
378
|
+
return True
|
379
|
+
time.sleep(0.01)
|
380
|
+
return False
|
381
|
+
|
382
|
+
def get_order(self, checkpoint: str) -> list[int]:
|
383
|
+
"""
|
384
|
+
Get order in which threads reached checkpoint.
|
385
|
+
|
386
|
+
Args:
|
387
|
+
checkpoint: Checkpoint name
|
388
|
+
|
389
|
+
Returns:
|
390
|
+
List of thread IDs in order
|
391
|
+
"""
|
392
|
+
if checkpoint not in self.checkpoints:
|
393
|
+
return []
|
394
|
+
return [tid for tid, _ in sorted(self.checkpoints[checkpoint], key=lambda x: x[1])]
|
395
|
+
|
396
|
+
def clear(self):
|
397
|
+
"""Clear all checkpoints."""
|
398
|
+
self.checkpoints.clear()
|
399
|
+
|
400
|
+
return ThreadSynchronizer()
|
401
|
+
|
402
|
+
|
403
|
+
@pytest.fixture
|
404
|
+
def deadlock_detector():
|
405
|
+
"""
|
406
|
+
Helper for detecting potential deadlocks in tests.
|
407
|
+
|
408
|
+
Returns:
|
409
|
+
Deadlock detection helper.
|
410
|
+
"""
|
411
|
+
class DeadlockDetector:
|
412
|
+
def __init__(self):
|
413
|
+
self.locks_held = {} # thread_id -> set of locks
|
414
|
+
self.lock = threading.Lock()
|
415
|
+
|
416
|
+
def acquire(self, lock_name: str, thread_id: Optional[int] = None):
|
417
|
+
"""Record lock acquisition."""
|
418
|
+
thread_id = thread_id or threading.get_ident()
|
419
|
+
with self.lock:
|
420
|
+
if thread_id not in self.locks_held:
|
421
|
+
self.locks_held[thread_id] = set()
|
422
|
+
self.locks_held[thread_id].add(lock_name)
|
423
|
+
|
424
|
+
def release(self, lock_name: str, thread_id: Optional[int] = None):
|
425
|
+
"""Record lock release."""
|
426
|
+
thread_id = thread_id or threading.get_ident()
|
427
|
+
with self.lock:
|
428
|
+
if thread_id in self.locks_held:
|
429
|
+
self.locks_held[thread_id].discard(lock_name)
|
430
|
+
|
431
|
+
def check_circular_wait(self) -> bool:
|
432
|
+
"""
|
433
|
+
Check for potential circular wait conditions.
|
434
|
+
|
435
|
+
Returns:
|
436
|
+
True if potential deadlock detected
|
437
|
+
"""
|
438
|
+
# Simplified check - in practice would need wait-for graph
|
439
|
+
with self.lock:
|
440
|
+
# Check if multiple threads hold multiple locks
|
441
|
+
multi_lock_threads = [
|
442
|
+
tid for tid, locks in self.locks_held.items()
|
443
|
+
if len(locks) > 1
|
444
|
+
]
|
445
|
+
return len(multi_lock_threads) > 1
|
446
|
+
|
447
|
+
def get_held_locks(self) -> dict[int, set[str]]:
|
448
|
+
"""Get current lock holdings."""
|
449
|
+
with self.lock:
|
450
|
+
return self.locks_held.copy()
|
451
|
+
|
452
|
+
return DeadlockDetector()
|
453
|
+
|
454
|
+
|
455
|
+
@pytest.fixture
|
456
|
+
def thread_exception_handler():
|
457
|
+
"""
|
458
|
+
Capture exceptions from threads for testing.
|
459
|
+
|
460
|
+
Returns:
|
461
|
+
Exception handler for threads.
|
462
|
+
"""
|
463
|
+
class ThreadExceptionHandler:
|
464
|
+
def __init__(self):
|
465
|
+
self.exceptions = []
|
466
|
+
self.lock = threading.Lock()
|
467
|
+
|
468
|
+
def handle(self, func: Callable) -> Callable:
|
469
|
+
"""
|
470
|
+
Wrap function to capture exceptions.
|
471
|
+
|
472
|
+
Args:
|
473
|
+
func: Function to wrap
|
474
|
+
|
475
|
+
Returns:
|
476
|
+
Wrapped function
|
477
|
+
"""
|
478
|
+
def wrapper(*args, **kwargs):
|
479
|
+
try:
|
480
|
+
return func(*args, **kwargs)
|
481
|
+
except Exception as e:
|
482
|
+
with self.lock:
|
483
|
+
self.exceptions.append({
|
484
|
+
'thread': threading.current_thread().name,
|
485
|
+
'exception': e,
|
486
|
+
'time': time.time()
|
487
|
+
})
|
488
|
+
raise
|
489
|
+
|
490
|
+
return wrapper
|
491
|
+
|
492
|
+
def get_exceptions(self) -> list[dict]:
|
493
|
+
"""Get all captured exceptions."""
|
494
|
+
with self.lock:
|
495
|
+
return self.exceptions.copy()
|
496
|
+
|
497
|
+
def assert_no_exceptions(self):
|
498
|
+
"""Assert no exceptions were raised."""
|
499
|
+
with self.lock:
|
500
|
+
if self.exceptions:
|
501
|
+
raise AssertionError(f"Thread exceptions occurred: {self.exceptions}")
|
502
|
+
|
503
|
+
return ThreadExceptionHandler()
|
504
|
+
|
505
|
+
|
506
|
+
__all__ = [
|
507
|
+
"test_thread",
|
508
|
+
"thread_pool",
|
509
|
+
"thread_barrier",
|
510
|
+
"thread_safe_list",
|
511
|
+
"thread_safe_counter",
|
512
|
+
"thread_event",
|
513
|
+
"thread_condition",
|
514
|
+
"mock_thread",
|
515
|
+
"thread_local_storage",
|
516
|
+
"concurrent_executor",
|
517
|
+
"thread_synchronizer",
|
518
|
+
"deadlock_detector",
|
519
|
+
"thread_exception_handler",
|
520
|
+
]
|
@@ -0,0 +1,32 @@
|
|
1
|
+
"""
|
2
|
+
Time testing utilities for the provide-io ecosystem.
|
3
|
+
|
4
|
+
Fixtures and utilities for mocking time, freezing time, and testing
|
5
|
+
time-dependent code across any project that depends on provide.foundation.
|
6
|
+
"""
|
7
|
+
|
8
|
+
from provide.foundation.testing.time.fixtures import (
|
9
|
+
freeze_time,
|
10
|
+
mock_sleep,
|
11
|
+
mock_sleep_with_callback,
|
12
|
+
time_machine,
|
13
|
+
timer,
|
14
|
+
mock_datetime,
|
15
|
+
time_travel,
|
16
|
+
rate_limiter_mock,
|
17
|
+
benchmark_timer,
|
18
|
+
advance_time,
|
19
|
+
)
|
20
|
+
|
21
|
+
__all__ = [
|
22
|
+
"freeze_time",
|
23
|
+
"mock_sleep",
|
24
|
+
"mock_sleep_with_callback",
|
25
|
+
"time_machine",
|
26
|
+
"timer",
|
27
|
+
"mock_datetime",
|
28
|
+
"time_travel",
|
29
|
+
"rate_limiter_mock",
|
30
|
+
"benchmark_timer",
|
31
|
+
"advance_time",
|
32
|
+
]
|