locust 2.30.1.dev4__tar.gz → 2.30.1.dev17__tar.gz

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.
Files changed (45) hide show
  1. {locust-2.30.1.dev4 → locust-2.30.1.dev17}/PKG-INFO +1 -1
  2. {locust-2.30.1.dev4 → locust-2.30.1.dev17}/locust/_version.py +2 -2
  3. {locust-2.30.1.dev4 → locust-2.30.1.dev17}/locust/argument_parser.py +27 -33
  4. {locust-2.30.1.dev4 → locust-2.30.1.dev17}/locust/env.py +3 -0
  5. {locust-2.30.1.dev4 → locust-2.30.1.dev17}/locust/main.py +3 -0
  6. {locust-2.30.1.dev4 → locust-2.30.1.dev17}/locust/runners.py +30 -16
  7. locust-2.30.1.dev17/locust/util/directory.py +12 -0
  8. locust-2.30.1.dev17/locust/util/url.py +15 -0
  9. {locust-2.30.1.dev4 → locust-2.30.1.dev17}/pyproject.toml +1 -1
  10. {locust-2.30.1.dev4 → locust-2.30.1.dev17}/LICENSE +0 -0
  11. {locust-2.30.1.dev4 → locust-2.30.1.dev17}/README.md +0 -0
  12. {locust-2.30.1.dev4 → locust-2.30.1.dev17}/locust/__init__.py +0 -0
  13. {locust-2.30.1.dev4 → locust-2.30.1.dev17}/locust/__main__.py +0 -0
  14. {locust-2.30.1.dev4 → locust-2.30.1.dev17}/locust/clients.py +0 -0
  15. {locust-2.30.1.dev4 → locust-2.30.1.dev17}/locust/contrib/__init__.py +0 -0
  16. {locust-2.30.1.dev4 → locust-2.30.1.dev17}/locust/contrib/fasthttp.py +0 -0
  17. {locust-2.30.1.dev4 → locust-2.30.1.dev17}/locust/debug.py +0 -0
  18. {locust-2.30.1.dev4 → locust-2.30.1.dev17}/locust/dispatch.py +0 -0
  19. {locust-2.30.1.dev4 → locust-2.30.1.dev17}/locust/event.py +0 -0
  20. {locust-2.30.1.dev4 → locust-2.30.1.dev17}/locust/exception.py +0 -0
  21. {locust-2.30.1.dev4 → locust-2.30.1.dev17}/locust/html.py +0 -0
  22. {locust-2.30.1.dev4 → locust-2.30.1.dev17}/locust/input_events.py +0 -0
  23. {locust-2.30.1.dev4 → locust-2.30.1.dev17}/locust/log.py +0 -0
  24. {locust-2.30.1.dev4 → locust-2.30.1.dev17}/locust/py.typed +0 -0
  25. {locust-2.30.1.dev4 → locust-2.30.1.dev17}/locust/rpc/__init__.py +0 -0
  26. {locust-2.30.1.dev4 → locust-2.30.1.dev17}/locust/rpc/protocol.py +0 -0
  27. {locust-2.30.1.dev4 → locust-2.30.1.dev17}/locust/rpc/zmqrpc.py +0 -0
  28. {locust-2.30.1.dev4 → locust-2.30.1.dev17}/locust/shape.py +0 -0
  29. {locust-2.30.1.dev4 → locust-2.30.1.dev17}/locust/stats.py +0 -0
  30. {locust-2.30.1.dev4 → locust-2.30.1.dev17}/locust/user/__init__.py +0 -0
  31. {locust-2.30.1.dev4 → locust-2.30.1.dev17}/locust/user/inspectuser.py +0 -0
  32. {locust-2.30.1.dev4 → locust-2.30.1.dev17}/locust/user/sequential_taskset.py +0 -0
  33. {locust-2.30.1.dev4 → locust-2.30.1.dev17}/locust/user/task.py +0 -0
  34. {locust-2.30.1.dev4 → locust-2.30.1.dev17}/locust/user/users.py +0 -0
  35. {locust-2.30.1.dev4 → locust-2.30.1.dev17}/locust/user/wait_time.py +0 -0
  36. {locust-2.30.1.dev4 → locust-2.30.1.dev17}/locust/util/__init__.py +0 -0
  37. {locust-2.30.1.dev4 → locust-2.30.1.dev17}/locust/util/cache.py +0 -0
  38. {locust-2.30.1.dev4 → locust-2.30.1.dev17}/locust/util/date.py +0 -0
  39. {locust-2.30.1.dev4 → locust-2.30.1.dev17}/locust/util/deprecation.py +0 -0
  40. {locust-2.30.1.dev4 → locust-2.30.1.dev17}/locust/util/exception_handler.py +0 -0
  41. {locust-2.30.1.dev4 → locust-2.30.1.dev17}/locust/util/load_locustfile.py +0 -0
  42. {locust-2.30.1.dev4 → locust-2.30.1.dev17}/locust/util/rounding.py +0 -0
  43. {locust-2.30.1.dev4 → locust-2.30.1.dev17}/locust/util/timespan.py +0 -0
  44. {locust-2.30.1.dev4 → locust-2.30.1.dev17}/locust/web.py +0 -0
  45. {locust-2.30.1.dev4 → locust-2.30.1.dev17}/pre_build.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: locust
3
- Version: 2.30.1.dev4
3
+ Version: 2.30.1.dev17
4
4
  Summary: Developer-friendly load testing framework
5
5
  Home-page: https://locust.io/
6
6
  License: MIT
@@ -14,7 +14,7 @@ __version_tuple__: VERSION_TUPLE
14
14
  version_tuple: VERSION_TUPLE
15
15
 
16
16
 
17
- __version__ = "2.30.1.dev4"
17
+ __version__ = "2.30.1.dev17"
18
18
  version = __version__
19
- __version_tuple__ = (2, 30, 1, "dev4")
19
+ __version_tuple__ = (2, 30, 1, "dev17")
20
20
  version_tuple = __version_tuple__
@@ -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
- for root, _dirs, fs in os.walk(path):
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", None, client_id))
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
- filename = msg.data["filename"]
275
- with open(os.path.join(tempfile.gettempdir(), filename), "w", encoding="utf-8") as locustfile:
276
- locustfile.write(msg.data["contents"])
255
+ tempclient.close()
256
+ return msg.data.get("locustfiles", [])
257
+
277
258
 
278
- atexit.register(exit_handler, locustfile.name)
259
+ def parse_locustfiles_from_master(locustfile_sources) -> list[str]:
260
+ locustfiles = []
279
261
 
280
- tempclient.close()
281
- return locustfile.name
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
- filename = download_locustfile_from_master(options.master_host, options.master_port)
343
- return [filename]
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:
@@ -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
@@ -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,
@@ -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.parsed_options
1029
- filename = self.environment.parsed_options.locustfile
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
- with open(filename) as f:
1032
- file_contents = f.read()
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
- logger.error(
1035
- f"--locustfile must be a full path to a single locustfile for file distribution to work {e}"
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={"filename": os.path.basename(filename), "contents": file_contents},
1067
+ data={"locustfiles": locustfiles},
1054
1068
  )
1055
1069
  continue
1056
1070
  elif msg.type == "client_stopped":
@@ -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
+ ]
@@ -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
@@ -5,7 +5,7 @@ build-backend = "poetry_dynamic_versioning.backend"
5
5
  [tool.poetry]
6
6
  name = "locust"
7
7
  description = "Developer-friendly load testing framework"
8
- version = "2.30.1.dev4"
8
+ version = "2.30.1.dev17"
9
9
  license = "MIT"
10
10
  readme = "README.md"
11
11
  authors = ["Jonatan Heyman", "Lars Holmberg"]
File without changes
File without changes
File without changes