locust 2.39.2.dev7__py3-none-any.whl → 2.39.2.dev11__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 +2 -2
- locust/main.py +11 -4
- locust/pytestplugin.py +44 -0
- locust/user/task.py +5 -1
- locust/user/users.py +31 -1
- locust/util/load_locustfile.py +45 -1
- locust/web.py +1 -1
- {locust-2.39.2.dev7.dist-info → locust-2.39.2.dev11.dist-info}/METADATA +3 -2
- {locust-2.39.2.dev7.dist-info → locust-2.39.2.dev11.dist-info}/RECORD +12 -11
- {locust-2.39.2.dev7.dist-info → locust-2.39.2.dev11.dist-info}/entry_points.txt +3 -0
- {locust-2.39.2.dev7.dist-info → locust-2.39.2.dev11.dist-info}/WHEEL +0 -0
- {locust-2.39.2.dev7.dist-info → locust-2.39.2.dev11.dist-info}/licenses/LICENSE +0 -0
locust/_version.py
CHANGED
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '2.39.2.
|
|
32
|
-
__version_tuple__ = version_tuple = (2, 39, 2, '
|
|
31
|
+
__version__ = version = '2.39.2.dev11'
|
|
32
|
+
__version_tuple__ = version_tuple = (2, 39, 2, 'dev11')
|
|
33
33
|
|
|
34
34
|
__commit_id__ = commit_id = None
|
locust/main.py
CHANGED
|
@@ -30,7 +30,7 @@ from .html import get_html_report, process_html_filename
|
|
|
30
30
|
from .input_events import input_listener
|
|
31
31
|
from .log import greenlet_exception_logger, setup_logging
|
|
32
32
|
from .user.inspectuser import print_task_ratio, print_task_ratio_json
|
|
33
|
-
from .util.load_locustfile import load_locustfile
|
|
33
|
+
from .util.load_locustfile import load_locustfile, load_locustfile_pytest
|
|
34
34
|
|
|
35
35
|
# import external plugins if installed to allow for registering custom arguments etc
|
|
36
36
|
try:
|
|
@@ -123,9 +123,7 @@ def merge_locustfiles_content(
|
|
|
123
123
|
# Check docs for real supported task attribute signature for User\TaskSet class.
|
|
124
124
|
available_user_tasks: dict[str, list[locust.TaskSet | Callable]] = {}
|
|
125
125
|
|
|
126
|
-
|
|
127
|
-
user_classes, shape_classes = load_locustfile(_locustfile)
|
|
128
|
-
|
|
126
|
+
def set_available_things(user_classes, shape_classes):
|
|
129
127
|
# Setting Available Shape Classes
|
|
130
128
|
for _shape_class in shape_classes:
|
|
131
129
|
shape_class_name = type(_shape_class).__name__
|
|
@@ -152,6 +150,15 @@ def merge_locustfiles_content(
|
|
|
152
150
|
available_user_classes[class_name] = class_definition
|
|
153
151
|
available_user_tasks[class_name] = class_definition.tasks
|
|
154
152
|
|
|
153
|
+
for _locustfile in locustfiles:
|
|
154
|
+
user_classes, shape_classes = load_locustfile(_locustfile)
|
|
155
|
+
set_available_things(user_classes, shape_classes)
|
|
156
|
+
|
|
157
|
+
if not available_user_classes: # only load pytest-based locustfiles if no regular ones were found
|
|
158
|
+
for _locustfile in locustfiles:
|
|
159
|
+
user_classes = load_locustfile_pytest(_locustfile)
|
|
160
|
+
set_available_things(user_classes, [])
|
|
161
|
+
|
|
155
162
|
shape_class = list(available_shape_classes.values())[0] if available_shape_classes else None
|
|
156
163
|
|
|
157
164
|
return available_user_classes, available_shape_classes, available_user_tasks, shape_class
|
locust/pytestplugin.py
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from locust.clients import HttpSession
|
|
2
|
+
from locust.contrib.fasthttp import FastHttpSession
|
|
3
|
+
from locust.event import EventHook
|
|
4
|
+
from locust.user.users import User
|
|
5
|
+
|
|
6
|
+
import pytest
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class NoOpEvent(EventHook):
|
|
10
|
+
pass
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
_config: pytest.Config
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# capture Config object instead of having to pass it explicitly to fixtures, which would make them more complex to call from Locust
|
|
17
|
+
def pytest_configure(config):
|
|
18
|
+
global _config
|
|
19
|
+
_config = config
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@pytest.fixture
|
|
23
|
+
def session(user: User | None = None):
|
|
24
|
+
s = HttpSession(
|
|
25
|
+
base_url=user.host if user else _config.getoption("--host"),
|
|
26
|
+
request_event=user.environment.events.request if user else NoOpEvent(),
|
|
27
|
+
user=user,
|
|
28
|
+
)
|
|
29
|
+
yield s
|
|
30
|
+
s.close()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@pytest.fixture
|
|
34
|
+
def fastsession(user: User | None = None):
|
|
35
|
+
s = FastHttpSession(
|
|
36
|
+
base_url=user.host if user else _config.getoption("--host"),
|
|
37
|
+
request_event=user.environment.events.request if user else NoOpEvent(),
|
|
38
|
+
user=user,
|
|
39
|
+
)
|
|
40
|
+
yield s
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def pytest_addoption(parser: pytest.Parser) -> None:
|
|
44
|
+
parser.addoption("--host", "-H", action="store", default=None)
|
locust/user/task.py
CHANGED
|
@@ -211,7 +211,11 @@ def filter_tasks_by_tags(
|
|
|
211
211
|
checked[task] = passing
|
|
212
212
|
|
|
213
213
|
task_holder.tasks = new_tasks
|
|
214
|
-
if
|
|
214
|
+
if (
|
|
215
|
+
not new_tasks
|
|
216
|
+
and not is_markov_taskset(task_holder)
|
|
217
|
+
and not getattr(task_holder, "functions", False) # PytestUser
|
|
218
|
+
):
|
|
215
219
|
logging.warning(f"{task_holder.__name__} had no tasks left after filtering, instantiating it will fail!")
|
|
216
220
|
|
|
217
221
|
|
locust/user/users.py
CHANGED
|
@@ -14,15 +14,24 @@ from locust.user.wait_time import constant
|
|
|
14
14
|
from locust.util import deprecation
|
|
15
15
|
|
|
16
16
|
import logging
|
|
17
|
+
import sys
|
|
17
18
|
import time
|
|
18
19
|
import traceback
|
|
19
20
|
from collections.abc import Callable
|
|
20
21
|
from typing import final
|
|
21
22
|
|
|
23
|
+
import pytest
|
|
22
24
|
from gevent import GreenletExit, greenlet
|
|
23
25
|
from gevent.pool import Group
|
|
26
|
+
from geventhttpclient.useragent import ConnectionError
|
|
27
|
+
from requests.exceptions import RequestException
|
|
24
28
|
from urllib3 import PoolManager
|
|
25
29
|
|
|
30
|
+
if sys.version_info >= (3, 12):
|
|
31
|
+
from typing import override
|
|
32
|
+
else:
|
|
33
|
+
from typing_extensions import override
|
|
34
|
+
|
|
26
35
|
logger = logging.getLogger(__name__)
|
|
27
36
|
|
|
28
37
|
|
|
@@ -144,7 +153,7 @@ class User(metaclass=UserMeta):
|
|
|
144
153
|
self._state = LOCUST_STATE_RUNNING
|
|
145
154
|
self._taskset_instance = DefaultTaskSet(self)
|
|
146
155
|
try:
|
|
147
|
-
# run the
|
|
156
|
+
# run the User on_start method, if it has one
|
|
148
157
|
try:
|
|
149
158
|
self.on_start()
|
|
150
159
|
except Exception as e:
|
|
@@ -274,3 +283,24 @@ class HttpUser(User):
|
|
|
274
283
|
The client supports cookies, and therefore keeps the session between HTTP requests.
|
|
275
284
|
"""
|
|
276
285
|
self.client.trust_env = False
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
class PytestUser(User):
|
|
289
|
+
abstract = True
|
|
290
|
+
functions: list[pytest.Function]
|
|
291
|
+
fixtures: list
|
|
292
|
+
|
|
293
|
+
@override
|
|
294
|
+
def run(self): # type: ignore[override] # We actually DO want to change the default User behavior
|
|
295
|
+
self._state = LOCUST_STATE_RUNNING
|
|
296
|
+
self.fixtures = [next(f.fixturedef.func(self)) for f in self.functions] # type: ignore[attr-defined]
|
|
297
|
+
while True:
|
|
298
|
+
for i in range(len(self.fixtures)):
|
|
299
|
+
try: # try-except is for supporting .raise_for_status() in tests
|
|
300
|
+
self.functions[i].obj(self.fixtures[i])
|
|
301
|
+
except RequestException as e:
|
|
302
|
+
if isinstance(e, ValueError): # things like MissingSchema etc, lets not catch that
|
|
303
|
+
raise
|
|
304
|
+
logger.debug("%s\n%s", e, traceback.format_exc())
|
|
305
|
+
except ConnectionError as e:
|
|
306
|
+
logger.debug("%s\n%s", e, traceback.format_exc())
|
locust/util/load_locustfile.py
CHANGED
|
@@ -6,8 +6,12 @@ import inspect
|
|
|
6
6
|
import os
|
|
7
7
|
import sys
|
|
8
8
|
|
|
9
|
+
import pytest
|
|
10
|
+
from _pytest.config import Config
|
|
11
|
+
|
|
9
12
|
from ..shape import LoadTestShape
|
|
10
13
|
from ..user import User
|
|
14
|
+
from ..user.users import PytestUser
|
|
11
15
|
|
|
12
16
|
|
|
13
17
|
def is_user_class(item) -> bool:
|
|
@@ -81,5 +85,45 @@ def load_locustfile(path) -> tuple[dict[str, type[User]], list[LoadTestShape]]:
|
|
|
81
85
|
|
|
82
86
|
# Find shape class, if any, return it
|
|
83
87
|
shape_classes = [value() for value in vars(imported).values() if is_shape_class(value)]
|
|
84
|
-
|
|
85
88
|
return user_classes, shape_classes
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def load_locustfile_pytest(path) -> dict[str, type[User]]:
|
|
92
|
+
"""
|
|
93
|
+
Create User classes from pytest test functions.
|
|
94
|
+
|
|
95
|
+
It relies on some pytest internals to collect test functions and their fixtures,
|
|
96
|
+
but it should be reasonably stable for simple use cases.
|
|
97
|
+
|
|
98
|
+
Fixtures (like `session` and `fastsession`) are defined in `pytestplugin.py`
|
|
99
|
+
|
|
100
|
+
See `examples/test_pytest.py` and `locust/test/test_pytest_locustfile.py`
|
|
101
|
+
"""
|
|
102
|
+
user_classes: dict[str, type[PytestUser]] = {}
|
|
103
|
+
# collect tests and set up fixture manager
|
|
104
|
+
config = Config.fromdictargs({}, [path])
|
|
105
|
+
config._do_configure()
|
|
106
|
+
session = pytest.Session.from_config(config)
|
|
107
|
+
config.hook.pytest_sessionstart(session=session)
|
|
108
|
+
session.perform_collect()
|
|
109
|
+
config.hook.pytest_collection_modifyitems(session=session, config=config, items=session.items)
|
|
110
|
+
fm = session._fixturemanager
|
|
111
|
+
|
|
112
|
+
for function in session.items:
|
|
113
|
+
if isinstance(function, pytest.Function):
|
|
114
|
+
sig = inspect.signature(function.obj)
|
|
115
|
+
function.kwargs = {} # type: ignore[attr-defined]
|
|
116
|
+
for name in sig.parameters:
|
|
117
|
+
defs = fm.getfixturedefs(name, function)
|
|
118
|
+
if not defs:
|
|
119
|
+
raise ValueError(f"Could not find fixture for parameter {name!r} in {function.name}")
|
|
120
|
+
if len(defs) > 1:
|
|
121
|
+
raise ValueError(f"Multiple fixtures found for parameter {name!r} in {function.name}: {defs}")
|
|
122
|
+
function.fixturedef = defs[0] # type: ignore[attr-defined]
|
|
123
|
+
if not function.name in user_classes:
|
|
124
|
+
user_classes[function.name] = type(function.name, (PytestUser,), {})
|
|
125
|
+
user_classes[function.name].functions = []
|
|
126
|
+
user_classes[function.name].functions.append(function)
|
|
127
|
+
else:
|
|
128
|
+
pass # Skipping non-function item
|
|
129
|
+
return user_classes # type: ignore
|
locust/web.py
CHANGED
|
@@ -320,7 +320,7 @@ class WebUI:
|
|
|
320
320
|
return jsonify({"success": False, "message": err_msg, "host": environment.host})
|
|
321
321
|
self._swarm_greenlet = gevent.spawn(environment.runner.start, user_count, spawn_rate)
|
|
322
322
|
self._swarm_greenlet.link_exception(greenlet_exception_handler)
|
|
323
|
-
response_data = {
|
|
323
|
+
response_data: dict[str, Any] = {
|
|
324
324
|
"success": True,
|
|
325
325
|
"message": "Swarming started",
|
|
326
326
|
"host": environment.host,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: locust
|
|
3
|
-
Version: 2.39.2.
|
|
3
|
+
Version: 2.39.2.dev11
|
|
4
4
|
Summary: Developer-friendly load testing framework
|
|
5
5
|
Project-URL: homepage, https://locust.io/
|
|
6
6
|
Project-URL: repository, https://github.com/locustio/locust
|
|
@@ -33,6 +33,7 @@ Requires-Dist: geventhttpclient>=2.3.1
|
|
|
33
33
|
Requires-Dist: locust-cloud>=1.26.3
|
|
34
34
|
Requires-Dist: msgpack>=1.0.0
|
|
35
35
|
Requires-Dist: psutil>=5.9.1
|
|
36
|
+
Requires-Dist: pytest<9.0.0,>=8.3.3
|
|
36
37
|
Requires-Dist: python-engineio>=4.12.2
|
|
37
38
|
Requires-Dist: python-socketio[client]>=5.13.0
|
|
38
39
|
Requires-Dist: pywin32; sys_platform == 'win32'
|
|
@@ -41,7 +42,7 @@ Requires-Dist: requests>=2.26.0; python_version <= '3.11'
|
|
|
41
42
|
Requires-Dist: requests>=2.32.2; python_version > '3.11'
|
|
42
43
|
Requires-Dist: setuptools>=70.0.0
|
|
43
44
|
Requires-Dist: tomli>=1.1.0; python_version < '3.11'
|
|
44
|
-
Requires-Dist: typing-extensions>=4.6.0; python_version < '3.
|
|
45
|
+
Requires-Dist: typing-extensions>=4.6.0; python_version < '3.12'
|
|
45
46
|
Requires-Dist: werkzeug>=2.0.0
|
|
46
47
|
Provides-Extra: milvus
|
|
47
48
|
Requires-Dist: pymilvus>=2.5.0; extra == 'milvus'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
locust/__init__.py,sha256=HadpgGidiyCDPSKwkxrk1Qw6eB7dTmftNJVftuJzAiw,1876
|
|
2
2
|
locust/__main__.py,sha256=vBQ82334kX06ImDbFlPFgiBRiLIinwNk3z8Khs6hd74,31
|
|
3
|
-
locust/_version.py,sha256=
|
|
3
|
+
locust/_version.py,sha256=Sw5XUxorTCx-oNt66pYOhN0i25NizH_aGicKPj8S2P8,721
|
|
4
4
|
locust/argument_parser.py,sha256=t6mAoK9u13DxC9UH-alVqS6fFABFTyNWSJG89yQ4QQQ,33056
|
|
5
5
|
locust/clients.py,sha256=CeyTYgKuASZS6QKc71T4V3bqYPGlJr3Ef_i6qP07gTQ,19498
|
|
6
6
|
locust/debug.py,sha256=7CCm8bIg44uGH2wqBlo1rXBzV2VzwPicLxLewz8r5CQ,5099
|
|
@@ -11,12 +11,13 @@ locust/exception.py,sha256=jGgJ32ubuf4pWdlaVOkbh2Y0LlG0_DHi-lv3ib8ppOE,1791
|
|
|
11
11
|
locust/html.py,sha256=18LlaL-NEQdthXVdS16gRAd4SOpW_bowOJKyuLvWrCY,4043
|
|
12
12
|
locust/input_events.py,sha256=lqLDB2Ax-OQ7-vtYNkGjySjfaWVobBzqf0GxRwjcLcQ,3323
|
|
13
13
|
locust/log.py,sha256=5E2ZUOa3V4sfCqa-470Gle1Ik9L5nxYitsjEB9tRwE0,3455
|
|
14
|
-
locust/main.py,sha256=
|
|
14
|
+
locust/main.py,sha256=rcUD3MnBnFqeXC41fjGkh4p9POLGWglgPvRAfHKtEnc,28896
|
|
15
15
|
locust/py.typed,sha256=gkWLl8yD4mIZnNYYAIRM8g9VarLvWmTAFeUfEbxJLBw,65
|
|
16
|
+
locust/pytestplugin.py,sha256=fc616tXWQ0FvZ6aa_sBzvaCwjqsBo-tg8uP3-0jxcdU,1116
|
|
16
17
|
locust/runners.py,sha256=niYmGsfOpxMfVmTXGod4MYTefpaZ2wirFlhqxRw5mq4,70617
|
|
17
18
|
locust/shape.py,sha256=t-lwBS8LOjWcKXNL7j2U3zroIXJ1b0fazUwpRYQOKXw,1973
|
|
18
19
|
locust/stats.py,sha256=qyoSKT0i7RunLDj5pMGqizK1Sp8bcqUsXwh2m4_DpR8,47203
|
|
19
|
-
locust/web.py,sha256=
|
|
20
|
+
locust/web.py,sha256=pLYuocmx9gifJ4vsVDgGDYIPkQhQxI7kKjxoXcUajqM,31865
|
|
20
21
|
locust/contrib/__init__.py,sha256=LtZN7MczpIAbZkN7PT2h8W2wgb9nBl6cDXbFCVsV4fo,290
|
|
21
22
|
locust/contrib/fasthttp.py,sha256=c8MznjqbtYTKZPc20OC4GCiaENDCJmmNSNuHqtMhh2Q,29883
|
|
22
23
|
locust/contrib/milvus.py,sha256=YabgLd0lImzWupJFCm0OZAW-Nxeibwn91ldWpZ2irDo,12811
|
|
@@ -31,8 +32,8 @@ locust/user/__init__.py,sha256=RgdRCflP2dIDcvwVMdhPQHAMhWVwQarQ9wWjF9HKk0w,151
|
|
|
31
32
|
locust/user/inspectuser.py,sha256=KgrWHyE5jhK6or58R7soLRf-_st42AaQrR72qbiXw9E,2641
|
|
32
33
|
locust/user/markov_taskset.py,sha256=eESre6OacbP7nTzZFwxUe7TO4X4l7WqOAEETtDzsIfU,11784
|
|
33
34
|
locust/user/sequential_taskset.py,sha256=SbrrGU9HV2nEWe6zQVtjymn8NgPISP7QSNoVdyoXjYg,2687
|
|
34
|
-
locust/user/task.py,sha256=
|
|
35
|
-
locust/user/users.py,sha256=
|
|
35
|
+
locust/user/task.py,sha256=QnNDs8lEZGgMqBe8l_O4FKgn4WUL9r5WTRHWw88nO-w,17242
|
|
36
|
+
locust/user/users.py,sha256=n2IE7MwIKj2JUgYU4_7Vv-cNk-NWDRhj62dQbxrGhIk,11204
|
|
36
37
|
locust/user/wait_time.py,sha256=bGRKMVx4lom75sX3POYJUa1CPeME2bEAXG6CEgxSO5U,2675
|
|
37
38
|
locust/util/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
38
39
|
locust/util/cache.py,sha256=IxbpGawl0-hoWKvCrtksxjSLf2GbBBTVns06F7mFBkM,1062
|
|
@@ -40,7 +41,7 @@ locust/util/date.py,sha256=2uZAY-fkJq7llUcVywTDTbe-_2IYumCv18n3Vrc75gw,833
|
|
|
40
41
|
locust/util/deprecation.py,sha256=4my4IcFpbM6yEKr569GMUKMsS0ywp0N4JPhhwm3J1-w,2026
|
|
41
42
|
locust/util/directory.py,sha256=2EeuVIIFEErm0OpNXdsEgQLx49jAXq-PvMj2uY0Mr8o,326
|
|
42
43
|
locust/util/exception_handler.py,sha256=jTMyBq2a0O07fRjmqGkheyaPj58tUgnbbcjoesKGPws,797
|
|
43
|
-
locust/util/load_locustfile.py,sha256=
|
|
44
|
+
locust/util/load_locustfile.py,sha256=KzrLXeLDhWo7ia9hxACkuES7xykgaBlIt8yJQkHYs8U,4929
|
|
44
45
|
locust/util/rounding.py,sha256=5haxR8mKhATqag6WvPby-MSRRgIw5Ob6thbyvMYZM7o,92
|
|
45
46
|
locust/util/timespan.py,sha256=Y0LtnhUq2Mq19p04u0XtBlYQ_-S2cRvwRdgru8W9WhA,986
|
|
46
47
|
locust/util/url.py,sha256=s_W2PCxvxTWxWX0yUvp-8VBuQm881KwI5X9iifogZG4,321
|
|
@@ -55,8 +56,8 @@ locust/webui/dist/assets/index-BjqxSg7R.js,sha256=3JyrKWfAg8LlTy2bxAJh73c6njNPhN
|
|
|
55
56
|
locust/webui/dist/assets/terminal.gif,sha256=iw80LO2u0dnf4wpGfFJZauBeKTcSpw9iUfISXT2nEF4,75302
|
|
56
57
|
locust/webui/dist/assets/testruns-dark.png,sha256=G4p2VZSBuuqF4neqUaPSshIp5OKQJ_Bvb69Luj6XuVs,125231
|
|
57
58
|
locust/webui/dist/assets/testruns-light.png,sha256=JinGDiiBPOkhpfF-XCbmQqhRInqItrjrBTLKt5MlqVI,130301
|
|
58
|
-
locust-2.39.2.
|
|
59
|
-
locust-2.39.2.
|
|
60
|
-
locust-2.39.2.
|
|
61
|
-
locust-2.39.2.
|
|
62
|
-
locust-2.39.2.
|
|
59
|
+
locust-2.39.2.dev11.dist-info/METADATA,sha256=aHhLh2Z_lKa_A1cQS-Ct36Y4b5MDZ7Dkktf0oeNe_uk,9599
|
|
60
|
+
locust-2.39.2.dev11.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
61
|
+
locust-2.39.2.dev11.dist-info/entry_points.txt,sha256=k0386Xi0vBR3OVIqeGNYz7cz8QUGJ3iLmQ0WQU91ogw,85
|
|
62
|
+
locust-2.39.2.dev11.dist-info/licenses/LICENSE,sha256=5hnz-Vpj0Z3kSCQl0LzV2hT1TLc4LHcbpBp3Cy-EuyM,1110
|
|
63
|
+
locust-2.39.2.dev11.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|