Skip to content

Commit c966788

Browse files
committed
1. fix parsing to support space seperated string
2. make comparison non-case sensitve
1 parent e423964 commit c966788

2 files changed

Lines changed: 141 additions & 7 deletions

File tree

RLTest/utils.py

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ def split_by_semicolon(s):
9292

9393
def args_list_to_dict(args_list):
9494
def dicty(args):
95-
return dict((seq.split(' ')[0], seq) for seq in args)
95+
return dict((seq.split(' ')[0].upper(), seq) for seq in args)
9696
return list(map(lambda args: dicty(args), args_list))
9797

9898
def join_lists(lists):
@@ -105,18 +105,32 @@ def fix_modulesArgs(modules, modulesArgs, defaultArgs=None, haveSeqs=True):
105105
# ['args ...', ...]: arg list for a single module
106106
# [['arg', ...', ...], ...]: arg strings for multiple modules
107107

108-
# arg string is a string of words seperated by whitespace
109-
# arg string can be seperated by semicolons into (logical) arg lists.
108+
# arg string is a string of words separated by whitespace.
109+
# arg string can be separated by semicolons into (logical) arg lists.
110110
# semicolons can be escaped with a backslash.
111+
# if no semicolons are present, the string is treated as space-separated key-value pairs,
112+
# where each consecutive pair of words forms a 'KEY VALUE' arg.
113+
# thus, 'K1 V1 K2 V2' becomes ['K1 V1', 'K2 V2']
114+
# an odd number of words without semicolons is an error.
115+
# for args with multiple values, semicolons are required:
116+
# thus, 'K1 V1; K2 V2 V3' becomes ['K1 V1', 'K2 V2 V3']
111117
# arg list is a list of arg strings.
112118
# arg list starts with an arg name that can later be used for argument overriding.
113-
# arg strings are transformed into arg lists (haveSeqs parameter controls this behavior):
114-
# thus, 'num 1; names a b' becomes ['num 1', 'names a b']
115119

116120
if type(modulesArgs) == str:
117121
# case # 'args ...': arg string for a single module
118122
# transformed into [['arg', ...]]
119-
modulesArgs = [split_by_semicolon(modulesArgs)]
123+
parts = split_by_semicolon(modulesArgs)
124+
if len(parts) == 1:
125+
# No semicolons found - treat as space-separated key-value pairs
126+
words = parts[0].split()
127+
if len(words) % 2 != 0:
128+
print(Colors.Bred('Error in args: odd number of words in key-value pairs: \'%s\'. '
129+
'Use semicolons to separate args with multiple values (e.g. \'KEY1 V1; KEY2 V2 V3\').' % modulesArgs))
130+
sys.exit(1)
131+
if len(words) > 2:
132+
parts = [words[i] + ' ' + words[i + 1] for i in range(0, len(words), 2)]
133+
modulesArgs = [parts]
120134
elif type(modulesArgs) == list:
121135
args = []
122136
is_list = False
@@ -180,7 +194,7 @@ def fix_modulesArgs(modules, modulesArgs, defaultArgs=None, haveSeqs=True):
180194
modules_args_dict = args_list_to_dict(modulesArgs)
181195
for imod, args_list in enumerate(defaultArgs):
182196
for arg in args_list:
183-
name = arg.split(' ')[0]
197+
name = arg.split(' ')[0].upper()
184198
if name not in modules_args_dict[imod]:
185199
modulesArgs[imod] += [arg]
186200

tests/unit/test_utils.py

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
from unittest import TestCase
2+
3+
from RLTest.utils import fix_modulesArgs
4+
5+
6+
class TestFixModulesArgs(TestCase):
7+
8+
# 1. Single key-value pair string
9+
def test_single_key_value_pair(self):
10+
result = fix_modulesArgs(['/mod.so'], 'WORKERS 4')
11+
self.assertEqual(result, [['WORKERS 4']])
12+
13+
# 2. Multiple key-value pairs without semicolons (new behavior)
14+
def test_multiple_kv_pairs_no_semicolons(self):
15+
result = fix_modulesArgs(['/mod.so'], '_FREE_RESOURCE_ON_THREAD FALSE TIMEOUT 80 WORKERS 4')
16+
self.assertEqual(result, [['_FREE_RESOURCE_ON_THREAD FALSE', 'TIMEOUT 80', 'WORKERS 4']])
17+
18+
# 3. Semicolon-separated args (existing behavior)
19+
def test_semicolon_separated_args(self):
20+
result = fix_modulesArgs(['/mod.so'], 'KEY1 V1; KEY2 V2')
21+
self.assertEqual(result, [['KEY1 V1', 'KEY2 V2']])
22+
23+
# 4a. Odd number of words without semicolons - should error
24+
def test_odd_words_no_semicolons_exits(self):
25+
with self.assertRaises(SystemExit):
26+
fix_modulesArgs(['/mod.so'], 'FLAG TIMEOUT 80')
27+
28+
# 4b. Odd number of words with semicolons - valid, semicolons split first
29+
def test_odd_words_with_semicolons_valid(self):
30+
result = fix_modulesArgs(['/mod.so'], 'FLAG; TIMEOUT 80')
31+
self.assertEqual(result, [['FLAG', 'TIMEOUT 80']])
32+
33+
# 5a. Space-separated string overrides matching defaults, non-matching defaults added
34+
def test_space_separated_overrides_defaults(self):
35+
defaults = [['WORKERS 8', 'TIMEOUT 60', 'EXTRA 1']]
36+
result = fix_modulesArgs(['/mod.so'], 'WORKERS 4 TIMEOUT 80', defaults)
37+
result_dict = {arg.split(' ')[0]: arg for arg in result[0]}
38+
self.assertEqual(result_dict['WORKERS'], 'WORKERS 4')
39+
self.assertEqual(result_dict['TIMEOUT'], 'TIMEOUT 80')
40+
self.assertEqual(result_dict['EXTRA'], 'EXTRA 1')
41+
42+
# 5b. Semicolon-separated string overrides matching defaults
43+
def test_semicolon_separated_overrides_defaults(self):
44+
defaults = [['WORKERS 8', 'TIMEOUT 60', 'EXTRA 1']]
45+
result = fix_modulesArgs(['/mod.so'], 'WORKERS 4; TIMEOUT 80', defaults)
46+
result_dict = {arg.split(' ')[0]: arg for arg in result[0]}
47+
self.assertEqual(result_dict['WORKERS'], 'WORKERS 4')
48+
self.assertEqual(result_dict['TIMEOUT'], 'TIMEOUT 80')
49+
self.assertEqual(result_dict['EXTRA'], 'EXTRA 1')
50+
51+
# 5c. Space-separated explicit overrides some defaults, non-overlapping defaults are merged
52+
def test_space_separated_partial_override_with_defaults(self):
53+
defaults = [['_FREE_RESOURCE_ON_THREAD TRUE', 'TIMEOUT 100', 'WORKERS 8']]
54+
result = fix_modulesArgs(['/mod.so'], 'WORKERS 4 TIMEOUT 80', defaults)
55+
result_dict = {arg.split(' ')[0]: arg for arg in result[0]}
56+
self.assertEqual(result_dict['WORKERS'], 'WORKERS 4')
57+
self.assertEqual(result_dict['TIMEOUT'], 'TIMEOUT 80')
58+
self.assertEqual(result_dict['_FREE_RESOURCE_ON_THREAD'], '_FREE_RESOURCE_ON_THREAD TRUE')
59+
60+
# 6. None input with defaults - deep copy of defaults
61+
def test_none_uses_defaults(self):
62+
defaults = [['WORKERS 8', 'TIMEOUT 60']]
63+
result = fix_modulesArgs(['/mod.so'], None, defaults)
64+
self.assertEqual(result, defaults)
65+
# Verify it's a deep copy
66+
result[0][0] = 'MODIFIED'
67+
self.assertEqual(defaults[0][0], 'WORKERS 8')
68+
69+
# 7. List of strings with defaults - overlapping and non-overlapping keys
70+
def test_list_of_strings_with_defaults(self):
71+
defaults = [['K1 default1', 'K2 default2', 'K4 default4']]
72+
result = fix_modulesArgs(['/mod.so'], ['K1 override1', 'K2 override2', 'K3 new3'], defaults)
73+
result_dict = {arg.split(' ')[0]: arg for arg in result[0]}
74+
self.assertEqual(result_dict['K1'], 'K1 override1')
75+
self.assertEqual(result_dict['K2'], 'K2 override2')
76+
self.assertEqual(result_dict['K3'], 'K3 new3')
77+
self.assertEqual(result_dict['K4'], 'K4 default4')
78+
79+
# 8. List of lists (multi-module) with defaults - overlapping and non-overlapping keys
80+
def test_multi_module_with_defaults(self):
81+
modules = ['/mod1.so', '/mod2.so']
82+
explicit = [['K1 v1', 'K2 v2'], ['K3 v3']]
83+
defaults = [['K1 d1', 'K5 d5'], ['K3 d3', 'K4 d4']]
84+
result = fix_modulesArgs(modules, explicit, defaults)
85+
# Module 1: K1 overridden, K5 added from defaults
86+
dict1 = {arg.split(' ')[0]: arg for arg in result[0]}
87+
self.assertEqual(dict1['K1'], 'K1 v1')
88+
self.assertEqual(dict1['K2'], 'K2 v2')
89+
self.assertEqual(dict1['K5'], 'K5 d5')
90+
# Module 2: K3 overridden, K4 added from defaults
91+
dict2 = {arg.split(' ')[0]: arg for arg in result[1]}
92+
self.assertEqual(dict2['K3'], 'K3 v3')
93+
self.assertEqual(dict2['K4'], 'K4 d4')
94+
95+
96+
# 9. Case-insensitive matching between explicit args and defaults (both directions)
97+
def test_case_insensitive_override(self):
98+
# Uppercase explicit overrides lowercase defaults
99+
defaults = [['workers 8', 'timeout 60', 'EXTRA 1', 'MIxEd 7', 'lower true']]
100+
result = fix_modulesArgs(['/mod.so'], 'WORKERS 4 TIMEOUT 80 miXed 0 LOWER false', defaults)
101+
result_dict = {arg.split(' ')[0]: arg for arg in result[0]}
102+
self.assertEqual(result_dict['WORKERS'], 'WORKERS 4')
103+
self.assertEqual(result_dict['TIMEOUT'], 'TIMEOUT 80')
104+
self.assertEqual(result_dict['EXTRA'], 'EXTRA 1')
105+
self.assertEqual(result_dict['miXed'], 'miXed 0')
106+
self.assertEqual(result_dict['LOWER'], 'LOWER false')
107+
self.assertNotIn('workers', result_dict)
108+
self.assertNotIn('timeout', result_dict)
109+
self.assertNotIn('MIxEd', result_dict)
110+
self.assertNotIn('lower', result_dict)
111+
112+
# Lowercase explicit overrides uppercase defaults
113+
defaults = [['WORKERS 8', 'TIMEOUT 60', 'EXTRA 1']]
114+
result = fix_modulesArgs(['/mod.so'], 'workers 4 timeout 80', defaults)
115+
result_dict = {arg.split(' ')[0]: arg for arg in result[0]}
116+
self.assertEqual(result_dict['workers'], 'workers 4')
117+
self.assertEqual(result_dict['timeout'], 'timeout 80')
118+
self.assertEqual(result_dict['EXTRA'], 'EXTRA 1')
119+
self.assertNotIn('WORKERS', result_dict)
120+
self.assertNotIn('TIMEOUT', result_dict)

0 commit comments

Comments
 (0)