locust 2.22.1.dev67__py3-none-any.whl → 2.23.1__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 +56 -9
- locust/main.py +4 -0
- locust/runners.py +1 -1
- locust/test/test_load_locustfile.py +18 -1
- locust/test/test_main.py +5 -2
- locust/webui/dist/assets/{index-5730ea01.js → index-fcdc76c8.js} +51 -51
- locust/webui/dist/auth.html +1 -1
- locust/webui/dist/index.html +1 -1
- {locust-2.22.1.dev67.dist-info → locust-2.23.1.dist-info}/METADATA +1 -1
- {locust-2.22.1.dev67.dist-info → locust-2.23.1.dist-info}/RECORD +15 -15
- {locust-2.22.1.dev67.dist-info → locust-2.23.1.dist-info}/LICENSE +0 -0
- {locust-2.22.1.dev67.dist-info → locust-2.23.1.dist-info}/WHEEL +0 -0
- {locust-2.22.1.dev67.dist-info → locust-2.23.1.dist-info}/entry_points.txt +0 -0
- {locust-2.22.1.dev67.dist-info → locust-2.23.1.dist-info}/top_level.txt +0 -0
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.
|
16
|
-
__version_tuple__ = version_tuple = (2,
|
15
|
+
__version__ = version = '2.23.1'
|
16
|
+
__version_tuple__ = version_tuple = (2, 23, 1)
|
locust/argument_parser.py
CHANGED
@@ -4,17 +4,20 @@ import locust
|
|
4
4
|
from locust import runners
|
5
5
|
from locust.rpc import Message, zmqrpc
|
6
6
|
|
7
|
+
import atexit
|
7
8
|
import os
|
8
9
|
import platform
|
9
10
|
import socket
|
10
11
|
import sys
|
12
|
+
import tempfile
|
11
13
|
import textwrap
|
12
14
|
from typing import Any, NamedTuple
|
15
|
+
from urllib.parse import urlparse
|
13
16
|
from uuid import uuid4
|
14
17
|
|
15
18
|
import configargparse
|
16
19
|
import gevent
|
17
|
-
|
20
|
+
import requests
|
18
21
|
|
19
22
|
version = locust.__version__
|
20
23
|
|
@@ -66,7 +69,7 @@ def _is_package(path):
|
|
66
69
|
return os.path.isdir(path) and os.path.exists(os.path.join(path, "__init__.py"))
|
67
70
|
|
68
71
|
|
69
|
-
def find_locustfile(locustfile):
|
72
|
+
def find_locustfile(locustfile: str) -> str | None:
|
70
73
|
"""
|
71
74
|
Attempt to locate a locustfile, either explicitly or by searching parent dirs.
|
72
75
|
"""
|
@@ -97,7 +100,8 @@ def find_locustfile(locustfile):
|
|
97
100
|
# we've reached the root path which has been checked this iteration
|
98
101
|
break
|
99
102
|
path = parent_path
|
100
|
-
|
103
|
+
|
104
|
+
return None
|
101
105
|
|
102
106
|
|
103
107
|
def find_locustfiles(locustfiles: list[str], is_directory: bool) -> list[str]:
|
@@ -141,6 +145,37 @@ def find_locustfiles(locustfiles: list[str], is_directory: bool) -> list[str]:
|
|
141
145
|
return file_paths
|
142
146
|
|
143
147
|
|
148
|
+
def is_url(url: str) -> bool:
|
149
|
+
try:
|
150
|
+
result = urlparse(url)
|
151
|
+
if result.scheme == "https" or result.scheme == "http":
|
152
|
+
return True
|
153
|
+
else:
|
154
|
+
return False
|
155
|
+
except ValueError:
|
156
|
+
return False
|
157
|
+
|
158
|
+
|
159
|
+
def download_locustfile_from_url(url: str) -> str:
|
160
|
+
try:
|
161
|
+
response = requests.get(url)
|
162
|
+
except requests.exceptions.RequestException as e:
|
163
|
+
sys.stderr.write(f"Failed to get locustfile from: {url}. Exception: {e}")
|
164
|
+
sys.exit(1)
|
165
|
+
|
166
|
+
with open(os.path.join(tempfile.gettempdir(), url.rsplit("/", 1)[-1]), "w") as locustfile:
|
167
|
+
locustfile.write(response.text)
|
168
|
+
|
169
|
+
def exit_handler():
|
170
|
+
try:
|
171
|
+
os.remove(locustfile.name)
|
172
|
+
except FileNotFoundError:
|
173
|
+
pass # this is normal when multiple workers are running on the same machine
|
174
|
+
|
175
|
+
atexit.register(exit_handler)
|
176
|
+
return locustfile.name
|
177
|
+
|
178
|
+
|
144
179
|
def get_empty_argument_parser(add_help=True, default_config_files=DEFAULT_CONFIG_FILES) -> LocustArgumentParser:
|
145
180
|
parser = LocustArgumentParser(
|
146
181
|
default_config_files=default_config_files,
|
@@ -214,11 +249,19 @@ def download_locustfile_from_master(master_host: str, master_port: int) -> str:
|
|
214
249
|
sys.exit(1)
|
215
250
|
|
216
251
|
filename = msg.data["filename"]
|
217
|
-
with open(filename, "w") as
|
218
|
-
|
252
|
+
with open(os.path.join(tempfile.gettempdir(), filename), "w") as locustfile:
|
253
|
+
locustfile.write(msg.data["contents"])
|
254
|
+
|
255
|
+
def exit_handler():
|
256
|
+
try:
|
257
|
+
os.remove(locustfile.name)
|
258
|
+
except FileNotFoundError:
|
259
|
+
pass # this is normal when multiple workers are running on the same machine
|
260
|
+
|
261
|
+
atexit.register(exit_handler)
|
219
262
|
|
220
263
|
tempclient.close()
|
221
|
-
return
|
264
|
+
return locustfile.name
|
222
265
|
|
223
266
|
|
224
267
|
def parse_locustfile_option(args=None) -> list[str]:
|
@@ -279,7 +322,9 @@ def parse_locustfile_option(args=None) -> list[str]:
|
|
279
322
|
return [filename]
|
280
323
|
|
281
324
|
# Comma separated string to list
|
282
|
-
locustfile_as_list = [
|
325
|
+
locustfile_as_list = [
|
326
|
+
download_locustfile_from_url(f) if is_url(f.strip()) else f.strip() for f in options.locustfile.split(",")
|
327
|
+
]
|
283
328
|
|
284
329
|
# Checking if the locustfile is a single file, multiple files or a directory
|
285
330
|
if locustfile_is_directory(locustfile_as_list):
|
@@ -298,8 +343,8 @@ def parse_locustfile_option(args=None) -> list[str]:
|
|
298
343
|
locustfile = None
|
299
344
|
else:
|
300
345
|
# Is a single file
|
301
|
-
locustfile = find_locustfile(
|
302
|
-
locustfiles = [
|
346
|
+
locustfile = find_locustfile(locustfile_as_list[0])
|
347
|
+
locustfiles = []
|
303
348
|
|
304
349
|
if not locustfile:
|
305
350
|
if options.help or options.version:
|
@@ -317,6 +362,8 @@ def parse_locustfile_option(args=None) -> list[str]:
|
|
317
362
|
f"Could not find '{user_friendly_locustfile_name}'. {note_about_file_endings}See --help for available options.\n"
|
318
363
|
)
|
319
364
|
sys.exit(1)
|
365
|
+
else:
|
366
|
+
locustfiles.append(locustfile)
|
320
367
|
|
321
368
|
return locustfiles
|
322
369
|
|
locust/main.py
CHANGED
@@ -132,6 +132,10 @@ def main():
|
|
132
132
|
logging.error("stats.PERCENTILES_TO_CHART parameter should be 2 parameters \n")
|
133
133
|
sys.exit(1)
|
134
134
|
|
135
|
+
if len(stats.MODERN_UI_PERCENTILES_TO_CHART) > 6:
|
136
|
+
logging.error("stats.MODERN_UI_PERCENTILES_TO_CHART parameter should be a maximum of 6 parameters \n")
|
137
|
+
sys.exit(1)
|
138
|
+
|
135
139
|
def is_valid_percentile(parameter):
|
136
140
|
try:
|
137
141
|
if 0 < float(parameter) < 1:
|
locust/runners.py
CHANGED
@@ -1064,7 +1064,7 @@ class MasterRunner(DistributedRunner):
|
|
1064
1064
|
self.send_message(
|
1065
1065
|
"locustfile",
|
1066
1066
|
client_id=client_id,
|
1067
|
-
data={"filename": filename, "contents": file_contents},
|
1067
|
+
data={"filename": os.path.basename(filename), "contents": file_contents},
|
1068
1068
|
)
|
1069
1069
|
continue
|
1070
1070
|
elif msg.type == "client_stopped":
|
@@ -1,10 +1,12 @@
|
|
1
1
|
from locust import main
|
2
|
-
from locust.argument_parser import parse_options
|
2
|
+
from locust.argument_parser import parse_locustfile_option, parse_options
|
3
3
|
from locust.main import create_environment
|
4
4
|
from locust.user import HttpUser, TaskSet, User
|
5
5
|
from locust.util.load_locustfile import is_user_class
|
6
6
|
|
7
|
+
import filecmp
|
7
8
|
import os
|
9
|
+
import pathlib
|
8
10
|
import textwrap
|
9
11
|
|
10
12
|
from .mock_locustfile import MOCK_LOCUSTFILE_CONTENT, mock_locustfile
|
@@ -209,3 +211,18 @@ class TestLoadLocustfile(LocustTestCase):
|
|
209
211
|
]
|
210
212
|
)
|
211
213
|
self.assertEqual("my_locust_file.py", options.locustfile)
|
214
|
+
|
215
|
+
def test_locustfile_from_url(self):
|
216
|
+
locustfiles = parse_locustfile_option(
|
217
|
+
args=[
|
218
|
+
"-f",
|
219
|
+
"https://raw.githubusercontent.com/locustio/locust/master/examples/basic.py",
|
220
|
+
]
|
221
|
+
)
|
222
|
+
self.assertEqual(len(locustfiles), 1)
|
223
|
+
self.assertTrue(
|
224
|
+
filecmp.cmp(
|
225
|
+
locustfiles[0],
|
226
|
+
f"{os.getcwd()}/examples/basic.py",
|
227
|
+
)
|
228
|
+
)
|
locust/test/test_main.py
CHANGED
@@ -1721,12 +1721,15 @@ class SecondUser(HttpUser):
|
|
1721
1721
|
text=True,
|
1722
1722
|
)
|
1723
1723
|
stdout = proc.communicate()[0]
|
1724
|
-
|
1725
|
-
|
1724
|
+
stdout_worker = proc_worker.communicate()[0]
|
1725
|
+
stdout_worker2 = proc_worker2.communicate()[0]
|
1726
1726
|
|
1727
1727
|
self.assertIn('All users spawned: {"User1": 1} (1 total users)', stdout)
|
1728
1728
|
self.assertIn("Locustfile contents changed on disk after first worker requested locustfile", stdout)
|
1729
1729
|
self.assertIn("Shutting down (exit code 0)", stdout)
|
1730
|
+
self.assertNotIn("Traceback", stdout)
|
1731
|
+
self.assertNotIn("Traceback", stdout_worker)
|
1732
|
+
self.assertNotIn("Traceback", stdout_worker2)
|
1730
1733
|
|
1731
1734
|
self.assertEqual(0, proc.returncode)
|
1732
1735
|
self.assertEqual(0, proc_worker.returncode)
|