Skip to content

Commit e49b8fb

Browse files
authored
Merge pull request #3049 from ktchani/master
Enable HTML Report Filename Parsing
2 parents 97529ef + 3f16f93 commit e49b8fb

File tree

5 files changed

+122
-41
lines changed

5 files changed

+122
-41
lines changed

Diff for: locust/argument_parser.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,9 @@ def get_empty_argument_parser(add_help=True, default_config_files=DEFAULT_CONFIG
208208
209209
locust --headless -u 100 -t 20m --processes 4 MyHttpUser AnotherUser
210210
211+
locust --headless -u 100 -r 10 -t 50 --print-stats --html "test_report_{u}_{r}_{t}.html" -H https://www.example.com
212+
(The above run would generate an html file with the name "test_report_100_10_50.html")
213+
211214
See documentation for more details, including how to set options using a file or environment variables: https://docs.locust.io/en/stable/configuration.html""",
212215
)
213216
parser.add_argument(
@@ -754,7 +757,7 @@ def setup_parser_arguments(parser):
754757
"--html",
755758
metavar="<filename>",
756759
dest="html_file",
757-
help="Store HTML report to file path specified",
760+
help="Store HTML report to file path specified. Able to parse certain tags - {u}, {r}, {t} and convert them to number of users, spawn rate and run time respectively.",
758761
env_var="LOCUST_HTML",
759762
)
760763
stats_group.add_argument(

Diff for: locust/html.py

+19
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,25 @@
1515
DEFAULT_BUILD_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "webui", "dist")
1616

1717

18+
def process_html_filename(options) -> None:
19+
num_users = options.num_users
20+
spawn_rate = options.spawn_rate
21+
run_time = options.run_time
22+
23+
option_mapping = {
24+
"{u}": num_users,
25+
"{r}": spawn_rate,
26+
"{t}": run_time,
27+
}
28+
29+
html_filename = options.html_file
30+
31+
for option_term, option_value in option_mapping.items():
32+
html_filename = html_filename.replace(option_term, str(int(option_value)))
33+
34+
options.html_file = html_filename
35+
36+
1837
def render_template_from(file, build_path=DEFAULT_BUILD_PATH, **kwargs):
1938
env = JinjaEnvironment(loader=FileSystemLoader(build_path))
2039
template = env.get_template(file)

Diff for: locust/main.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
from . import log, stats
2121
from .argument_parser import parse_locustfile_option, parse_options
2222
from .env import Environment
23-
from .html import get_html_report
23+
from .html import get_html_report, process_html_filename
2424
from .input_events import input_listener
2525
from .log import greenlet_exception_logger, setup_logging
2626
from .stats import (
@@ -651,6 +651,7 @@ def sig_term_handler():
651651

652652
def save_html_report():
653653
html_report = get_html_report(environment, show_download_link=False)
654+
process_html_filename(options)
654655
logger.info("writing html report to file: %s", options.html_file)
655656
with open(options.html_file, "w", encoding="utf-8") as file:
656657
file.write(html_report)

Diff for: locust/test/test_html_filename.py

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
from locust.html import process_html_filename
2+
3+
import unittest
4+
from unittest.mock import MagicMock
5+
6+
7+
class TestProcessHtmlFilename(unittest.TestCase):
8+
def test_process_html_filename(self):
9+
mock_options = MagicMock()
10+
mock_options.num_users = 100
11+
mock_options.spawn_rate = 10
12+
mock_options.run_time = 60
13+
mock_options.html_file = "report_u{u}_r{r}_t{t}.html"
14+
15+
process_html_filename(mock_options)
16+
17+
expected_filename = "report_u100_r10_t60.html"
18+
self.assertEqual(mock_options.html_file, expected_filename)
19+
20+
def test_process_html_filename_partial_replacement(self):
21+
mock_options = MagicMock()
22+
mock_options.num_users = 50
23+
mock_options.spawn_rate = 5
24+
mock_options.run_time = 30
25+
mock_options.html_file = "loadtest_{u}_{r}.html"
26+
27+
process_html_filename(mock_options)
28+
29+
expected_filename = "loadtest_50_5.html"
30+
self.assertEqual(mock_options.html_file, expected_filename)
31+
32+
def test_process_html_filename_no_replacement(self):
33+
mock_options = MagicMock()
34+
mock_options.num_users = 50
35+
mock_options.spawn_rate = 5
36+
mock_options.run_time = 30
37+
mock_options.html_file = "static_report.html"
38+
39+
process_html_filename(mock_options)
40+
41+
expected_filename = "static_report.html"
42+
self.assertEqual(mock_options.html_file, expected_filename)

Diff for: locust/test/test_main.py

+55-39
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import socket
88
import subprocess
99
import sys
10+
import tempfile
1011
import textwrap
1112
import unittest
1213
from subprocess import PIPE, STDOUT
@@ -110,7 +111,7 @@ def my_task(self):
110111
gevent.sleep(1)
111112

112113
requests.post(
113-
"http://127.0.0.1:%i/swarm" % port,
114+
f"http://127.0.0.1:{port}/swarm",
114115
data={"user_count": 1, "spawn_rate": 1, "host": "https://localhost", "custom_string_arg": "web_form_value"},
115116
)
116117
gevent.sleep(1)
@@ -859,7 +860,7 @@ def test_web_options(self):
859860
stderr=PIPE,
860861
)
861862
gevent.sleep(1)
862-
self.assertEqual(200, requests.get("http://127.0.0.1:%i/" % port, timeout=3).status_code)
863+
self.assertEqual(200, requests.get(f"http://127.0.0.1:{port}/", timeout=3).status_code)
863864
proc.terminate()
864865

865866
@unittest.skipIf(os.name == "nt", reason="termios doesnt exist on windows, and thus we cannot import pty")
@@ -1150,44 +1151,59 @@ def t(self):
11501151
self.assertEqual(0, proc.returncode)
11511152

11521153
def test_html_report_option(self):
1154+
html_template = "some_name_{u}_{r}_{t}.html"
1155+
expected_filename = "some_name_11_5_2.html"
1156+
11531157
with mock_locustfile() as mocked:
1154-
with temporary_file("", suffix=".html") as html_report_file_path:
1155-
try:
1156-
subprocess.check_output(
1157-
[
1158-
"locust",
1159-
"-f",
1160-
mocked.file_path,
1161-
"--host",
1162-
"https://test.com/",
1163-
"--run-time",
1164-
"2s",
1165-
"--headless",
1166-
"--exit-code-on-error",
1167-
"0",
1168-
"--html",
1169-
html_report_file_path,
1170-
],
1171-
stderr=subprocess.STDOUT,
1172-
timeout=10,
1173-
text=True,
1174-
).strip()
1175-
except subprocess.CalledProcessError as e:
1176-
raise AssertionError(f"Running locust command failed. Output was:\n\n{e.stdout}") from e
1177-
1178-
with open(html_report_file_path, encoding="utf-8") as f:
1179-
html_report_content = f.read()
1180-
1181-
# make sure title appears in the report
1182-
_, locustfile = os.path.split(mocked.file_path)
1183-
self.assertIn(locustfile, html_report_content)
1184-
1185-
# make sure host appears in the report
1186-
self.assertIn("https://test.com/", html_report_content)
1187-
self.assertIn('"show_download_link": false', html_report_content)
1188-
self.assertRegex(html_report_content, r'"start_time": "\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z"')
1189-
self.assertRegex(html_report_content, r'"end_time": "\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z"')
1190-
self.assertRegex(html_report_content, r'"duration": "\d* seconds?"')
1158+
# Get system temp directory
1159+
temp_dir = tempfile.gettempdir()
1160+
1161+
# Define the input filename as well as the resulting filename within the temp directory
1162+
html_report_file_path = os.path.join(temp_dir, html_template)
1163+
output_html_report_file_path = os.path.join(temp_dir, expected_filename)
1164+
1165+
try:
1166+
output = subprocess.check_output(
1167+
[
1168+
"locust",
1169+
"-f",
1170+
mocked.file_path,
1171+
"--host",
1172+
"https://test.com/",
1173+
"--run-time",
1174+
"2s",
1175+
"--headless",
1176+
"--exit-code-on-error",
1177+
"0",
1178+
"-u",
1179+
"11",
1180+
"-r",
1181+
"5",
1182+
"--html",
1183+
html_report_file_path,
1184+
],
1185+
stderr=subprocess.STDOUT,
1186+
timeout=10,
1187+
text=True,
1188+
).strip()
1189+
1190+
except subprocess.CalledProcessError as e:
1191+
raise AssertionError(f"Running locust command failed. Output was:\n\n{e.stdout}") from e
1192+
with open(output_html_report_file_path, encoding="utf-8") as f:
1193+
html_report_content = f.read()
1194+
1195+
# make sure correct name is generated based on filename arguments
1196+
self.assertIn(expected_filename, output)
1197+
1198+
_, locustfile = os.path.split(mocked.file_path)
1199+
self.assertIn(locustfile, html_report_content)
1200+
1201+
# make sure host appears in the report
1202+
self.assertIn("https://test.com/", html_report_content)
1203+
self.assertIn('"show_download_link": false', html_report_content)
1204+
self.assertRegex(html_report_content, r'"start_time": "\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z"')
1205+
self.assertRegex(html_report_content, r'"end_time": "\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z"')
1206+
self.assertRegex(html_report_content, r'"duration": "\d* seconds?"')
11911207

11921208
def test_run_with_userclass_picker(self):
11931209
with temporary_file(content=MOCK_LOCUSTFILE_CONTENT_A) as file1:

0 commit comments

Comments
 (0)