locust 2.29.2.dev34__py3-none-any.whl → 2.29.2.dev45__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 +6 -2
- locust/contrib/fasthttp.py +1 -1
- locust/dispatch.py +7 -6
- locust/main.py +0 -1
- {locust-2.29.2.dev34.dist-info → locust-2.29.2.dev45.dist-info}/METADATA +31 -26
- locust-2.29.2.dev45.dist-info/RECORD +49 -0
- locust-2.29.2.dev45.dist-info/WHEEL +4 -0
- locust-2.29.2.dev45.dist-info/entry_points.txt +3 -0
- locust/test/__init__.py +0 -15
- locust/test/fake_module1_for_env_test.py +0 -7
- locust/test/fake_module2_for_env_test.py +0 -7
- locust/test/mock_locustfile.py +0 -56
- locust/test/mock_logging.py +0 -28
- locust/test/test_debugging.py +0 -39
- locust/test/test_dispatch.py +0 -4170
- locust/test/test_env.py +0 -283
- locust/test/test_fasthttp.py +0 -785
- locust/test/test_http.py +0 -325
- locust/test/test_interruptable_task.py +0 -48
- locust/test/test_load_locustfile.py +0 -228
- locust/test/test_locust_class.py +0 -831
- locust/test/test_log.py +0 -237
- locust/test/test_main.py +0 -2264
- locust/test/test_old_wait_api.py +0 -0
- locust/test/test_parser.py +0 -450
- locust/test/test_runners.py +0 -4476
- locust/test/test_sequential_taskset.py +0 -157
- locust/test/test_stats.py +0 -866
- locust/test/test_tags.py +0 -440
- locust/test/test_taskratio.py +0 -94
- locust/test/test_users.py +0 -69
- locust/test/test_util.py +0 -33
- locust/test/test_wait_time.py +0 -79
- locust/test/test_web.py +0 -1257
- locust/test/test_zmqrpc.py +0 -58
- locust/test/testcases.py +0 -248
- locust/test/util.py +0 -88
- locust-2.29.2.dev34.dist-info/RECORD +0 -79
- locust-2.29.2.dev34.dist-info/WHEEL +0 -5
- locust-2.29.2.dev34.dist-info/entry_points.txt +0 -2
- locust-2.29.2.dev34.dist-info/top_level.txt +0 -1
- {locust-2.29.2.dev34.dist-info → locust-2.29.2.dev45.dist-info}/LICENSE +0 -0
locust/test/test_main.py
DELETED
@@ -1,2264 +0,0 @@
|
|
1
|
-
from __future__ import annotations
|
2
|
-
|
3
|
-
import json
|
4
|
-
import os
|
5
|
-
import platform
|
6
|
-
import signal
|
7
|
-
import socket
|
8
|
-
import subprocess
|
9
|
-
import sys
|
10
|
-
import textwrap
|
11
|
-
import unittest
|
12
|
-
from subprocess import DEVNULL, PIPE, STDOUT
|
13
|
-
from tempfile import TemporaryDirectory
|
14
|
-
from unittest import TestCase
|
15
|
-
|
16
|
-
import gevent
|
17
|
-
import psutil
|
18
|
-
import requests
|
19
|
-
from pyquery import PyQuery as pq
|
20
|
-
|
21
|
-
from .mock_locustfile import MOCK_LOCUSTFILE_CONTENT, mock_locustfile
|
22
|
-
from .util import get_free_tcp_port, patch_env, temporary_file
|
23
|
-
|
24
|
-
SHORT_SLEEP = 2 if sys.platform == "darwin" else 1 # macOS is slow on GH, give it some extra time
|
25
|
-
|
26
|
-
|
27
|
-
def is_port_in_use(port: int) -> bool:
|
28
|
-
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
29
|
-
return s.connect_ex(("localhost", port)) == 0
|
30
|
-
|
31
|
-
|
32
|
-
MOCK_LOCUSTFILE_CONTENT_A = textwrap.dedent(
|
33
|
-
"""
|
34
|
-
from locust import User, task, constant, events
|
35
|
-
class TestUser1(User):
|
36
|
-
wait_time = constant(1)
|
37
|
-
@task
|
38
|
-
def my_task(self):
|
39
|
-
print("running my_task()")
|
40
|
-
"""
|
41
|
-
)
|
42
|
-
MOCK_LOCUSTFILE_CONTENT_B = textwrap.dedent(
|
43
|
-
"""
|
44
|
-
from locust import User, task, constant, events
|
45
|
-
class TestUser2(User):
|
46
|
-
wait_time = constant(1)
|
47
|
-
@task
|
48
|
-
def my_task(self):
|
49
|
-
print("running my_task()")
|
50
|
-
"""
|
51
|
-
)
|
52
|
-
|
53
|
-
|
54
|
-
class ProcessIntegrationTest(TestCase):
|
55
|
-
def setUp(self):
|
56
|
-
super().setUp()
|
57
|
-
self.timeout = gevent.Timeout(10)
|
58
|
-
self.timeout.start()
|
59
|
-
|
60
|
-
def tearDown(self):
|
61
|
-
self.timeout.cancel()
|
62
|
-
super().tearDown()
|
63
|
-
|
64
|
-
def assert_run(self, cmd: list[str], timeout: int = 5) -> subprocess.CompletedProcess[str]:
|
65
|
-
out = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout)
|
66
|
-
self.assertEqual(0, out.returncode, f"locust run failed with exit code {out.returncode}:\n{out.stderr}")
|
67
|
-
return out
|
68
|
-
|
69
|
-
|
70
|
-
class StandaloneIntegrationTests(ProcessIntegrationTest):
|
71
|
-
def test_help_arg(self):
|
72
|
-
output = subprocess.check_output(
|
73
|
-
["locust", "--help"],
|
74
|
-
stderr=subprocess.STDOUT,
|
75
|
-
timeout=5,
|
76
|
-
text=True,
|
77
|
-
).strip()
|
78
|
-
self.assertTrue(output.startswith("Usage: locust [options] [UserClass"))
|
79
|
-
self.assertIn("Common options:", output)
|
80
|
-
self.assertIn("-f <filename>, --locustfile <filename>", output)
|
81
|
-
self.assertIn("Logging options:", output)
|
82
|
-
self.assertIn("--skip-log-setup Disable Locust's logging setup.", output)
|
83
|
-
|
84
|
-
@unittest.skipIf(os.name == "nt", reason="Signal handling on windows is hard")
|
85
|
-
def test_custom_arguments(self):
|
86
|
-
port = get_free_tcp_port()
|
87
|
-
with temporary_file(
|
88
|
-
content=textwrap.dedent(
|
89
|
-
"""
|
90
|
-
from locust import User, task, constant, events
|
91
|
-
@events.init_command_line_parser.add_listener
|
92
|
-
def _(parser, **kw):
|
93
|
-
parser.add_argument("--custom-string-arg")
|
94
|
-
|
95
|
-
class TestUser(User):
|
96
|
-
wait_time = constant(10)
|
97
|
-
@task
|
98
|
-
def my_task(self):
|
99
|
-
print(self.environment.parsed_options.custom_string_arg)
|
100
|
-
"""
|
101
|
-
)
|
102
|
-
) as file_path:
|
103
|
-
# print(subprocess.check_output(["cat", file_path]))
|
104
|
-
proc = subprocess.Popen(
|
105
|
-
["locust", "-f", file_path, "--custom-string-arg", "command_line_value", "--web-port", str(port)],
|
106
|
-
stdout=PIPE,
|
107
|
-
stderr=PIPE,
|
108
|
-
text=True,
|
109
|
-
)
|
110
|
-
gevent.sleep(1)
|
111
|
-
|
112
|
-
requests.post(
|
113
|
-
"http://127.0.0.1:%i/swarm" % port,
|
114
|
-
data={"user_count": 1, "spawn_rate": 1, "host": "https://localhost", "custom_string_arg": "web_form_value"},
|
115
|
-
)
|
116
|
-
gevent.sleep(1)
|
117
|
-
|
118
|
-
proc.send_signal(signal.SIGTERM)
|
119
|
-
stdout, stderr = proc.communicate(timeout=3)
|
120
|
-
self.assertIn("Starting Locust", stderr)
|
121
|
-
self.assertRegex(stderr, r".*Shutting down[\S\s]*Aggregated.*", "no stats table printed after shutting down")
|
122
|
-
self.assertNotRegex(stderr, r".*Aggregated[\S\s]*Shutting down.*", "stats table printed BEFORE shutting down")
|
123
|
-
self.assertNotIn("command_line_value", stdout)
|
124
|
-
self.assertIn("web_form_value", stdout)
|
125
|
-
|
126
|
-
@unittest.skipIf(os.name == "nt", reason="Signal handling on windows is hard")
|
127
|
-
def test_custom_arguments_in_file(self):
|
128
|
-
with temporary_file(
|
129
|
-
content=textwrap.dedent(
|
130
|
-
"""
|
131
|
-
from locust import User, task, constant, events
|
132
|
-
@events.init_command_line_parser.add_listener
|
133
|
-
def _(parser, **kw):
|
134
|
-
parser.add_argument("--custom-string-arg")
|
135
|
-
|
136
|
-
class TestUser(User):
|
137
|
-
wait_time = constant(10)
|
138
|
-
@task
|
139
|
-
def my_task(self):
|
140
|
-
print(self.environment.parsed_options.custom_string_arg)
|
141
|
-
"""
|
142
|
-
)
|
143
|
-
) as file_path:
|
144
|
-
try:
|
145
|
-
with open("locust.conf", "w") as conf_file:
|
146
|
-
conf_file.write("custom-string-arg config_file_value")
|
147
|
-
proc = subprocess.Popen(
|
148
|
-
["locust", "-f", file_path, "--autostart"],
|
149
|
-
stdout=PIPE,
|
150
|
-
stderr=PIPE,
|
151
|
-
text=True,
|
152
|
-
)
|
153
|
-
gevent.sleep(1)
|
154
|
-
finally:
|
155
|
-
os.remove("locust.conf")
|
156
|
-
|
157
|
-
proc.send_signal(signal.SIGTERM)
|
158
|
-
stdout, stderr = proc.communicate(timeout=3)
|
159
|
-
self.assertIn("Starting Locust", stderr)
|
160
|
-
self.assertIn("config_file_value", stdout)
|
161
|
-
|
162
|
-
@unittest.skipIf(os.name == "nt", reason="Signal handling on windows is hard")
|
163
|
-
def test_custom_exit_code(self):
|
164
|
-
with temporary_file(
|
165
|
-
content=textwrap.dedent(
|
166
|
-
"""
|
167
|
-
from locust import User, task, constant, events
|
168
|
-
@events.quitting.add_listener
|
169
|
-
def _(environment, **kw):
|
170
|
-
environment.process_exit_code = 42
|
171
|
-
@events.quit.add_listener
|
172
|
-
def _(exit_code, **kw):
|
173
|
-
print(f"Exit code in quit event {exit_code}")
|
174
|
-
class TestUser(User):
|
175
|
-
wait_time = constant(3)
|
176
|
-
@task
|
177
|
-
def my_task(self):
|
178
|
-
print("running my_task()")
|
179
|
-
"""
|
180
|
-
)
|
181
|
-
) as file_path:
|
182
|
-
proc = subprocess.Popen(["locust", "-f", file_path], stdout=PIPE, stderr=PIPE, text=True)
|
183
|
-
gevent.sleep(1)
|
184
|
-
proc.send_signal(signal.SIGTERM)
|
185
|
-
stdout, stderr = proc.communicate()
|
186
|
-
self.assertIn("Starting web interface at", stderr)
|
187
|
-
self.assertIn("Starting Locust", stderr)
|
188
|
-
self.assertIn("Shutting down (exit code 42)", stderr)
|
189
|
-
self.assertIn("Exit code in quit event 42", stdout)
|
190
|
-
self.assertEqual(42, proc.returncode)
|
191
|
-
|
192
|
-
@unittest.skipIf(os.name == "nt", reason="Signal handling on windows is hard")
|
193
|
-
def test_webserver(self):
|
194
|
-
with temporary_file(
|
195
|
-
content=textwrap.dedent(
|
196
|
-
"""
|
197
|
-
from locust import User, task, constant, events
|
198
|
-
class TestUser(User):
|
199
|
-
wait_time = constant(3)
|
200
|
-
@task
|
201
|
-
def my_task(self):
|
202
|
-
print("running my_task()")
|
203
|
-
"""
|
204
|
-
)
|
205
|
-
) as file_path:
|
206
|
-
proc = subprocess.Popen(["locust", "-f", file_path], stdout=PIPE, stderr=PIPE, text=True)
|
207
|
-
gevent.sleep(SHORT_SLEEP)
|
208
|
-
proc.send_signal(signal.SIGTERM)
|
209
|
-
stdout, stderr = proc.communicate()
|
210
|
-
self.assertIn("Starting web interface at", stderr)
|
211
|
-
self.assertNotIn("Locust is running with the UserClass Picker Enabled", stderr)
|
212
|
-
self.assertIn("Starting Locust", stderr)
|
213
|
-
self.assertIn("Shutting down (exit code 0)", stderr)
|
214
|
-
self.assertEqual(0, proc.returncode)
|
215
|
-
|
216
|
-
def test_percentile_parameter(self):
|
217
|
-
port = get_free_tcp_port()
|
218
|
-
with temporary_file(
|
219
|
-
content=textwrap.dedent(
|
220
|
-
"""
|
221
|
-
from locust import User, task, constant, events
|
222
|
-
from locust import stats
|
223
|
-
stats.PERCENTILES_TO_CHART = [0.9, 0.4]
|
224
|
-
class TestUser(User):
|
225
|
-
wait_time = constant(3)
|
226
|
-
@task
|
227
|
-
def my_task(self):
|
228
|
-
print("running my_task()")
|
229
|
-
"""
|
230
|
-
)
|
231
|
-
) as file_path:
|
232
|
-
proc = subprocess.Popen(
|
233
|
-
["locust", "-f", file_path, "--web-port", str(port), "--autostart"], stdout=PIPE, stderr=PIPE, text=True
|
234
|
-
)
|
235
|
-
gevent.sleep(1)
|
236
|
-
response = requests.get(f"http://localhost:{port}/")
|
237
|
-
self.assertEqual(200, response.status_code)
|
238
|
-
proc.send_signal(signal.SIGTERM)
|
239
|
-
stdout, stderr = proc.communicate()
|
240
|
-
self.assertIn("Starting web interface at", stderr)
|
241
|
-
|
242
|
-
def test_percentiles_to_statistics(self):
|
243
|
-
port = get_free_tcp_port()
|
244
|
-
with temporary_file(
|
245
|
-
content=textwrap.dedent(
|
246
|
-
"""
|
247
|
-
from locust import User, task, constant, events
|
248
|
-
from locust.stats import PERCENTILES_TO_STATISTICS
|
249
|
-
PERCENTILES_TO_STATISTICS = [0.9, 0.99]
|
250
|
-
class TestUser(User):
|
251
|
-
wait_time = constant(3)
|
252
|
-
@task
|
253
|
-
def my_task(self):
|
254
|
-
print("running my_task()")
|
255
|
-
"""
|
256
|
-
)
|
257
|
-
) as file_path:
|
258
|
-
proc = subprocess.Popen(
|
259
|
-
["locust", "-f", file_path, "--web-port", str(port), "--autostart"],
|
260
|
-
stdout=PIPE,
|
261
|
-
stderr=PIPE,
|
262
|
-
text=True,
|
263
|
-
)
|
264
|
-
gevent.sleep(1)
|
265
|
-
response = requests.get(f"http://localhost:{port}/")
|
266
|
-
self.assertEqual(200, response.status_code)
|
267
|
-
proc.send_signal(signal.SIGTERM)
|
268
|
-
stdout, stderr = proc.communicate()
|
269
|
-
self.assertIn("Starting web interface at", stderr)
|
270
|
-
|
271
|
-
def test_invalid_percentile_parameter(self):
|
272
|
-
with temporary_file(
|
273
|
-
content=textwrap.dedent(
|
274
|
-
"""
|
275
|
-
from locust import User, task, constant, events
|
276
|
-
from locust import stats
|
277
|
-
stats.PERCENTILES_TO_CHART = [1.2]
|
278
|
-
class TestUser(User):
|
279
|
-
wait_time = constant(3)
|
280
|
-
@task
|
281
|
-
def my_task(self):
|
282
|
-
print("running my_task()")
|
283
|
-
"""
|
284
|
-
)
|
285
|
-
) as file_path:
|
286
|
-
proc = subprocess.Popen(["locust", "-f", file_path, "--autostart"], stdout=PIPE, stderr=PIPE, text=True)
|
287
|
-
gevent.sleep(1)
|
288
|
-
stdout, stderr = proc.communicate()
|
289
|
-
self.assertIn("parameter need to be float and value between. 0 < percentile < 1 Eg 0.95", stderr)
|
290
|
-
self.assertEqual(1, proc.returncode)
|
291
|
-
|
292
|
-
@unittest.skipIf(os.name == "nt", reason="Signal handling on windows is hard")
|
293
|
-
def test_webserver_multiple_locustfiles(self):
|
294
|
-
with mock_locustfile(content=MOCK_LOCUSTFILE_CONTENT_A) as mocked1:
|
295
|
-
with mock_locustfile(content=MOCK_LOCUSTFILE_CONTENT_B) as mocked2:
|
296
|
-
proc = subprocess.Popen(
|
297
|
-
["locust", "-f", f"{mocked1.file_path},{mocked2.file_path}"], stdout=PIPE, stderr=PIPE, text=True
|
298
|
-
)
|
299
|
-
gevent.sleep(SHORT_SLEEP)
|
300
|
-
proc.send_signal(signal.SIGTERM)
|
301
|
-
stdout, stderr = proc.communicate()
|
302
|
-
self.assertIn("Starting web interface at", stderr)
|
303
|
-
self.assertNotIn("Locust is running with the UserClass Picker Enabled", stderr)
|
304
|
-
self.assertIn("Starting Locust", stderr)
|
305
|
-
self.assertIn("Shutting down (exit code 0)", stderr)
|
306
|
-
self.assertEqual(0, proc.returncode)
|
307
|
-
|
308
|
-
@unittest.skipIf(os.name == "nt", reason="Signal handling on windows is hard")
|
309
|
-
def test_webserver_multiple_locustfiles_in_directory(self):
|
310
|
-
with TemporaryDirectory() as temp_dir:
|
311
|
-
with mock_locustfile(content=MOCK_LOCUSTFILE_CONTENT_A, dir=temp_dir):
|
312
|
-
with mock_locustfile(content=MOCK_LOCUSTFILE_CONTENT_B, dir=temp_dir):
|
313
|
-
proc = subprocess.Popen(["locust", "-f", temp_dir], stdout=PIPE, stderr=PIPE, text=True)
|
314
|
-
gevent.sleep(SHORT_SLEEP)
|
315
|
-
proc.send_signal(signal.SIGTERM)
|
316
|
-
stdout, stderr = proc.communicate()
|
317
|
-
self.assertIn("Starting web interface at", stderr)
|
318
|
-
self.assertNotIn("Locust is running with the UserClass Picker Enabled", stderr)
|
319
|
-
self.assertIn("Starting Locust", stderr)
|
320
|
-
self.assertIn("Shutting down (exit code 0)", stderr)
|
321
|
-
self.assertEqual(0, proc.returncode)
|
322
|
-
|
323
|
-
@unittest.skipIf(os.name == "nt", reason="Signal handling on windows is hard")
|
324
|
-
def test_webserver_multiple_locustfiles_with_shape(self):
|
325
|
-
content = textwrap.dedent(
|
326
|
-
"""
|
327
|
-
from locust import User, task, between
|
328
|
-
class TestUser2(User):
|
329
|
-
wait_time = between(2, 4)
|
330
|
-
@task
|
331
|
-
def my_task(self):
|
332
|
-
print("running my_task() again")
|
333
|
-
"""
|
334
|
-
)
|
335
|
-
with mock_locustfile(content=content) as mocked1:
|
336
|
-
with temporary_file(
|
337
|
-
content=textwrap.dedent(
|
338
|
-
"""
|
339
|
-
from locust import User, task, between, LoadTestShape
|
340
|
-
class LoadTestShape(LoadTestShape):
|
341
|
-
def tick(self):
|
342
|
-
run_time = self.get_run_time()
|
343
|
-
if run_time < 2:
|
344
|
-
return (10, 1)
|
345
|
-
|
346
|
-
return None
|
347
|
-
|
348
|
-
class TestUser(User):
|
349
|
-
wait_time = between(2, 4)
|
350
|
-
@task
|
351
|
-
def my_task(self):
|
352
|
-
print("running my_task()")
|
353
|
-
"""
|
354
|
-
)
|
355
|
-
) as mocked2:
|
356
|
-
proc = subprocess.Popen(
|
357
|
-
["locust", "-f", f"{mocked1.file_path},{mocked2}"], stdout=PIPE, stderr=PIPE, text=True
|
358
|
-
)
|
359
|
-
gevent.sleep(SHORT_SLEEP)
|
360
|
-
proc.send_signal(signal.SIGTERM)
|
361
|
-
stdout, stderr = proc.communicate()
|
362
|
-
self.assertIn("Starting web interface at", stderr)
|
363
|
-
self.assertNotIn("Locust is running with the UserClass Picker Enabled", stderr)
|
364
|
-
self.assertIn("Starting Locust", stderr)
|
365
|
-
self.assertIn("Shutting down (exit code 0)", stderr)
|
366
|
-
self.assertEqual(0, proc.returncode)
|
367
|
-
|
368
|
-
def test_default_headless_spawn_options(self):
|
369
|
-
with mock_locustfile() as mocked:
|
370
|
-
proc = subprocess.Popen(
|
371
|
-
[
|
372
|
-
"locust",
|
373
|
-
"-f",
|
374
|
-
mocked.file_path,
|
375
|
-
"--host",
|
376
|
-
"https://test.com/",
|
377
|
-
"--run-time",
|
378
|
-
"1s",
|
379
|
-
"--headless",
|
380
|
-
"--loglevel",
|
381
|
-
"DEBUG",
|
382
|
-
"--exit-code-on-error",
|
383
|
-
"0",
|
384
|
-
# just to test --stop-timeout argument parsing, doesnt actually validate its function:
|
385
|
-
"--stop-timeout",
|
386
|
-
"1s",
|
387
|
-
],
|
388
|
-
stdout=PIPE,
|
389
|
-
stderr=PIPE,
|
390
|
-
text=True,
|
391
|
-
)
|
392
|
-
stdout, stderr = proc.communicate(timeout=4)
|
393
|
-
self.assertNotIn("Traceback", stderr)
|
394
|
-
self.assertIn('Spawning additional {"UserSubclass": 1} ({"UserSubclass": 0} already running)...', stderr)
|
395
|
-
self.assertEqual(0, proc.returncode)
|
396
|
-
|
397
|
-
def test_invalid_stop_timeout_string(self):
|
398
|
-
with mock_locustfile() as mocked:
|
399
|
-
proc = subprocess.Popen(
|
400
|
-
[
|
401
|
-
"locust",
|
402
|
-
"-f",
|
403
|
-
mocked.file_path,
|
404
|
-
"--host",
|
405
|
-
"https://test.com/",
|
406
|
-
"--stop-timeout",
|
407
|
-
"asdf1",
|
408
|
-
],
|
409
|
-
stdout=PIPE,
|
410
|
-
stderr=PIPE,
|
411
|
-
text=True,
|
412
|
-
)
|
413
|
-
stdout, stderr = proc.communicate()
|
414
|
-
self.assertIn("ERROR/locust.main: Valid --stop-timeout formats are", stderr)
|
415
|
-
self.assertEqual(1, proc.returncode)
|
416
|
-
|
417
|
-
@unittest.skipIf(os.name == "nt", reason="Signal handling on windows is hard")
|
418
|
-
def test_headless_spawn_options_wo_run_time(self):
|
419
|
-
with mock_locustfile() as mocked:
|
420
|
-
proc = subprocess.Popen(
|
421
|
-
[
|
422
|
-
"locust",
|
423
|
-
"-f",
|
424
|
-
mocked.file_path,
|
425
|
-
"--host",
|
426
|
-
"https://test.com/",
|
427
|
-
"--headless",
|
428
|
-
"--exit-code-on-error",
|
429
|
-
"0",
|
430
|
-
],
|
431
|
-
stdout=PIPE,
|
432
|
-
stderr=PIPE,
|
433
|
-
text=True,
|
434
|
-
)
|
435
|
-
gevent.sleep(1)
|
436
|
-
proc.send_signal(signal.SIGTERM)
|
437
|
-
stdout, stderr = proc.communicate()
|
438
|
-
self.assertIn("Starting Locust", stderr)
|
439
|
-
self.assertIn("No run time limit set, use CTRL+C to interrupt", stderr)
|
440
|
-
self.assertIn("Shutting down (exit code 0)", stderr)
|
441
|
-
self.assertEqual(0, proc.returncode)
|
442
|
-
|
443
|
-
@unittest.skipIf(os.name == "nt", reason="Signal handling on windows is hard")
|
444
|
-
def test_run_headless_with_multiple_locustfiles(self):
|
445
|
-
with TemporaryDirectory() as temp_dir:
|
446
|
-
with mock_locustfile(dir=temp_dir):
|
447
|
-
with temporary_file(
|
448
|
-
content=textwrap.dedent(
|
449
|
-
"""
|
450
|
-
from locust import User, task, constant, events
|
451
|
-
class TestUser(User):
|
452
|
-
wait_time = constant(1)
|
453
|
-
@task
|
454
|
-
def my_task(self):
|
455
|
-
print("running my_task()")
|
456
|
-
"""
|
457
|
-
),
|
458
|
-
dir=temp_dir,
|
459
|
-
):
|
460
|
-
proc = subprocess.Popen(
|
461
|
-
[
|
462
|
-
"locust",
|
463
|
-
"-f",
|
464
|
-
temp_dir,
|
465
|
-
"--headless",
|
466
|
-
"-u",
|
467
|
-
"2",
|
468
|
-
"--exit-code-on-error",
|
469
|
-
"0",
|
470
|
-
],
|
471
|
-
stdout=PIPE,
|
472
|
-
stderr=PIPE,
|
473
|
-
text=True,
|
474
|
-
)
|
475
|
-
gevent.sleep(3)
|
476
|
-
proc.send_signal(signal.SIGTERM)
|
477
|
-
stdout, stderr = proc.communicate()
|
478
|
-
self.assertIn("Starting Locust", stderr)
|
479
|
-
self.assertIn("All users spawned:", stderr)
|
480
|
-
self.assertIn('"TestUser": 1', stderr)
|
481
|
-
self.assertIn('"UserSubclass": 1', stderr)
|
482
|
-
self.assertIn("Shutting down (exit code 0)", stderr)
|
483
|
-
self.assertEqual(0, proc.returncode)
|
484
|
-
|
485
|
-
def test_default_headless_spawn_options_with_shape(self):
|
486
|
-
content = MOCK_LOCUSTFILE_CONTENT + textwrap.dedent(
|
487
|
-
"""
|
488
|
-
class LoadTestShape(LoadTestShape):
|
489
|
-
def tick(self):
|
490
|
-
run_time = self.get_run_time()
|
491
|
-
if run_time < 2:
|
492
|
-
return (10, 1)
|
493
|
-
|
494
|
-
return None
|
495
|
-
"""
|
496
|
-
)
|
497
|
-
with mock_locustfile(content=content) as mocked:
|
498
|
-
proc = subprocess.Popen(
|
499
|
-
[
|
500
|
-
"locust",
|
501
|
-
"-f",
|
502
|
-
mocked.file_path,
|
503
|
-
"--host",
|
504
|
-
"https://test.com/",
|
505
|
-
"--headless",
|
506
|
-
"--exit-code-on-error",
|
507
|
-
"0",
|
508
|
-
],
|
509
|
-
stdout=PIPE,
|
510
|
-
stderr=PIPE,
|
511
|
-
text=True,
|
512
|
-
)
|
513
|
-
|
514
|
-
try:
|
515
|
-
success = True
|
516
|
-
_, stderr = proc.communicate(timeout=5)
|
517
|
-
except subprocess.TimeoutExpired:
|
518
|
-
success = False
|
519
|
-
proc.send_signal(signal.SIGTERM)
|
520
|
-
_, stderr = proc.communicate()
|
521
|
-
|
522
|
-
proc.send_signal(signal.SIGTERM)
|
523
|
-
_, stderr = proc.communicate()
|
524
|
-
self.assertIn("Shape test updating to 10 users at 1.00 spawn rate", stderr)
|
525
|
-
self.assertTrue(success, "Got timeout and had to kill the process")
|
526
|
-
# ensure stats printer printed at least one report before shutting down and that there was a final report printed as well
|
527
|
-
self.assertRegex(stderr, r".*Aggregated[\S\s]*Shutting down[\S\s]*Aggregated.*")
|
528
|
-
self.assertIn("Shutting down (exit code 0)", stderr)
|
529
|
-
self.assertEqual(0, proc.returncode)
|
530
|
-
|
531
|
-
def test_run_headless_with_multiple_locustfiles_with_shape(self):
|
532
|
-
content = textwrap.dedent(
|
533
|
-
"""
|
534
|
-
from locust import User, task, between
|
535
|
-
class TestUser2(User):
|
536
|
-
wait_time = between(2, 4)
|
537
|
-
@task
|
538
|
-
def my_task(self):
|
539
|
-
print("running my_task() again")
|
540
|
-
"""
|
541
|
-
)
|
542
|
-
with mock_locustfile(content=content) as mocked1:
|
543
|
-
with temporary_file(
|
544
|
-
content=textwrap.dedent(
|
545
|
-
"""
|
546
|
-
from locust import User, task, between, LoadTestShape
|
547
|
-
class LoadTestShape(LoadTestShape):
|
548
|
-
def tick(self):
|
549
|
-
run_time = self.get_run_time()
|
550
|
-
if run_time < 2:
|
551
|
-
return (10, 1)
|
552
|
-
|
553
|
-
return None
|
554
|
-
|
555
|
-
class TestUser(User):
|
556
|
-
wait_time = between(2, 4)
|
557
|
-
@task
|
558
|
-
def my_task(self):
|
559
|
-
print("running my_task()")
|
560
|
-
"""
|
561
|
-
)
|
562
|
-
) as mocked2:
|
563
|
-
proc = subprocess.Popen(
|
564
|
-
[
|
565
|
-
"locust",
|
566
|
-
"-f",
|
567
|
-
f"{mocked1.file_path},{mocked2}",
|
568
|
-
"--host",
|
569
|
-
"https://test.com/",
|
570
|
-
"--headless",
|
571
|
-
"--exit-code-on-error",
|
572
|
-
"0",
|
573
|
-
],
|
574
|
-
stdout=PIPE,
|
575
|
-
stderr=PIPE,
|
576
|
-
text=True,
|
577
|
-
)
|
578
|
-
|
579
|
-
try:
|
580
|
-
success = True
|
581
|
-
_, stderr = proc.communicate(timeout=5)
|
582
|
-
except subprocess.TimeoutExpired:
|
583
|
-
success = False
|
584
|
-
|
585
|
-
proc.send_signal(signal.SIGTERM)
|
586
|
-
_, stderr = proc.communicate()
|
587
|
-
self.assertIn("Shape test updating to 10 users at 1.00 spawn rate", stderr)
|
588
|
-
self.assertTrue(success, "Got timeout and had to kill the process")
|
589
|
-
# ensure stats printer printed at least one report before shutting down and that there was a final report printed as well
|
590
|
-
self.assertRegex(stderr, r".*Aggregated[\S\s]*Shutting down[\S\s]*Aggregated.*")
|
591
|
-
self.assertIn("Shutting down (exit code 0)", stderr)
|
592
|
-
self.assertEqual(0, proc.returncode)
|
593
|
-
|
594
|
-
@unittest.skipIf(os.name == "nt", reason="Signal handling on windows is hard")
|
595
|
-
def test_autostart_wo_run_time(self):
|
596
|
-
port = get_free_tcp_port()
|
597
|
-
with mock_locustfile() as mocked:
|
598
|
-
proc = subprocess.Popen(
|
599
|
-
[
|
600
|
-
"locust",
|
601
|
-
"-f",
|
602
|
-
mocked.file_path,
|
603
|
-
"--web-port",
|
604
|
-
str(port),
|
605
|
-
"--autostart",
|
606
|
-
],
|
607
|
-
stdout=PIPE,
|
608
|
-
stderr=PIPE,
|
609
|
-
text=True,
|
610
|
-
)
|
611
|
-
gevent.sleep(1.9)
|
612
|
-
try:
|
613
|
-
response = requests.get(f"http://localhost:{port}/")
|
614
|
-
except Exception:
|
615
|
-
pass
|
616
|
-
self.assertEqual(200, response.status_code)
|
617
|
-
proc.send_signal(signal.SIGTERM)
|
618
|
-
stdout, stderr = proc.communicate()
|
619
|
-
self.assertIn("Starting Locust", stderr)
|
620
|
-
self.assertIn("No run time limit set, use CTRL+C to interrupt", stderr)
|
621
|
-
self.assertIn("Shutting down ", stderr)
|
622
|
-
self.assertNotIn("Traceback", stderr)
|
623
|
-
# check response afterwards, because it really isn't as informative as stderr
|
624
|
-
d = pq(response.content.decode("utf-8"))
|
625
|
-
|
626
|
-
self.assertEqual(200, response.status_code)
|
627
|
-
self.assertIn('"state": "running"', str(d))
|
628
|
-
|
629
|
-
@unittest.skipIf(sys.platform == "darwin", reason="This is too messy on macOS")
|
630
|
-
def test_autostart_w_run_time(self):
|
631
|
-
port = get_free_tcp_port()
|
632
|
-
with mock_locustfile() as mocked:
|
633
|
-
proc = subprocess.Popen(
|
634
|
-
[
|
635
|
-
"locust",
|
636
|
-
"-f",
|
637
|
-
mocked.file_path,
|
638
|
-
"--web-port",
|
639
|
-
str(port),
|
640
|
-
"-t",
|
641
|
-
"3",
|
642
|
-
"--autostart",
|
643
|
-
"--autoquit",
|
644
|
-
"1",
|
645
|
-
],
|
646
|
-
stdout=PIPE,
|
647
|
-
stderr=PIPE,
|
648
|
-
text=True,
|
649
|
-
)
|
650
|
-
gevent.sleep(2.8)
|
651
|
-
try:
|
652
|
-
response = requests.get(f"http://localhost:{port}/")
|
653
|
-
except Exception:
|
654
|
-
pass
|
655
|
-
_, stderr = proc.communicate(timeout=4)
|
656
|
-
self.assertIn("Starting Locust", stderr)
|
657
|
-
self.assertIn("Run time limit set to 3 seconds", stderr)
|
658
|
-
self.assertIn("Shutting down ", stderr)
|
659
|
-
self.assertNotIn("Traceback", stderr)
|
660
|
-
# check response afterwards, because it really isn't as informative as stderr
|
661
|
-
d = pq(response.content.decode("utf-8"))
|
662
|
-
|
663
|
-
self.assertEqual(200, response.status_code)
|
664
|
-
self.assertIn('"state": "running"', str(d))
|
665
|
-
|
666
|
-
@unittest.skipIf(os.name == "nt", reason="Signal handling on windows is hard")
|
667
|
-
def test_run_autostart_with_multiple_locustfiles(self):
|
668
|
-
with TemporaryDirectory() as temp_dir:
|
669
|
-
with mock_locustfile(dir=temp_dir):
|
670
|
-
with temporary_file(
|
671
|
-
content=textwrap.dedent(
|
672
|
-
"""
|
673
|
-
from locust import User, task, constant, events
|
674
|
-
class TestUser(User):
|
675
|
-
wait_time = constant(1)
|
676
|
-
@task
|
677
|
-
def my_task(self):
|
678
|
-
print("running my_task()")
|
679
|
-
"""
|
680
|
-
),
|
681
|
-
dir=temp_dir,
|
682
|
-
):
|
683
|
-
proc = subprocess.Popen(
|
684
|
-
[
|
685
|
-
"locust",
|
686
|
-
"-f",
|
687
|
-
temp_dir,
|
688
|
-
"--autostart",
|
689
|
-
"-u",
|
690
|
-
"2",
|
691
|
-
"--exit-code-on-error",
|
692
|
-
"0",
|
693
|
-
],
|
694
|
-
stdout=PIPE,
|
695
|
-
stderr=PIPE,
|
696
|
-
text=True,
|
697
|
-
)
|
698
|
-
gevent.sleep(3)
|
699
|
-
proc.send_signal(signal.SIGTERM)
|
700
|
-
stdout, stderr = proc.communicate()
|
701
|
-
self.assertIn("Starting Locust", stderr)
|
702
|
-
self.assertIn("All users spawned:", stderr)
|
703
|
-
self.assertIn('"TestUser": 1', stderr)
|
704
|
-
self.assertIn('"UserSubclass": 1', stderr)
|
705
|
-
self.assertIn("Shutting down (exit code 0)", stderr)
|
706
|
-
self.assertEqual(0, proc.returncode)
|
707
|
-
|
708
|
-
def test_autostart_w_load_shape(self):
|
709
|
-
port = get_free_tcp_port()
|
710
|
-
with mock_locustfile(
|
711
|
-
content=MOCK_LOCUSTFILE_CONTENT
|
712
|
-
+ textwrap.dedent(
|
713
|
-
"""
|
714
|
-
from locust import LoadTestShape
|
715
|
-
class LoadTestShape(LoadTestShape):
|
716
|
-
def tick(self):
|
717
|
-
run_time = self.get_run_time()
|
718
|
-
if run_time < 2:
|
719
|
-
return (10, 1)
|
720
|
-
|
721
|
-
return None
|
722
|
-
"""
|
723
|
-
)
|
724
|
-
) as mocked:
|
725
|
-
proc = subprocess.Popen(
|
726
|
-
[
|
727
|
-
"locust",
|
728
|
-
"-f",
|
729
|
-
mocked.file_path,
|
730
|
-
"--web-port",
|
731
|
-
str(port),
|
732
|
-
"--autostart",
|
733
|
-
"--autoquit",
|
734
|
-
"3",
|
735
|
-
],
|
736
|
-
stdout=PIPE,
|
737
|
-
stderr=PIPE,
|
738
|
-
text=True,
|
739
|
-
)
|
740
|
-
gevent.sleep(2.8)
|
741
|
-
response = requests.get(f"http://localhost:{port}/")
|
742
|
-
try:
|
743
|
-
success = True
|
744
|
-
_, stderr = proc.communicate(timeout=5)
|
745
|
-
except subprocess.TimeoutExpired:
|
746
|
-
success = False
|
747
|
-
proc.send_signal(signal.SIGTERM)
|
748
|
-
_, stderr = proc.communicate()
|
749
|
-
|
750
|
-
self.assertIn("Starting Locust", stderr)
|
751
|
-
self.assertIn("Shape test starting", stderr)
|
752
|
-
self.assertIn("Shutting down ", stderr)
|
753
|
-
self.assertIn("autoquit time reached", stderr)
|
754
|
-
# check response afterwards, because it really isn't as informative as stderr
|
755
|
-
self.assertEqual(200, response.status_code)
|
756
|
-
self.assertTrue(success, "got timeout and had to kill the process")
|
757
|
-
|
758
|
-
def test_autostart_multiple_locustfiles_with_shape(self):
|
759
|
-
port = get_free_tcp_port()
|
760
|
-
content = textwrap.dedent(
|
761
|
-
"""
|
762
|
-
from locust import User, task, between
|
763
|
-
class TestUser2(User):
|
764
|
-
wait_time = between(2, 4)
|
765
|
-
@task
|
766
|
-
def my_task(self):
|
767
|
-
print("running my_task() again")
|
768
|
-
"""
|
769
|
-
)
|
770
|
-
with mock_locustfile(content=content) as mocked1:
|
771
|
-
with temporary_file(
|
772
|
-
content=textwrap.dedent(
|
773
|
-
"""
|
774
|
-
from locust import User, task, between, LoadTestShape
|
775
|
-
class LoadTestShape(LoadTestShape):
|
776
|
-
def tick(self):
|
777
|
-
run_time = self.get_run_time()
|
778
|
-
if run_time < 2:
|
779
|
-
return (10, 1)
|
780
|
-
|
781
|
-
return None
|
782
|
-
|
783
|
-
class TestUser(User):
|
784
|
-
wait_time = between(2, 4)
|
785
|
-
@task
|
786
|
-
def my_task(self):
|
787
|
-
print("running my_task()")
|
788
|
-
"""
|
789
|
-
)
|
790
|
-
) as mocked2:
|
791
|
-
proc = subprocess.Popen(
|
792
|
-
[
|
793
|
-
"locust",
|
794
|
-
"-f",
|
795
|
-
f"{mocked1.file_path},{mocked2}",
|
796
|
-
"--web-port",
|
797
|
-
str(port),
|
798
|
-
"--autostart",
|
799
|
-
"--autoquit",
|
800
|
-
"3",
|
801
|
-
],
|
802
|
-
stdout=PIPE,
|
803
|
-
stderr=PIPE,
|
804
|
-
text=True,
|
805
|
-
)
|
806
|
-
gevent.sleep(2.8)
|
807
|
-
success = True
|
808
|
-
try:
|
809
|
-
response = requests.get(f"http://localhost:{port}/")
|
810
|
-
except ConnectionError:
|
811
|
-
success = False
|
812
|
-
try:
|
813
|
-
_, stderr = proc.communicate(timeout=5)
|
814
|
-
except subprocess.TimeoutExpired:
|
815
|
-
success = False
|
816
|
-
proc.send_signal(signal.SIGTERM)
|
817
|
-
_, stderr = proc.communicate()
|
818
|
-
|
819
|
-
self.assertIn("Starting Locust", stderr)
|
820
|
-
self.assertIn("Shape test starting", stderr)
|
821
|
-
self.assertIn("Shutting down ", stderr)
|
822
|
-
self.assertIn("autoquit time reached", stderr)
|
823
|
-
# check response afterwards, because it really isn't as informative as stderr
|
824
|
-
self.assertEqual(200, response.status_code)
|
825
|
-
self.assertTrue(success, "got timeout and had to kill the process")
|
826
|
-
|
827
|
-
@unittest.skipIf(platform.system() == "Darwin", reason="Messy on macOS on GH")
|
828
|
-
@unittest.skipIf(os.name == "nt", reason="Signal handling on windows is hard")
|
829
|
-
def test_web_options(self):
|
830
|
-
port = get_free_tcp_port()
|
831
|
-
if platform.system() != "Darwin":
|
832
|
-
# MacOS only sets up the loopback interface for 127.0.0.1 and not for 127.*.*.*, so we can't test this
|
833
|
-
with mock_locustfile() as mocked:
|
834
|
-
proc = subprocess.Popen(
|
835
|
-
["locust", "-f", mocked.file_path, "--web-host", "127.0.0.2", "--web-port", str(port)],
|
836
|
-
stdout=PIPE,
|
837
|
-
stderr=PIPE,
|
838
|
-
)
|
839
|
-
gevent.sleep(1)
|
840
|
-
self.assertEqual(200, requests.get(f"http://127.0.0.2:{port}/", timeout=1).status_code)
|
841
|
-
proc.terminate()
|
842
|
-
|
843
|
-
with mock_locustfile() as mocked:
|
844
|
-
proc = subprocess.Popen(
|
845
|
-
[
|
846
|
-
"locust",
|
847
|
-
"-f",
|
848
|
-
mocked.file_path,
|
849
|
-
"--web-host",
|
850
|
-
"*",
|
851
|
-
"--web-port",
|
852
|
-
str(port),
|
853
|
-
],
|
854
|
-
stdout=PIPE,
|
855
|
-
stderr=PIPE,
|
856
|
-
)
|
857
|
-
gevent.sleep(1)
|
858
|
-
self.assertEqual(200, requests.get("http://127.0.0.1:%i/" % port, timeout=3).status_code)
|
859
|
-
proc.terminate()
|
860
|
-
|
861
|
-
@unittest.skipIf(os.name == "nt", reason="termios doesnt exist on windows, and thus we cannot import pty")
|
862
|
-
def test_input(self):
|
863
|
-
import pty
|
864
|
-
|
865
|
-
LOCUSTFILE_CONTENT = textwrap.dedent(
|
866
|
-
"""
|
867
|
-
from locust import User, TaskSet, task, between
|
868
|
-
|
869
|
-
class UserSubclass(User):
|
870
|
-
wait_time = between(0.2, 0.8)
|
871
|
-
@task
|
872
|
-
def t(self):
|
873
|
-
print("Test task is running")
|
874
|
-
"""
|
875
|
-
)
|
876
|
-
with mock_locustfile(content=LOCUSTFILE_CONTENT) as mocked:
|
877
|
-
stdin_m, stdin_s = pty.openpty()
|
878
|
-
stdin = os.fdopen(stdin_m, "wb", 0)
|
879
|
-
|
880
|
-
proc = subprocess.Popen(
|
881
|
-
" ".join(
|
882
|
-
[
|
883
|
-
"locust",
|
884
|
-
"-f",
|
885
|
-
mocked.file_path,
|
886
|
-
"--headless",
|
887
|
-
"--run-time",
|
888
|
-
"7s",
|
889
|
-
"-u",
|
890
|
-
"0",
|
891
|
-
"--loglevel",
|
892
|
-
"INFO",
|
893
|
-
]
|
894
|
-
),
|
895
|
-
stderr=STDOUT,
|
896
|
-
stdin=stdin_s,
|
897
|
-
stdout=PIPE,
|
898
|
-
shell=True,
|
899
|
-
text=True,
|
900
|
-
)
|
901
|
-
gevent.sleep(1)
|
902
|
-
|
903
|
-
stdin.write(b"w")
|
904
|
-
gevent.sleep(1)
|
905
|
-
stdin.write(b"W")
|
906
|
-
gevent.sleep(1)
|
907
|
-
stdin.write(b"s")
|
908
|
-
gevent.sleep(1)
|
909
|
-
stdin.write(b"S")
|
910
|
-
gevent.sleep(1)
|
911
|
-
|
912
|
-
# This should not do anything since we are already at zero users
|
913
|
-
stdin.write(b"S")
|
914
|
-
gevent.sleep(1)
|
915
|
-
|
916
|
-
output = proc.communicate()[0]
|
917
|
-
stdin.close()
|
918
|
-
self.assertIn("Ramping to 1 users at a rate of 100.00 per second", output)
|
919
|
-
self.assertIn('All users spawned: {"UserSubclass": 1} (1 total users)', output)
|
920
|
-
self.assertIn("Ramping to 11 users at a rate of 100.00 per second", output)
|
921
|
-
self.assertIn('All users spawned: {"UserSubclass": 11} (11 total users)', output)
|
922
|
-
self.assertIn("Ramping to 10 users at a rate of 100.00 per second", output)
|
923
|
-
self.assertIn('All users spawned: {"UserSubclass": 10} (10 total users)', output)
|
924
|
-
self.assertIn("Ramping to 0 users at a rate of 100.00 per second", output)
|
925
|
-
self.assertIn('All users spawned: {"UserSubclass": 0} (0 total users)', output)
|
926
|
-
self.assertIn("Test task is running", output)
|
927
|
-
# ensure stats printer printed at least one report before shutting down and that there was a final report printed as well
|
928
|
-
self.assertRegex(output, r".*Aggregated[\S\s]*Shutting down[\S\s]*Aggregated.*")
|
929
|
-
self.assertIn("Shutting down (exit code 0)", output)
|
930
|
-
self.assertEqual(0, proc.returncode)
|
931
|
-
|
932
|
-
def test_spawning_with_fixed(self):
|
933
|
-
LOCUSTFILE_CONTENT = textwrap.dedent(
|
934
|
-
"""
|
935
|
-
from locust import User, task, constant
|
936
|
-
|
937
|
-
class User1(User):
|
938
|
-
fixed_count = 2
|
939
|
-
wait_time = constant(1)
|
940
|
-
|
941
|
-
@task
|
942
|
-
def t(self):
|
943
|
-
print("Test task is running")
|
944
|
-
|
945
|
-
class User2(User):
|
946
|
-
wait_time = constant(1)
|
947
|
-
@task
|
948
|
-
def t(self):
|
949
|
-
print("Test task is running")
|
950
|
-
|
951
|
-
class User3(User):
|
952
|
-
wait_time = constant(1)
|
953
|
-
@task
|
954
|
-
def t(self):
|
955
|
-
print("Test task is running")
|
956
|
-
"""
|
957
|
-
)
|
958
|
-
with mock_locustfile(content=LOCUSTFILE_CONTENT) as mocked:
|
959
|
-
proc = subprocess.Popen(
|
960
|
-
" ".join(
|
961
|
-
[
|
962
|
-
"locust",
|
963
|
-
"-f",
|
964
|
-
mocked.file_path,
|
965
|
-
"--headless",
|
966
|
-
"--run-time",
|
967
|
-
"5s",
|
968
|
-
"-u",
|
969
|
-
"10",
|
970
|
-
"-r",
|
971
|
-
"10",
|
972
|
-
"--loglevel",
|
973
|
-
"INFO",
|
974
|
-
]
|
975
|
-
),
|
976
|
-
stderr=STDOUT,
|
977
|
-
stdout=PIPE,
|
978
|
-
shell=True,
|
979
|
-
text=True,
|
980
|
-
)
|
981
|
-
|
982
|
-
output = proc.communicate()[0]
|
983
|
-
self.assertIn("Ramping to 10 users at a rate of 10.00 per second", output)
|
984
|
-
self.assertIn('All users spawned: {"User1": 2, "User2": 4, "User3": 4} (10 total users)', output)
|
985
|
-
self.assertIn("Test task is running", output)
|
986
|
-
# ensure stats printer printed at least one report before shutting down and that there was a final report printed as well
|
987
|
-
self.assertRegex(output, r".*Aggregated[\S\s]*Shutting down[\S\s]*Aggregated.*")
|
988
|
-
self.assertIn("Shutting down (exit code 0)", output)
|
989
|
-
self.assertEqual(0, proc.returncode)
|
990
|
-
|
991
|
-
def test_spawing_with_fixed_multiple_locustfiles(self):
|
992
|
-
with mock_locustfile(content=MOCK_LOCUSTFILE_CONTENT_A) as mocked1:
|
993
|
-
with mock_locustfile(content=MOCK_LOCUSTFILE_CONTENT_B) as mocked2:
|
994
|
-
proc = subprocess.Popen(
|
995
|
-
" ".join(
|
996
|
-
[
|
997
|
-
"locust",
|
998
|
-
"-f",
|
999
|
-
f"{mocked1.file_path},{mocked2.file_path}",
|
1000
|
-
"--headless",
|
1001
|
-
"--run-time",
|
1002
|
-
"5s",
|
1003
|
-
"-u",
|
1004
|
-
"10",
|
1005
|
-
"-r",
|
1006
|
-
"10",
|
1007
|
-
"--loglevel",
|
1008
|
-
"INFO",
|
1009
|
-
]
|
1010
|
-
),
|
1011
|
-
stderr=STDOUT,
|
1012
|
-
stdout=PIPE,
|
1013
|
-
shell=True,
|
1014
|
-
text=True,
|
1015
|
-
)
|
1016
|
-
|
1017
|
-
output = proc.communicate()[0]
|
1018
|
-
self.assertIn("Ramping to 10 users at a rate of 10.00 per second", output)
|
1019
|
-
self.assertIn('All users spawned: {"TestUser1": 5, "TestUser2": 5} (10 total users)', output)
|
1020
|
-
self.assertIn("running my_task()", output)
|
1021
|
-
# ensure stats printer printed at least one report before shutting down and that there was a final report printed as well
|
1022
|
-
self.assertRegex(output, r".*Aggregated[\S\s]*Shutting down[\S\s]*Aggregated.*")
|
1023
|
-
self.assertIn("Shutting down (exit code 0)", output)
|
1024
|
-
self.assertEqual(0, proc.returncode)
|
1025
|
-
|
1026
|
-
def test_with_package_as_locustfile(self):
|
1027
|
-
with TemporaryDirectory() as temp_dir:
|
1028
|
-
with open(f"{temp_dir}/__init__.py", mode="w"):
|
1029
|
-
with mock_locustfile(dir=temp_dir):
|
1030
|
-
proc = subprocess.Popen(
|
1031
|
-
[
|
1032
|
-
"locust",
|
1033
|
-
"-f",
|
1034
|
-
temp_dir,
|
1035
|
-
"--headless",
|
1036
|
-
"--exit-code-on-error",
|
1037
|
-
"0",
|
1038
|
-
"--run-time",
|
1039
|
-
"2",
|
1040
|
-
],
|
1041
|
-
stdout=PIPE,
|
1042
|
-
stderr=PIPE,
|
1043
|
-
text=True,
|
1044
|
-
)
|
1045
|
-
stdout, stderr = proc.communicate()
|
1046
|
-
self.assertIn("Starting Locust", stderr)
|
1047
|
-
self.assertIn("All users spawned:", stderr)
|
1048
|
-
self.assertIn('"UserSubclass": 1', stderr)
|
1049
|
-
self.assertIn("Shutting down (exit code 0)", stderr)
|
1050
|
-
self.assertEqual(0, proc.returncode)
|
1051
|
-
|
1052
|
-
def test_command_line_user_selection(self):
|
1053
|
-
LOCUSTFILE_CONTENT = textwrap.dedent(
|
1054
|
-
"""
|
1055
|
-
from locust import User, task, constant
|
1056
|
-
|
1057
|
-
class User1(User):
|
1058
|
-
wait_time = constant(1)
|
1059
|
-
@task
|
1060
|
-
def t(self):
|
1061
|
-
print("User1 is running")
|
1062
|
-
|
1063
|
-
class User2(User):
|
1064
|
-
wait_time = constant(1)
|
1065
|
-
@task
|
1066
|
-
def t(self):
|
1067
|
-
print("User2 is running")
|
1068
|
-
|
1069
|
-
class User3(User):
|
1070
|
-
wait_time = constant(1)
|
1071
|
-
@task
|
1072
|
-
def t(self):
|
1073
|
-
print("User3 is running")
|
1074
|
-
"""
|
1075
|
-
)
|
1076
|
-
with mock_locustfile(content=LOCUSTFILE_CONTENT) as mocked:
|
1077
|
-
proc = subprocess.Popen(
|
1078
|
-
" ".join(
|
1079
|
-
[
|
1080
|
-
"locust",
|
1081
|
-
"-f",
|
1082
|
-
mocked.file_path,
|
1083
|
-
"--headless",
|
1084
|
-
"--run-time",
|
1085
|
-
"2s",
|
1086
|
-
"-u",
|
1087
|
-
"5",
|
1088
|
-
"-r",
|
1089
|
-
"10",
|
1090
|
-
"User2",
|
1091
|
-
"User3",
|
1092
|
-
]
|
1093
|
-
),
|
1094
|
-
stderr=STDOUT,
|
1095
|
-
stdout=PIPE,
|
1096
|
-
shell=True,
|
1097
|
-
text=True,
|
1098
|
-
)
|
1099
|
-
|
1100
|
-
output = proc.communicate()[0]
|
1101
|
-
self.assertNotIn("User1 is running", output)
|
1102
|
-
self.assertIn("User2 is running", output)
|
1103
|
-
self.assertIn("User3 is running", output)
|
1104
|
-
self.assertEqual(0, proc.returncode)
|
1105
|
-
|
1106
|
-
def test_html_report_option(self):
|
1107
|
-
with mock_locustfile() as mocked:
|
1108
|
-
with temporary_file("", suffix=".html") as html_report_file_path:
|
1109
|
-
try:
|
1110
|
-
subprocess.check_output(
|
1111
|
-
[
|
1112
|
-
"locust",
|
1113
|
-
"-f",
|
1114
|
-
mocked.file_path,
|
1115
|
-
"--host",
|
1116
|
-
"https://test.com/",
|
1117
|
-
"--run-time",
|
1118
|
-
"2s",
|
1119
|
-
"--headless",
|
1120
|
-
"--exit-code-on-error",
|
1121
|
-
"0",
|
1122
|
-
"--html",
|
1123
|
-
html_report_file_path,
|
1124
|
-
],
|
1125
|
-
stderr=subprocess.STDOUT,
|
1126
|
-
timeout=10,
|
1127
|
-
text=True,
|
1128
|
-
).strip()
|
1129
|
-
except subprocess.CalledProcessError as e:
|
1130
|
-
raise AssertionError(f"Running locust command failed. Output was:\n\n{e.stdout}") from e
|
1131
|
-
|
1132
|
-
with open(html_report_file_path, encoding="utf-8") as f:
|
1133
|
-
html_report_content = f.read()
|
1134
|
-
|
1135
|
-
# make sure title appears in the report
|
1136
|
-
_, locustfile = os.path.split(mocked.file_path)
|
1137
|
-
self.assertIn(locustfile, html_report_content)
|
1138
|
-
|
1139
|
-
# make sure host appears in the report
|
1140
|
-
self.assertIn("https://test.com/", html_report_content)
|
1141
|
-
self.assertIn('"show_download_link": false', html_report_content)
|
1142
|
-
|
1143
|
-
def test_run_with_userclass_picker(self):
|
1144
|
-
with temporary_file(content=MOCK_LOCUSTFILE_CONTENT_A) as file1:
|
1145
|
-
with temporary_file(content=MOCK_LOCUSTFILE_CONTENT_B) as file2:
|
1146
|
-
proc = subprocess.Popen(
|
1147
|
-
["locust", "-f", f"{file1},{file2}", "--class-picker"],
|
1148
|
-
stdout=PIPE,
|
1149
|
-
stderr=PIPE,
|
1150
|
-
text=True,
|
1151
|
-
)
|
1152
|
-
gevent.sleep(2)
|
1153
|
-
proc.send_signal(signal.SIGTERM)
|
1154
|
-
stdout, stderr = proc.communicate()
|
1155
|
-
|
1156
|
-
self.assertIn("Locust is running with the UserClass Picker Enabled", stderr)
|
1157
|
-
self.assertIn("Starting Locust", stderr)
|
1158
|
-
self.assertIn("Starting web interface at", stderr)
|
1159
|
-
|
1160
|
-
def test_error_when_duplicate_userclass_names(self):
|
1161
|
-
MOCK_LOCUSTFILE_CONTENT_C = textwrap.dedent(
|
1162
|
-
"""
|
1163
|
-
from locust import User, task, constant, events
|
1164
|
-
class TestUser1(User):
|
1165
|
-
wait_time = constant(3)
|
1166
|
-
@task
|
1167
|
-
def my_task(self):
|
1168
|
-
print("running my_task()")
|
1169
|
-
"""
|
1170
|
-
)
|
1171
|
-
with temporary_file(content=MOCK_LOCUSTFILE_CONTENT_A) as file1:
|
1172
|
-
with temporary_file(content=MOCK_LOCUSTFILE_CONTENT_C) as file2:
|
1173
|
-
proc = subprocess.Popen(["locust", "-f", f"{file1},{file2}"], stdout=PIPE, stderr=PIPE, text=True)
|
1174
|
-
gevent.sleep(1)
|
1175
|
-
stdout, stderr = proc.communicate()
|
1176
|
-
|
1177
|
-
self.assertIn("Duplicate user class names: TestUser1 is defined", stderr)
|
1178
|
-
self.assertEqual(1, proc.returncode)
|
1179
|
-
|
1180
|
-
def test_no_error_when_same_userclass_in_two_files(self):
|
1181
|
-
with temporary_file(content=MOCK_LOCUSTFILE_CONTENT_A) as file1:
|
1182
|
-
MOCK_LOCUSTFILE_CONTENT_C = textwrap.dedent(
|
1183
|
-
f"""
|
1184
|
-
from {os.path.basename(file1)[:-3]} import TestUser1
|
1185
|
-
"""
|
1186
|
-
)
|
1187
|
-
print(MOCK_LOCUSTFILE_CONTENT_C)
|
1188
|
-
with temporary_file(content=MOCK_LOCUSTFILE_CONTENT_C) as file2:
|
1189
|
-
proc = subprocess.Popen(
|
1190
|
-
["locust", "-f", f"{file1},{file2}", "-t", "1", "--headless"], stdout=PIPE, stderr=PIPE, text=True
|
1191
|
-
)
|
1192
|
-
gevent.sleep(1)
|
1193
|
-
stdout, stderr = proc.communicate()
|
1194
|
-
|
1195
|
-
self.assertIn("running my_task", stdout)
|
1196
|
-
self.assertEqual(0, proc.returncode)
|
1197
|
-
|
1198
|
-
def test_error_when_duplicate_shape_class_names(self):
|
1199
|
-
MOCK_LOCUSTFILE_CONTENT_C = MOCK_LOCUSTFILE_CONTENT_A + textwrap.dedent(
|
1200
|
-
"""
|
1201
|
-
from locust import LoadTestShape
|
1202
|
-
class TestShape(LoadTestShape):
|
1203
|
-
def tick(self):
|
1204
|
-
run_time = self.get_run_time()
|
1205
|
-
if run_time < 2:
|
1206
|
-
return (10, 1)
|
1207
|
-
|
1208
|
-
return None
|
1209
|
-
"""
|
1210
|
-
)
|
1211
|
-
MOCK_LOCUSTFILE_CONTENT_D = MOCK_LOCUSTFILE_CONTENT_B + textwrap.dedent(
|
1212
|
-
"""
|
1213
|
-
from locust import LoadTestShape
|
1214
|
-
class TestShape(LoadTestShape):
|
1215
|
-
def tick(self):
|
1216
|
-
run_time = self.get_run_time()
|
1217
|
-
if run_time < 2:
|
1218
|
-
return (10, 1)
|
1219
|
-
|
1220
|
-
return None
|
1221
|
-
"""
|
1222
|
-
)
|
1223
|
-
with temporary_file(content=MOCK_LOCUSTFILE_CONTENT_C) as file1:
|
1224
|
-
with temporary_file(content=MOCK_LOCUSTFILE_CONTENT_D) as file2:
|
1225
|
-
proc = subprocess.Popen(["locust", "-f", f"{file1},{file2}"], stdout=PIPE, stderr=PIPE, text=True)
|
1226
|
-
gevent.sleep(1)
|
1227
|
-
stdout, stderr = proc.communicate()
|
1228
|
-
|
1229
|
-
self.assertIn("Duplicate shape classes: TestShape", stderr)
|
1230
|
-
self.assertEqual(1, proc.returncode)
|
1231
|
-
|
1232
|
-
def test_error_when_providing_both_run_time_and_a_shape_class(self):
|
1233
|
-
content = MOCK_LOCUSTFILE_CONTENT + textwrap.dedent(
|
1234
|
-
"""
|
1235
|
-
from locust import LoadTestShape
|
1236
|
-
class TestShape(LoadTestShape):
|
1237
|
-
def tick(self):
|
1238
|
-
return None
|
1239
|
-
"""
|
1240
|
-
)
|
1241
|
-
with mock_locustfile(content=content) as mocked:
|
1242
|
-
out = self.assert_run(
|
1243
|
-
[
|
1244
|
-
"locust",
|
1245
|
-
"-f",
|
1246
|
-
mocked.file_path,
|
1247
|
-
"--run-time=1s",
|
1248
|
-
"--headless",
|
1249
|
-
"--exit-code-on-error",
|
1250
|
-
"0",
|
1251
|
-
]
|
1252
|
-
)
|
1253
|
-
|
1254
|
-
self.assertIn("--run-time, --users or --spawn-rate have no impact on LoadShapes", out.stderr)
|
1255
|
-
self.assertIn("The following option(s) will be ignored: --run-time", out.stderr)
|
1256
|
-
|
1257
|
-
def test_shape_class_log_disabled_parameters(self):
|
1258
|
-
content = MOCK_LOCUSTFILE_CONTENT + textwrap.dedent(
|
1259
|
-
"""
|
1260
|
-
from locust import LoadTestShape
|
1261
|
-
|
1262
|
-
class TestShape(LoadTestShape):
|
1263
|
-
def tick(self):
|
1264
|
-
return None
|
1265
|
-
"""
|
1266
|
-
)
|
1267
|
-
with mock_locustfile(content=content) as mocked:
|
1268
|
-
out = self.assert_run(
|
1269
|
-
[
|
1270
|
-
"locust",
|
1271
|
-
"--headless",
|
1272
|
-
"-f",
|
1273
|
-
mocked.file_path,
|
1274
|
-
"--exit-code-on-error=0",
|
1275
|
-
"--users=1",
|
1276
|
-
"--spawn-rate=1",
|
1277
|
-
]
|
1278
|
-
)
|
1279
|
-
self.assertIn("Shape test starting.", out.stderr)
|
1280
|
-
self.assertIn("--run-time, --users or --spawn-rate have no impact on LoadShapes", out.stderr)
|
1281
|
-
self.assertIn("The following option(s) will be ignored: --users, --spawn-rate", out.stderr)
|
1282
|
-
|
1283
|
-
def test_shape_class_with_use_common_options(self):
|
1284
|
-
content = MOCK_LOCUSTFILE_CONTENT + textwrap.dedent(
|
1285
|
-
"""
|
1286
|
-
from locust import LoadTestShape
|
1287
|
-
|
1288
|
-
class TestShape(LoadTestShape):
|
1289
|
-
use_common_options = True
|
1290
|
-
|
1291
|
-
def tick(self):
|
1292
|
-
return None
|
1293
|
-
"""
|
1294
|
-
)
|
1295
|
-
with mock_locustfile(content=content) as mocked:
|
1296
|
-
out = self.assert_run(
|
1297
|
-
[
|
1298
|
-
"locust",
|
1299
|
-
"-f",
|
1300
|
-
mocked.file_path,
|
1301
|
-
"--run-time=1s",
|
1302
|
-
"--users=1",
|
1303
|
-
"--spawn-rate=1",
|
1304
|
-
"--headless",
|
1305
|
-
"--exit-code-on-error=0",
|
1306
|
-
]
|
1307
|
-
)
|
1308
|
-
self.assertIn("Shape test starting.", out.stderr)
|
1309
|
-
self.assertNotIn("--run-time, --users or --spawn-rate have no impact on LoadShapes", out.stderr)
|
1310
|
-
self.assertNotIn("The following option(s) will be ignored:", out.stderr)
|
1311
|
-
|
1312
|
-
def test_error_when_locustfiles_directory_is_empty(self):
|
1313
|
-
with TemporaryDirectory() as temp_dir:
|
1314
|
-
proc = subprocess.Popen(["locust", "-f", temp_dir], stdout=PIPE, stderr=PIPE, text=True)
|
1315
|
-
gevent.sleep(1)
|
1316
|
-
stdout, stderr = proc.communicate()
|
1317
|
-
|
1318
|
-
self.assertIn(f"Could not find any locustfiles in directory '{temp_dir}'", stderr)
|
1319
|
-
self.assertEqual(1, proc.returncode)
|
1320
|
-
|
1321
|
-
def test_error_when_no_tasks_match_tags(self):
|
1322
|
-
content = """
|
1323
|
-
from locust import HttpUser, TaskSet, task, constant, LoadTestShape, tag
|
1324
|
-
class MyUser(HttpUser):
|
1325
|
-
host = "http://127.0.0.1:8089"
|
1326
|
-
wait_time = constant(1)
|
1327
|
-
@tag("tag1")
|
1328
|
-
@task
|
1329
|
-
def task1(self):
|
1330
|
-
print("task1")
|
1331
|
-
"""
|
1332
|
-
with mock_locustfile(content=content) as mocked:
|
1333
|
-
proc = subprocess.Popen(
|
1334
|
-
[
|
1335
|
-
"locust",
|
1336
|
-
"-f",
|
1337
|
-
mocked.file_path,
|
1338
|
-
"--headless",
|
1339
|
-
"-t",
|
1340
|
-
"1",
|
1341
|
-
"--tags",
|
1342
|
-
"tag2",
|
1343
|
-
],
|
1344
|
-
stdout=PIPE,
|
1345
|
-
stderr=PIPE,
|
1346
|
-
text=True,
|
1347
|
-
)
|
1348
|
-
stdout, stderr = proc.communicate()
|
1349
|
-
self.assertIn("MyUser had no tasks left after filtering", stderr)
|
1350
|
-
self.assertIn("No tasks defined on MyUser", stderr)
|
1351
|
-
self.assertEqual(1, proc.returncode)
|
1352
|
-
|
1353
|
-
@unittest.skipIf(os.name == "nt", reason="Signal handling on windows is hard")
|
1354
|
-
def test_graceful_exit_when_keyboard_interrupt(self):
|
1355
|
-
with temporary_file(
|
1356
|
-
content=textwrap.dedent(
|
1357
|
-
"""
|
1358
|
-
from locust import User, events, task, constant, LoadTestShape
|
1359
|
-
@events.test_stop.add_listener
|
1360
|
-
def on_test_stop(environment, **kwargs) -> None:
|
1361
|
-
print("Test Stopped")
|
1362
|
-
|
1363
|
-
class LoadTestShape(LoadTestShape):
|
1364
|
-
def tick(self):
|
1365
|
-
run_time = self.get_run_time()
|
1366
|
-
if run_time < 2:
|
1367
|
-
return (10, 1)
|
1368
|
-
|
1369
|
-
return None
|
1370
|
-
|
1371
|
-
class TestUser(User):
|
1372
|
-
wait_time = constant(3)
|
1373
|
-
@task
|
1374
|
-
def my_task(self):
|
1375
|
-
print("running my_task()")
|
1376
|
-
"""
|
1377
|
-
)
|
1378
|
-
) as mocked:
|
1379
|
-
proc = subprocess.Popen(
|
1380
|
-
[
|
1381
|
-
"locust",
|
1382
|
-
"-f",
|
1383
|
-
mocked,
|
1384
|
-
"--headless",
|
1385
|
-
],
|
1386
|
-
stdout=PIPE,
|
1387
|
-
stderr=PIPE,
|
1388
|
-
text=True,
|
1389
|
-
)
|
1390
|
-
gevent.sleep(1.9)
|
1391
|
-
proc.send_signal(signal.SIGINT)
|
1392
|
-
stdout, stderr = proc.communicate()
|
1393
|
-
print(stderr, stdout)
|
1394
|
-
self.assertIn("Shape test starting", stderr)
|
1395
|
-
self.assertIn("Exiting due to CTRL+C interruption", stderr)
|
1396
|
-
self.assertIn("Test Stopped", stdout)
|
1397
|
-
# ensure stats printer printed at least one report before shutting down and that there was a final report printed as well
|
1398
|
-
self.assertRegex(stderr, r".*Aggregated[\S\s]*Shutting down[\S\s]*Aggregated.*")
|
1399
|
-
|
1400
|
-
|
1401
|
-
class DistributedIntegrationTests(ProcessIntegrationTest):
|
1402
|
-
failed_port_check = False
|
1403
|
-
|
1404
|
-
def setUp(self):
|
1405
|
-
if self.failed_port_check:
|
1406
|
-
# fail immediately
|
1407
|
-
raise Exception("Port 5557 was (still) busy when starting a new test case")
|
1408
|
-
for _ in range(5):
|
1409
|
-
if not is_port_in_use(5557):
|
1410
|
-
break
|
1411
|
-
else:
|
1412
|
-
gevent.sleep(1)
|
1413
|
-
else:
|
1414
|
-
self.failed_port_check = True
|
1415
|
-
raise Exception("Port 5557 was (still) busy when starting a new test case")
|
1416
|
-
super().setUp()
|
1417
|
-
|
1418
|
-
def test_expect_workers(self):
|
1419
|
-
with mock_locustfile() as mocked:
|
1420
|
-
proc = subprocess.Popen(
|
1421
|
-
[
|
1422
|
-
"locust",
|
1423
|
-
"-f",
|
1424
|
-
mocked.file_path,
|
1425
|
-
"--headless",
|
1426
|
-
"--master",
|
1427
|
-
"--expect-workers",
|
1428
|
-
"2",
|
1429
|
-
"--expect-workers-max-wait",
|
1430
|
-
"1",
|
1431
|
-
],
|
1432
|
-
stdout=PIPE,
|
1433
|
-
stderr=PIPE,
|
1434
|
-
text=True,
|
1435
|
-
)
|
1436
|
-
_, stderr = proc.communicate()
|
1437
|
-
self.assertIn("Waiting for workers to be ready, 0 of 2 connected", stderr)
|
1438
|
-
self.assertIn("Gave up waiting for workers to connect", stderr)
|
1439
|
-
self.assertNotIn("Traceback", stderr)
|
1440
|
-
self.assertEqual(1, proc.returncode)
|
1441
|
-
|
1442
|
-
def test_distributed_events(self):
|
1443
|
-
content = (
|
1444
|
-
MOCK_LOCUSTFILE_CONTENT
|
1445
|
-
+ """
|
1446
|
-
from locust import events
|
1447
|
-
from locust.runners import MasterRunner
|
1448
|
-
@events.test_start.add_listener
|
1449
|
-
def on_test_start(environment, **kwargs):
|
1450
|
-
if isinstance(environment.runner, MasterRunner):
|
1451
|
-
print("test_start on master")
|
1452
|
-
else:
|
1453
|
-
print("test_start on worker")
|
1454
|
-
|
1455
|
-
@events.test_stop.add_listener
|
1456
|
-
def on_test_stop(environment, **kwargs):
|
1457
|
-
if isinstance(environment.runner, MasterRunner):
|
1458
|
-
print("test_stop on master")
|
1459
|
-
else:
|
1460
|
-
print("test_stop on worker")
|
1461
|
-
"""
|
1462
|
-
)
|
1463
|
-
with mock_locustfile(content=content) as mocked:
|
1464
|
-
proc = subprocess.Popen(
|
1465
|
-
[
|
1466
|
-
"locust",
|
1467
|
-
"-f",
|
1468
|
-
mocked.file_path,
|
1469
|
-
"--headless",
|
1470
|
-
"--master",
|
1471
|
-
"--expect-workers",
|
1472
|
-
"1",
|
1473
|
-
"-t",
|
1474
|
-
"1",
|
1475
|
-
"--exit-code-on-error",
|
1476
|
-
"0",
|
1477
|
-
"-L",
|
1478
|
-
"DEBUG",
|
1479
|
-
],
|
1480
|
-
stdout=PIPE,
|
1481
|
-
stderr=PIPE,
|
1482
|
-
text=True,
|
1483
|
-
)
|
1484
|
-
proc_worker = subprocess.Popen(
|
1485
|
-
[
|
1486
|
-
"locust",
|
1487
|
-
"-f",
|
1488
|
-
mocked.file_path,
|
1489
|
-
"--worker",
|
1490
|
-
"-L",
|
1491
|
-
"DEBUG",
|
1492
|
-
],
|
1493
|
-
stdout=PIPE,
|
1494
|
-
stderr=PIPE,
|
1495
|
-
text=True,
|
1496
|
-
)
|
1497
|
-
stdout, stderr = proc.communicate()
|
1498
|
-
stdout_worker, stderr_worker = proc_worker.communicate()
|
1499
|
-
self.assertIn("test_start on master", stdout)
|
1500
|
-
self.assertIn("test_stop on master", stdout)
|
1501
|
-
self.assertIn("test_stop on worker", stdout_worker)
|
1502
|
-
self.assertIn("test_start on worker", stdout_worker)
|
1503
|
-
self.assertNotIn("Traceback", stderr)
|
1504
|
-
self.assertNotIn("Traceback", stderr_worker)
|
1505
|
-
self.assertEqual(0, proc.returncode)
|
1506
|
-
self.assertEqual(0, proc_worker.returncode)
|
1507
|
-
|
1508
|
-
def test_distributed_tags(self):
|
1509
|
-
content = """
|
1510
|
-
from locust import HttpUser, TaskSet, task, between, LoadTestShape, tag
|
1511
|
-
class SecondUser(HttpUser):
|
1512
|
-
host = "http://127.0.0.1:8089"
|
1513
|
-
wait_time = between(0, 0.1)
|
1514
|
-
@tag("tag1")
|
1515
|
-
@task
|
1516
|
-
def task1(self):
|
1517
|
-
print("task1")
|
1518
|
-
|
1519
|
-
@tag("tag2")
|
1520
|
-
@task
|
1521
|
-
def task2(self):
|
1522
|
-
print("task2")
|
1523
|
-
"""
|
1524
|
-
with mock_locustfile(content=content) as mocked:
|
1525
|
-
proc = subprocess.Popen(
|
1526
|
-
[
|
1527
|
-
"locust",
|
1528
|
-
"-f",
|
1529
|
-
mocked.file_path,
|
1530
|
-
"--headless",
|
1531
|
-
"--master",
|
1532
|
-
"--expect-workers",
|
1533
|
-
"1",
|
1534
|
-
"-t",
|
1535
|
-
"1",
|
1536
|
-
"-u",
|
1537
|
-
"2",
|
1538
|
-
"--exit-code-on-error",
|
1539
|
-
"0",
|
1540
|
-
"-L",
|
1541
|
-
"DEBUG",
|
1542
|
-
"--tags",
|
1543
|
-
"tag1",
|
1544
|
-
],
|
1545
|
-
stdout=PIPE,
|
1546
|
-
stderr=PIPE,
|
1547
|
-
text=True,
|
1548
|
-
)
|
1549
|
-
proc_worker = subprocess.Popen(
|
1550
|
-
[
|
1551
|
-
"locust",
|
1552
|
-
"-f",
|
1553
|
-
mocked.file_path,
|
1554
|
-
"--worker",
|
1555
|
-
"-L",
|
1556
|
-
"DEBUG",
|
1557
|
-
],
|
1558
|
-
stdout=PIPE,
|
1559
|
-
stderr=PIPE,
|
1560
|
-
text=True,
|
1561
|
-
)
|
1562
|
-
stdout, stderr = proc.communicate()
|
1563
|
-
stdout_worker, stderr_worker = proc_worker.communicate()
|
1564
|
-
self.assertNotIn("ERROR", stderr_worker)
|
1565
|
-
self.assertIn("task1", stdout_worker)
|
1566
|
-
self.assertNotIn("task2", stdout_worker)
|
1567
|
-
self.assertNotIn("Traceback", stderr)
|
1568
|
-
self.assertNotIn("Traceback", stderr_worker)
|
1569
|
-
self.assertEqual(0, proc.returncode)
|
1570
|
-
self.assertEqual(0, proc_worker.returncode)
|
1571
|
-
|
1572
|
-
def test_distributed(self):
|
1573
|
-
LOCUSTFILE_CONTENT = textwrap.dedent(
|
1574
|
-
"""
|
1575
|
-
from locust import User, task, constant
|
1576
|
-
|
1577
|
-
class User1(User):
|
1578
|
-
wait_time = constant(1)
|
1579
|
-
|
1580
|
-
@task
|
1581
|
-
def t(self):
|
1582
|
-
pass
|
1583
|
-
"""
|
1584
|
-
)
|
1585
|
-
with mock_locustfile(content=LOCUSTFILE_CONTENT) as mocked:
|
1586
|
-
proc = subprocess.Popen(
|
1587
|
-
[
|
1588
|
-
"locust",
|
1589
|
-
"-f",
|
1590
|
-
mocked.file_path,
|
1591
|
-
"--headless",
|
1592
|
-
"--master",
|
1593
|
-
"--expect-workers",
|
1594
|
-
"1",
|
1595
|
-
"-u",
|
1596
|
-
"3",
|
1597
|
-
"-t",
|
1598
|
-
"5s",
|
1599
|
-
],
|
1600
|
-
stderr=STDOUT,
|
1601
|
-
stdout=PIPE,
|
1602
|
-
text=True,
|
1603
|
-
)
|
1604
|
-
proc_worker = subprocess.Popen(
|
1605
|
-
[
|
1606
|
-
"locust",
|
1607
|
-
"-f",
|
1608
|
-
mocked.file_path,
|
1609
|
-
"--worker",
|
1610
|
-
],
|
1611
|
-
stderr=STDOUT,
|
1612
|
-
stdout=PIPE,
|
1613
|
-
text=True,
|
1614
|
-
)
|
1615
|
-
stdout = proc.communicate()[0]
|
1616
|
-
proc_worker.communicate()
|
1617
|
-
|
1618
|
-
self.assertIn('All users spawned: {"User1": 3} (3 total users)', stdout)
|
1619
|
-
self.assertIn("Shutting down (exit code 0)", stdout)
|
1620
|
-
|
1621
|
-
self.assertEqual(0, proc.returncode)
|
1622
|
-
self.assertEqual(0, proc_worker.returncode)
|
1623
|
-
|
1624
|
-
def test_distributed_report_timeout_expired(self):
|
1625
|
-
LOCUSTFILE_CONTENT = textwrap.dedent(
|
1626
|
-
"""
|
1627
|
-
from locust import User, task, constant
|
1628
|
-
|
1629
|
-
class User1(User):
|
1630
|
-
wait_time = constant(1)
|
1631
|
-
|
1632
|
-
@task
|
1633
|
-
def t(self):
|
1634
|
-
pass
|
1635
|
-
"""
|
1636
|
-
)
|
1637
|
-
with (
|
1638
|
-
mock_locustfile(content=LOCUSTFILE_CONTENT) as mocked,
|
1639
|
-
patch_env("LOCUST_WAIT_FOR_WORKERS_REPORT_AFTER_RAMP_UP", "0.01") as _,
|
1640
|
-
):
|
1641
|
-
proc = subprocess.Popen(
|
1642
|
-
[
|
1643
|
-
"locust",
|
1644
|
-
"-f",
|
1645
|
-
mocked.file_path,
|
1646
|
-
"--headless",
|
1647
|
-
"--master",
|
1648
|
-
"--expect-workers",
|
1649
|
-
"1",
|
1650
|
-
"-u",
|
1651
|
-
"3",
|
1652
|
-
"-t",
|
1653
|
-
"5s",
|
1654
|
-
],
|
1655
|
-
stderr=STDOUT,
|
1656
|
-
stdout=PIPE,
|
1657
|
-
text=True,
|
1658
|
-
)
|
1659
|
-
proc_worker = subprocess.Popen(
|
1660
|
-
[
|
1661
|
-
"locust",
|
1662
|
-
"-f",
|
1663
|
-
mocked.file_path,
|
1664
|
-
"--worker",
|
1665
|
-
],
|
1666
|
-
stderr=STDOUT,
|
1667
|
-
stdout=PIPE,
|
1668
|
-
text=True,
|
1669
|
-
)
|
1670
|
-
stdout = proc.communicate()[0]
|
1671
|
-
proc_worker.communicate()
|
1672
|
-
|
1673
|
-
self.assertIn(
|
1674
|
-
'Spawning is complete and report waittime is expired, but not all reports received from workers: {"User1": 2} (2 total users)',
|
1675
|
-
stdout,
|
1676
|
-
)
|
1677
|
-
self.assertIn("Shutting down (exit code 0)", stdout)
|
1678
|
-
|
1679
|
-
self.assertEqual(0, proc.returncode)
|
1680
|
-
self.assertEqual(0, proc_worker.returncode)
|
1681
|
-
|
1682
|
-
def test_locustfile_distribution(self):
|
1683
|
-
LOCUSTFILE_CONTENT = textwrap.dedent(
|
1684
|
-
"""
|
1685
|
-
from locust import User, task, constant
|
1686
|
-
|
1687
|
-
class User1(User):
|
1688
|
-
wait_time = constant(1)
|
1689
|
-
|
1690
|
-
@task
|
1691
|
-
def t(self):
|
1692
|
-
pass
|
1693
|
-
"""
|
1694
|
-
)
|
1695
|
-
with mock_locustfile(content=LOCUSTFILE_CONTENT) as mocked:
|
1696
|
-
proc = subprocess.Popen(
|
1697
|
-
[
|
1698
|
-
"locust",
|
1699
|
-
"-f",
|
1700
|
-
mocked.file_path,
|
1701
|
-
"--headless",
|
1702
|
-
"--master",
|
1703
|
-
"--expect-workers",
|
1704
|
-
"2",
|
1705
|
-
"-t",
|
1706
|
-
"1s",
|
1707
|
-
],
|
1708
|
-
stderr=STDOUT,
|
1709
|
-
stdout=PIPE,
|
1710
|
-
text=True,
|
1711
|
-
)
|
1712
|
-
proc_worker = subprocess.Popen(
|
1713
|
-
[
|
1714
|
-
"locust",
|
1715
|
-
"-f",
|
1716
|
-
"-",
|
1717
|
-
"--worker",
|
1718
|
-
],
|
1719
|
-
stderr=STDOUT,
|
1720
|
-
stdout=PIPE,
|
1721
|
-
text=True,
|
1722
|
-
)
|
1723
|
-
gevent.sleep(2)
|
1724
|
-
# modify the locustfile to trigger warning about file change when the second worker connects
|
1725
|
-
with open(mocked.file_path, "w") as locustfile:
|
1726
|
-
locustfile.write(LOCUSTFILE_CONTENT)
|
1727
|
-
locustfile.write("\n# New comment\n")
|
1728
|
-
gevent.sleep(2)
|
1729
|
-
proc_worker2 = subprocess.Popen(
|
1730
|
-
[
|
1731
|
-
"locust",
|
1732
|
-
"-f",
|
1733
|
-
"-",
|
1734
|
-
"--worker",
|
1735
|
-
],
|
1736
|
-
stderr=STDOUT,
|
1737
|
-
stdout=PIPE,
|
1738
|
-
text=True,
|
1739
|
-
)
|
1740
|
-
stdout = proc.communicate()[0]
|
1741
|
-
stdout_worker = proc_worker.communicate()[0]
|
1742
|
-
stdout_worker2 = proc_worker2.communicate()[0]
|
1743
|
-
|
1744
|
-
self.assertIn('All users spawned: {"User1": 1} (1 total users)', stdout)
|
1745
|
-
self.assertIn("Locustfile contents changed on disk after first worker requested locustfile", stdout)
|
1746
|
-
self.assertIn("Shutting down (exit code 0)", stdout)
|
1747
|
-
self.assertNotIn("Traceback", stdout)
|
1748
|
-
self.assertNotIn("Traceback", stdout_worker)
|
1749
|
-
self.assertNotIn("Traceback", stdout_worker2)
|
1750
|
-
|
1751
|
-
self.assertEqual(0, proc.returncode)
|
1752
|
-
self.assertEqual(0, proc_worker.returncode)
|
1753
|
-
|
1754
|
-
def test_locustfile_distribution_with_workers_started_first(self):
|
1755
|
-
LOCUSTFILE_CONTENT = textwrap.dedent(
|
1756
|
-
"""
|
1757
|
-
from locust import User, task, constant
|
1758
|
-
|
1759
|
-
class User1(User):
|
1760
|
-
wait_time = constant(1)
|
1761
|
-
|
1762
|
-
@task
|
1763
|
-
def t(self):
|
1764
|
-
print("hello")
|
1765
|
-
"""
|
1766
|
-
)
|
1767
|
-
with mock_locustfile(content=LOCUSTFILE_CONTENT) as mocked:
|
1768
|
-
proc_worker = subprocess.Popen(
|
1769
|
-
[
|
1770
|
-
"locust",
|
1771
|
-
"-f",
|
1772
|
-
"-",
|
1773
|
-
"--worker",
|
1774
|
-
],
|
1775
|
-
stderr=STDOUT,
|
1776
|
-
stdout=PIPE,
|
1777
|
-
text=True,
|
1778
|
-
)
|
1779
|
-
gevent.sleep(2)
|
1780
|
-
proc = subprocess.Popen(
|
1781
|
-
[
|
1782
|
-
"locust",
|
1783
|
-
"-f",
|
1784
|
-
mocked.file_path,
|
1785
|
-
"--headless",
|
1786
|
-
"--master",
|
1787
|
-
"--expect-workers",
|
1788
|
-
"1",
|
1789
|
-
"-t",
|
1790
|
-
"1",
|
1791
|
-
],
|
1792
|
-
stderr=STDOUT,
|
1793
|
-
stdout=PIPE,
|
1794
|
-
text=True,
|
1795
|
-
)
|
1796
|
-
|
1797
|
-
stdout = proc.communicate()[0]
|
1798
|
-
worker_stdout = proc_worker.communicate()[0]
|
1799
|
-
|
1800
|
-
self.assertIn('All users spawned: {"User1": ', stdout)
|
1801
|
-
self.assertIn("Shutting down (exit code 0)", stdout)
|
1802
|
-
|
1803
|
-
self.assertEqual(0, proc.returncode)
|
1804
|
-
self.assertEqual(0, proc_worker.returncode)
|
1805
|
-
self.assertIn("hello", worker_stdout)
|
1806
|
-
|
1807
|
-
def test_distributed_with_locustfile_distribution_not_plain_filename(self):
|
1808
|
-
LOCUSTFILE_CONTENT = textwrap.dedent(
|
1809
|
-
"""
|
1810
|
-
from locust import User, task, constant
|
1811
|
-
|
1812
|
-
class User1(User):
|
1813
|
-
wait_time = constant(1)
|
1814
|
-
|
1815
|
-
@task
|
1816
|
-
def t(self):
|
1817
|
-
pass
|
1818
|
-
"""
|
1819
|
-
)
|
1820
|
-
with mock_locustfile(content=LOCUSTFILE_CONTENT) as mocked:
|
1821
|
-
with mock_locustfile() as mocked2:
|
1822
|
-
proc = subprocess.Popen(
|
1823
|
-
[
|
1824
|
-
"locust",
|
1825
|
-
"-f",
|
1826
|
-
f"{mocked.file_path}, {mocked2.file_path}",
|
1827
|
-
"--headless",
|
1828
|
-
"--master",
|
1829
|
-
"-L",
|
1830
|
-
"debug",
|
1831
|
-
],
|
1832
|
-
stderr=STDOUT,
|
1833
|
-
stdout=PIPE,
|
1834
|
-
text=True,
|
1835
|
-
)
|
1836
|
-
proc_worker = subprocess.Popen(
|
1837
|
-
[
|
1838
|
-
"locust",
|
1839
|
-
"-f",
|
1840
|
-
"-",
|
1841
|
-
"--worker",
|
1842
|
-
],
|
1843
|
-
stderr=STDOUT,
|
1844
|
-
stdout=PIPE,
|
1845
|
-
text=True,
|
1846
|
-
)
|
1847
|
-
|
1848
|
-
try:
|
1849
|
-
stdout = proc_worker.communicate(timeout=5)[0]
|
1850
|
-
self.assertIn(
|
1851
|
-
"Got error from master: locustfile must be a full path to a single locustfile for file distribution to work",
|
1852
|
-
stdout,
|
1853
|
-
)
|
1854
|
-
proc.kill()
|
1855
|
-
master_stdout = proc.communicate()[0]
|
1856
|
-
self.assertIn(
|
1857
|
-
"--locustfile must be a full path to a single locustfile for file distribution", master_stdout
|
1858
|
-
)
|
1859
|
-
except Exception:
|
1860
|
-
proc.kill()
|
1861
|
-
proc_worker.kill()
|
1862
|
-
stdout, worker_stderr = proc_worker.communicate()
|
1863
|
-
assert False, f"worker never finished: {stdout}"
|
1864
|
-
|
1865
|
-
def test_json_schema(self):
|
1866
|
-
LOCUSTFILE_CONTENT = textwrap.dedent(
|
1867
|
-
"""
|
1868
|
-
from locust import HttpUser, task, constant
|
1869
|
-
|
1870
|
-
class QuickstartUser(HttpUser):
|
1871
|
-
wait_time = constant(1)
|
1872
|
-
|
1873
|
-
@task
|
1874
|
-
def hello_world(self):
|
1875
|
-
self.client.get("/")
|
1876
|
-
|
1877
|
-
"""
|
1878
|
-
)
|
1879
|
-
with mock_locustfile(content=LOCUSTFILE_CONTENT) as mocked:
|
1880
|
-
proc = subprocess.Popen(
|
1881
|
-
[
|
1882
|
-
"locust",
|
1883
|
-
"-f",
|
1884
|
-
mocked.file_path,
|
1885
|
-
"--host",
|
1886
|
-
"http://google.com",
|
1887
|
-
"--headless",
|
1888
|
-
"-u",
|
1889
|
-
"1",
|
1890
|
-
"-t",
|
1891
|
-
"2s",
|
1892
|
-
"--json",
|
1893
|
-
],
|
1894
|
-
stderr=DEVNULL,
|
1895
|
-
stdout=PIPE,
|
1896
|
-
text=True,
|
1897
|
-
)
|
1898
|
-
stdout, stderr = proc.communicate()
|
1899
|
-
|
1900
|
-
try:
|
1901
|
-
data = json.loads(stdout)
|
1902
|
-
except json.JSONDecodeError:
|
1903
|
-
self.fail(f"Trying to parse {stdout} as json failed")
|
1904
|
-
|
1905
|
-
self.assertEqual(0, proc.returncode)
|
1906
|
-
|
1907
|
-
result = data[0]
|
1908
|
-
self.assertEqual(float, type(result["last_request_timestamp"]))
|
1909
|
-
self.assertEqual(float, type(result["start_time"]))
|
1910
|
-
self.assertEqual(int, type(result["num_requests"]))
|
1911
|
-
self.assertEqual(int, type(result["num_none_requests"]))
|
1912
|
-
self.assertEqual(float, type(result["total_response_time"]))
|
1913
|
-
self.assertEqual(float, type(result["max_response_time"]))
|
1914
|
-
self.assertEqual(float, type(result["min_response_time"]))
|
1915
|
-
self.assertEqual(int, type(result["total_content_length"]))
|
1916
|
-
self.assertEqual(dict, type(result["response_times"]))
|
1917
|
-
self.assertEqual(dict, type(result["num_reqs_per_sec"]))
|
1918
|
-
self.assertEqual(dict, type(result["num_fail_per_sec"]))
|
1919
|
-
|
1920
|
-
def test_worker_indexes(self):
|
1921
|
-
content = """
|
1922
|
-
from locust import HttpUser, task, between
|
1923
|
-
|
1924
|
-
class AnyUser(HttpUser):
|
1925
|
-
host = "http://127.0.0.1:8089"
|
1926
|
-
wait_time = between(0, 0.1)
|
1927
|
-
@task
|
1928
|
-
def my_task(self):
|
1929
|
-
print("worker index:", self.environment.runner.worker_index)
|
1930
|
-
"""
|
1931
|
-
with mock_locustfile(content=content) as mocked:
|
1932
|
-
master = subprocess.Popen(
|
1933
|
-
[
|
1934
|
-
"locust",
|
1935
|
-
"-f",
|
1936
|
-
mocked.file_path,
|
1937
|
-
"--headless",
|
1938
|
-
"--master",
|
1939
|
-
"--expect-workers",
|
1940
|
-
"2",
|
1941
|
-
"-t",
|
1942
|
-
"5",
|
1943
|
-
"-u",
|
1944
|
-
"2",
|
1945
|
-
"-L",
|
1946
|
-
"DEBUG",
|
1947
|
-
],
|
1948
|
-
stdout=PIPE,
|
1949
|
-
stderr=PIPE,
|
1950
|
-
text=True,
|
1951
|
-
)
|
1952
|
-
proc_worker_1 = subprocess.Popen(
|
1953
|
-
[
|
1954
|
-
"locust",
|
1955
|
-
"-f",
|
1956
|
-
mocked.file_path,
|
1957
|
-
"--worker",
|
1958
|
-
"-L",
|
1959
|
-
"DEBUG",
|
1960
|
-
],
|
1961
|
-
stdout=PIPE,
|
1962
|
-
stderr=PIPE,
|
1963
|
-
text=True,
|
1964
|
-
)
|
1965
|
-
proc_worker_2 = subprocess.Popen(
|
1966
|
-
[
|
1967
|
-
"locust",
|
1968
|
-
"-f",
|
1969
|
-
mocked.file_path,
|
1970
|
-
"--worker",
|
1971
|
-
"-L",
|
1972
|
-
"DEBUG",
|
1973
|
-
],
|
1974
|
-
stdout=PIPE,
|
1975
|
-
stderr=PIPE,
|
1976
|
-
text=True,
|
1977
|
-
)
|
1978
|
-
stdout, stderr = master.communicate()
|
1979
|
-
self.assertNotIn("Traceback", stderr)
|
1980
|
-
self.assertIn("Shutting down (exit code 0)", stderr)
|
1981
|
-
self.assertEqual(0, master.returncode)
|
1982
|
-
|
1983
|
-
stdout_worker_1, stderr_worker_1 = proc_worker_1.communicate()
|
1984
|
-
stdout_worker_2, stderr_worker_2 = proc_worker_2.communicate()
|
1985
|
-
self.assertEqual(0, proc_worker_1.returncode)
|
1986
|
-
self.assertEqual(0, proc_worker_2.returncode)
|
1987
|
-
self.assertNotIn("Traceback", stderr_worker_1)
|
1988
|
-
self.assertNotIn("Traceback", stderr_worker_2)
|
1989
|
-
|
1990
|
-
PREFIX = "worker index: "
|
1991
|
-
p1 = stdout_worker_1.find(PREFIX)
|
1992
|
-
if p1 == -1:
|
1993
|
-
raise Exception(stdout_worker_1 + stderr_worker_1)
|
1994
|
-
self.assertNotEqual(-1, p1)
|
1995
|
-
p2 = stdout_worker_2.find(PREFIX)
|
1996
|
-
if p2 == -1:
|
1997
|
-
raise Exception(stdout_worker_2 + stderr_worker_2)
|
1998
|
-
self.assertNotEqual(-1, p2)
|
1999
|
-
found = [
|
2000
|
-
int(stdout_worker_1[p1 + len(PREFIX) :].split("\n")[0]),
|
2001
|
-
int(stdout_worker_2[p1 + len(PREFIX) :].split("\n")[0]),
|
2002
|
-
]
|
2003
|
-
found.sort()
|
2004
|
-
for i in range(2):
|
2005
|
-
if found[i] != i:
|
2006
|
-
raise Exception(f"expected index {i} but got", found[i])
|
2007
|
-
|
2008
|
-
@unittest.skipIf(os.name == "nt", reason="--processes doesnt work on windows")
|
2009
|
-
def test_processes(self):
|
2010
|
-
with mock_locustfile() as mocked:
|
2011
|
-
command = f"locust -f {mocked.file_path} --processes 4 --headless --run-time 1 --exit-code-on-error 0"
|
2012
|
-
proc = subprocess.Popen(
|
2013
|
-
command,
|
2014
|
-
shell=True,
|
2015
|
-
stdout=PIPE,
|
2016
|
-
stderr=PIPE,
|
2017
|
-
text=True,
|
2018
|
-
)
|
2019
|
-
try:
|
2020
|
-
_, stderr = proc.communicate(timeout=9)
|
2021
|
-
except Exception:
|
2022
|
-
proc.kill()
|
2023
|
-
assert False, f"locust process never finished: {command}"
|
2024
|
-
self.assertNotIn("Traceback", stderr)
|
2025
|
-
self.assertIn("(index 3) reported as ready", stderr)
|
2026
|
-
self.assertIn("Shutting down (exit code 0)", stderr)
|
2027
|
-
|
2028
|
-
@unittest.skipIf(os.name == "nt", reason="--processes doesnt work on windows")
|
2029
|
-
def test_processes_autodetect(self):
|
2030
|
-
with mock_locustfile() as mocked:
|
2031
|
-
command = f"locust -f {mocked.file_path} --processes -1 --headless --run-time 1 --exit-code-on-error 0"
|
2032
|
-
proc = subprocess.Popen(
|
2033
|
-
command,
|
2034
|
-
shell=True,
|
2035
|
-
stdout=PIPE,
|
2036
|
-
stderr=PIPE,
|
2037
|
-
text=True,
|
2038
|
-
)
|
2039
|
-
try:
|
2040
|
-
_, stderr = proc.communicate(timeout=9)
|
2041
|
-
except Exception:
|
2042
|
-
proc.kill()
|
2043
|
-
assert False, f"locust process never finished: {command}"
|
2044
|
-
self.assertNotIn("Traceback", stderr)
|
2045
|
-
self.assertIn("(index 0) reported as ready", stderr)
|
2046
|
-
self.assertIn("Shutting down (exit code 0)", stderr)
|
2047
|
-
|
2048
|
-
@unittest.skipIf(os.name == "nt", reason="--processes doesnt work on windows")
|
2049
|
-
def test_processes_separate_worker(self):
|
2050
|
-
with mock_locustfile() as mocked:
|
2051
|
-
master_proc = subprocess.Popen(
|
2052
|
-
f"locust -f {mocked.file_path} --master --headless --run-time 1 --exit-code-on-error 0 --expect-workers-max-wait 2",
|
2053
|
-
shell=True,
|
2054
|
-
stdout=PIPE,
|
2055
|
-
stderr=PIPE,
|
2056
|
-
text=True,
|
2057
|
-
)
|
2058
|
-
|
2059
|
-
worker_parent_proc = subprocess.Popen(
|
2060
|
-
f"locust -f {mocked.file_path} --processes 4 --worker",
|
2061
|
-
shell=True,
|
2062
|
-
stdout=PIPE,
|
2063
|
-
stderr=PIPE,
|
2064
|
-
text=True,
|
2065
|
-
)
|
2066
|
-
|
2067
|
-
try:
|
2068
|
-
_, worker_stderr = worker_parent_proc.communicate(timeout=9)
|
2069
|
-
except Exception:
|
2070
|
-
master_proc.kill()
|
2071
|
-
worker_parent_proc.kill()
|
2072
|
-
_, worker_stderr = worker_parent_proc.communicate()
|
2073
|
-
_, master_stderr = master_proc.communicate()
|
2074
|
-
assert False, f"worker never finished: {worker_stderr}"
|
2075
|
-
|
2076
|
-
try:
|
2077
|
-
_, master_stderr = master_proc.communicate(timeout=9)
|
2078
|
-
except Exception:
|
2079
|
-
master_proc.kill()
|
2080
|
-
worker_parent_proc.kill()
|
2081
|
-
_, worker_stderr = worker_parent_proc.communicate()
|
2082
|
-
_, master_stderr = master_proc.communicate()
|
2083
|
-
assert False, f"master never finished: {master_stderr}"
|
2084
|
-
|
2085
|
-
_, worker_stderr = worker_parent_proc.communicate()
|
2086
|
-
_, master_stderr = master_proc.communicate()
|
2087
|
-
self.assertNotIn("Traceback", worker_stderr)
|
2088
|
-
self.assertNotIn("Traceback", master_stderr)
|
2089
|
-
self.assertNotIn("Gave up waiting for workers to connect", master_stderr)
|
2090
|
-
self.assertIn("(index 3) reported as ready", master_stderr)
|
2091
|
-
self.assertIn("Shutting down (exit code 0)", master_stderr)
|
2092
|
-
|
2093
|
-
@unittest.skipIf(os.name == "nt", reason="--processes doesnt work on windows")
|
2094
|
-
def test_processes_ctrl_c(self):
|
2095
|
-
with mock_locustfile() as mocked:
|
2096
|
-
proc = psutil.Popen( # use psutil.Popen instead of subprocess.Popen to use extra features
|
2097
|
-
[
|
2098
|
-
"locust",
|
2099
|
-
"-f",
|
2100
|
-
mocked.file_path,
|
2101
|
-
"--processes",
|
2102
|
-
"4",
|
2103
|
-
"--headless",
|
2104
|
-
"-L",
|
2105
|
-
"DEBUG",
|
2106
|
-
],
|
2107
|
-
stdout=PIPE,
|
2108
|
-
stderr=PIPE,
|
2109
|
-
text=True,
|
2110
|
-
)
|
2111
|
-
gevent.sleep(3)
|
2112
|
-
children = proc.children(recursive=True)
|
2113
|
-
self.assertEqual(len(children), 4, "unexpected number of child worker processes")
|
2114
|
-
|
2115
|
-
proc.send_signal(signal.SIGINT)
|
2116
|
-
gevent.sleep(2)
|
2117
|
-
|
2118
|
-
for child in children:
|
2119
|
-
self.assertFalse(child.is_running(), "child processes failed to terminate")
|
2120
|
-
|
2121
|
-
try:
|
2122
|
-
_, stderr = proc.communicate(timeout=1)
|
2123
|
-
except Exception:
|
2124
|
-
proc.kill()
|
2125
|
-
_, stderr = proc.communicate()
|
2126
|
-
assert False, f"locust process never finished: {stderr}"
|
2127
|
-
|
2128
|
-
self.assertNotIn("Traceback", stderr)
|
2129
|
-
self.assertIn("(index 3) reported as ready", stderr)
|
2130
|
-
self.assertIn("The last worker quit, stopping test", stderr)
|
2131
|
-
self.assertIn("Shutting down (exit code 0)", stderr)
|
2132
|
-
# ensure no weird escaping in error report. Not really related to ctrl-c...
|
2133
|
-
self.assertIn(", 'Connection refused') ", stderr)
|
2134
|
-
|
2135
|
-
@unittest.skipIf(os.name == "nt", reason="--processes doesnt work on windows")
|
2136
|
-
def test_workers_shut_down_if_master_is_gone(self):
|
2137
|
-
content = """
|
2138
|
-
from locust import HttpUser, task, constant, runners
|
2139
|
-
runners.MASTER_HEARTBEAT_TIMEOUT = 2
|
2140
|
-
|
2141
|
-
class AnyUser(HttpUser):
|
2142
|
-
host = "http://127.0.0.1:8089"
|
2143
|
-
wait_time = constant(1)
|
2144
|
-
@task
|
2145
|
-
def my_task(self):
|
2146
|
-
print("worker index:", self.environment.runner.worker_index)
|
2147
|
-
"""
|
2148
|
-
with mock_locustfile(content=content) as mocked:
|
2149
|
-
master_proc = subprocess.Popen(
|
2150
|
-
[
|
2151
|
-
"locust",
|
2152
|
-
"-f",
|
2153
|
-
mocked.file_path,
|
2154
|
-
"--master",
|
2155
|
-
"--headless",
|
2156
|
-
"--expect-workers",
|
2157
|
-
"2",
|
2158
|
-
],
|
2159
|
-
stdout=PIPE,
|
2160
|
-
stderr=PIPE,
|
2161
|
-
text=True,
|
2162
|
-
)
|
2163
|
-
|
2164
|
-
worker_parent_proc = subprocess.Popen(
|
2165
|
-
[
|
2166
|
-
"locust",
|
2167
|
-
"-f",
|
2168
|
-
mocked.file_path,
|
2169
|
-
"--worker",
|
2170
|
-
"--processes",
|
2171
|
-
"2",
|
2172
|
-
"--headless",
|
2173
|
-
],
|
2174
|
-
stdout=PIPE,
|
2175
|
-
stderr=PIPE,
|
2176
|
-
text=True,
|
2177
|
-
start_new_session=True,
|
2178
|
-
)
|
2179
|
-
gevent.sleep(2)
|
2180
|
-
master_proc.kill()
|
2181
|
-
master_proc.wait()
|
2182
|
-
try:
|
2183
|
-
worker_stdout, worker_stderr = worker_parent_proc.communicate(timeout=7)
|
2184
|
-
except Exception:
|
2185
|
-
os.killpg(worker_parent_proc.pid, signal.SIGTERM)
|
2186
|
-
worker_stdout, worker_stderr = worker_parent_proc.communicate()
|
2187
|
-
assert False, f"worker never finished: {worker_stdout} / {worker_stderr}"
|
2188
|
-
|
2189
|
-
self.assertNotIn("Traceback", worker_stderr)
|
2190
|
-
self.assertIn("Didn't get heartbeat from master in over ", worker_stderr)
|
2191
|
-
self.assertIn("worker index:", worker_stdout)
|
2192
|
-
|
2193
|
-
@unittest.skipIf(os.name == "nt", reason="--processes doesnt work on windows")
|
2194
|
-
def test_processes_error_doesnt_blow_up_completely(self):
|
2195
|
-
with mock_locustfile() as mocked:
|
2196
|
-
proc = subprocess.Popen(
|
2197
|
-
[
|
2198
|
-
"locust",
|
2199
|
-
"-f",
|
2200
|
-
mocked.file_path,
|
2201
|
-
"--processes",
|
2202
|
-
"4",
|
2203
|
-
"-L",
|
2204
|
-
"DEBUG",
|
2205
|
-
"UserThatDoesntExist",
|
2206
|
-
],
|
2207
|
-
stdout=PIPE,
|
2208
|
-
stderr=PIPE,
|
2209
|
-
text=True,
|
2210
|
-
)
|
2211
|
-
_, stderr = proc.communicate()
|
2212
|
-
self.assertIn("Unknown User(s): UserThatDoesntExist", stderr)
|
2213
|
-
# the error message should repeat 4 times for the workers and once for the master
|
2214
|
-
self.assertEqual(stderr.count("Unknown User(s): UserThatDoesntExist"), 5)
|
2215
|
-
self.assertNotIn("Traceback", stderr)
|
2216
|
-
|
2217
|
-
@unittest.skipIf(os.name == "nt", reason="--processes doesnt work on windows")
|
2218
|
-
@unittest.skipIf(sys.platform == "darwin", reason="Flaky on macOS :-/")
|
2219
|
-
def test_processes_workers_quit_unexpected(self):
|
2220
|
-
content = """
|
2221
|
-
from locust import runners, events, User, task
|
2222
|
-
import sys
|
2223
|
-
runners.HEARTBEAT_INTERVAL = 0.1
|
2224
|
-
|
2225
|
-
@events.test_start.add_listener
|
2226
|
-
def on_test_start(environment, **_kwargs):
|
2227
|
-
if isinstance(environment.runner, runners.WorkerRunner):
|
2228
|
-
sys.exit(42)
|
2229
|
-
|
2230
|
-
class AnyUser(User):
|
2231
|
-
@task
|
2232
|
-
def mytask(self):
|
2233
|
-
pass
|
2234
|
-
"""
|
2235
|
-
with mock_locustfile(content=content) as mocked:
|
2236
|
-
worker_proc = subprocess.Popen(
|
2237
|
-
["locust", "-f", mocked.file_path, "--processes", "2", "--worker"],
|
2238
|
-
stdout=PIPE,
|
2239
|
-
stderr=PIPE,
|
2240
|
-
text=True,
|
2241
|
-
)
|
2242
|
-
master_proc = subprocess.Popen(
|
2243
|
-
["locust", "-f", mocked.file_path, "--master", "--headless", "-t", "5"],
|
2244
|
-
stdout=PIPE,
|
2245
|
-
stderr=PIPE,
|
2246
|
-
text=True,
|
2247
|
-
)
|
2248
|
-
try:
|
2249
|
-
_, stderr = worker_proc.communicate(timeout=3)
|
2250
|
-
status_code = worker_proc.wait()
|
2251
|
-
except Exception:
|
2252
|
-
worker_proc.kill()
|
2253
|
-
_, stderr = worker_proc.communicate()
|
2254
|
-
assert False, f"worker process never finished: {stderr}"
|
2255
|
-
finally:
|
2256
|
-
gevent.sleep(4)
|
2257
|
-
master_proc.kill()
|
2258
|
-
_, master_stderr = master_proc.communicate()
|
2259
|
-
|
2260
|
-
self.assertNotIn("Traceback", stderr)
|
2261
|
-
self.assertIn("INFO/locust.runners: sys.exit(42) called", stderr)
|
2262
|
-
self.assertEqual(status_code, 42)
|
2263
|
-
self.assertNotIn("Traceback", master_stderr)
|
2264
|
-
self.assertIn("failed to send heartbeat, setting state to missing", master_stderr)
|