Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions lib/ramble/docs/tutorials/Workspace_config_command.rst
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ around configuring a workspace. Configuring experiments within a workspace will
not be covered in this tutorial, however we will use pre-configured workspaces
to illustrate the utility of the ``workspace config`` command.

---------------------
------------------------
Create Complex Workspace
---------------------
------------------------

To begin, we will construct a complex workspace to serve as an example of
something we want to share with other users. Before configuring the workspace,
Expand Down
3 changes: 2 additions & 1 deletion lib/ramble/docs/workspace_config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -506,8 +506,9 @@ Variant Expansion

Variants can be expanded like variables into a Spack-like syntax by using the syntax ``{{object_type}::variant::{variant_name}``. For example, a boolean variant with a value of ``True`` formats to ``+bool``, whereas ``False`` formats to ``~bool``. A value-based variant formats to ``key=value``.

^^^^^^^^^^^^^^^^^^^^^^^^^
Variant Expansion Example
~~~~~~~~~~~~~~~~~~~~~~~~~
^^^^^^^^^^^^^^^^^^^^^^^^^

Suppose multiple applications in a workspace use the variant ``openmp`` (boolean) to parameterize their software specs for Spack. We can define it under the workspace ``variants:`` section:

Expand Down
12 changes: 11 additions & 1 deletion lib/ramble/ramble/cmd/workspace.py
Original file line number Diff line number Diff line change
Expand Up @@ -850,6 +850,15 @@ def workspace_info_setup_parser(subparser):


def workspace_info(args):
def _hashable_val(val):
if isinstance(val, (list, tuple)):
return tuple(_hashable_val(x) for x in val)
elif isinstance(val, dict):
return tuple(
(k, _hashable_val(v)) for k, v in sorted(val.items(), key=lambda x: str(x[0]))
)
return val
Comment thread
douglasjacobsen marked this conversation as resolved.
Comment thread
douglasjacobsen marked this conversation as resolved.
Comment thread
douglasjacobsen marked this conversation as resolved.

ws = ramble.cmd.require_active_workspace("workspace info", args.dry_run)

args.where = ramble.filters.resolve_and_apply_filter_groups(args, args.where)
Expand Down Expand Up @@ -1128,9 +1137,10 @@ def workspace_info(args):
for utility_name, utility_conf in utilities.items():
if utility_name not in all_utilities:
all_utilities[utility_name] = set()

conf_tuple = tuple(
sorted(
(k, tuple(v) if isinstance(v, list) else v)
(k, _hashable_val(v))
for k, v in utility_conf.items()
if k != "when"
)
Expand Down
1 change: 1 addition & 0 deletions lib/ramble/ramble/language/workflow_manager_language.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ def workflow_manager_variable(
**kwargs,
):
"""Define a variable for this wm

Args:
name: Name of variable
default: Default value if the variable is not defined
Expand Down
29 changes: 15 additions & 14 deletions lib/ramble/ramble/reports.py
Original file line number Diff line number Diff line change
Expand Up @@ -339,24 +339,25 @@ def filter_exp_results(experiments: list):
def generate_result_index(experiments: list, all_vars=False, where_query=None):
"""Creates an index from the results in the list of experiments

Index format is:
{
"applications": {
application_name: {
workload: {
Index format is::

{
"applications": {
application_name: {
workload: {
"Contexts": set(),
"FOMs": set(),
"Template Variables": set(),
}
}
}
"modifiers": {
modifier_name: {
"Contexts": set(),
"FOMs": set(),
"Template Variables": set(),
}
}
(all other object types)
}
"modifiers": {
modifier_name: {
"Contexts": set(),
"FOMs": set(),
}
(all other object types)
}
"""
result_index: Dict[str, dict] = {}
for obj_name in OBJECT_NAMES.values():
Expand Down
22 changes: 10 additions & 12 deletions lib/ramble/ramble/test/end_to_end/setup_analyze.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,19 +35,17 @@
def test_setup_analyze(test_case_path, workspace_name):
"""test_setup_analyze tests ramble objects that contain a `test_cases` directory.

Specifically, it assumes the following structure for the `test_cases` directory:
Specifically, it assumes the following structure for the `test_cases` directory::

```
test_cases/
└── test_scenario_1 (can have multiple scenarios)
├── artifacts
│ └── <application-name>__<workload_name>__<experiment_name>
│ └── <experiment_name>.out (can have other artifacts)
├── expected_analyze.out
├── setup.yaml (contains workspace commands for setting up the ramble config)
└── configs (either this or the setup.yaml must be present)
└── ramble.yaml (can contain more config files)
```
test_cases/
└── test_scenario_1 (can have multiple scenarios)
├── artifacts
│ └── <application-name>__<workload_name>__<experiment_name>
│ └── <experiment_name>.out (can have other artifacts)
├── expected_analyze.out
├── setup.yaml (contains workspace commands for setting up the ramble config)
└── configs (either this or the setup.yaml must be present)
└── ramble.yaml (can contain more config files)

When writing a Ramble object, if a `test_cases` directory is included, then
the test case will be run to verify the output of analyze.
Expand Down
177 changes: 177 additions & 0 deletions lib/ramble/ramble/test/test_base_classes_extra.py
Original file line number Diff line number Diff line change
Expand Up @@ -430,3 +430,180 @@ def mock_is_available(*args, **kwargs):

ws.dry_run = False
app_inst._bootstrap_utilities(ws)


def test_application_base_bootstrap_utilities_empty_conf_and_none_variables(
mutable_config, mutable_mock_workspace_path, monkeypatch, mock_applications, mock_utilities
):
import ramble.workspace

ws = ramble.workspace.create("test_app_bootstrap")

# Just need a mocked application instance
app_inst = ramble.repository.get("basic")
app_inst.variables = None # Explicitly set to None to test the getattr fallback
Comment thread
douglasjacobsen marked this conversation as resolved.
app_inst.utilities = {} # No utilities, just bypasses

app_inst._bootstrap_utilities(ws)


def test_application_base_is_available_typeerror_fallback(
mutable_config, mutable_mock_workspace_path, monkeypatch
):
import ramble.workspace
from ramble.app.builtin.gromacs.application import Gromacs

workspace = ramble.workspace.create("test_fallback_workspace")
workspace.dry_run = False
app = Gromacs("/tmp/dummy")

class MockExpander:
def expand_var_name(self, name):
return name

def satisfies(self, when_key, variant_set):
return True

def expand_var(self, name):
return name

class MockAppInst:
def __init__(self):
self.variables = {"gromacs_version": "1.2"}
self.expander = MockExpander()

app_inst = MockAppInst()
app._app_inst = app_inst
app.expander = MockExpander()
app._is_experiment = True

class MockUtilityType:
def __init__(self):
# self.bootstrappable removed
self.object_variables = {}

def is_available(self, workspace, min_version=None, max_version=None):
return True

class MockUtilityInst:
def get(self, *args, **kwargs):
return MockUtilityType()

app.required_utilities = {
frozenset(): {
"spack": {
"require_utility": True,
"utility_name": "spack",
"allow_external": "True",
"min_version": "1.0",
"version": "1.0",
"url": "http://foo",
}
}
}
import ramble.repository

monkeypatch.setitem(
ramble.repository.paths, ramble.repository.ObjectTypes.utilities, MockUtilityInst()
)

def mock_bootstrap(workspace, ext_dep_paths):
pass

app.bootstrap_utility = mock_bootstrap

import ramble.config

monkeypatch.setattr(ramble.config, "get", lambda *args, **kwargs: True)

app._bootstrap_utilities(workspace)
assert app._bootstrapped_utility_paths["spack"] == "system"


def test_application_base_validate_versions_typeerror_fallback(
mutable_config, mutable_mock_workspace_path, monkeypatch
):
import ramble.workspace
from ramble.app.builtin.gromacs.application import Gromacs
from ramble.util.logger import logger

workspace = ramble.workspace.create("test_fallback_val_workspace")
workspace.dry_run = False
app = Gromacs("/tmp/dummy")

class MockExpander:
def expand_var_name(self, name):
return name

def satisfies(self, when_key, variant_set):
return True

def expand_var(self, name):
return name

class MockAppInst:
def __init__(self):
self.variables = {"gromacs_version": "1.2"}
self.expander = MockExpander()

app_inst = MockAppInst()
app._app_inst = app_inst
app.expander = MockExpander()
app._is_experiment = True

class MockUtilityType:
def __init__(self):
# self.bootstrappable removed
self.object_variables = {}
self.availability_error = "Mock error"

def is_available(self, workspace, min_version=None, max_version=None, exact_version=None):
return False

def setup_runner_environment(self, workspace, app_inst):
return None

def validate_versions(
self, min_version=None, max_version=None, env=None, origin_name=None, origin_type=None
):
return False

class MockUtilityInst:
def get(self, *args, **kwargs):
return MockUtilityType()

app.required_utilities = {
frozenset(): {
"spack": {
"require_utility": True,
"utility_name": "spack",
"allow_external": "True",
"version": "1.0",
"url": "http://foo",
}
}
}
import ramble.repository

monkeypatch.setitem(
ramble.repository.paths, ramble.repository.ObjectTypes.utilities, MockUtilityInst()
)

def mock_bootstrap(workspace, ext_dep_paths):
pass

app.bootstrap_utility = mock_bootstrap

import ramble.config

monkeypatch.setattr(ramble.config, "get", lambda *args, **kwargs: True)

warn_called = []
monkeypatch.setattr(logger, "warn", lambda msg: warn_called.append(msg))
import ramble.stage

monkeypatch.setattr(ramble.stage.InputStage, "fetch", lambda *args, **kwargs: None)
monkeypatch.setattr(ramble.stage.InputStage, "expand_archive", lambda *args, **kwargs: None)

app._bootstrap_utilities(workspace)
assert any("proceeding due to explicit version request" in msg for msg in warn_called)
Loading
Loading