Skip to content

Commit 991a6a0

Browse files
authored
Merge pull request #443 from ExaWorks/per_test_custom_attrs
Added a way to specify test filters for custom attributes.
2 parents 58ff5ec + f244906 commit 991a6a0

File tree

4 files changed

+62
-7
lines changed

4 files changed

+62
-7
lines changed

testing.conf

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,10 +126,15 @@ project_name =
126126
#
127127
# If a site needs specific job attributes to be specified, such as billing
128128
# accounts, they can be added here. The value is a comma separated list
129-
# of "executor.name": "value" pairs.
129+
# of "executor.attr_name": "value" pairs. A regular expression filter enclosed
130+
# in square brackets can be used after "custom_attributes" to specify that the
131+
# attributes should only apply to certain tests. The specified custom_attributes
132+
# directives are processed in the order in which they appear and a lack of a
133+
# filter is equivalent to a ".*" filter.
130134
#
131135
# custom_attributes = "slurm.account": "xyz", \
132136
# "slurm.constraint": "knl"
137+
# custom_attributes[test_nodefile\[slurm:.*\]] = "slurm.qos": "debug"
133138

134139
custom_attributes =
135140

tests/ci_runner.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ def read_line(f: TextIO) -> Optional[str]:
3535

3636

3737
def read_conf(fname: str) -> Dict[str, str]:
38-
conf = {}
38+
conf: Dict[str, str] = {}
3939
with open(fname, 'r') as f:
4040
line = read_line(f)
4141
while line is not None:
@@ -47,11 +47,28 @@ def read_conf(fname: str) -> Dict[str, str]:
4747
kv = line.split('=', 2)
4848
if len(kv) != 2:
4949
raise ValueError('Invalid line in configuration file: "%s"' % line)
50-
conf[kv[0].strip()] = kv[1].strip()
50+
add_conf(conf, kv[0].strip(), kv[1].strip())
5151
line = read_line(f)
5252
return conf
5353

5454

55+
def add_conf(conf: Dict[str, str], key: str, value: str) -> None:
56+
if key.startswith('custom_attributes'):
57+
if not key.startswith('custom_attributes['):
58+
key = key + '[.*]'
59+
if not key.endswith(']'):
60+
raise ValueError('Invalid custom_attributes entry. Missing closing bracket.')
61+
filter = key[len('custom_attributes['):-1]
62+
key = 'custom_attributes'
63+
value = '{"filter": "%s", "value": {%s}}' % (filter.replace('\\', '\\\\'), value)
64+
if key in conf:
65+
conf[key] = conf[key] + ', ' + value
66+
else:
67+
conf[key] = value
68+
else:
69+
conf[key] = value
70+
71+
5572
def run(*args: str, cwd: Optional[str] = None) -> str:
5673
p = subprocess.run(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, check=False,
5774
cwd=cwd, text=True)

tests/conftest.py

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ def pytest_generate_tests(metafunc):
154154
for x in _get_executors((metafunc.config)):
155155
etp = ExecutorTestParams(x, queue_name=metafunc.config.option.queue_name,
156156
project_name=metafunc.config.option.project_name,
157-
custom_attributes=metafunc.config.option.custom_attributes)
157+
custom_attributes_raw=metafunc.config.option.custom_attributes)
158158
etps.append(etp)
159159

160160
metafunc.parametrize('execparams', etps, ids=lambda x: str(x))
@@ -361,7 +361,13 @@ def _parse_custom_attributes(s: Optional[str]) -> Dict[str, object]:
361361
if s is None:
362362
return None
363363
else:
364-
return json.loads('{' + s + '}')
364+
attrspec = json.loads('[' + s + ']')
365+
d = {}
366+
for item in attrspec:
367+
if item['filter'] not in d:
368+
d[item['filter']] = {}
369+
d[item['filter']].update(item['value'])
370+
return d
365371

366372

367373
def _strip_home(path: List[str]) -> List[str]:
@@ -434,8 +440,31 @@ def _now():
434440
return datetime.datetime.now(tz=datetime.timezone.utc).isoformat(' ')
435441

436442

443+
def _process_custom_attributes(item):
444+
if not hasattr(item, 'callspec'):
445+
return
446+
if 'execparams' not in item.callspec.params:
447+
return
448+
ep: Optional[List[Dict[str, Dict[str, object]]]] = item.callspec.params['execparams']
449+
if ep.custom_attributes_raw is None:
450+
return
451+
if ep is None:
452+
return
453+
test_name = item.name
454+
for filter, attrs in ep.custom_attributes_raw.items():
455+
if re.match(filter, test_name):
456+
ep.custom_attributes.update(attrs)
457+
458+
459+
def _set_attrs(execparams, attrs):
460+
for k, v in attrs.items():
461+
execparams.customattributes[k] = v
462+
463+
437464
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
438465
def pytest_runtest_makereport(item, call):
466+
_process_custom_attributes(item)
467+
439468
outcome = yield
440469
report = outcome.get_result()
441470

@@ -588,6 +617,8 @@ def _upload_report(config, data):
588617
env = config.option.environment
589618

590619
url = config.getoption('server_url')
620+
if not url:
621+
return
591622
minimal = config.getoption('minimal_uploads')
592623
if minimal:
593624
data = _sanitize(data)

tests/executor_test_params.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ class ExecutorTestParams:
77

88
def __init__(self, spec: str, queue_name: Optional[str] = None,
99
project_name: Optional[str] = None,
10-
custom_attributes: Optional[Dict[str, object]] = None) -> None:
10+
custom_attributes_raw: Optional[Dict[str, Dict[str, object]]] = None) \
11+
-> None:
1112
"""
1213
Construct a new instance.
1314
@@ -35,7 +36,8 @@ def __init__(self, spec: str, queue_name: Optional[str] = None,
3536

3637
self.queue_name = queue_name
3738
self.project_name = project_name
38-
self.custom_attributes = custom_attributes
39+
self.custom_attributes_raw = custom_attributes_raw
40+
self.custom_attributes: Dict[str, object] = {}
3941

4042
def __repr__(self) -> str:
4143
"""Returns a string representation of this object."""

0 commit comments

Comments
 (0)