locust 2.23.2.dev7__py3-none-any.whl → 2.23.2.dev18__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.
locust/_version.py CHANGED
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '2.23.2.dev7'
16
- __version_tuple__ = version_tuple = (2, 23, 2, 'dev7')
15
+ __version__ = version = '2.23.2.dev18'
16
+ __version_tuple__ = version_tuple = (2, 23, 2, 'dev18')
locust/dispatch.py CHANGED
@@ -5,9 +5,8 @@ import itertools
5
5
  import math
6
6
  import time
7
7
  from collections import defaultdict
8
- from collections.abc import Iterator
9
8
  from operator import attrgetter
10
- from typing import TYPE_CHECKING, Generator
9
+ from typing import TYPE_CHECKING, Generator, Iterator
11
10
 
12
11
  import gevent
13
12
  from roundrobin import smooth
@@ -164,7 +163,9 @@ class UsersDispatcher(Iterator):
164
163
 
165
164
  self._dispatch_in_progress = False
166
165
 
167
- def new_dispatch(self, target_user_count: int, spawn_rate: float, user_classes: list | None = None) -> None:
166
+ def new_dispatch(
167
+ self, target_user_count: int, spawn_rate: float, user_classes: list[type[User]] | None = None
168
+ ) -> None:
168
169
  """
169
170
  Initialize a new dispatch cycle.
170
171
 
locust/env.py CHANGED
@@ -5,6 +5,7 @@ from typing import Callable, TypeVar
5
5
 
6
6
  from configargparse import Namespace
7
7
 
8
+ from .dispatch import UsersDispatcher
8
9
  from .event import Events
9
10
  from .exception import RunnerAlreadyExistsError
10
11
  from .runners import LocalRunner, MasterRunner, Runner, WorkerRunner
@@ -35,6 +36,7 @@ class Environment:
35
36
  available_user_classes: dict[str, User] | None = None,
36
37
  available_shape_classes: dict[str, LoadTestShape] | None = None,
37
38
  available_user_tasks: dict[str, list[TaskSet | Callable]] | None = None,
39
+ dispatcher_class: type[UsersDispatcher] = UsersDispatcher,
38
40
  ):
39
41
  self.runner: Runner | None = None
40
42
  """Reference to the :class:`Runner <locust.runners.Runner>` instance"""
@@ -95,6 +97,8 @@ class Environment:
95
97
  """List of the available Shape Classes to pick from in the ShapeClass Picker"""
96
98
  self.available_user_tasks = available_user_tasks
97
99
  """List of the available Tasks per User Classes to pick from in the Task Picker"""
100
+ self.dispatcher_class = dispatcher_class
101
+ """A user dispatcher class that decides how users are spawned, default :class:`UsersDispatcher <locust.dispatch.UsersDispatcher>`"""
98
102
 
99
103
  self._remove_user_classes_with_weight_zero()
100
104
  self._validate_user_class_name_uniqueness()
locust/runners.py CHANGED
@@ -492,13 +492,13 @@ class LocalRunner(Runner):
492
492
  self.update_state(STATE_SPAWNING)
493
493
 
494
494
  if self._users_dispatcher is None:
495
- self._users_dispatcher = UsersDispatcher(
495
+ self._users_dispatcher = self.environment.dispatcher_class(
496
496
  worker_nodes=[self._local_worker_node], user_classes=self.user_classes
497
497
  )
498
498
 
499
499
  logger.info("Ramping to %d users at a rate of %.2f per second" % (user_count, spawn_rate))
500
500
 
501
- cast(UsersDispatcher, self._users_dispatcher).new_dispatch(user_count, spawn_rate, user_classes)
501
+ self._users_dispatcher.new_dispatch(user_count, spawn_rate, user_classes)
502
502
 
503
503
  try:
504
504
  for dispatched_users in self._users_dispatcher:
@@ -750,7 +750,7 @@ class MasterRunner(DistributedRunner):
750
750
  self.spawn_rate = spawn_rate
751
751
 
752
752
  if self._users_dispatcher is None:
753
- self._users_dispatcher = UsersDispatcher(
753
+ self._users_dispatcher = self.environment.dispatcher_class(
754
754
  worker_nodes=list(self.clients.values()), user_classes=self.user_classes
755
755
  )
756
756
 
locust/test/test_env.py CHANGED
@@ -1,6 +1,7 @@
1
1
  from locust import (
2
2
  constant,
3
3
  )
4
+ from locust.dispatch import UsersDispatcher
4
5
  from locust.env import Environment, LoadTestShape
5
6
  from locust.user import (
6
7
  User,
@@ -201,6 +202,18 @@ class TestEnvironment(LocustTestCase):
201
202
  ):
202
203
  Environment(user_classes=[MyUserWithSameName1], shape_class=SubLoadTestShape)
203
204
 
205
+ def test_dispatcher_class_attribute(self):
206
+ environment = Environment(user_classes=[MyUserWithSameName1])
207
+
208
+ self.assertEqual(environment.dispatcher_class, UsersDispatcher)
209
+
210
+ class MyUsersDispatcher(UsersDispatcher):
211
+ pass
212
+
213
+ environment = Environment(user_classes=[MyUserWithSameName1], dispatcher_class=MyUsersDispatcher)
214
+
215
+ self.assertEqual(environment.dispatcher_class, MyUsersDispatcher)
216
+
204
217
  def test_update_user_class(self):
205
218
  class MyUser1(User):
206
219
  @task
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import locust
2
4
  from locust import (
3
5
  LoadTestShape,
@@ -6,6 +8,7 @@ from locust import (
6
8
  runners,
7
9
  )
8
10
  from locust.argument_parser import parse_options
11
+ from locust.dispatch import UsersDispatcher
9
12
  from locust.env import Environment
10
13
  from locust.exception import RPCError, RPCReceiveError, StopUser
11
14
  from locust.main import create_environment
@@ -843,6 +846,42 @@ class TestLocustRunner(LocustRunnerTestCase):
843
846
  local_runner.stop()
844
847
  web_ui.stop()
845
848
 
849
+ def test_custom_dispatcher_class(self):
850
+ @mock.create_autospec
851
+ class MyUsersDispatcher(UsersDispatcher):
852
+ def __init__(self, worker_nodes: list[WorkerNode], user_classes: list[type[User]]) -> None:
853
+ super().__init__(worker_nodes, user_classes)
854
+
855
+ def add_worker(self, worker_node: WorkerNode) -> None:
856
+ pass
857
+
858
+ def remove_worker(self, worker_node: WorkerNode) -> None:
859
+ pass
860
+
861
+ @property
862
+ def dispatch_in_progress(self) -> bool:
863
+ return True
864
+
865
+ def new_dispatch(self, target_user_count: int, spawn_rate: float, user_classes: list | None = None) -> None:
866
+ return super().new_dispatch(target_user_count, spawn_rate, user_classes)
867
+
868
+ class MyUser1(User):
869
+ wait_time = constant(0)
870
+ fixed_count = 1
871
+
872
+ @task
873
+ def my_task(self):
874
+ pass
875
+
876
+ environment = Environment(user_classes=[MyUser1], dispatcher_class=MyUsersDispatcher)
877
+ runner = LocalRunner(environment)
878
+
879
+ runner.start(user_count=1, spawn_rate=1)
880
+ sleep(1)
881
+ runner._users_dispatcher.new_dispatch.assert_called_with(1, 1, None)
882
+ runner._users_dispatcher.__iter__.assert_called_once()
883
+ runner.stop()
884
+
846
885
 
847
886
  class TestMasterWorkerRunners(LocustTestCase):
848
887
  def test_distributed_integration_run(self):
locust/test/util.py CHANGED
@@ -3,6 +3,7 @@ import functools
3
3
  import gc
4
4
  import os
5
5
  import socket
6
+ import warnings
6
7
  from contextlib import contextmanager
7
8
  from tempfile import NamedTemporaryFile
8
9
 
@@ -78,7 +79,9 @@ def create_tls_cert(hostname):
78
79
  def clear_all_functools_lru_cache() -> None:
79
80
  # Clear all `functools.lru_cache` to ensure that no state are persisted from one test to another.
80
81
  # Taken from https://stackoverflow.com/a/50699209.
81
- gc.collect()
82
+ with warnings.catch_warnings():
83
+ warnings.simplefilter(action="ignore", category=ResourceWarning)
84
+ gc.collect()
82
85
  wrappers = [a for a in gc.get_objects() if isinstance(a, functools._lru_cache_wrapper)]
83
86
  assert len(wrappers) > 0
84
87
  for wrapper in wrappers:
locust/user/task.py CHANGED
@@ -308,7 +308,7 @@ class TaskSet(metaclass=TaskSetMeta):
308
308
  """Parent TaskSet instance of this TaskSet (or :py:class:`User <locust.User>` if this is not a nested TaskSet)"""
309
309
  return self._parent
310
310
 
311
- def on_start(self):
311
+ def on_start(self) -> None:
312
312
  """
313
313
  Called when a User starts executing this TaskSet
314
314
  """
locust/user/users.py CHANGED
@@ -128,7 +128,7 @@ class User(metaclass=UserMeta):
128
128
  self._taskset_instance: TaskSet = None
129
129
  self._cp_last_run = time.time() # used by constant_pacing wait_time
130
130
 
131
- def on_start(self):
131
+ def on_start(self) -> None:
132
132
  """
133
133
  Called when a User starts running.
134
134
  """