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.
Files changed (42) hide show
  1. locust/_version.py +6 -2
  2. locust/contrib/fasthttp.py +1 -1
  3. locust/dispatch.py +7 -6
  4. locust/main.py +0 -1
  5. {locust-2.29.2.dev34.dist-info → locust-2.29.2.dev45.dist-info}/METADATA +31 -26
  6. locust-2.29.2.dev45.dist-info/RECORD +49 -0
  7. locust-2.29.2.dev45.dist-info/WHEEL +4 -0
  8. locust-2.29.2.dev45.dist-info/entry_points.txt +3 -0
  9. locust/test/__init__.py +0 -15
  10. locust/test/fake_module1_for_env_test.py +0 -7
  11. locust/test/fake_module2_for_env_test.py +0 -7
  12. locust/test/mock_locustfile.py +0 -56
  13. locust/test/mock_logging.py +0 -28
  14. locust/test/test_debugging.py +0 -39
  15. locust/test/test_dispatch.py +0 -4170
  16. locust/test/test_env.py +0 -283
  17. locust/test/test_fasthttp.py +0 -785
  18. locust/test/test_http.py +0 -325
  19. locust/test/test_interruptable_task.py +0 -48
  20. locust/test/test_load_locustfile.py +0 -228
  21. locust/test/test_locust_class.py +0 -831
  22. locust/test/test_log.py +0 -237
  23. locust/test/test_main.py +0 -2264
  24. locust/test/test_old_wait_api.py +0 -0
  25. locust/test/test_parser.py +0 -450
  26. locust/test/test_runners.py +0 -4476
  27. locust/test/test_sequential_taskset.py +0 -157
  28. locust/test/test_stats.py +0 -866
  29. locust/test/test_tags.py +0 -440
  30. locust/test/test_taskratio.py +0 -94
  31. locust/test/test_users.py +0 -69
  32. locust/test/test_util.py +0 -33
  33. locust/test/test_wait_time.py +0 -79
  34. locust/test/test_web.py +0 -1257
  35. locust/test/test_zmqrpc.py +0 -58
  36. locust/test/testcases.py +0 -248
  37. locust/test/util.py +0 -88
  38. locust-2.29.2.dev34.dist-info/RECORD +0 -79
  39. locust-2.29.2.dev34.dist-info/WHEEL +0 -5
  40. locust-2.29.2.dev34.dist-info/entry_points.txt +0 -2
  41. locust-2.29.2.dev34.dist-info/top_level.txt +0 -1
  42. {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)