Skip to content

Commit e9a9339

Browse files
committed
fix(tests): make threading tests deterministic
Replace time-based loops and sleep-waits with iteration counts and thread.join(), eliminating flaky failures on slow CI runners.
1 parent 4d49dab commit e9a9339

1 file changed

Lines changed: 28 additions & 46 deletions

File tree

tests/test_threading.py

Lines changed: 28 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,8 @@ def test_regression_443():
3434
"""
3535
Test for https://github.com/fgmacedo/python-statemachine/issues/443
3636
"""
37-
time_collecting = 0.2
38-
time_to_send = 0.125
39-
time_sampling_current_state = 0.05
37+
total_iterations = 4
38+
send_at_iteration = 3 # 0-indexed: send before the 4th sample
4039

4140
class TrafficLightMachine(StateChart):
4241
"A traffic light machine"
@@ -52,25 +51,20 @@ def __init__(self):
5251
self.statuses_history = []
5352
self.fsm = TrafficLightMachine()
5453
# set up thread
55-
t = threading.Thread(target=self.recv_cmds)
56-
t.start()
54+
self.thread = threading.Thread(target=self.recv_cmds)
55+
self.thread.start()
5756

5857
def recv_cmds(self):
59-
"""Pretend we receive a command triggering a state change after Xs."""
60-
waiting_time = 0
61-
sent = False
62-
while waiting_time < time_collecting:
63-
if waiting_time >= time_to_send and not sent:
58+
"""Pretend we receive a command triggering a state change."""
59+
for i in range(total_iterations):
60+
if i == send_at_iteration:
6461
self.fsm.cycle()
65-
sent = True
66-
67-
waiting_time += time_sampling_current_state
6862
self.statuses_history.append(self.fsm.current_state_value)
69-
time.sleep(time_sampling_current_state)
7063

7164
c1 = Controller()
7265
c2 = Controller()
73-
time.sleep(time_collecting + 0.01)
66+
c1.thread.join()
67+
c2.thread.join()
7468
assert c1.statuses_history == ["green", "green", "green", "yellow"]
7569
assert c2.statuses_history == ["green", "green", "green", "yellow"]
7670

@@ -79,9 +73,8 @@ def test_regression_443_with_modifications():
7973
"""
8074
Test for https://github.com/fgmacedo/python-statemachine/issues/443
8175
"""
82-
time_collecting = 0.2
83-
time_to_send = 0.125
84-
time_sampling_current_state = 0.05
76+
total_iterations = 4
77+
send_at_iteration = 3 # 0-indexed: send before the 4th sample
8578

8679
class TrafficLightMachine(StateChart):
8780
"A traffic light machine"
@@ -98,42 +91,36 @@ def __init__(self, name):
9891
super().__init__()
9992

10093
def beat(self):
101-
waiting_time = 0
102-
sent = False
103-
while waiting_time < time_collecting:
104-
if waiting_time >= time_to_send and not sent:
94+
for i in range(total_iterations):
95+
if i == send_at_iteration:
10596
self.cycle()
106-
sent = True
107-
10897
self.statuses_history.append(f"{self.name}.{self.current_state_value}")
10998

110-
time.sleep(time_sampling_current_state)
111-
waiting_time += time_sampling_current_state
112-
11399
class Controller:
114100
def __init__(self, name):
115101
self.fsm = TrafficLightMachine(name)
116102
# set up thread
117-
t = threading.Thread(target=self.fsm.beat)
118-
t.start()
103+
self.thread = threading.Thread(target=self.fsm.beat)
104+
self.thread.start()
119105

120106
c1 = Controller("c1")
121107
c2 = Controller("c2")
122108
c3 = Controller("c3")
123-
time.sleep(time_collecting + 0.01)
109+
c1.thread.join()
110+
c2.thread.join()
111+
c3.thread.join()
124112

125113
assert c1.fsm.statuses_history == ["c1.green", "c1.green", "c1.green", "c1.yellow"]
126114
assert c2.fsm.statuses_history == ["c2.green", "c2.green", "c2.green", "c2.yellow"]
127115
assert c3.fsm.statuses_history == ["c3.green", "c3.green", "c3.green", "c3.yellow"]
128116

129117

130-
async def test_regression_443_with_modifications_for_async_engine(): # noqa: C901
118+
async def test_regression_443_with_modifications_for_async_engine():
131119
"""
132120
Test for https://github.com/fgmacedo/python-statemachine/issues/443
133121
"""
134-
time_collecting = 0.2
135-
time_to_send = 0.125
136-
time_sampling_current_state = 0.05
122+
total_iterations = 4
123+
send_at_iteration = 3 # 0-indexed: send before the 4th sample
137124

138125
class TrafficLightMachine(StateChart):
139126
"A traffic light machine"
@@ -153,35 +140,30 @@ def __init__(self, name):
153140
super().__init__()
154141

155142
def beat(self):
156-
waiting_time = 0
157-
sent = False
158-
while waiting_time < time_collecting:
159-
if waiting_time >= time_to_send and not sent:
143+
for i in range(total_iterations):
144+
if i == send_at_iteration:
160145
self.cycle()
161-
sent = True
162-
163146
self.statuses_history.append(f"{self.name}.{self.current_state_value}")
164147

165-
time.sleep(time_sampling_current_state)
166-
waiting_time += time_sampling_current_state
167-
168148
class Controller:
169149
def __init__(self, name):
170150
self.fsm = TrafficLightMachine(name)
171151

172152
async def start(self):
173153
# set up thread
174154
await self.fsm.activate_initial_state()
175-
t = threading.Thread(target=self.fsm.beat)
176-
t.start()
155+
self.thread = threading.Thread(target=self.fsm.beat)
156+
self.thread.start()
177157

178158
c1 = Controller("c1")
179159
c2 = Controller("c2")
180160
c3 = Controller("c3")
181161
await c1.start()
182162
await c2.start()
183163
await c3.start()
184-
time.sleep(time_collecting + 0.01)
164+
c1.thread.join()
165+
c2.thread.join()
166+
c3.thread.join()
185167

186168
assert c1.fsm.statuses_history == ["c1.green", "c1.green", "c1.green", "c1.yellow"]
187169
assert c2.fsm.statuses_history == ["c2.green", "c2.green", "c2.green", "c2.yellow"]

0 commit comments

Comments
 (0)