locust 2.30.1.dev4__py3-none-any.whl → 2.30.1.dev14__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/argument_parser.py +27 -33
- locust/env.py +3 -0
- locust/main.py +3 -0
- locust/runners.py +30 -16
- locust/util/directory.py +12 -0
- locust/util/url.py +15 -0
- {locust-2.30.1.dev4.dist-info → locust-2.30.1.dev14.dist-info}/METADATA +1 -1
- {locust-2.30.1.dev4.dist-info → locust-2.30.1.dev14.dist-info}/RECORD +12 -10
- {locust-2.30.1.dev4.dist-info → locust-2.30.1.dev14.dist-info}/LICENSE +0 -0
- {locust-2.30.1.dev4.dist-info → locust-2.30.1.dev14.dist-info}/WHEEL +0 -0
- {locust-2.30.1.dev4.dist-info → locust-2.30.1.dev14.dist-info}/entry_points.txt +0 -0
locust/_version.py
CHANGED
@@ -14,7 +14,7 @@ __version_tuple__: VERSION_TUPLE
|
|
14
14
|
version_tuple: VERSION_TUPLE
|
15
15
|
|
16
16
|
|
17
|
-
__version__ = "2.30.1.
|
17
|
+
__version__ = "2.30.1.dev14"
|
18
18
|
version = __version__
|
19
|
-
__version_tuple__ = (2, 30, 1, "
|
19
|
+
__version_tuple__ = (2, 30, 1, "dev14")
|
20
20
|
version_tuple = __version_tuple__
|
locust/argument_parser.py
CHANGED
@@ -15,7 +15,6 @@ import tempfile
|
|
15
15
|
import textwrap
|
16
16
|
from collections import OrderedDict
|
17
17
|
from typing import Any, NamedTuple
|
18
|
-
from urllib.parse import urlparse
|
19
18
|
from uuid import uuid4
|
20
19
|
|
21
20
|
if sys.version_info >= (3, 11):
|
@@ -27,6 +26,9 @@ import configargparse
|
|
27
26
|
import gevent
|
28
27
|
import requests
|
29
28
|
|
29
|
+
from .util.directory import get_abspaths_in
|
30
|
+
from .util.url import is_url
|
31
|
+
|
30
32
|
version = locust.__version__
|
31
33
|
|
32
34
|
|
@@ -125,14 +127,7 @@ def _parse_locustfile_path(path: str) -> list[str]:
|
|
125
127
|
parsed_paths.append(download_locustfile_from_url(path))
|
126
128
|
elif os.path.isdir(path):
|
127
129
|
# Find all .py files in directory tree
|
128
|
-
|
129
|
-
parsed_paths.extend(
|
130
|
-
[
|
131
|
-
os.path.abspath(os.path.join(root, f))
|
132
|
-
for f in fs
|
133
|
-
if os.path.isfile(os.path.join(root, f)) and f.endswith(".py") and not f.startswith("_")
|
134
|
-
]
|
135
|
-
)
|
130
|
+
parsed_paths.extend(get_abspaths_in(path, extension=".py"))
|
136
131
|
if not parsed_paths:
|
137
132
|
sys.stderr.write(f"Could not find any locustfiles in directory '{path}'")
|
138
133
|
sys.exit(1)
|
@@ -148,20 +143,6 @@ def _parse_locustfile_path(path: str) -> list[str]:
|
|
148
143
|
return parsed_paths
|
149
144
|
|
150
145
|
|
151
|
-
def is_url(url: str) -> bool:
|
152
|
-
"""
|
153
|
-
Check if path is an url
|
154
|
-
"""
|
155
|
-
try:
|
156
|
-
result = urlparse(url)
|
157
|
-
if result.scheme == "https" or result.scheme == "http":
|
158
|
-
return True
|
159
|
-
else:
|
160
|
-
return False
|
161
|
-
except ValueError:
|
162
|
-
return False
|
163
|
-
|
164
|
-
|
165
146
|
def download_locustfile_from_url(url: str) -> str:
|
166
147
|
"""
|
167
148
|
Attempt to download and save locustfile from url.
|
@@ -238,7 +219,7 @@ def download_locustfile_from_master(master_host: str, master_port: int) -> str:
|
|
238
219
|
|
239
220
|
def ask_for_locustfile():
|
240
221
|
while not got_reply:
|
241
|
-
tempclient.send(Message("locustfile",
|
222
|
+
tempclient.send(Message("locustfile", {"version": version}, client_id))
|
242
223
|
gevent.sleep(1)
|
243
224
|
|
244
225
|
def log_warning():
|
@@ -271,14 +252,26 @@ def download_locustfile_from_master(master_host: str, master_port: int) -> str:
|
|
271
252
|
sys.stderr.write(f"Got error from master: {msg.data['error']}\n")
|
272
253
|
sys.exit(1)
|
273
254
|
|
274
|
-
|
275
|
-
|
276
|
-
|
255
|
+
tempclient.close()
|
256
|
+
return msg.data.get("locustfiles", [])
|
257
|
+
|
277
258
|
|
278
|
-
|
259
|
+
def parse_locustfiles_from_master(locustfile_sources) -> list[str]:
|
260
|
+
locustfiles = []
|
279
261
|
|
280
|
-
|
281
|
-
|
262
|
+
for source in locustfile_sources:
|
263
|
+
if "contents" in source:
|
264
|
+
filename = source["filename"]
|
265
|
+
file_contents = source["contents"]
|
266
|
+
|
267
|
+
with open(os.path.join(tempfile.gettempdir(), filename), "w", encoding="utf-8") as locustfile:
|
268
|
+
locustfile.write(file_contents)
|
269
|
+
|
270
|
+
locustfiles.append(locustfile.name)
|
271
|
+
else:
|
272
|
+
locustfiles.append(source)
|
273
|
+
|
274
|
+
return locustfiles
|
282
275
|
|
283
276
|
|
284
277
|
def parse_locustfile_option(args=None) -> list[str]:
|
@@ -339,10 +332,11 @@ def parse_locustfile_option(args=None) -> list[str]:
|
|
339
332
|
)
|
340
333
|
sys.exit(1)
|
341
334
|
# having this in argument_parser module is a bit weird, but it needs to be done early
|
342
|
-
|
343
|
-
|
335
|
+
locustfile_sources = download_locustfile_from_master(options.master_host, options.master_port)
|
336
|
+
locustfile_list = parse_locustfiles_from_master(locustfile_sources)
|
337
|
+
else:
|
338
|
+
locustfile_list = [f.strip() for f in options.locustfile.split(",")]
|
344
339
|
|
345
|
-
locustfile_list = [f.strip() for f in options.locustfile.split(",")]
|
346
340
|
parsed_paths = parse_locustfile_paths(locustfile_list)
|
347
341
|
|
348
342
|
if not parsed_paths:
|
locust/env.py
CHANGED
@@ -33,6 +33,7 @@ class Environment:
|
|
33
33
|
stop_timeout: float | None = None,
|
34
34
|
catch_exceptions=True,
|
35
35
|
parsed_options: Namespace | None = None,
|
36
|
+
parsed_locustfiles: list[str] | None = None,
|
36
37
|
available_user_classes: dict[str, User] | None = None,
|
37
38
|
available_shape_classes: dict[str, LoadTestShape] | None = None,
|
38
39
|
available_user_tasks: dict[str, list[TaskSet | Callable]] | None = None,
|
@@ -91,6 +92,8 @@ class Environment:
|
|
91
92
|
"""
|
92
93
|
self.parsed_options = parsed_options
|
93
94
|
"""Reference to the parsed command line options (used to pre-populate fields in Web UI). When using Locust as a library, this should either be `None` or an object created by `argument_parser.parse_args()`"""
|
95
|
+
self.parsed_locustfiles = parsed_locustfiles
|
96
|
+
"""A list of all locustfiles for the test"""
|
94
97
|
self.available_user_classes = available_user_classes
|
95
98
|
"""List of the available User Classes to pick from in the UserClass Picker"""
|
96
99
|
self.available_shape_classes = available_shape_classes
|
locust/main.py
CHANGED
@@ -59,6 +59,7 @@ def create_environment(
|
|
59
59
|
events=None,
|
60
60
|
shape_class=None,
|
61
61
|
locustfile=None,
|
62
|
+
parsed_locustfiles=None,
|
62
63
|
available_user_classes=None,
|
63
64
|
available_shape_classes=None,
|
64
65
|
available_user_tasks=None,
|
@@ -74,6 +75,7 @@ def create_environment(
|
|
74
75
|
host=options.host,
|
75
76
|
reset_stats=options.reset_stats,
|
76
77
|
parsed_options=options,
|
78
|
+
parsed_locustfiles=parsed_locustfiles,
|
77
79
|
available_user_classes=available_user_classes,
|
78
80
|
available_shape_classes=available_shape_classes,
|
79
81
|
available_user_tasks=available_user_tasks,
|
@@ -349,6 +351,7 @@ See https://github.com/locustio/locust/wiki/Installation#increasing-maximum-numb
|
|
349
351
|
events=locust.events,
|
350
352
|
shape_class=shape_class,
|
351
353
|
locustfile=locustfile_path,
|
354
|
+
parsed_locustfiles=locustfiles,
|
352
355
|
available_user_classes=available_user_classes,
|
353
356
|
available_shape_classes=available_shape_classes,
|
354
357
|
available_user_tasks=available_user_tasks,
|
locust/runners.py
CHANGED
@@ -32,6 +32,8 @@ from .exception import RPCError, RPCReceiveError, RPCSendError
|
|
32
32
|
from .log import get_logs, greenlet_exception_logger
|
33
33
|
from .rpc import Message, rpc
|
34
34
|
from .stats import RequestStats, StatsError, setup_distributed_stats_event_listeners
|
35
|
+
from .util.directory import get_abspaths_in
|
36
|
+
from .util.url import is_url
|
35
37
|
|
36
38
|
if TYPE_CHECKING:
|
37
39
|
from . import User
|
@@ -1024,33 +1026,45 @@ class MasterRunner(DistributedRunner):
|
|
1024
1026
|
# if abs(time() - msg.data["time"]) > 5.0:
|
1025
1027
|
# warnings.warn("The worker node's clock seem to be out of sync. For the statistics to be correct the different locust servers need to have synchronized clocks.")
|
1026
1028
|
elif msg.type == "locustfile":
|
1029
|
+
if msg.data["version"][0:4] == __version__[0:4]:
|
1030
|
+
logger.debug(
|
1031
|
+
f"A worker ({msg.node_id}) running a different patch version ({msg.data['version']}) connected, master version is {__version__}"
|
1032
|
+
)
|
1033
|
+
|
1027
1034
|
logging.debug("Worker requested locust file")
|
1028
|
-
assert self.environment.
|
1029
|
-
|
1035
|
+
assert self.environment.parsed_locustfiles
|
1036
|
+
locustfile_options = self.environment.parsed_locustfiles
|
1037
|
+
locustfile_list = [f.strip() for f in locustfile_options if not os.path.isdir(f)]
|
1038
|
+
|
1039
|
+
for locustfile_option in locustfile_options:
|
1040
|
+
if os.path.isdir(locustfile_option):
|
1041
|
+
locustfile_list.extend(get_abspaths_in(locustfile_option, extension=".py"))
|
1042
|
+
|
1030
1043
|
try:
|
1031
|
-
|
1032
|
-
|
1044
|
+
locustfiles: list[str | dict[str, str]] = []
|
1045
|
+
|
1046
|
+
for filename in locustfile_list:
|
1047
|
+
if is_url(filename):
|
1048
|
+
locustfiles.append(filename)
|
1049
|
+
else:
|
1050
|
+
with open(filename) as f:
|
1051
|
+
filename = os.path.basename(filename)
|
1052
|
+
file_contents = f.read()
|
1053
|
+
|
1054
|
+
locustfiles.append({"filename": filename, "contents": file_contents})
|
1033
1055
|
except Exception as e:
|
1034
|
-
|
1035
|
-
|
1036
|
-
)
|
1056
|
+
error_message = "locustfile must be a full path to a single locustfile, a comma-separated list of .py files, or a URL for file distribution to work"
|
1057
|
+
logger.error(f"{error_message} {e}")
|
1037
1058
|
self.send_message(
|
1038
1059
|
"locustfile",
|
1039
1060
|
client_id=client_id,
|
1040
|
-
data={
|
1041
|
-
"error": f"locustfile must be a full path to a single locustfile for file distribution to work (was '{filename}')"
|
1042
|
-
},
|
1061
|
+
data={"error": f"{error_message} (was '{filename}')"},
|
1043
1062
|
)
|
1044
1063
|
else:
|
1045
|
-
if getattr(self, "_old_file_contents", file_contents) != file_contents:
|
1046
|
-
logger.warning(
|
1047
|
-
"Locustfile contents changed on disk after first worker requested locustfile, sending new content. If you make any major changes (like changing User class names) you need to restart master."
|
1048
|
-
)
|
1049
|
-
self._old_file_contents = file_contents
|
1050
1064
|
self.send_message(
|
1051
1065
|
"locustfile",
|
1052
1066
|
client_id=client_id,
|
1053
|
-
data={"
|
1067
|
+
data={"locustfiles": locustfiles},
|
1054
1068
|
)
|
1055
1069
|
continue
|
1056
1070
|
elif msg.type == "client_stopped":
|
locust/util/directory.py
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
import os
|
2
|
+
|
3
|
+
|
4
|
+
def get_abspaths_in(path, extension=None):
|
5
|
+
return [
|
6
|
+
os.path.abspath(os.path.join(root, f))
|
7
|
+
for root, _dirs, fs in os.walk(path)
|
8
|
+
for f in fs
|
9
|
+
if os.path.isfile(os.path.join(root, f))
|
10
|
+
and (f.endswith(extension) or extension is None)
|
11
|
+
and not f.startswith("_")
|
12
|
+
]
|
locust/util/url.py
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
from urllib.parse import urlparse
|
2
|
+
|
3
|
+
|
4
|
+
def is_url(url: str) -> bool:
|
5
|
+
"""
|
6
|
+
Check if path is an url
|
7
|
+
"""
|
8
|
+
try:
|
9
|
+
result = urlparse(url)
|
10
|
+
if result.scheme == "https" or result.scheme == "http":
|
11
|
+
return True
|
12
|
+
else:
|
13
|
+
return False
|
14
|
+
except ValueError:
|
15
|
+
return False
|
@@ -1,24 +1,24 @@
|
|
1
1
|
locust/__init__.py,sha256=Jit8eNUrwuMLqavyFvMZr69e61DILq_KB4yT4MciScw,1681
|
2
2
|
locust/__main__.py,sha256=vBQ82334kX06ImDbFlPFgiBRiLIinwNk3z8Khs6hd74,31
|
3
|
-
locust/_version.py,sha256
|
4
|
-
locust/argument_parser.py,sha256=
|
3
|
+
locust/_version.py,sha256=9MRgueYS9PajXS5gOQQEKWJKNyGf1KYKegbUYuvrdM8,460
|
4
|
+
locust/argument_parser.py,sha256=bCDe3BxazFH6P39pS0u_jvHYbHePfCQg9vgNyvaS_po,28838
|
5
5
|
locust/clients.py,sha256=OHPv6hBAt4gt3HI67yqyT1qrSsF8uMdCwIRu0kIsRWI,19491
|
6
6
|
locust/contrib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
7
7
|
locust/contrib/fasthttp.py,sha256=1hR2WK-3ACGVsU6Fu4Z4KucVzL1LXMLG6k54KeZdt78,28509
|
8
8
|
locust/debug.py,sha256=We6Z9W0btkKSc7PxWmrZx-xMynvOOsKhG6jmDgQin0g,5134
|
9
9
|
locust/dispatch.py,sha256=0rGny9P8dEq2Kg7exRWPorKQ5D1LnhHd2xG5Jq7ge7g,17107
|
10
|
-
locust/env.py,sha256=
|
10
|
+
locust/env.py,sha256=1Cw8i4TRqj9f_nwxUinbvBGbzq3J1LeTIwM7cG-E8WQ,12926
|
11
11
|
locust/event.py,sha256=iXEwIYFzra-j1WRldXB9SUibydtD8q8EIKaFPGTTIjk,8729
|
12
12
|
locust/exception.py,sha256=jGgJ32ubuf4pWdlaVOkbh2Y0LlG0_DHi-lv3ib8ppOE,1791
|
13
13
|
locust/html.py,sha256=_n3aB3fxiYzSeE_7RqHF3iiEPjPnbQ3e2Pw9P8AVtPU,3920
|
14
14
|
locust/input_events.py,sha256=ZIyePyAMuA_YFYWg18g_pE4kwuQV3RbEB250MzXRwjY,3314
|
15
15
|
locust/log.py,sha256=Wrkn0Ibugh5Sqjm4hGQ2-jUsy1tNMBdTctp4FyXQI24,3457
|
16
|
-
locust/main.py,sha256=
|
16
|
+
locust/main.py,sha256=HD1FcdmZNb4TCcCYcU626qVtjEgOqK4DEPMQCcpG7fE,27979
|
17
17
|
locust/py.typed,sha256=gkWLl8yD4mIZnNYYAIRM8g9VarLvWmTAFeUfEbxJLBw,65
|
18
18
|
locust/rpc/__init__.py,sha256=nVGoHWFQxZjnhCDWjbgXIbmFbN9sizAjkhvSs9_642c,58
|
19
19
|
locust/rpc/protocol.py,sha256=n-rb3GZQcAlldYDj4E4GuFGylYj_26GSS5U29meft5Y,1282
|
20
20
|
locust/rpc/zmqrpc.py,sha256=AAY6w7wSFHsW34qqN28666boHFf6dTSAXPQJnAO6iUI,2707
|
21
|
-
locust/runners.py,sha256=
|
21
|
+
locust/runners.py,sha256=lgcEJBj6hyxAY-nz-ffgs_vyP6m4stSVqz-DwMBLYqo,70433
|
22
22
|
locust/shape.py,sha256=t-lwBS8LOjWcKXNL7j2U3zroIXJ1b0fazUwpRYQOKXw,1973
|
23
23
|
locust/stats.py,sha256=cFOLYdfD6wfgie-_IZw1tX13h1rsZNjOxdw2VdG8eJE,45877
|
24
24
|
locust/user/__init__.py,sha256=S2yvmI_AU9kXirtTIVqiV_Hs7yXzqXvaSgkNo9ig-fk,71
|
@@ -31,10 +31,12 @@ locust/util/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
31
31
|
locust/util/cache.py,sha256=IxbpGawl0-hoWKvCrtksxjSLf2GbBBTVns06F7mFBkM,1062
|
32
32
|
locust/util/date.py,sha256=pqyUuqzPds-A8ai6ZYTxe5Eak1M1BR9iuQcqEcrtHwc,179
|
33
33
|
locust/util/deprecation.py,sha256=pFipULkYFjYnuFSycJZ0s95r0CTSlvx8otGnqSu1bsQ,2028
|
34
|
+
locust/util/directory.py,sha256=2EeuVIIFEErm0OpNXdsEgQLx49jAXq-PvMj2uY0Mr8o,326
|
34
35
|
locust/util/exception_handler.py,sha256=jTMyBq2a0O07fRjmqGkheyaPj58tUgnbbcjoesKGPws,797
|
35
36
|
locust/util/load_locustfile.py,sha256=hn70KcIG8jHmZyuKv2pcEmwgWtOEu24Efeji1KRYNUE,2964
|
36
37
|
locust/util/rounding.py,sha256=5haxR8mKhATqag6WvPby-MSRRgIw5Ob6thbyvMYZM7o,92
|
37
38
|
locust/util/timespan.py,sha256=Y0LtnhUq2Mq19p04u0XtBlYQ_-S2cRvwRdgru8W9WhA,986
|
39
|
+
locust/util/url.py,sha256=s_W2PCxvxTWxWX0yUvp-8VBuQm881KwI5X9iifogZG4,321
|
38
40
|
locust/web.py,sha256=HdW4rUDILXOB1y1fVV1VIyx7vozmImCXsgNfW1T2WVc,27282
|
39
41
|
locust/webui/dist/assets/favicon.ico,sha256=IUl-rYqfpHdV38e-s0bkmFIeLS-n3Ug0DQxk-h202hI,8348
|
40
42
|
locust/webui/dist/assets/index-24a281b8.js,sha256=DKjo_f4oXQ7cVm-YTE2nBWid2gz0kjEXeUuC1dprPao,1647927
|
@@ -42,8 +44,8 @@ locust/webui/dist/assets/logo.png,sha256=EIVPqr6wE_yqguHaqFHIsH0ZACLSrvNWyYO7Pby
|
|
42
44
|
locust/webui/dist/auth.html,sha256=7VRcfmnP6ATNJdlbZPAy6Ckfu_C4YxcSxqlWrPqL9F4,501
|
43
45
|
locust/webui/dist/index.html,sha256=vUxz_vys--tECpiWnhHQWLL7YJUBGSE4mVPF-c3-U3g,507
|
44
46
|
locust/webui/dist/report.html,sha256=sOdZZVgZbqgu86BBCSQf3uQUYXgmgSnXF32JpnyAII8,513
|
45
|
-
locust-2.30.1.
|
46
|
-
locust-2.30.1.
|
47
|
-
locust-2.30.1.
|
48
|
-
locust-2.30.1.
|
49
|
-
locust-2.30.1.
|
47
|
+
locust-2.30.1.dev14.dist-info/LICENSE,sha256=78XGpIn3fHVBfaxlPNUfjVufSN7QsdhpJMRJHv2AFpo,1095
|
48
|
+
locust-2.30.1.dev14.dist-info/METADATA,sha256=9RFFHvBg3CrLz540GVYc-P-TPtzLNQklPCNGfiF59yg,7679
|
49
|
+
locust-2.30.1.dev14.dist-info/WHEEL,sha256=HBsDV7Hj4OTiS1GX6ua7iQXUQTB9UHftbBxr7Q8Xm9c,110
|
50
|
+
locust-2.30.1.dev14.dist-info/entry_points.txt,sha256=RhIxlLwU_Ae_WjimS5COUDLagdCh6IOMyLtgaQxNmlM,43
|
51
|
+
locust-2.30.1.dev14.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|