Skip to content

Commit 64dc330

Browse files
Add tests for usage filters
1 parent 946900a commit 64dc330

File tree

3 files changed

+323
-8
lines changed

3 files changed

+323
-8
lines changed
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
# Copyright 2022-2025 The Ramble Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4+
# https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5+
# <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
6+
# option. This file may not be copied, modified, or distributed
7+
# except according to those terms.
8+
9+
import os
10+
import re
11+
12+
import pytest
13+
14+
import ramble.workspace
15+
from ramble.error import RambleCommandError
16+
from ramble.main import RambleCommand
17+
18+
workspace = RambleCommand("workspace")
19+
20+
21+
@pytest.mark.parametrize(
22+
"filter_type,expected_count",
23+
[
24+
("none", -1),
25+
("once", 1),
26+
("all_mpi", 6),
27+
("first_mpi", 1),
28+
],
29+
)
30+
def test_executable_modifier_usage_filters(
31+
mutable_mock_workspace_path,
32+
mutable_applications,
33+
mock_modifiers,
34+
workspace_name,
35+
filter_type,
36+
expected_count,
37+
):
38+
39+
global_args = ["-w", workspace_name]
40+
41+
pre_str = f"exec_mod_{filter_type}_pre_applied"
42+
post_str = f"exec_mod_{filter_type}_post_applied"
43+
44+
with ramble.workspace.create(workspace_name) as ws:
45+
ws.write()
46+
47+
workspace(
48+
"manage",
49+
"experiments",
50+
"openfoam",
51+
"--wf",
52+
"motorbike_20m",
53+
"-v",
54+
"n_ranks=1",
55+
"-v",
56+
"n_nodes=1",
57+
"-v",
58+
"openfoam_path=/not/needed",
59+
global_args=global_args,
60+
)
61+
62+
workspace(
63+
"manage",
64+
"modifiers",
65+
"--add",
66+
"--scope",
67+
"workspace",
68+
"--name",
69+
"exec-mod-usage-filters",
70+
global_args=global_args,
71+
)
72+
73+
with open(os.path.join(ws.config_dir, "variants.yaml"), "w+") as f:
74+
f.write(
75+
f"""variants:
76+
usage_filter_type: {filter_type}"""
77+
)
78+
79+
ws._re_read()
80+
81+
workspace("setup", "--dry-run", global_args=global_args)
82+
83+
exec_path = os.path.join(
84+
ws.experiment_dir, "openfoam", "motorbike_20m", "generated", "execute_experiment"
85+
)
86+
87+
assert os.path.isfile(exec_path)
88+
89+
pre_regex = re.compile(pre_str)
90+
post_regex = re.compile(post_str)
91+
92+
with open(exec_path) as f:
93+
pre_count = 0
94+
post_count = 0
95+
96+
for line in f.readlines():
97+
pre_m = pre_regex.search(line)
98+
if pre_m:
99+
pre_count += 1
100+
101+
post_m = post_regex.search(line)
102+
if post_m:
103+
post_count += 1
104+
105+
if expected_count >= 0:
106+
assert pre_count == expected_count
107+
assert post_count == expected_count
108+
else:
109+
assert pre_count > 0
110+
assert post_count > 0
111+
112+
113+
def test_executable_modifier_usage_filters_broken_errors(
114+
mutable_mock_workspace_path,
115+
mutable_applications,
116+
mock_modifiers,
117+
workspace_name,
118+
):
119+
120+
expected_err = (
121+
"When extracting a usage_filter for an executable_modifier "
122+
"on modifier exec-mod-usage-filters the filter __broken__ does not exist"
123+
)
124+
global_args = ["-w", workspace_name]
125+
126+
with ramble.workspace.create(workspace_name) as ws:
127+
ws.write()
128+
129+
workspace(
130+
"manage",
131+
"experiments",
132+
"openfoam",
133+
"--wf",
134+
"motorbike_20m",
135+
"-v",
136+
"n_ranks=1",
137+
"-v",
138+
"n_nodes=1",
139+
"-v",
140+
"openfoam_path=/not/needed",
141+
global_args=global_args,
142+
)
143+
144+
workspace(
145+
"manage",
146+
"modifiers",
147+
"--add",
148+
"--scope",
149+
"workspace",
150+
"--name",
151+
"exec-mod-usage-filters",
152+
global_args=global_args,
153+
)
154+
155+
with open(os.path.join(ws.config_dir, "variants.yaml"), "w+") as f:
156+
f.write(
157+
"""variants:
158+
usage_filter_type: broken"""
159+
)
160+
161+
ws._re_read()
162+
163+
with pytest.raises(RambleCommandError, match=expected_err):
164+
workspace("setup", "--dry-run", global_args=global_args)
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
# Copyright 2022-2025 The Ramble Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4+
# https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5+
# <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
6+
# option. This file may not be copied, modified, or distributed
7+
# except according to those terms.
8+
9+
from ramble.modkit import *
10+
11+
12+
class ExecModUsageFilters(BasicModifier):
13+
"""Define a modifier to test usage filters on executable modifiers"""
14+
15+
name = "exec-mod-usage-filters"
16+
17+
tags("test")
18+
19+
mode("test", description="This is a test mode")
20+
default_mode("test")
21+
22+
modifier_conflict(MODIFIER_CONFLICT.name_mode_executables)
23+
24+
variant(
25+
"usage_filter_type",
26+
default="none",
27+
values=["none", "once", "all_mpi", "first_mpi", "broken"],
28+
description="Control which usage filter to use on exec mods",
29+
)
30+
31+
executable_modifier(
32+
"exec_mod_none", usage_filter=None, when=["usage_filter_type=none"]
33+
)
34+
35+
def exec_mod_none(self, executable_name, executable, app_inst=None):
36+
from ramble.util.executable import CommandExecutable
37+
38+
pre_cmds = [
39+
CommandExecutable(
40+
"exec-mod-none-pre", template=["exec_mod_none_pre_applied"]
41+
)
42+
]
43+
44+
post_cmds = [
45+
CommandExecutable(
46+
"exec-mod-once-post", template=["exec_mod_none_post_applied"]
47+
)
48+
]
49+
50+
return pre_cmds, post_cmds
51+
52+
executable_modifier(
53+
"exec_mod_once", usage_filter="once", when=["usage_filter_type=once"]
54+
)
55+
56+
def exec_mod_once(self, executable_name, executable, app_inst=None):
57+
from ramble.util.executable import CommandExecutable
58+
59+
pre_cmds = [
60+
CommandExecutable(
61+
"exec-mod-once-pre", template=["exec_mod_once_pre_applied"]
62+
)
63+
]
64+
65+
post_cmds = [
66+
CommandExecutable(
67+
"exec-mod-once-post", template=["exec_mod_once_post_applied"]
68+
)
69+
]
70+
71+
return pre_cmds, post_cmds
72+
73+
executable_modifier(
74+
"exec_mod_first_mpi",
75+
usage_filter="first_mpi",
76+
when=["usage_filter_type=first_mpi"],
77+
)
78+
79+
def exec_mod_first_mpi(self, executable_name, executable, app_inst=None):
80+
from ramble.util.executable import CommandExecutable
81+
82+
pre_cmds = [
83+
CommandExecutable(
84+
"exec-mod-first-mpi-pre",
85+
template=["exec_mod_first_mpi_pre_applied"],
86+
)
87+
]
88+
89+
post_cmds = [
90+
CommandExecutable(
91+
"exec-mod-first-mpi-post",
92+
template=["exec_mod_first_mpi_post_applied"],
93+
)
94+
]
95+
96+
return pre_cmds, post_cmds
97+
98+
executable_modifier(
99+
"exec_mod_all_mpi",
100+
usage_filter="all_mpi",
101+
when=["usage_filter_type=all_mpi"],
102+
)
103+
104+
def exec_mod_all_mpi(self, executable_name, executable, app_inst=None):
105+
from ramble.util.executable import CommandExecutable
106+
107+
pre_cmds = [
108+
CommandExecutable(
109+
"exec-mod-all-mpi-pre",
110+
template=["exec_mod_all_mpi_pre_applied"],
111+
)
112+
]
113+
114+
post_cmds = [
115+
CommandExecutable(
116+
"exec-mod-all-mpi-post",
117+
template=["exec_mod_all_mpi_post_applied"],
118+
)
119+
]
120+
121+
return pre_cmds, post_cmds
122+
123+
executable_modifier(
124+
"exec_mod_broken",
125+
usage_filter="__broken__",
126+
when=["usage_filter_type=broken"],
127+
)
128+
129+
def exec_mod_broken(self, executable_name, executable, app_inst=None):
130+
from ramble.util.executable import CommandExecutable
131+
132+
pre_cmds = [
133+
CommandExecutable(
134+
"exec-mod-all-mpi-pre",
135+
template=["exec_mod_all_mpi_pre_applied"],
136+
)
137+
]
138+
139+
post_cmds = [
140+
CommandExecutable(
141+
"exec-mod-all-mpi-post",
142+
template=["exec_mod_all_mpi_post_applied"],
143+
)
144+
]
145+
146+
return pre_cmds, post_cmds

var/ramble/repos/builtin/base_classes/modifier-base/base_class.py

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,9 @@ def get_executable_modifier_filter(self, filter_name):
427427
Returns:
428428
Reference to function, if found. None otherwise"""
429429

430+
if filter_name is None or filter_name == "None":
431+
return None
432+
430433
filter_names = set()
431434
for attr in dir(self):
432435
method = getattr(self, attr)
@@ -438,16 +441,17 @@ def get_executable_modifier_filter(self, filter_name):
438441
test_filter_name = method_attributes["filter_name"]
439442
filter_names.add(test_filter_name)
440443
if filter_name == test_filter_name:
444+
logger.all_msg(
445+
f" Found matching filter? {filter_name} == {test_filter_name}"
446+
)
441447
return method
442448

443-
if filter_name is not None and filter_name != "None":
444-
logger.die(
445-
f"When extracting a usage_filter for an executable_modifier "
446-
f"on modifier {self.name} "
447-
f"the filter {filter_name} does not exist. Registered filters are: \n"
448-
f"{filter_names}"
449-
)
450-
return None
449+
logger.die(
450+
f"When extracting a usage_filter for an executable_modifier "
451+
f"on modifier {self.name} "
452+
f"the filter {filter_name} does not exist. Registered filters are: \n"
453+
f"{filter_names}"
454+
)
451455

452456
def executable_modification_applies(
453457
self, exec_mod, filter_name, executable
@@ -465,6 +469,7 @@ def executable_modification_applies(
465469
filter_func = self.get_executable_modifier_filter(filter_name)
466470

467471
if filter_func is not None:
472+
logger.all_msg(f" Filter func = {filter_func}")
468473
apply = filter_func(exec_mod, executable)
469474

470475
return apply

0 commit comments

Comments
 (0)