Skip to content

Commit 0ccad33

Browse files
Merge pull request #1172 from mnadzam/test/threading
test(threading_utils): add coverage for thread safety decorator
2 parents 3342010 + b44d115 commit 0ccad33

1 file changed

Lines changed: 91 additions & 0 deletions

File tree

tests/test_threading_utils.py

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import threading
2+
import time
3+
4+
import pytest
5+
6+
from fromager.threading_utils import with_thread_lock
7+
8+
9+
def test_decorated_function_returns_value() -> None:
10+
@with_thread_lock()
11+
def add(a: int, b: int) -> int:
12+
return a + b
13+
14+
assert add(2, 3) == 5
15+
assert add(a=10, b=20) == 30
16+
17+
18+
def test_mutual_exclusion() -> None:
19+
events: list[str] = []
20+
ready = threading.Barrier(2)
21+
22+
@with_thread_lock()
23+
def slow() -> None:
24+
events.append("enter")
25+
time.sleep(0.05)
26+
events.append("exit")
27+
28+
def worker() -> None:
29+
ready.wait() # ensure both threads race for the lock
30+
slow()
31+
32+
t1 = threading.Thread(target=worker)
33+
t2 = threading.Thread(target=worker)
34+
t1.start()
35+
t2.start()
36+
t1.join()
37+
t2.join()
38+
39+
assert events == ["enter", "exit", "enter", "exit"]
40+
41+
42+
def test_independent_locks_do_not_block_each_other() -> None:
43+
barrier = threading.Barrier(2)
44+
45+
@with_thread_lock()
46+
def func_a() -> None:
47+
barrier.wait(timeout=2)
48+
49+
@with_thread_lock()
50+
def func_b() -> None:
51+
barrier.wait(timeout=2)
52+
53+
t1 = threading.Thread(target=func_a)
54+
t2 = threading.Thread(target=func_b)
55+
t1.start()
56+
t2.start()
57+
t1.join(timeout=3)
58+
t2.join(timeout=3)
59+
60+
assert not t1.is_alive()
61+
assert not t2.is_alive()
62+
63+
64+
def test_lock_released_after_exception() -> None:
65+
call_count = 0
66+
67+
@with_thread_lock()
68+
def throws_on_first_call() -> str:
69+
nonlocal call_count
70+
call_count += 1
71+
if call_count == 1:
72+
raise ValueError("first call exception")
73+
return "ok"
74+
75+
with pytest.raises(ValueError, match="first call exception"):
76+
throws_on_first_call()
77+
78+
assert throws_on_first_call() == "ok"
79+
80+
81+
def test_nested_call_to_different_decorated_function() -> None:
82+
@with_thread_lock()
83+
def inner() -> str:
84+
return "ok"
85+
86+
@with_thread_lock()
87+
def outer() -> str:
88+
result: str = inner()
89+
return result
90+
91+
assert outer() == "ok"

0 commit comments

Comments
 (0)