Skip to content

Commit 714e693

Browse files
authored
Merge pull request Webperf-se#489 from cockroacher/i488
Temporarily Apply Configuration for Current Run from Command Line
2 parents a8572db + 1b27dfc commit 714e693

29 files changed

+1075
-647
lines changed

default.py

+36-8
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from engines.gov import write_tests as gov_write_tests
2424
from engines.sql import write_tests as sql_write_tests
2525
from engines.markdown_engine import write_tests as markdown_write_tests
26+
from helpers.setting_helper import config_mapping, set_config_from_cmd
2627
from tests.utils import clean_cache_files
2728
from utils import TEST_FUNCS, TEST_ALL, restart_failures_log, test_sites
2829

@@ -251,6 +252,31 @@ def set_input_take(self, arg):
251252
print(self.language('TEXT_COMMAND_USAGE'))
252253
sys.exit(2)
253254

255+
def show_available_settings(self):
256+
"""
257+
Display valid settings and their aliases.
258+
Prints the available settings along with their corresponding aliases.
259+
"""
260+
print()
261+
print('Valid settings:')
262+
for aliases in config_mapping:
263+
print(f"--setting {aliases[1]}=<value> ( alias: {aliases[0]} )")
264+
print()
265+
266+
def set_setting(self, arg):
267+
"""
268+
Set configuration settings based on user input.
269+
270+
Parses the input argument to determine the setting name and value.
271+
If the input is not in the correct format, it displays available settings.
272+
Otherwise, it sets the specified configuration value.
273+
274+
Args:
275+
arg (str): Input argument in the format "<setting_name>=<value>".
276+
"""
277+
if not set_config_from_cmd(arg):
278+
self.show_available_settings()
279+
254280
def set_output_filename(self, arg):
255281
"""
256282
Sets the output filename for the instance.
@@ -422,14 +448,15 @@ def handle_option(self, opt, arg):
422448
("-L", "--language"): self.try_load_language,
423449
("-t", "--test"): self.set_test_types,
424450
("-i", "--input"): self.set_input_handlers,
425-
("--input-skip"): self.set_input_skip,
426-
("--input-take"): self.set_input_take,
451+
("--is", "--input-skip"): self.set_input_skip,
452+
("--it", "--input-take"): self.set_input_take,
427453
("-o", "--output"): self.set_output_filename,
428454
("-r", "--review", "--report"): self.enable_reviews,
455+
("-s", "--setting"): self.set_setting
429456
}
430457

431-
for option, handler in option_handlers.items():
432-
if opt in option:
458+
for options, handler in option_handlers.items():
459+
if opt in options:
433460
handler(arg)
434461
return
435462

@@ -451,16 +478,19 @@ def main(argv):
451478
-A/--addUrl <site url>\t: website url (required in combination with -i/--input)
452479
-D/--deleteUrl <site url>\t: website url (required in combination with -i/--input)
453480
-L/--language <lang code>\t: language used for output(en = default/sv)
481+
--setting <key>=<value>\t: override configuration for current run
482+
(use ? to list available settings)
454483
"""
455484

456485
options = CommandLineOptions()
457486
options.load_language(options.lang_code)
458487

459488
try:
460-
opts, _ = getopt.getopt(argv, "hu:t:i:o:rA:D:L:", [
489+
opts, _ = getopt.getopt(argv, "hu:t:i:o:rA:D:L:s:", [
461490
"help", "url=", "test=", "input=", "output=",
462491
"review", "report", "addUrl=", "deleteUrl=",
463-
"language=", "input-skip=", "input-take="])
492+
"language=", "input-skip=", "input-take=",
493+
"is=", "it=", "setting="])
464494
except getopt.GetoptError:
465495
print(main.__doc__)
466496
sys.exit(2)
@@ -507,7 +537,5 @@ def main(argv):
507537
else:
508538
print(options.language('TEXT_COMMAND_USAGE'))
509539

510-
511-
512540
if __name__ == '__main__':
513541
main(sys.argv[1:])

helpers/csp_helper.py

+121-53
Large diffs are not rendered by default.

helpers/setting_helper.py

+265
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,265 @@
1+
config = {}
2+
3+
config_mapping = {
4+
("useragent", "general.useragent", "agent", "ua"): "string|USERAGENT",
5+
(
6+
"details",
7+
"general.review.details",
8+
"use_detailed_report"): "bool|USE_DETAILED_REPORT",
9+
(
10+
"improve-only",
11+
"general.review.improve-only",
12+
"review_show_improvements_only"): "bool|REVIEW_SHOW_IMPROVEMENTS_ONLY",
13+
(
14+
"timeout",
15+
"general.request.timeout",
16+
"http_request_timeout"): "int|HTTP_REQUEST_TIMEOUT",
17+
(
18+
"cache",
19+
"general.cache.use",
20+
"cache_when_possible"): "bool|CACHE_WHEN_POSSIBLE",
21+
(
22+
"cachetime",
23+
"general.cache.time",
24+
"cache_time_delta"): "int|CACHE_TIME_DELTA",
25+
(
26+
"dnsserver",
27+
"general.dns.address",
28+
"dns_server"): "string|DNS_SERVER",
29+
(
30+
"githubkey",
31+
"github.api.key",
32+
"github_api_key"): "string|GITHUB_API_KEY",
33+
(
34+
"googleapikey",
35+
"tests.lighthouse.api.key",
36+
"googlepagespeedapikey"): "string|GOOGLEPAGESPEEDAPIKEY",
37+
(
38+
"googleuseapi",
39+
"tests.lighthouse.api.use",
40+
"lighthouse_use_api"): "bool|LIGHTHOUSE_USE_API",
41+
(
42+
"webbkollsleep",
43+
"tests.webbkoll.sleep",
44+
"webbkoll_sleep"): "int|WEBBKOLL_SLEEP",
45+
(
46+
"tests.w3c.group",
47+
"tests.css.group",
48+
"css_review_group_errors"): "bool|CSS_REVIEW_GROUP_ERRORS",
49+
(
50+
"yellowlabtoolskey",
51+
"tests.ylt.api.key",
52+
"ylt_use_api"): "bool|YLT_USE_API",
53+
(
54+
"yellowlabtoolsaddress",
55+
"tests.ylt.api.address",
56+
"ylt_server_address"): "string|YLT_SERVER_ADDRESS",
57+
(
58+
"sitespeeddocker",
59+
"tests.sitespeed.docker.use",
60+
"sitespeed_use_docker"): "bool|SITESPEED_USE_DOCKER",
61+
(
62+
"sitespeedtimeout",
63+
"tests.sitespeed.timeout",
64+
"sitespeed_timeout"): "int|SITESPEED_TIMEOUT",
65+
(
66+
"sitespeediterations",
67+
"tests.sitespeed.iterations",
68+
"sitespeed_iterations"): "int|SITESPEED_ITERATIONS",
69+
(
70+
"csponly",
71+
"tests.http.csp-only",
72+
"csp_only"): "bool|CSP_ONLY",
73+
(
74+
"stealth",
75+
"tests.software.stealth.use",
76+
"software_use_stealth"): "bool|SOFTWARE_USE_STEALTH",
77+
(
78+
"advisorydatabase",
79+
"tests.software.advisory.path",
80+
"software_github_adadvisory_database_path"
81+
): "string|SOFTWARE_GITHUB_ADADVISORY_DATABASE_PATH",
82+
(
83+
"browser",
84+
"tests.software.browser",
85+
"software_browser"): "string|SOFTWARE_BROWSER",
86+
(
87+
"mailport25",
88+
"tests.email.support.port25",
89+
"email_network_support_port25_traffic"): "bool|EMAIL_NETWORK_SUPPORT_PORT25_TRAFFIC",
90+
(
91+
"mailipv6",
92+
"tests.email.support.ipv6",
93+
"email_network_support_ipv6_traffic"): "bool|EMAIL_NETWORK_SUPPORT_IPV6_TRAFFIC"
94+
}
95+
96+
97+
def get_config(name):
98+
"""
99+
Retrieve a configuration value based on the specified name.
100+
101+
Args:
102+
name (str): The name of the configuration setting.
103+
104+
Returns:
105+
The configuration value if found, otherwise None.
106+
"""
107+
# Lets see if we have it from terminal or in cache
108+
name = name.lower()
109+
if name in config:
110+
return config[name]
111+
112+
# Try get config from our configuration file
113+
value = get_config_from_module(name, 'config')
114+
if value is not None:
115+
config[name] = value
116+
return value
117+
118+
name = name.upper()
119+
value = get_config_from_module(name, 'config')
120+
if value is not None:
121+
config[name] = value
122+
return value
123+
124+
# do we have fallback value we can use in our defaults/config.py file?
125+
value = get_config_from_module(name, 'defaults.config')
126+
if value is not None:
127+
config[name] = value
128+
return value
129+
130+
return None
131+
132+
def set_config(name, value):
133+
"""
134+
Set a configuration value.
135+
136+
Args:
137+
name (str): The name of the configuration setting.
138+
value: The value to set for the specified configuration.
139+
"""
140+
name = name.lower()
141+
config[name] = value
142+
143+
def get_config_from_module(config_name, module_name):
144+
"""
145+
Retrieves the configuration value for a given name from the specified module file.
146+
147+
Parameters:
148+
config_name (str): The name of the configuration value to retrieve.
149+
module_name (str): The name of the module the values should be retrieved from.
150+
151+
Returns:
152+
The configuration value associated with the given config_name and module_name.
153+
"""
154+
# do we have fallback value we can use in our defaults/config.py file?
155+
try:
156+
from importlib import import_module # pylint: disable=import-outside-toplevel
157+
tmp_config = import_module(module_name) # pylint: disable=invalid-name
158+
if hasattr(tmp_config, config_name):
159+
return getattr(tmp_config, config_name)
160+
except ModuleNotFoundError:
161+
_ = 1
162+
163+
return None
164+
165+
def set_config_from_cmd(arg):
166+
"""
167+
Set configuration settings based on user input.
168+
169+
Parses the input argument to determine the setting name and value.
170+
If the input is not in the correct format, it displays available settings.
171+
Otherwise, it sets the specified configuration value.
172+
173+
Args:
174+
arg (str): Input argument in the format "<setting_name>=<value>".
175+
"""
176+
pair = arg.split('=')
177+
nof_pair = len(pair)
178+
if nof_pair > 2:
179+
return False
180+
name = pair[0].lower()
181+
value = 'true'
182+
183+
if nof_pair > 1:
184+
value = pair[1]
185+
186+
config_name = None
187+
188+
for aliases, setting_name in config_mapping.items():
189+
if name in aliases:
190+
config_name = setting_name
191+
break
192+
193+
if config_name is None:
194+
return False
195+
196+
config_name_pair = config_name.split('|')
197+
config_name_type = config_name_pair[0]
198+
config_name = config_name_pair[1]
199+
200+
config_value = None
201+
value_type_handlers = {
202+
"bool": handle_cmd_bool_value,
203+
"int": handle_cmd_int_value,
204+
"str": handle_cmd_str_value,
205+
}
206+
for value_type, handler in value_type_handlers.items():
207+
if config_name_type in value_type:
208+
config_value = handler(config_name, value)
209+
set_config(config_name.lower(), config_value)
210+
return True
211+
return False
212+
213+
214+
def handle_cmd_bool_value(setting_name, value):
215+
"""
216+
Converts a string value to a boolean based on common true/false representations.
217+
218+
Args:
219+
setting_name (str): The name of the setting.
220+
value (str): The input value to be converted.
221+
222+
Returns:
223+
bool or None: The converted boolean value if valid, otherwise None.
224+
"""
225+
setting_value = None
226+
if value in ('true', 'True', 'yes', 'Y', 'y'):
227+
setting_value = True
228+
elif value in ('false', 'False', 'no', 'N', 'n'):
229+
setting_value = False
230+
else:
231+
print(
232+
'Warning: Ignoring setting,'
233+
f'"{setting_name}":s value has to be true or false.')
234+
return setting_value
235+
236+
def handle_cmd_int_value(setting_name, value):
237+
"""
238+
Converts a string value to an integer.
239+
240+
Args:
241+
setting_name (str): The name of the setting.
242+
value (str): The input value to be converted.
243+
244+
Returns:
245+
int or None: The converted integer value if valid, otherwise None.
246+
"""
247+
setting_value = None
248+
try:
249+
setting_value = int(value)
250+
except TypeError:
251+
print(f'Warning: Ignoring setting, "{setting_name}":s value has to be a number.')
252+
return setting_value
253+
254+
def handle_cmd_str_value(_, value):
255+
"""
256+
Returns the input string value without any modification.
257+
258+
Args:
259+
_ (Any): Placeholder for an unused argument.
260+
value (str): The input string value.
261+
262+
Returns:
263+
str: The original input string value.
264+
"""
265+
return value

0 commit comments

Comments
 (0)