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,268 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Thread execution and testing helper fixtures.
|
|
3
|
+
|
|
4
|
+
Advanced fixtures for concurrent execution, synchronization testing, deadlock detection,
|
|
5
|
+
and exception handling in threaded code.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from collections.abc import Callable
|
|
9
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
10
|
+
import threading
|
|
11
|
+
import time
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
import pytest
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@pytest.fixture
|
|
18
|
+
def concurrent_executor():
|
|
19
|
+
"""
|
|
20
|
+
Helper for executing functions concurrently in tests.
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
Concurrent execution helper.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
class ConcurrentExecutor:
|
|
27
|
+
def __init__(self):
|
|
28
|
+
self.results = []
|
|
29
|
+
self.exceptions = []
|
|
30
|
+
|
|
31
|
+
def run_concurrent(self, func: Callable, args_list: list[tuple], max_workers: int = 4) -> list[Any]:
|
|
32
|
+
"""
|
|
33
|
+
Run function concurrently with different arguments.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
func: Function to execute
|
|
37
|
+
args_list: List of argument tuples
|
|
38
|
+
max_workers: Maximum concurrent workers
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
List of results in order
|
|
42
|
+
"""
|
|
43
|
+
with ThreadPoolExecutor(max_workers=max_workers) as executor:
|
|
44
|
+
futures = []
|
|
45
|
+
for args in args_list:
|
|
46
|
+
if isinstance(args, tuple):
|
|
47
|
+
future = executor.submit(func, *args)
|
|
48
|
+
else:
|
|
49
|
+
future = executor.submit(func, args)
|
|
50
|
+
futures.append(future)
|
|
51
|
+
|
|
52
|
+
results = []
|
|
53
|
+
for future in futures:
|
|
54
|
+
try:
|
|
55
|
+
result = future.result(timeout=10)
|
|
56
|
+
results.append(result)
|
|
57
|
+
self.results.append(result)
|
|
58
|
+
except Exception as e:
|
|
59
|
+
self.exceptions.append(e)
|
|
60
|
+
results.append(None)
|
|
61
|
+
|
|
62
|
+
return results
|
|
63
|
+
|
|
64
|
+
def run_parallel(self, funcs: list[Callable], timeout: float = 10) -> list[Any]:
|
|
65
|
+
"""
|
|
66
|
+
Run different functions in parallel.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
funcs: List of functions to execute
|
|
70
|
+
timeout: Timeout for each function
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
List of results
|
|
74
|
+
"""
|
|
75
|
+
with ThreadPoolExecutor(max_workers=len(funcs)) as executor:
|
|
76
|
+
futures = [executor.submit(func) for func in funcs]
|
|
77
|
+
results = []
|
|
78
|
+
|
|
79
|
+
for future in futures:
|
|
80
|
+
try:
|
|
81
|
+
result = future.result(timeout=timeout)
|
|
82
|
+
results.append(result)
|
|
83
|
+
except Exception as e:
|
|
84
|
+
self.exceptions.append(e)
|
|
85
|
+
results.append(None)
|
|
86
|
+
|
|
87
|
+
return results
|
|
88
|
+
|
|
89
|
+
return ConcurrentExecutor()
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@pytest.fixture
|
|
93
|
+
def thread_synchronizer():
|
|
94
|
+
"""
|
|
95
|
+
Helper for synchronizing test threads.
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
Thread synchronization helper.
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
class ThreadSynchronizer:
|
|
102
|
+
def __init__(self):
|
|
103
|
+
self.checkpoints = {}
|
|
104
|
+
|
|
105
|
+
def checkpoint(self, name: str, thread_id: int | None = None):
|
|
106
|
+
"""
|
|
107
|
+
Record that a thread reached a checkpoint.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
name: Checkpoint name
|
|
111
|
+
thread_id: Optional thread ID (uses current if None)
|
|
112
|
+
"""
|
|
113
|
+
thread_id = thread_id or threading.get_ident()
|
|
114
|
+
if name not in self.checkpoints:
|
|
115
|
+
self.checkpoints[name] = []
|
|
116
|
+
self.checkpoints[name].append((thread_id, time.time()))
|
|
117
|
+
|
|
118
|
+
def wait_for_checkpoint(self, name: str, count: int, timeout: float = 5.0) -> bool:
|
|
119
|
+
"""
|
|
120
|
+
Wait for N threads to reach a checkpoint.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
name: Checkpoint name
|
|
124
|
+
count: Number of threads to wait for
|
|
125
|
+
timeout: Maximum wait time
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
True if checkpoint reached, False if timeout
|
|
129
|
+
"""
|
|
130
|
+
start = time.time()
|
|
131
|
+
while time.time() - start < timeout:
|
|
132
|
+
if name in self.checkpoints and len(self.checkpoints[name]) >= count:
|
|
133
|
+
return True
|
|
134
|
+
time.sleep(0.01)
|
|
135
|
+
return False
|
|
136
|
+
|
|
137
|
+
def get_order(self, checkpoint: str) -> list[int]:
|
|
138
|
+
"""
|
|
139
|
+
Get order in which threads reached checkpoint.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
checkpoint: Checkpoint name
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
List of thread IDs in order
|
|
146
|
+
"""
|
|
147
|
+
if checkpoint not in self.checkpoints:
|
|
148
|
+
return []
|
|
149
|
+
return [tid for tid, _ in sorted(self.checkpoints[checkpoint], key=lambda x: x[1])]
|
|
150
|
+
|
|
151
|
+
def clear(self):
|
|
152
|
+
"""Clear all checkpoints."""
|
|
153
|
+
self.checkpoints.clear()
|
|
154
|
+
|
|
155
|
+
return ThreadSynchronizer()
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
@pytest.fixture
|
|
159
|
+
def deadlock_detector():
|
|
160
|
+
"""
|
|
161
|
+
Helper for detecting potential deadlocks in tests.
|
|
162
|
+
|
|
163
|
+
Returns:
|
|
164
|
+
Deadlock detection helper.
|
|
165
|
+
"""
|
|
166
|
+
|
|
167
|
+
class DeadlockDetector:
|
|
168
|
+
def __init__(self):
|
|
169
|
+
self.locks_held = {} # thread_id -> set of locks
|
|
170
|
+
self.lock = threading.Lock()
|
|
171
|
+
|
|
172
|
+
def acquire(self, lock_name: str, thread_id: int | None = None):
|
|
173
|
+
"""Record lock acquisition."""
|
|
174
|
+
thread_id = thread_id or threading.get_ident()
|
|
175
|
+
with self.lock:
|
|
176
|
+
if thread_id not in self.locks_held:
|
|
177
|
+
self.locks_held[thread_id] = set()
|
|
178
|
+
self.locks_held[thread_id].add(lock_name)
|
|
179
|
+
|
|
180
|
+
def release(self, lock_name: str, thread_id: int | None = None):
|
|
181
|
+
"""Record lock release."""
|
|
182
|
+
thread_id = thread_id or threading.get_ident()
|
|
183
|
+
with self.lock:
|
|
184
|
+
if thread_id in self.locks_held:
|
|
185
|
+
self.locks_held[thread_id].discard(lock_name)
|
|
186
|
+
|
|
187
|
+
def check_circular_wait(self) -> bool:
|
|
188
|
+
"""
|
|
189
|
+
Check for potential circular wait conditions.
|
|
190
|
+
|
|
191
|
+
Returns:
|
|
192
|
+
True if potential deadlock detected
|
|
193
|
+
"""
|
|
194
|
+
# Simplified check - in practice would need wait-for graph
|
|
195
|
+
with self.lock:
|
|
196
|
+
# Check if multiple threads hold multiple locks
|
|
197
|
+
multi_lock_threads = [tid for tid, locks in self.locks_held.items() if len(locks) > 1]
|
|
198
|
+
return len(multi_lock_threads) > 1
|
|
199
|
+
|
|
200
|
+
def get_held_locks(self) -> dict[int, set[str]]:
|
|
201
|
+
"""Get current lock holdings."""
|
|
202
|
+
with self.lock:
|
|
203
|
+
return self.locks_held.copy()
|
|
204
|
+
|
|
205
|
+
return DeadlockDetector()
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
@pytest.fixture
|
|
209
|
+
def thread_exception_handler():
|
|
210
|
+
"""
|
|
211
|
+
Capture exceptions from threads for testing.
|
|
212
|
+
|
|
213
|
+
Returns:
|
|
214
|
+
Exception handler for threads.
|
|
215
|
+
"""
|
|
216
|
+
|
|
217
|
+
class ThreadExceptionHandler:
|
|
218
|
+
def __init__(self):
|
|
219
|
+
self.exceptions = []
|
|
220
|
+
self.lock = threading.Lock()
|
|
221
|
+
|
|
222
|
+
def handle(self, func: Callable) -> Callable:
|
|
223
|
+
"""
|
|
224
|
+
Wrap function to capture exceptions.
|
|
225
|
+
|
|
226
|
+
Args:
|
|
227
|
+
func: Function to wrap
|
|
228
|
+
|
|
229
|
+
Returns:
|
|
230
|
+
Wrapped function
|
|
231
|
+
"""
|
|
232
|
+
|
|
233
|
+
def wrapper(*args, **kwargs):
|
|
234
|
+
try:
|
|
235
|
+
return func(*args, **kwargs)
|
|
236
|
+
except Exception as e:
|
|
237
|
+
with self.lock:
|
|
238
|
+
self.exceptions.append(
|
|
239
|
+
{
|
|
240
|
+
"thread": threading.current_thread().name,
|
|
241
|
+
"exception": e,
|
|
242
|
+
"time": time.time(),
|
|
243
|
+
}
|
|
244
|
+
)
|
|
245
|
+
raise
|
|
246
|
+
|
|
247
|
+
return wrapper
|
|
248
|
+
|
|
249
|
+
def get_exceptions(self) -> list[dict]:
|
|
250
|
+
"""Get all captured exceptions."""
|
|
251
|
+
with self.lock:
|
|
252
|
+
return self.exceptions.copy()
|
|
253
|
+
|
|
254
|
+
def assert_no_exceptions(self):
|
|
255
|
+
"""Assert no exceptions were raised."""
|
|
256
|
+
with self.lock:
|
|
257
|
+
if self.exceptions:
|
|
258
|
+
raise AssertionError(f"Thread exceptions occurred: {self.exceptions}")
|
|
259
|
+
|
|
260
|
+
return ThreadExceptionHandler()
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
__all__ = [
|
|
264
|
+
"concurrent_executor",
|
|
265
|
+
"deadlock_detector",
|
|
266
|
+
"thread_exception_handler",
|
|
267
|
+
"thread_synchronizer",
|
|
268
|
+
]
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Threading Test Fixtures and Utilities.
|
|
3
|
+
|
|
4
|
+
Core threading fixtures with re-exports from specialized modules.
|
|
5
|
+
Fixtures for testing multi-threaded code, thread synchronization,
|
|
6
|
+
and concurrent operations across the provide-io ecosystem.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
# Re-export all fixtures from specialized modules
|
|
10
|
+
from provide.testkit.threading.basic_fixtures import (
|
|
11
|
+
mock_thread,
|
|
12
|
+
test_thread,
|
|
13
|
+
thread_local_storage,
|
|
14
|
+
thread_pool,
|
|
15
|
+
)
|
|
16
|
+
from provide.testkit.threading.data_fixtures import (
|
|
17
|
+
thread_safe_counter,
|
|
18
|
+
thread_safe_list,
|
|
19
|
+
)
|
|
20
|
+
from provide.testkit.threading.execution_fixtures import (
|
|
21
|
+
concurrent_executor,
|
|
22
|
+
deadlock_detector,
|
|
23
|
+
thread_exception_handler,
|
|
24
|
+
thread_synchronizer,
|
|
25
|
+
)
|
|
26
|
+
from provide.testkit.threading.sync_fixtures import (
|
|
27
|
+
thread_barrier,
|
|
28
|
+
thread_condition,
|
|
29
|
+
thread_event,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
__all__ = [
|
|
33
|
+
# Basic threading fixtures
|
|
34
|
+
"test_thread",
|
|
35
|
+
"thread_pool",
|
|
36
|
+
"mock_thread",
|
|
37
|
+
"thread_local_storage",
|
|
38
|
+
# Synchronization fixtures
|
|
39
|
+
"thread_barrier",
|
|
40
|
+
"thread_event",
|
|
41
|
+
"thread_condition",
|
|
42
|
+
# Thread-safe data structures
|
|
43
|
+
"thread_safe_list",
|
|
44
|
+
"thread_safe_counter",
|
|
45
|
+
# Execution and testing helpers
|
|
46
|
+
"concurrent_executor",
|
|
47
|
+
"thread_synchronizer",
|
|
48
|
+
"deadlock_detector",
|
|
49
|
+
"thread_exception_handler",
|
|
50
|
+
]
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Thread synchronization test fixtures.
|
|
3
|
+
|
|
4
|
+
Fixtures for thread barriers, events, conditions, and other synchronization primitives.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import threading
|
|
8
|
+
|
|
9
|
+
import pytest
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@pytest.fixture
|
|
13
|
+
def thread_barrier():
|
|
14
|
+
"""
|
|
15
|
+
Create a barrier for thread synchronization.
|
|
16
|
+
|
|
17
|
+
Returns:
|
|
18
|
+
Function to create barriers for N threads.
|
|
19
|
+
"""
|
|
20
|
+
barriers = []
|
|
21
|
+
|
|
22
|
+
def _create_barrier(n_threads: int, timeout: float | None = None) -> threading.Barrier:
|
|
23
|
+
"""
|
|
24
|
+
Create a barrier for synchronizing threads.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
n_threads: Number of threads to synchronize
|
|
28
|
+
timeout: Optional timeout for barrier
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
Barrier instance
|
|
32
|
+
"""
|
|
33
|
+
barrier = threading.Barrier(n_threads, timeout=timeout)
|
|
34
|
+
barriers.append(barrier)
|
|
35
|
+
return barrier
|
|
36
|
+
|
|
37
|
+
yield _create_barrier
|
|
38
|
+
|
|
39
|
+
# Cleanup: abort all barriers
|
|
40
|
+
for barrier in barriers:
|
|
41
|
+
try:
|
|
42
|
+
barrier.abort()
|
|
43
|
+
except threading.BrokenBarrierError:
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@pytest.fixture
|
|
48
|
+
def thread_event():
|
|
49
|
+
"""
|
|
50
|
+
Create thread events for signaling.
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
Function to create thread events.
|
|
54
|
+
"""
|
|
55
|
+
events = []
|
|
56
|
+
|
|
57
|
+
def _create_event() -> threading.Event:
|
|
58
|
+
"""Create a thread event."""
|
|
59
|
+
event = threading.Event()
|
|
60
|
+
events.append(event)
|
|
61
|
+
return event
|
|
62
|
+
|
|
63
|
+
yield _create_event
|
|
64
|
+
|
|
65
|
+
# Cleanup: set all events to release waiting threads
|
|
66
|
+
for event in events:
|
|
67
|
+
event.set()
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@pytest.fixture
|
|
71
|
+
def thread_condition():
|
|
72
|
+
"""
|
|
73
|
+
Create condition variables for thread coordination.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
Function to create condition variables.
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
def _create_condition(lock: threading.Lock | None = None) -> threading.Condition:
|
|
80
|
+
"""
|
|
81
|
+
Create a condition variable.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
lock: Optional lock to use (creates new if None)
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
Condition variable
|
|
88
|
+
"""
|
|
89
|
+
return threading.Condition(lock)
|
|
90
|
+
|
|
91
|
+
return _create_condition
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
__all__ = [
|
|
95
|
+
"thread_barrier",
|
|
96
|
+
"thread_condition",
|
|
97
|
+
"thread_event",
|
|
98
|
+
]
|
|
@@ -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.testkit.time.fixtures import (
|
|
9
|
+
advance_time,
|
|
10
|
+
benchmark_timer,
|
|
11
|
+
freeze_time,
|
|
12
|
+
mock_datetime,
|
|
13
|
+
mock_sleep,
|
|
14
|
+
mock_sleep_with_callback,
|
|
15
|
+
rate_limiter_mock,
|
|
16
|
+
time_machine,
|
|
17
|
+
time_travel,
|
|
18
|
+
timer,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
"advance_time",
|
|
23
|
+
"benchmark_timer",
|
|
24
|
+
"freeze_time",
|
|
25
|
+
"mock_datetime",
|
|
26
|
+
"mock_sleep",
|
|
27
|
+
"mock_sleep_with_callback",
|
|
28
|
+
"rate_limiter_mock",
|
|
29
|
+
"time_machine",
|
|
30
|
+
"time_travel",
|
|
31
|
+
"timer",
|
|
32
|
+
]
|