Skip to content

Commit 7e9e15a

Browse files
authored
Merge pull request #701 from douglasjacobsen/fix-hashes
Fix experiment and workspace inventories and hashes
2 parents e88d902 + 2807327 commit 7e9e15a

File tree

3 files changed

+191
-32
lines changed

3 files changed

+191
-32
lines changed

lib/ramble/ramble/pipeline.py

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -68,15 +68,24 @@ def __init__(self, workspace, filters):
6868
self.workspace.software_environments = self._software_environments
6969
self._experiment_set = workspace.build_experiment_set()
7070

71-
def _construct_hash(self):
72-
"""Hash all of the experiments, construct workspace inventory"""
71+
def _construct_experiment_hashes(self):
72+
"""Hash all of the experiments.
73+
74+
Populate the workspace inventory information with experiment hash data.
75+
"""
7376
for exp, app_inst, _ in self._experiment_set.all_experiments():
7477
app_inst.populate_inventory(
7578
self.workspace,
7679
force_compute=self.force_inventory,
7780
require_exist=self.require_inventory,
7881
)
7982

83+
def _construct_workspace_hash(self):
84+
"""Construct workspace inventory
85+
86+
Assumes experiment hashes are already constructed and populated into
87+
the workspace.
88+
"""
8089
workspace_inventory = os.path.join(self.workspace.root, self.workspace.inventory_file_name)
8190
workspace_hash_file = os.path.join(self.workspace.root, self.workspace.hash_file_name)
8291

@@ -280,7 +289,8 @@ def _prepare(self):
280289
" Make sure your workspace is setup with\n"
281290
" ramble workspace setup"
282291
)
283-
super()._construct_hash()
292+
super()._construct_experiment_hashes()
293+
super()._construct_workspace_hash()
284294
super()._prepare()
285295

286296
def _complete(self):
@@ -328,7 +338,8 @@ def __init__(
328338
)
329339

330340
def _prepare(self):
331-
super()._construct_hash()
341+
super()._construct_experiment_hashes()
342+
super()._construct_workspace_hash()
332343
super()._prepare()
333344

334345
date_str = self.workspace.date_string()
@@ -488,20 +499,22 @@ def __init__(self, workspace, filters):
488499
self.action_string = "Setting up"
489500

490501
def _prepare(self):
502+
# Check if the selected phases require the inventory is successful
503+
if "write_inventory" in self.filters.phases or "*" in self.filters.phases:
504+
self.require_inventory = True
505+
491506
super()._prepare()
492507
experiment_file = open(self.workspace.all_experiments_path, "w+")
493508
shell = ramble.config.get("config:shell")
494509
shell_path = os.path.join("/bin/", shell)
495510
experiment_file.write(f"#!{shell_path}\n")
496511
self.workspace.experiments_script = experiment_file
497512

498-
def _complete(self):
499-
# Check if the selected phases require the inventory is successful
500-
if "write_inventory" in self.filters.phases or "*" in self.filters.phases:
501-
self.require_inventory = True
513+
super()._construct_experiment_hashes()
502514

515+
def _complete(self):
503516
try:
504-
super()._construct_hash()
517+
super()._construct_workspace_hash()
505518
except FileNotFoundError as e:
506519
tty.warn("Unable to construct workspace hash due to missing file")
507520
tty.warn(e)
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
# Copyright 2022-2024 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+
11+
import ramble.workspace
12+
import spack.util.spack_json as sjson
13+
from ramble.main import RambleCommand
14+
from ramble.application import ApplicationBase
15+
16+
17+
workspace = RambleCommand("workspace")
18+
19+
20+
def test_experiment_hashes(mutable_config, mutable_mock_workspace_path, request):
21+
workspace_name = request.node.name
22+
23+
ws1 = ramble.workspace.create(workspace_name)
24+
25+
global_args = ["-w", workspace_name]
26+
27+
workspace(
28+
"generate-config",
29+
"gromacs",
30+
"--wf",
31+
"water_bare",
32+
"-e",
33+
"unit_test",
34+
"-v",
35+
"n_nodes=1",
36+
"-v",
37+
"n_ranks=1",
38+
"-p",
39+
"spack",
40+
global_args=global_args,
41+
)
42+
43+
workspace("concretize", global_args=global_args)
44+
workspace("setup", "--dry-run", global_args=global_args)
45+
46+
experiment_inventory = os.path.join(
47+
ws1.experiment_dir,
48+
"gromacs",
49+
"water_bare",
50+
"unit_test",
51+
ApplicationBase._inventory_file_name,
52+
)
53+
54+
workspace_inventory = os.path.join(ws1.root, ramble.workspace.Workspace.inventory_file_name)
55+
56+
# Test experiment inventory
57+
assert os.path.isfile(experiment_inventory)
58+
with open(experiment_inventory) as f:
59+
data = sjson.load(f)
60+
61+
assert "application_definition" in data
62+
assert data["application_definition"] != ""
63+
assert data["application_definition"] is not None
64+
65+
# Test Attributes
66+
expected_attrs = {"variables", "modifiers", "env_vars", "internals", "chained_experiments"}
67+
assert "attributes" in data
68+
for attr in data["attributes"]:
69+
if attr["name"] in expected_attrs:
70+
assert attr["digest"] != ""
71+
assert attr["digest"] is not None
72+
expected_attrs.remove(attr["name"])
73+
74+
assert len(expected_attrs) == 0
75+
76+
# Test Templates
77+
expected_templates = {"execute_experiment"}
78+
assert "templates" in data
79+
for temp in data["templates"]:
80+
if temp["name"] in expected_templates:
81+
assert temp["digest"] != ""
82+
assert temp["digest"] is not None
83+
expected_templates.remove(temp["name"])
84+
85+
assert len(expected_templates) == 0
86+
87+
# Test software environments
88+
expected_envs = {"software/gromacs"}
89+
assert "software" in data
90+
for env in data["software"]:
91+
if env["name"] in expected_envs:
92+
assert env["digest"] != ""
93+
assert env["digest"] is not None
94+
expected_envs.remove(env["name"])
95+
96+
assert len(expected_envs) == 0
97+
98+
# Test package manager
99+
expected_pkgmans = {"spack"}
100+
assert "package_manager" in data
101+
for pkgman in data["package_manager"]:
102+
if pkgman["name"] in expected_pkgmans:
103+
assert pkgman["digest"] != ""
104+
assert pkgman["digest"] is not None
105+
assert pkgman["version"] != ""
106+
assert pkgman["version"] is not None
107+
expected_pkgmans.remove(pkgman["name"])
108+
109+
assert len(expected_pkgmans) == 0
110+
111+
# Test workspace inventory
112+
assert os.path.isfile(workspace_inventory)
113+
with open(workspace_inventory) as f:
114+
data = sjson.load(f)
115+
116+
# Test experiments
117+
expected_experiments = {"gromacs.water_bare.unit_test"}
118+
119+
assert "experiments" in data
120+
for exp in data["experiments"]:
121+
if exp["name"] in expected_experiments:
122+
assert exp["digest"] != ""
123+
assert exp["digest"] is not None
124+
assert "contents" in exp
125+
expected_experiments.remove(exp["name"])
126+
127+
assert len(expected_experiments) == 0
128+
129+
# Test versions
130+
expected_versions = {"ramble"}
131+
132+
assert "versions" in data
133+
for ver in data["versions"]:
134+
if ver["name"] in expected_versions:
135+
assert ver["digest"] != ""
136+
assert ver["digest"] is not None
137+
expected_versions.remove(ver["name"])
138+
assert len(expected_versions) == 0

var/ramble/repos/builtin/package_managers/environment-modules/package_manager.py

Lines changed: 31 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -35,19 +35,43 @@ class EnvironmentModules(PackageManagerBase):
3535
run_before=["make_experiments"],
3636
)
3737

38+
def _generate_loads_content(self, workspace):
39+
if not hasattr(self, "_load_string"):
40+
app_context = self.app_inst.expander.expand_var_name(
41+
self.keywords.env_name
42+
)
43+
44+
require_env = self.environment_required()
45+
46+
software_envs = workspace.software_environments
47+
software_env = software_envs.render_environment(
48+
app_context, self.app_inst.expander, self, require=require_env
49+
)
50+
51+
load_content = []
52+
53+
if software_env is not None:
54+
for spec in software_envs.package_specs_for_environment(
55+
software_env
56+
):
57+
load_content.append(f"module load {spec}")
58+
59+
self._load_string = "\n".join(load_content)
60+
61+
return self._load_string
62+
3863
def populate_inventory(
3964
self, workspace, force_compute=False, require_exist=False
4065
):
41-
env_path = self.app_inst.expander.env_path
42-
4366
self.app_inst.hash_inventory["package_manager"].append(
4467
{
4568
"name": self.name,
4669
}
4770
)
4871

49-
env_hash = ramble.util.hashing.hash_file(
50-
os.path.join(env_path, "module_loads")
72+
env_path = self.app_inst.expander.env_path
73+
env_hash = ramble.util.hashing.hash_string(
74+
self._generate_loads_content(workspace)
5175
)
5276

5377
self.app_inst.hash_inventory["software"].append(
@@ -58,32 +82,16 @@ def populate_inventory(
5882
)
5983

6084
def _write_module_commands(self, workspace, app_inst=None):
61-
62-
app_context = self.app_inst.expander.expand_var_name(
63-
self.keywords.env_name
64-
)
65-
66-
require_env = self.environment_required()
67-
68-
software_envs = workspace.software_environments
69-
software_env = software_envs.render_environment(
70-
app_context, self.app_inst.expander, self, require=require_env
71-
)
72-
7385
env_path = self.app_inst.expander.env_path
7486

7587
module_file_path = os.path.join(env_path, "module_loads")
7688

7789
fs.mkdirp(env_path)
7890

79-
module_file = open(module_file_path, "w+")
91+
loads_content = self._generate_loads_content(workspace)
8092

81-
if software_env is not None:
82-
for spec in software_envs.package_specs_for_environment(
83-
software_env
84-
):
85-
module_file.write(f"module load {spec}\n")
86-
module_file.close()
93+
with open(module_file_path, "w+") as f:
94+
f.write(loads_content)
8795

8896
register_builtin("module_load", required=True)
8997

0 commit comments

Comments
 (0)