Skip to content

Commit cfbace0

Browse files
committed
ruff
1 parent aa9ade9 commit cfbace0

File tree

2 files changed

+86
-74
lines changed

2 files changed

+86
-74
lines changed

src/grasshopper/lib/grasshopper.py

Lines changed: 37 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,10 @@
1212
if os.name == "posix":
1313
import resource # pylint: disable=import-error
1414

15+
from functools import wraps
16+
1517
import gevent
1618
import locust
17-
from functools import wraps
1819
from grasshopper.lib.journeys.base_journey import BaseJourney
1920
from grasshopper.lib.util.listeners import GrasshopperListeners
2021
from locust import LoadTestShape
@@ -177,37 +178,47 @@ def launch_test(
177178
# processed because eventually, we will need to consult the shape to get the max
178179
# number of virtual users
179180
Grasshopper._assign_weights_to_user_classes(weighted_user_classes)
180-
181+
181182
# Set up iteration limit if specified
182183
iterations = kwargs.get("iterations", 0)
183184
runtime = kwargs.get("runtime")
184-
185+
185186
logger.info(f"Test run limits: {runtime} seconds and {iterations} iterations.")
186187
# Log test limits at the start
187188
if runtime and iterations > 0:
188189
logger.info("Test will stop when either limit is reached first.")
189190
elif runtime:
190-
logger.info(f"Test will stop when runtime limit of {runtime} seconds will be reached.")
191+
logger.info(
192+
f"Test will stop when runtime limit of {runtime} seconds will be reached."
193+
)
191194
elif iterations > 0:
192-
logger.info(f"Test will stop when ietration limit of {iterations} will be reached.")
193-
195+
logger.info(
196+
f"Test will stop when ietration limit of {iterations} will be reached."
197+
)
198+
194199
if iterations > 0:
195200
Grasshopper._setup_iteration_limit(env, iterations)
196-
201+
197202
env.runner.start_shape()
198-
203+
199204
# Always set runtime-based termination if runtime is specified
200205
# This allows both runtime and iterations to work together (whichever happens first)
201206
if runtime:
207+
202208
def handle_runtime_limit():
203209
# Check if iteration limit was already reached
204-
if not (hasattr(env.runner, 'iteration_target_reached') and env.runner.iteration_target_reached):
210+
if not (
211+
hasattr(env.runner, "iteration_target_reached")
212+
and env.runner.iteration_target_reached
213+
):
205214
# Runtime limit reached first
206-
logger.info(f"Test stopped: Runtime limit of {runtime} seconds reached.")
215+
logger.info(
216+
f"Test stopped: Runtime limit of {runtime} seconds reached."
217+
)
207218
os.kill(os.getpid(), signal.SIGINT)
208-
219+
209220
gevent.spawn_later(runtime, handle_runtime_limit)
210-
221+
211222
env.runner.greenlet.join()
212223

213224
return env
@@ -220,38 +231,44 @@ def _assign_weights_to_user_classes(weighted_user_classes):
220231
@staticmethod
221232
def _setup_iteration_limit(env: Environment, iterations: int):
222233
"""Set up iteration limiting for the test.
223-
234+
224235
This method patches TaskSet.execute_task to track iterations and stop
225236
the test when the iteration limit is reached.
226-
237+
227238
Args:
228239
env: The Locust Environment object
229240
iterations: Maximum number of iterations to run
230241
"""
231242
runner = env.runner
232243
runner.iterations_started = 0
233244
runner.iteration_target_reached = False
234-
245+
235246
def iteration_limit_wrapper(method):
236247
@wraps(method)
237248
def wrapped(self, task):
238249
if runner.iterations_started >= iterations:
239250
if not runner.iteration_target_reached:
240251
runner.iteration_target_reached = True
241-
logger.info(f"Test stopped: Iteration limit of {iterations} reached.")
252+
logger.info(
253+
f"Test stopped: Iteration limit of {iterations} reached."
254+
)
242255
if runner.user_count == 1:
243256
logger.info("Last user stopped, quitting runner")
244257
# Send final stats and quit
245-
gevent.spawn_later(0.1, lambda: os.kill(os.getpid(), signal.SIGINT))
258+
gevent.spawn_later(
259+
0.1, lambda: os.kill(os.getpid(), signal.SIGINT)
260+
)
246261
raise StopUser()
247262
runner.iterations_started = runner.iterations_started + 1
248263
method(self, task)
249-
264+
250265
return wrapped
251-
266+
252267
# Patch TaskSet methods to add iteration limiting
253268
TaskSet.execute_task = iteration_limit_wrapper(TaskSet.execute_task)
254-
DefaultTaskSet.execute_task = iteration_limit_wrapper(DefaultTaskSet.execute_task)
269+
DefaultTaskSet.execute_task = iteration_limit_wrapper(
270+
DefaultTaskSet.execute_task
271+
)
255272

256273
@staticmethod
257274
def load_shape(shape_name: str, **kwargs) -> LoadTestShape:

tests/unit/test_iterations.py

Lines changed: 49 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,23 @@
11
"""Unit tests for the iterations feature."""
22

3+
from unittest.mock import MagicMock, create_autospec, patch
4+
35
import pytest
4-
from unittest.mock import MagicMock, patch, create_autospec
56
from grasshopper.lib.grasshopper import Grasshopper
67
from grasshopper.lib.journeys.base_journey import BaseJourney
78
from locust.env import Environment
9+
from locust.exception import StopUser
810
from locust.runners import LocalRunner
911
from locust.user.task import DefaultTaskSet, TaskSet
10-
from locust.exception import StopUser
1112

1213

1314
class MockJourney(BaseJourney):
1415
"""Mock journey for testing."""
15-
16+
1617
def __init__(self, *args, **kwargs):
1718
super().__init__(*args, **kwargs)
1819
self.task_count = 0
19-
20+
2021
def example_task(self):
2122
self.task_count += 1
2223

@@ -35,24 +36,24 @@ def mock_environment():
3536
def test_setup_iteration_limit_initializes_runner_attributes(mock_environment):
3637
"""Test that _setup_iteration_limit properly initializes runner attributes."""
3738
Grasshopper._setup_iteration_limit(mock_environment, 10)
38-
39+
3940
assert mock_environment.runner.iterations_started == 0
40-
assert mock_environment.runner.iteration_target_reached == False
41+
assert mock_environment.runner.iteration_target_reached is False
4142

4243

4344
def test_setup_iteration_limit_patches_taskset_methods(mock_environment):
4445
"""Test that _setup_iteration_limit patches TaskSet methods."""
4546
# Store original methods
4647
original_taskset_execute = TaskSet.execute_task
4748
original_default_taskset_execute = DefaultTaskSet.execute_task
48-
49+
4950
try:
5051
Grasshopper._setup_iteration_limit(mock_environment, 10)
51-
52+
5253
# Check that methods have been patched (wrapped)
5354
assert TaskSet.execute_task != original_taskset_execute
5455
assert DefaultTaskSet.execute_task != original_default_taskset_execute
55-
56+
5657
finally:
5758
# Restore original methods
5859
TaskSet.execute_task = original_taskset_execute
@@ -63,104 +64,98 @@ def test_iteration_limit_stops_after_reaching_limit(mock_environment):
6364
"""Test that tasks stop executing after iteration limit is reached."""
6465
# Store original method
6566
original_taskset_execute = TaskSet.execute_task
66-
67+
6768
try:
6869
Grasshopper._setup_iteration_limit(mock_environment, 2)
69-
70+
7071
# Create a mock TaskSet
7172
mock_taskset = MagicMock(spec=TaskSet)
7273
mock_task = MagicMock()
73-
74+
7475
# Execute tasks up to the limit
7576
TaskSet.execute_task(mock_taskset, mock_task)
7677
assert mock_environment.runner.iterations_started == 1
77-
78+
7879
TaskSet.execute_task(mock_taskset, mock_task)
7980
assert mock_environment.runner.iterations_started == 2
80-
81+
8182
# Next execution should raise StopUser
8283
with pytest.raises(StopUser):
8384
TaskSet.execute_task(mock_taskset, mock_task)
84-
85+
8586
# Verify that iteration target was reached
86-
assert mock_environment.runner.iteration_target_reached == True
87-
87+
assert mock_environment.runner.iteration_target_reached is True
88+
8889
finally:
8990
# Restore original method
9091
TaskSet.execute_task = original_taskset_execute
9192

9293

9394
def test_launch_test_with_iterations_no_runtime_timer():
9495
"""Test that launch_test doesn't set runtime timer when iterations is specified."""
95-
with patch.object(Grasshopper, 'set_ulimit'), \
96-
patch.object(Grasshopper, '_setup_iteration_limit') as mock_setup_iterations, \
97-
patch('grasshopper.lib.grasshopper.Environment') as MockEnvironment, \
98-
patch('grasshopper.lib.grasshopper.gevent.spawn_later') as mock_spawn_later, \
99-
patch('grasshopper.lib.grasshopper.gevent.spawn'), \
100-
patch('grasshopper.lib.grasshopper.locust.stats.stats_history'), \
101-
patch('grasshopper.lib.grasshopper.GrasshopperListeners'):
102-
96+
with patch.object(Grasshopper, "set_ulimit"), patch.object(
97+
Grasshopper, "_setup_iteration_limit"
98+
) as mock_setup_iterations, patch(
99+
"grasshopper.lib.grasshopper.Environment"
100+
) as MockEnvironment, patch(
101+
"grasshopper.lib.grasshopper.gevent.spawn_later"
102+
) as mock_spawn_later, patch("grasshopper.lib.grasshopper.gevent.spawn"), patch(
103+
"grasshopper.lib.grasshopper.locust.stats.stats_history"
104+
), patch("grasshopper.lib.grasshopper.GrasshopperListeners"):
103105
# Setup mock environment
104106
mock_env = MagicMock()
105107
mock_env.runner = MagicMock()
106108
mock_env.runner.greenlet = MagicMock()
107109
mock_env.runner.greenlet.join = MagicMock()
108110
MockEnvironment.return_value = mock_env
109-
111+
110112
# Mock shape instance
111113
mock_shape = MagicMock()
112114
mock_shape.configured_runtime = 120
113-
115+
114116
# Launch test with iterations
115-
kwargs = {
116-
'iterations': 10,
117-
'runtime': 120,
118-
'shape_instance': mock_shape
119-
}
120-
117+
kwargs = {"iterations": 10, "runtime": 120, "shape_instance": mock_shape}
118+
121119
Grasshopper.launch_test(MockJourney, **kwargs)
122-
120+
123121
# Verify that _setup_iteration_limit was called
124122
mock_setup_iterations.assert_called_once_with(mock_env, 10)
125-
123+
126124
# Verify that runtime timer was NOT set (spawn_later not called)
127125
mock_spawn_later.assert_not_called()
128126

129127

130128
def test_launch_test_without_iterations_sets_runtime_timer():
131129
"""Test that launch_test sets runtime timer when iterations is not specified."""
132-
with patch.object(Grasshopper, 'set_ulimit'), \
133-
patch.object(Grasshopper, '_setup_iteration_limit') as mock_setup_iterations, \
134-
patch('grasshopper.lib.grasshopper.Environment') as MockEnvironment, \
135-
patch('grasshopper.lib.grasshopper.gevent.spawn_later') as mock_spawn_later, \
136-
patch('grasshopper.lib.grasshopper.gevent.spawn'), \
137-
patch('grasshopper.lib.grasshopper.locust.stats.stats_history'), \
138-
patch('grasshopper.lib.grasshopper.GrasshopperListeners'):
139-
130+
with patch.object(Grasshopper, "set_ulimit"), patch.object(
131+
Grasshopper, "_setup_iteration_limit"
132+
) as mock_setup_iterations, patch(
133+
"grasshopper.lib.grasshopper.Environment"
134+
) as MockEnvironment, patch(
135+
"grasshopper.lib.grasshopper.gevent.spawn_later"
136+
) as mock_spawn_later, patch("grasshopper.lib.grasshopper.gevent.spawn"), patch(
137+
"grasshopper.lib.grasshopper.locust.stats.stats_history"
138+
), patch("grasshopper.lib.grasshopper.GrasshopperListeners"):
140139
# Setup mock environment
141140
mock_env = MagicMock()
142141
mock_env.runner = MagicMock()
143142
mock_env.runner.greenlet = MagicMock()
144143
mock_env.runner.greenlet.join = MagicMock()
145144
MockEnvironment.return_value = mock_env
146-
145+
147146
# Mock shape instance
148147
mock_shape = MagicMock()
149148
mock_shape.configured_runtime = 120
150-
149+
151150
# Launch test without iterations (or with 0)
152-
kwargs = {
153-
'iterations': 0,
154-
'runtime': 120,
155-
'shape_instance': mock_shape
156-
}
157-
151+
kwargs = {"iterations": 0, "runtime": 120, "shape_instance": mock_shape}
152+
158153
Grasshopper.launch_test(MockJourney, **kwargs)
159-
154+
160155
# Verify that _setup_iteration_limit was NOT called
161156
mock_setup_iterations.assert_not_called()
162-
157+
163158
# Verify that runtime timer WAS set
164159
mock_spawn_later.assert_called_once()
165160
# Check that it was called with the runtime value
166-
assert mock_spawn_later.call_args[0][0] == 120
161+
assert mock_spawn_later.call_args[0][0] == 120

0 commit comments

Comments
 (0)