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 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.22.1.dev67'
16
- __version_tuple__ = version_tuple = (2, 22, 1, 'dev67')
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
- from gevent.event import Event
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
- # Implicit 'return None' if nothing was found
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 local_file:
218
- local_file.write(msg.data["contents"])
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 filename
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 = [locustfile.strip() for locustfile in options.locustfile.split(",")]
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(options.locustfile)
302
- locustfiles = [locustfile]
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
- proc_worker2.communicate()
1725
- proc_worker.communicate()
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)