locust 2.35.1.dev11__tar.gz → 2.35.1.dev22__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.
- {locust-2.35.1.dev11 → locust-2.35.1.dev22}/PKG-INFO +1 -1
- {locust-2.35.1.dev11 → locust-2.35.1.dev22}/locust/_version.py +2 -2
- {locust-2.35.1.dev11 → locust-2.35.1.dev22}/locust/main.py +66 -40
- {locust-2.35.1.dev11 → locust-2.35.1.dev22}/locust/util/load_locustfile.py +4 -4
- {locust-2.35.1.dev11 → locust-2.35.1.dev22}/.gitignore +0 -0
- {locust-2.35.1.dev11 → locust-2.35.1.dev22}/LICENSE +0 -0
- {locust-2.35.1.dev11 → locust-2.35.1.dev22}/README.md +0 -0
- {locust-2.35.1.dev11 → locust-2.35.1.dev22}/hatch_build.py +0 -0
- {locust-2.35.1.dev11 → locust-2.35.1.dev22}/locust/__init__.py +0 -0
- {locust-2.35.1.dev11 → locust-2.35.1.dev22}/locust/__main__.py +0 -0
- {locust-2.35.1.dev11 → locust-2.35.1.dev22}/locust/argument_parser.py +0 -0
- {locust-2.35.1.dev11 → locust-2.35.1.dev22}/locust/clients.py +0 -0
- {locust-2.35.1.dev11 → locust-2.35.1.dev22}/locust/contrib/__init__.py +0 -0
- {locust-2.35.1.dev11 → locust-2.35.1.dev22}/locust/contrib/fasthttp.py +0 -0
- {locust-2.35.1.dev11 → locust-2.35.1.dev22}/locust/contrib/mongodb.py +0 -0
- {locust-2.35.1.dev11 → locust-2.35.1.dev22}/locust/contrib/oai.py +0 -0
- {locust-2.35.1.dev11 → locust-2.35.1.dev22}/locust/contrib/postgres.py +0 -0
- {locust-2.35.1.dev11 → locust-2.35.1.dev22}/locust/debug.py +0 -0
- {locust-2.35.1.dev11 → locust-2.35.1.dev22}/locust/dispatch.py +0 -0
- {locust-2.35.1.dev11 → locust-2.35.1.dev22}/locust/env.py +0 -0
- {locust-2.35.1.dev11 → locust-2.35.1.dev22}/locust/event.py +0 -0
- {locust-2.35.1.dev11 → locust-2.35.1.dev22}/locust/exception.py +0 -0
- {locust-2.35.1.dev11 → locust-2.35.1.dev22}/locust/html.py +0 -0
- {locust-2.35.1.dev11 → locust-2.35.1.dev22}/locust/input_events.py +0 -0
- {locust-2.35.1.dev11 → locust-2.35.1.dev22}/locust/log.py +0 -0
- {locust-2.35.1.dev11 → locust-2.35.1.dev22}/locust/py.typed +0 -0
- {locust-2.35.1.dev11 → locust-2.35.1.dev22}/locust/rpc/__init__.py +0 -0
- {locust-2.35.1.dev11 → locust-2.35.1.dev22}/locust/rpc/protocol.py +0 -0
- {locust-2.35.1.dev11 → locust-2.35.1.dev22}/locust/rpc/zmqrpc.py +0 -0
- {locust-2.35.1.dev11 → locust-2.35.1.dev22}/locust/runners.py +0 -0
- {locust-2.35.1.dev11 → locust-2.35.1.dev22}/locust/shape.py +0 -0
- {locust-2.35.1.dev11 → locust-2.35.1.dev22}/locust/stats.py +0 -0
- {locust-2.35.1.dev11 → locust-2.35.1.dev22}/locust/user/__init__.py +0 -0
- {locust-2.35.1.dev11 → locust-2.35.1.dev22}/locust/user/inspectuser.py +0 -0
- {locust-2.35.1.dev11 → locust-2.35.1.dev22}/locust/user/sequential_taskset.py +0 -0
- {locust-2.35.1.dev11 → locust-2.35.1.dev22}/locust/user/task.py +0 -0
- {locust-2.35.1.dev11 → locust-2.35.1.dev22}/locust/user/users.py +0 -0
- {locust-2.35.1.dev11 → locust-2.35.1.dev22}/locust/user/wait_time.py +0 -0
- {locust-2.35.1.dev11 → locust-2.35.1.dev22}/locust/util/__init__.py +0 -0
- {locust-2.35.1.dev11 → locust-2.35.1.dev22}/locust/util/cache.py +0 -0
- {locust-2.35.1.dev11 → locust-2.35.1.dev22}/locust/util/date.py +0 -0
- {locust-2.35.1.dev11 → locust-2.35.1.dev22}/locust/util/deprecation.py +0 -0
- {locust-2.35.1.dev11 → locust-2.35.1.dev22}/locust/util/directory.py +0 -0
- {locust-2.35.1.dev11 → locust-2.35.1.dev22}/locust/util/exception_handler.py +0 -0
- {locust-2.35.1.dev11 → locust-2.35.1.dev22}/locust/util/rounding.py +0 -0
- {locust-2.35.1.dev11 → locust-2.35.1.dev22}/locust/util/timespan.py +0 -0
- {locust-2.35.1.dev11 → locust-2.35.1.dev22}/locust/util/url.py +0 -0
- {locust-2.35.1.dev11 → locust-2.35.1.dev22}/locust/web.py +0 -0
- {locust-2.35.1.dev11 → locust-2.35.1.dev22}/locust/webui/dist/assets/favicon-dark.png +0 -0
- {locust-2.35.1.dev11 → locust-2.35.1.dev22}/locust/webui/dist/assets/favicon-light.png +0 -0
- {locust-2.35.1.dev11 → locust-2.35.1.dev22}/locust/webui/dist/assets/graphs-dark.png +0 -0
- {locust-2.35.1.dev11 → locust-2.35.1.dev22}/locust/webui/dist/assets/graphs-light.png +0 -0
- {locust-2.35.1.dev11 → locust-2.35.1.dev22}/locust/webui/dist/assets/index-DQLt1q6M.js +0 -0
- {locust-2.35.1.dev11 → locust-2.35.1.dev22}/locust/webui/dist/assets/testruns-dark.png +0 -0
- {locust-2.35.1.dev11 → locust-2.35.1.dev22}/locust/webui/dist/assets/testruns-light.png +0 -0
- {locust-2.35.1.dev11 → locust-2.35.1.dev22}/locust/webui/dist/auth.html +0 -0
- {locust-2.35.1.dev11 → locust-2.35.1.dev22}/locust/webui/dist/index.html +0 -0
- {locust-2.35.1.dev11 → locust-2.35.1.dev22}/locust/webui/dist/report.html +0 -0
- {locust-2.35.1.dev11 → locust-2.35.1.dev22}/pyproject.toml +0 -0
@@ -17,5 +17,5 @@ __version__: str
|
|
17
17
|
__version_tuple__: VERSION_TUPLE
|
18
18
|
version_tuple: VERSION_TUPLE
|
19
19
|
|
20
|
-
__version__ = version = '2.35.1.
|
21
|
-
__version_tuple__ = version_tuple = (2, 35, 1, '
|
20
|
+
__version__ = version = '2.35.1.dev22'
|
21
|
+
__version_tuple__ = version_tuple = (2, 35, 1, 'dev22')
|
@@ -15,6 +15,7 @@ import sys
|
|
15
15
|
import time
|
16
16
|
import traceback
|
17
17
|
import webbrowser
|
18
|
+
from typing import TYPE_CHECKING
|
18
19
|
|
19
20
|
import gevent
|
20
21
|
|
@@ -42,6 +43,9 @@ except ModuleNotFoundError as e:
|
|
42
43
|
if e.msg != "No module named 'locust_cloud'":
|
43
44
|
raise
|
44
45
|
|
46
|
+
if TYPE_CHECKING:
|
47
|
+
from collections.abc import Callable
|
48
|
+
|
45
49
|
version = locust.__version__
|
46
50
|
|
47
51
|
# Options to ignore when using a custom shape class without `use_common_options=True`
|
@@ -83,53 +87,73 @@ def create_environment(
|
|
83
87
|
)
|
84
88
|
|
85
89
|
|
86
|
-
def
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
90
|
+
def merge_locustfiles_content(
|
91
|
+
locustfiles: list[str],
|
92
|
+
) -> tuple[
|
93
|
+
dict[str, type[locust.User]],
|
94
|
+
dict[str, locust.LoadTestShape],
|
95
|
+
dict[str, list[locust.TaskSet | Callable]],
|
96
|
+
locust.LoadTestShape | None,
|
97
|
+
]:
|
98
|
+
"""
|
99
|
+
Validate content of each locustfile in locustfiles and merge data to single objects output.
|
91
100
|
|
92
|
-
|
93
|
-
|
94
|
-
|
101
|
+
Can stop locust execution on errors.
|
102
|
+
"""
|
103
|
+
available_user_classes: dict[str, type[locust.User]] = {}
|
104
|
+
available_shape_classes: dict[str, locust.LoadTestShape] = {}
|
105
|
+
# TODO: list[locust.TaskSet | Callable] should be replaced with correct type,
|
106
|
+
# supported by User class task attribute. This require additional rewrite,
|
107
|
+
# out of main refactoring.
|
108
|
+
# Check docs for real supported task attribute signature for User\TaskSet class.
|
109
|
+
available_user_tasks: dict[str, list[locust.TaskSet | Callable]] = {}
|
95
110
|
|
96
|
-
# Importing Locustfile(s) - setting available UserClasses and ShapeClasses to choose from in UI
|
97
|
-
user_classes: dict[str, locust.User] = {}
|
98
|
-
available_user_classes = {}
|
99
|
-
available_shape_classes = {}
|
100
|
-
available_user_tasks = {}
|
101
|
-
shape_class = None
|
102
111
|
for _locustfile in locustfiles:
|
103
|
-
|
112
|
+
user_classes, shape_classes = load_locustfile(_locustfile)
|
104
113
|
|
105
114
|
# Setting Available Shape Classes
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
sys.stderr.write(f"Duplicate shape classes: {shape_class_name}\n")
|
112
|
-
sys.exit(1)
|
115
|
+
for _shape_class in shape_classes:
|
116
|
+
shape_class_name = type(_shape_class).__name__
|
117
|
+
if shape_class_name in available_shape_classes.keys():
|
118
|
+
sys.stderr.write(f"Duplicate shape classes: {shape_class_name}\n")
|
119
|
+
sys.exit(1)
|
113
120
|
|
114
|
-
|
121
|
+
available_shape_classes[shape_class_name] = _shape_class
|
115
122
|
|
116
123
|
# Setting Available User Classes
|
117
|
-
for
|
118
|
-
if
|
119
|
-
previous_path = inspect.getfile(
|
120
|
-
new_path = inspect.getfile(
|
124
|
+
for class_name, class_definition in user_classes.items():
|
125
|
+
if class_name in available_user_classes.keys():
|
126
|
+
previous_path = inspect.getfile(available_user_classes[class_name])
|
127
|
+
new_path = inspect.getfile(class_definition)
|
121
128
|
if previous_path == new_path:
|
122
129
|
# The same User class was defined in two locustfiles but one probably imported the other, so we just ignore it
|
123
130
|
continue
|
124
131
|
else:
|
125
132
|
sys.stderr.write(
|
126
|
-
f"Duplicate user class names: {
|
133
|
+
f"Duplicate user class names: {class_name} is defined in both {previous_path} and {new_path}\n"
|
127
134
|
)
|
128
135
|
sys.exit(1)
|
129
136
|
|
130
|
-
|
131
|
-
|
132
|
-
|
137
|
+
available_user_classes[class_name] = class_definition
|
138
|
+
available_user_tasks[class_name] = class_definition.tasks
|
139
|
+
|
140
|
+
shape_class = list(available_shape_classes.values())[0] if available_shape_classes else None
|
141
|
+
|
142
|
+
return available_user_classes, available_shape_classes, available_user_tasks, shape_class
|
143
|
+
|
144
|
+
|
145
|
+
def main():
|
146
|
+
# find specified locustfile(s) and make sure it exists, using a very simplified
|
147
|
+
# command line parser that is only used to parse the -f option.
|
148
|
+
locustfiles = parse_locustfile_option()
|
149
|
+
|
150
|
+
# Importing Locustfile(s) - setting available UserClasses and ShapeClasses to choose from in UI
|
151
|
+
(
|
152
|
+
available_user_classes,
|
153
|
+
available_shape_classes,
|
154
|
+
available_user_tasks,
|
155
|
+
shape_class,
|
156
|
+
) = merge_locustfiles_content(locustfiles)
|
133
157
|
|
134
158
|
stats.validate_stats_configuration()
|
135
159
|
|
@@ -256,25 +280,25 @@ def main():
|
|
256
280
|
|
257
281
|
if options.list_commands:
|
258
282
|
print("Available Users:")
|
259
|
-
for name in
|
283
|
+
for name in available_user_classes:
|
260
284
|
print(" " + name)
|
261
285
|
sys.exit(0)
|
262
286
|
|
263
|
-
if not
|
287
|
+
if not available_user_classes:
|
264
288
|
logger.error("No User class found!")
|
265
289
|
sys.exit(1)
|
266
290
|
|
267
291
|
# make sure specified User exists
|
268
292
|
if options.user_classes:
|
269
|
-
if missing := set(options.user_classes) - set(
|
293
|
+
if missing := set(options.user_classes) - set(available_user_classes.keys()):
|
270
294
|
logger.error(f"Unknown User(s): {', '.join(missing)}\n")
|
271
295
|
sys.exit(1)
|
272
296
|
else:
|
273
|
-
names = set(options.user_classes) & set(
|
274
|
-
user_classes = [
|
297
|
+
names = set(options.user_classes) & set(available_user_classes.keys())
|
298
|
+
user_classes = [available_user_classes[n] for n in names]
|
275
299
|
else:
|
276
300
|
# list() call is needed to consume the dict_view object in Python 3
|
277
|
-
user_classes = list(
|
301
|
+
user_classes = list(available_user_classes.values())
|
278
302
|
|
279
303
|
if not shape_class and options.num_users:
|
280
304
|
fixed_count_total = sum([user_class.fixed_count for user_class in user_classes])
|
@@ -293,7 +317,8 @@ def main():
|
|
293
317
|
if soft_limit < minimum_open_file_limit:
|
294
318
|
# Increasing the limit to 10000 within a running process should work on at least MacOS.
|
295
319
|
# It does not work on all OS:es, but we should be no worse off for trying.
|
296
|
-
|
320
|
+
limits = minimum_open_file_limit, hard_limit
|
321
|
+
resource.setrlimit(resource.RLIMIT_NOFILE, limits)
|
297
322
|
except BaseException:
|
298
323
|
logger.warning(
|
299
324
|
f"""System open file limit '{soft_limit} is below minimum setting '{minimum_open_file_limit}'.
|
@@ -301,9 +326,10 @@ It's not high enough for load testing, and the OS didn't allow locust to increas
|
|
301
326
|
See https://github.com/locustio/locust/wiki/Installation#increasing-maximum-number-of-open-files-limit for more info."""
|
302
327
|
)
|
303
328
|
|
304
|
-
#
|
305
|
-
locustfile_path =
|
329
|
+
# At least one locust file exists, or system will exit earlier
|
330
|
+
locustfile_path = os.path.basename(locustfiles[0])
|
306
331
|
|
332
|
+
# create locust Environment
|
307
333
|
environment = create_environment(
|
308
334
|
user_classes,
|
309
335
|
options,
|
@@ -10,21 +10,21 @@ from ..shape import LoadTestShape
|
|
10
10
|
from ..user import User
|
11
11
|
|
12
12
|
|
13
|
-
def is_user_class(item):
|
13
|
+
def is_user_class(item) -> bool:
|
14
14
|
"""
|
15
15
|
Check if a variable is a runnable (non-abstract) User class
|
16
16
|
"""
|
17
17
|
return bool(inspect.isclass(item) and issubclass(item, User) and item.abstract is False)
|
18
18
|
|
19
19
|
|
20
|
-
def is_shape_class(item):
|
20
|
+
def is_shape_class(item) -> bool:
|
21
21
|
"""
|
22
22
|
Check if a class is a LoadTestShape
|
23
23
|
"""
|
24
24
|
return bool(inspect.isclass(item) and issubclass(item, LoadTestShape) and not getattr(item, "abstract", True))
|
25
25
|
|
26
26
|
|
27
|
-
def load_locustfile(path) -> tuple[
|
27
|
+
def load_locustfile(path) -> tuple[dict[str, type[User]], list[LoadTestShape]]:
|
28
28
|
"""
|
29
29
|
Import given locustfile path and return (docstring, callables).
|
30
30
|
|
@@ -82,4 +82,4 @@ def load_locustfile(path) -> tuple[str | None, dict[str, User], list[LoadTestSha
|
|
82
82
|
# Find shape class, if any, return it
|
83
83
|
shape_classes = [value() for value in vars(imported).values() if is_shape_class(value)]
|
84
84
|
|
85
|
-
return
|
85
|
+
return user_classes, shape_classes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|