hpcflow 0.1.9__py3-none-any.whl → 0.2.0a271__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- hpcflow/__init__.py +2 -11
- hpcflow/__pyinstaller/__init__.py +5 -0
- hpcflow/__pyinstaller/hook-hpcflow.py +40 -0
- hpcflow/_version.py +1 -1
- hpcflow/app.py +43 -0
- hpcflow/cli.py +2 -462
- hpcflow/data/demo_data_manifest/__init__.py +3 -0
- hpcflow/data/demo_data_manifest/demo_data_manifest.json +6 -0
- hpcflow/data/jinja_templates/test/test_template.txt +8 -0
- hpcflow/data/programs/hello_world/README.md +1 -0
- hpcflow/data/programs/hello_world/hello_world.c +87 -0
- hpcflow/data/programs/hello_world/linux/hello_world +0 -0
- hpcflow/data/programs/hello_world/macos/hello_world +0 -0
- hpcflow/data/programs/hello_world/win/hello_world.exe +0 -0
- hpcflow/data/scripts/__init__.py +1 -0
- hpcflow/data/scripts/bad_script.py +2 -0
- hpcflow/data/scripts/demo_task_1_generate_t1_infile_1.py +8 -0
- hpcflow/data/scripts/demo_task_1_generate_t1_infile_2.py +8 -0
- hpcflow/data/scripts/demo_task_1_parse_p3.py +7 -0
- hpcflow/data/scripts/do_nothing.py +2 -0
- hpcflow/data/scripts/env_specifier_test/input_file_generator_pass_env_spec.py +4 -0
- hpcflow/data/scripts/env_specifier_test/main_script_test_pass_env_spec.py +8 -0
- hpcflow/data/scripts/env_specifier_test/output_file_parser_pass_env_spec.py +4 -0
- hpcflow/data/scripts/env_specifier_test/v1/input_file_generator_basic.py +4 -0
- hpcflow/data/scripts/env_specifier_test/v1/main_script_test_direct_in_direct_out.py +7 -0
- hpcflow/data/scripts/env_specifier_test/v1/output_file_parser_basic.py +4 -0
- hpcflow/data/scripts/env_specifier_test/v2/main_script_test_direct_in_direct_out.py +7 -0
- hpcflow/data/scripts/generate_t1_file_01.py +7 -0
- hpcflow/data/scripts/import_future_script.py +7 -0
- hpcflow/data/scripts/input_file_generator_basic.py +3 -0
- hpcflow/data/scripts/input_file_generator_basic_FAIL.py +3 -0
- hpcflow/data/scripts/input_file_generator_test_stdout_stderr.py +8 -0
- hpcflow/data/scripts/main_script_test_direct_in.py +3 -0
- hpcflow/data/scripts/main_script_test_direct_in_direct_out.py +6 -0
- hpcflow/data/scripts/main_script_test_direct_in_direct_out_2.py +6 -0
- hpcflow/data/scripts/main_script_test_direct_in_direct_out_2_fail_allowed.py +6 -0
- hpcflow/data/scripts/main_script_test_direct_in_direct_out_2_fail_allowed_group.py +7 -0
- hpcflow/data/scripts/main_script_test_direct_in_direct_out_3.py +6 -0
- hpcflow/data/scripts/main_script_test_direct_in_direct_out_all_iters_test.py +15 -0
- hpcflow/data/scripts/main_script_test_direct_in_direct_out_env_spec.py +7 -0
- hpcflow/data/scripts/main_script_test_direct_in_direct_out_labels.py +8 -0
- hpcflow/data/scripts/main_script_test_direct_in_group_direct_out_3.py +6 -0
- hpcflow/data/scripts/main_script_test_direct_in_group_one_fail_direct_out_3.py +6 -0
- hpcflow/data/scripts/main_script_test_direct_sub_param_in_direct_out.py +6 -0
- hpcflow/data/scripts/main_script_test_hdf5_in_obj.py +12 -0
- hpcflow/data/scripts/main_script_test_hdf5_in_obj_2.py +12 -0
- hpcflow/data/scripts/main_script_test_hdf5_in_obj_group.py +12 -0
- hpcflow/data/scripts/main_script_test_hdf5_out_obj.py +11 -0
- hpcflow/data/scripts/main_script_test_json_and_direct_in_json_out.py +14 -0
- hpcflow/data/scripts/main_script_test_json_in_json_and_direct_out.py +17 -0
- hpcflow/data/scripts/main_script_test_json_in_json_out.py +14 -0
- hpcflow/data/scripts/main_script_test_json_in_json_out_labels.py +16 -0
- hpcflow/data/scripts/main_script_test_json_in_obj.py +12 -0
- hpcflow/data/scripts/main_script_test_json_out_FAIL.py +3 -0
- hpcflow/data/scripts/main_script_test_json_out_obj.py +10 -0
- hpcflow/data/scripts/main_script_test_json_sub_param_in_json_out_labels.py +16 -0
- hpcflow/data/scripts/main_script_test_shell_env_vars.py +12 -0
- hpcflow/data/scripts/main_script_test_std_out_std_err.py +6 -0
- hpcflow/data/scripts/output_file_parser_basic.py +3 -0
- hpcflow/data/scripts/output_file_parser_basic_FAIL.py +7 -0
- hpcflow/data/scripts/output_file_parser_test_stdout_stderr.py +8 -0
- hpcflow/data/scripts/parse_t1_file_01.py +4 -0
- hpcflow/data/scripts/script_exit_test.py +5 -0
- hpcflow/data/template_components/__init__.py +1 -0
- hpcflow/data/template_components/command_files.yaml +26 -0
- hpcflow/data/template_components/environments.yaml +13 -0
- hpcflow/data/template_components/parameters.yaml +14 -0
- hpcflow/data/template_components/task_schemas.yaml +139 -0
- hpcflow/data/workflows/workflow_1.yaml +5 -0
- hpcflow/examples.ipynb +1037 -0
- hpcflow/sdk/__init__.py +149 -0
- hpcflow/sdk/app.py +4266 -0
- hpcflow/sdk/cli.py +1479 -0
- hpcflow/sdk/cli_common.py +385 -0
- hpcflow/sdk/config/__init__.py +5 -0
- hpcflow/sdk/config/callbacks.py +246 -0
- hpcflow/sdk/config/cli.py +388 -0
- hpcflow/sdk/config/config.py +1410 -0
- hpcflow/sdk/config/config_file.py +501 -0
- hpcflow/sdk/config/errors.py +272 -0
- hpcflow/sdk/config/types.py +150 -0
- hpcflow/sdk/core/__init__.py +38 -0
- hpcflow/sdk/core/actions.py +3857 -0
- hpcflow/sdk/core/app_aware.py +25 -0
- hpcflow/sdk/core/cache.py +224 -0
- hpcflow/sdk/core/command_files.py +814 -0
- hpcflow/sdk/core/commands.py +424 -0
- hpcflow/sdk/core/element.py +2071 -0
- hpcflow/sdk/core/enums.py +221 -0
- hpcflow/sdk/core/environment.py +256 -0
- hpcflow/sdk/core/errors.py +1043 -0
- hpcflow/sdk/core/execute.py +207 -0
- hpcflow/sdk/core/json_like.py +809 -0
- hpcflow/sdk/core/loop.py +1320 -0
- hpcflow/sdk/core/loop_cache.py +282 -0
- hpcflow/sdk/core/object_list.py +933 -0
- hpcflow/sdk/core/parameters.py +3371 -0
- hpcflow/sdk/core/rule.py +196 -0
- hpcflow/sdk/core/run_dir_files.py +57 -0
- hpcflow/sdk/core/skip_reason.py +7 -0
- hpcflow/sdk/core/task.py +3792 -0
- hpcflow/sdk/core/task_schema.py +993 -0
- hpcflow/sdk/core/test_utils.py +538 -0
- hpcflow/sdk/core/types.py +447 -0
- hpcflow/sdk/core/utils.py +1207 -0
- hpcflow/sdk/core/validation.py +87 -0
- hpcflow/sdk/core/values.py +477 -0
- hpcflow/sdk/core/workflow.py +4820 -0
- hpcflow/sdk/core/zarr_io.py +206 -0
- hpcflow/sdk/data/__init__.py +13 -0
- hpcflow/sdk/data/config_file_schema.yaml +34 -0
- hpcflow/sdk/data/config_schema.yaml +260 -0
- hpcflow/sdk/data/environments_spec_schema.yaml +21 -0
- hpcflow/sdk/data/files_spec_schema.yaml +5 -0
- hpcflow/sdk/data/parameters_spec_schema.yaml +7 -0
- hpcflow/sdk/data/task_schema_spec_schema.yaml +3 -0
- hpcflow/sdk/data/workflow_spec_schema.yaml +22 -0
- hpcflow/sdk/demo/__init__.py +3 -0
- hpcflow/sdk/demo/cli.py +242 -0
- hpcflow/sdk/helper/__init__.py +3 -0
- hpcflow/sdk/helper/cli.py +137 -0
- hpcflow/sdk/helper/helper.py +300 -0
- hpcflow/sdk/helper/watcher.py +192 -0
- hpcflow/sdk/log.py +288 -0
- hpcflow/sdk/persistence/__init__.py +18 -0
- hpcflow/sdk/persistence/base.py +2817 -0
- hpcflow/sdk/persistence/defaults.py +6 -0
- hpcflow/sdk/persistence/discovery.py +39 -0
- hpcflow/sdk/persistence/json.py +954 -0
- hpcflow/sdk/persistence/pending.py +948 -0
- hpcflow/sdk/persistence/store_resource.py +203 -0
- hpcflow/sdk/persistence/types.py +309 -0
- hpcflow/sdk/persistence/utils.py +73 -0
- hpcflow/sdk/persistence/zarr.py +2388 -0
- hpcflow/sdk/runtime.py +320 -0
- hpcflow/sdk/submission/__init__.py +3 -0
- hpcflow/sdk/submission/enums.py +70 -0
- hpcflow/sdk/submission/jobscript.py +2379 -0
- hpcflow/sdk/submission/schedulers/__init__.py +281 -0
- hpcflow/sdk/submission/schedulers/direct.py +233 -0
- hpcflow/sdk/submission/schedulers/sge.py +376 -0
- hpcflow/sdk/submission/schedulers/slurm.py +598 -0
- hpcflow/sdk/submission/schedulers/utils.py +25 -0
- hpcflow/sdk/submission/shells/__init__.py +52 -0
- hpcflow/sdk/submission/shells/base.py +229 -0
- hpcflow/sdk/submission/shells/bash.py +504 -0
- hpcflow/sdk/submission/shells/os_version.py +115 -0
- hpcflow/sdk/submission/shells/powershell.py +352 -0
- hpcflow/sdk/submission/submission.py +1402 -0
- hpcflow/sdk/submission/types.py +140 -0
- hpcflow/sdk/typing.py +194 -0
- hpcflow/sdk/utils/arrays.py +69 -0
- hpcflow/sdk/utils/deferred_file.py +55 -0
- hpcflow/sdk/utils/hashing.py +16 -0
- hpcflow/sdk/utils/patches.py +31 -0
- hpcflow/sdk/utils/strings.py +69 -0
- hpcflow/tests/api/test_api.py +32 -0
- hpcflow/tests/conftest.py +123 -0
- hpcflow/tests/data/__init__.py +0 -0
- hpcflow/tests/data/benchmark_N_elements.yaml +6 -0
- hpcflow/tests/data/benchmark_script_runner.yaml +26 -0
- hpcflow/tests/data/multi_path_sequences.yaml +29 -0
- hpcflow/tests/data/workflow_1.json +10 -0
- hpcflow/tests/data/workflow_1.yaml +5 -0
- hpcflow/tests/data/workflow_1_slurm.yaml +8 -0
- hpcflow/tests/data/workflow_1_wsl.yaml +8 -0
- hpcflow/tests/data/workflow_test_run_abort.yaml +42 -0
- hpcflow/tests/jinja_templates/test_jinja_templates.py +161 -0
- hpcflow/tests/programs/test_programs.py +180 -0
- hpcflow/tests/schedulers/direct_linux/test_direct_linux_submission.py +12 -0
- hpcflow/tests/schedulers/sge/test_sge_submission.py +36 -0
- hpcflow/tests/schedulers/slurm/test_slurm_submission.py +14 -0
- hpcflow/tests/scripts/test_input_file_generators.py +282 -0
- hpcflow/tests/scripts/test_main_scripts.py +1361 -0
- hpcflow/tests/scripts/test_non_snippet_script.py +46 -0
- hpcflow/tests/scripts/test_ouput_file_parsers.py +353 -0
- hpcflow/tests/shells/wsl/test_wsl_submission.py +14 -0
- hpcflow/tests/unit/test_action.py +1066 -0
- hpcflow/tests/unit/test_action_rule.py +24 -0
- hpcflow/tests/unit/test_app.py +132 -0
- hpcflow/tests/unit/test_cache.py +46 -0
- hpcflow/tests/unit/test_cli.py +172 -0
- hpcflow/tests/unit/test_command.py +377 -0
- hpcflow/tests/unit/test_config.py +195 -0
- hpcflow/tests/unit/test_config_file.py +162 -0
- hpcflow/tests/unit/test_element.py +666 -0
- hpcflow/tests/unit/test_element_iteration.py +88 -0
- hpcflow/tests/unit/test_element_set.py +158 -0
- hpcflow/tests/unit/test_group.py +115 -0
- hpcflow/tests/unit/test_input_source.py +1479 -0
- hpcflow/tests/unit/test_input_value.py +398 -0
- hpcflow/tests/unit/test_jobscript_unit.py +757 -0
- hpcflow/tests/unit/test_json_like.py +1247 -0
- hpcflow/tests/unit/test_loop.py +2674 -0
- hpcflow/tests/unit/test_meta_task.py +325 -0
- hpcflow/tests/unit/test_multi_path_sequences.py +259 -0
- hpcflow/tests/unit/test_object_list.py +116 -0
- hpcflow/tests/unit/test_parameter.py +243 -0
- hpcflow/tests/unit/test_persistence.py +664 -0
- hpcflow/tests/unit/test_resources.py +243 -0
- hpcflow/tests/unit/test_run.py +286 -0
- hpcflow/tests/unit/test_run_directories.py +29 -0
- hpcflow/tests/unit/test_runtime.py +9 -0
- hpcflow/tests/unit/test_schema_input.py +372 -0
- hpcflow/tests/unit/test_shell.py +129 -0
- hpcflow/tests/unit/test_slurm.py +39 -0
- hpcflow/tests/unit/test_submission.py +502 -0
- hpcflow/tests/unit/test_task.py +2560 -0
- hpcflow/tests/unit/test_task_schema.py +182 -0
- hpcflow/tests/unit/test_utils.py +616 -0
- hpcflow/tests/unit/test_value_sequence.py +549 -0
- hpcflow/tests/unit/test_values.py +91 -0
- hpcflow/tests/unit/test_workflow.py +827 -0
- hpcflow/tests/unit/test_workflow_template.py +186 -0
- hpcflow/tests/unit/utils/test_arrays.py +40 -0
- hpcflow/tests/unit/utils/test_deferred_file_writer.py +34 -0
- hpcflow/tests/unit/utils/test_hashing.py +65 -0
- hpcflow/tests/unit/utils/test_patches.py +5 -0
- hpcflow/tests/unit/utils/test_redirect_std.py +50 -0
- hpcflow/tests/unit/utils/test_strings.py +97 -0
- hpcflow/tests/workflows/__init__.py +0 -0
- hpcflow/tests/workflows/test_directory_structure.py +31 -0
- hpcflow/tests/workflows/test_jobscript.py +355 -0
- hpcflow/tests/workflows/test_run_status.py +198 -0
- hpcflow/tests/workflows/test_skip_downstream.py +696 -0
- hpcflow/tests/workflows/test_submission.py +140 -0
- hpcflow/tests/workflows/test_workflows.py +564 -0
- hpcflow/tests/workflows/test_zip.py +18 -0
- hpcflow/viz_demo.ipynb +6794 -0
- hpcflow-0.2.0a271.dist-info/LICENSE +375 -0
- hpcflow-0.2.0a271.dist-info/METADATA +65 -0
- hpcflow-0.2.0a271.dist-info/RECORD +237 -0
- {hpcflow-0.1.9.dist-info → hpcflow-0.2.0a271.dist-info}/WHEEL +4 -5
- hpcflow-0.2.0a271.dist-info/entry_points.txt +6 -0
- hpcflow/api.py +0 -458
- hpcflow/archive/archive.py +0 -308
- hpcflow/archive/cloud/cloud.py +0 -47
- hpcflow/archive/cloud/errors.py +0 -9
- hpcflow/archive/cloud/providers/dropbox.py +0 -432
- hpcflow/archive/errors.py +0 -5
- hpcflow/base_db.py +0 -4
- hpcflow/config.py +0 -232
- hpcflow/copytree.py +0 -66
- hpcflow/data/examples/_config.yml +0 -14
- hpcflow/data/examples/damask/demo/1.run.yml +0 -4
- hpcflow/data/examples/damask/demo/2.process.yml +0 -29
- hpcflow/data/examples/damask/demo/geom.geom +0 -2052
- hpcflow/data/examples/damask/demo/load.load +0 -1
- hpcflow/data/examples/damask/demo/material.config +0 -185
- hpcflow/data/examples/damask/inputs/geom.geom +0 -2052
- hpcflow/data/examples/damask/inputs/load.load +0 -1
- hpcflow/data/examples/damask/inputs/material.config +0 -185
- hpcflow/data/examples/damask/profiles/_variable_lookup.yml +0 -21
- hpcflow/data/examples/damask/profiles/damask.yml +0 -4
- hpcflow/data/examples/damask/profiles/damask_process.yml +0 -8
- hpcflow/data/examples/damask/profiles/damask_run.yml +0 -5
- hpcflow/data/examples/damask/profiles/default.yml +0 -6
- hpcflow/data/examples/thinking.yml +0 -177
- hpcflow/errors.py +0 -2
- hpcflow/init_db.py +0 -37
- hpcflow/models.py +0 -2549
- hpcflow/nesting.py +0 -9
- hpcflow/profiles.py +0 -455
- hpcflow/project.py +0 -81
- hpcflow/scheduler.py +0 -323
- hpcflow/utils.py +0 -103
- hpcflow/validation.py +0 -167
- hpcflow/variables.py +0 -544
- hpcflow-0.1.9.dist-info/METADATA +0 -168
- hpcflow-0.1.9.dist-info/RECORD +0 -45
- hpcflow-0.1.9.dist-info/entry_points.txt +0 -8
- hpcflow-0.1.9.dist-info/top_level.txt +0 -1
- /hpcflow/{archive → data/jinja_templates}/__init__.py +0 -0
- /hpcflow/{archive/cloud → data/programs}/__init__.py +0 -0
- /hpcflow/{archive/cloud/providers → data/workflows}/__init__.py +0 -0
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
from hpcflow.app import app as hf
|
|
7
|
+
from hpcflow.sdk.core.parameters import NullDefault
|
|
8
|
+
from hpcflow.sdk.core.test_utils import P1_parameter_cls as P1
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from hpcflow.sdk.core.json_like import JSONDocument
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@pytest.fixture
|
|
15
|
+
def null_config(tmp_path):
|
|
16
|
+
if not hf.is_config_loaded:
|
|
17
|
+
hf.load_config(config_dir=tmp_path)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def test_null_default_value(null_config) -> None:
|
|
21
|
+
p1 = hf.Parameter("p1")
|
|
22
|
+
p1_inp = hf.SchemaInput(parameter=p1)
|
|
23
|
+
assert "default_value" not in p1_inp.labels[""]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def test_null_default_value_property(null_config) -> None:
|
|
27
|
+
p1 = hf.Parameter("p1")
|
|
28
|
+
p1_inp = hf.SchemaInput(parameter=p1)
|
|
29
|
+
assert p1_inp.default_value is NullDefault.NULL
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def test_none_default_value(null_config) -> None:
|
|
33
|
+
"""A `None` default value is set with a value of `None`"""
|
|
34
|
+
p1 = hf.Parameter("p1")
|
|
35
|
+
p1_inp = hf.SchemaInput(parameter=p1, default_value=None)
|
|
36
|
+
def_val_exp = hf.InputValue(parameter=p1, label="", value=None)
|
|
37
|
+
def_val_exp._schema_input = p1_inp
|
|
38
|
+
assert p1_inp.labels[""]["default_value"].value == def_val_exp.value
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def test_from_json_like_labels_and_default(null_config) -> None:
|
|
42
|
+
json_like: JSONDocument = {
|
|
43
|
+
"parameter": "p1",
|
|
44
|
+
"labels": {"0": {}},
|
|
45
|
+
"default_value": None,
|
|
46
|
+
}
|
|
47
|
+
inp = hf.SchemaInput.from_json_like(
|
|
48
|
+
json_like=json_like,
|
|
49
|
+
shared_data=hf.template_components,
|
|
50
|
+
)
|
|
51
|
+
assert inp.labels["0"]["default_value"].value == None
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def test_element_get_removes_schema_param_trivial_label(null_config, tmp_path: Path):
|
|
55
|
+
p1_val = 101
|
|
56
|
+
label = "my_label"
|
|
57
|
+
s1 = hf.TaskSchema(
|
|
58
|
+
objective="t1", inputs=[hf.SchemaInput(parameter="p1", labels={label: {}})]
|
|
59
|
+
)
|
|
60
|
+
t1 = hf.Task(schema=[s1], inputs=[hf.InputValue("p1", p1_val, label=label)])
|
|
61
|
+
wk = hf.Workflow.from_template_data(
|
|
62
|
+
tasks=[t1],
|
|
63
|
+
path=tmp_path,
|
|
64
|
+
template_name="temp",
|
|
65
|
+
)
|
|
66
|
+
assert f"inputs.p1[{label}]" in wk.tasks[0].elements[0].get_data_idx("inputs")
|
|
67
|
+
assert wk.tasks[0].elements[0].get("inputs") == {"p1": p1_val}
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def test_element_inputs_removes_schema_param_trivial_label(null_config, tmp_path: Path):
|
|
71
|
+
p1_val = 101
|
|
72
|
+
label = "my_label"
|
|
73
|
+
s1 = hf.TaskSchema(
|
|
74
|
+
objective="t1",
|
|
75
|
+
inputs=[hf.SchemaInput(parameter="p1", labels={label: {}})],
|
|
76
|
+
actions=[
|
|
77
|
+
hf.Action(
|
|
78
|
+
environments=[hf.ActionEnvironment("null_env")],
|
|
79
|
+
commands=[hf.Command(command=f"echo <<parameter:p1[{label}]>>")],
|
|
80
|
+
),
|
|
81
|
+
],
|
|
82
|
+
)
|
|
83
|
+
t1 = hf.Task(schema=[s1], inputs=[hf.InputValue("p1", p1_val, label=label)])
|
|
84
|
+
wk = hf.Workflow.from_template_data(
|
|
85
|
+
tasks=[t1],
|
|
86
|
+
path=tmp_path,
|
|
87
|
+
template_name="temp",
|
|
88
|
+
)
|
|
89
|
+
element = wk.tasks[0].elements[0]
|
|
90
|
+
# element inputs:
|
|
91
|
+
assert element.inputs._get_prefixed_names() == ["p1"]
|
|
92
|
+
|
|
93
|
+
# element iteration inputs:
|
|
94
|
+
assert element.iterations[0].inputs._get_prefixed_names() == ["p1"]
|
|
95
|
+
|
|
96
|
+
# run inputs:
|
|
97
|
+
assert element.iterations[0].action_runs[0].inputs._get_prefixed_names() == ["p1"]
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def test_element_get_does_not_removes_multiple_schema_param_label(
|
|
101
|
+
null_config, tmp_path: Path
|
|
102
|
+
):
|
|
103
|
+
p1_val = 101
|
|
104
|
+
label = "my_label"
|
|
105
|
+
s1 = hf.TaskSchema(
|
|
106
|
+
objective="t1",
|
|
107
|
+
inputs=[hf.SchemaInput(parameter="p1", labels={label: {}}, multiple=True)],
|
|
108
|
+
)
|
|
109
|
+
t1 = hf.Task(schema=[s1], inputs=[hf.InputValue("p1", p1_val, label=label)])
|
|
110
|
+
wk = hf.Workflow.from_template_data(
|
|
111
|
+
tasks=[t1],
|
|
112
|
+
path=tmp_path,
|
|
113
|
+
template_name="temp",
|
|
114
|
+
)
|
|
115
|
+
assert f"inputs.p1[{label}]" in wk.tasks[0].elements[0].get_data_idx("inputs")
|
|
116
|
+
assert wk.tasks[0].elements[0].get("inputs") == {f"p1[{label}]": p1_val}
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def test_element_inputs_does_not_remove_multiple_schema_param_label(
|
|
120
|
+
null_config, tmp_path: Path
|
|
121
|
+
):
|
|
122
|
+
p1_val = 101
|
|
123
|
+
label = "my_label"
|
|
124
|
+
s1 = hf.TaskSchema(
|
|
125
|
+
objective="t1",
|
|
126
|
+
inputs=[hf.SchemaInput(parameter="p1", labels={label: {}}, multiple=True)],
|
|
127
|
+
actions=[
|
|
128
|
+
hf.Action(
|
|
129
|
+
environments=[hf.ActionEnvironment("null_env")],
|
|
130
|
+
commands=[hf.Command(command=f"echo <<parameter:p1[{label}]>>")],
|
|
131
|
+
),
|
|
132
|
+
],
|
|
133
|
+
)
|
|
134
|
+
t1 = hf.Task(schema=[s1], inputs=[hf.InputValue("p1", p1_val, label=label)])
|
|
135
|
+
wk = hf.Workflow.from_template_data(
|
|
136
|
+
tasks=[t1],
|
|
137
|
+
path=tmp_path,
|
|
138
|
+
template_name="temp",
|
|
139
|
+
)
|
|
140
|
+
element = wk.tasks[0].elements[0]
|
|
141
|
+
# element inputs:
|
|
142
|
+
assert element.inputs._get_prefixed_names() == [f"p1[{label}]"]
|
|
143
|
+
|
|
144
|
+
# element iteration inputs:
|
|
145
|
+
assert element.iterations[0].inputs._get_prefixed_names() == [f"p1[{label}]"]
|
|
146
|
+
|
|
147
|
+
# run inputs:
|
|
148
|
+
assert element.iterations[0].action_runs[0].inputs._get_prefixed_names() == [
|
|
149
|
+
f"p1[{label}]"
|
|
150
|
+
]
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def test_get_input_values_for_multiple_schema_input_single_label(
|
|
154
|
+
null_config, tmp_path: Path
|
|
155
|
+
):
|
|
156
|
+
p1_val = 101
|
|
157
|
+
label = "my_label"
|
|
158
|
+
s1 = hf.TaskSchema(
|
|
159
|
+
objective="t1",
|
|
160
|
+
inputs=[
|
|
161
|
+
hf.SchemaInput(parameter="p1", labels={label: {}}, multiple=False),
|
|
162
|
+
hf.SchemaInput(parameter="p2", default_value=201),
|
|
163
|
+
],
|
|
164
|
+
actions=[
|
|
165
|
+
hf.Action(
|
|
166
|
+
environments=[hf.ActionEnvironment("null_env")],
|
|
167
|
+
commands=[
|
|
168
|
+
hf.Command(command=f"echo <<parameter:p1[{label}]>> <<parameter:p2>>")
|
|
169
|
+
],
|
|
170
|
+
),
|
|
171
|
+
],
|
|
172
|
+
)
|
|
173
|
+
t1 = hf.Task(schema=[s1], inputs=[hf.InputValue("p1", p1_val, label=label)])
|
|
174
|
+
wk = hf.Workflow.from_template_data(
|
|
175
|
+
tasks=[t1],
|
|
176
|
+
path=tmp_path,
|
|
177
|
+
template_name="temp",
|
|
178
|
+
)
|
|
179
|
+
run = wk.tasks[0].elements[0].iterations[0].action_runs[0]
|
|
180
|
+
assert run.get_data_in_values() == {"p2": 201, "p1": 101}
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def test_get_input_values_subset(null_config, tmp_path: Path):
|
|
184
|
+
p1_val = 101
|
|
185
|
+
s1 = hf.TaskSchema(
|
|
186
|
+
objective="t1",
|
|
187
|
+
inputs=[
|
|
188
|
+
hf.SchemaInput(parameter="p1"),
|
|
189
|
+
hf.SchemaInput(parameter="p2", default_value=201),
|
|
190
|
+
],
|
|
191
|
+
actions=[
|
|
192
|
+
hf.Action(
|
|
193
|
+
environments=[hf.ActionEnvironment("null_env")],
|
|
194
|
+
commands=[hf.Command(command=f"echo <<parameter:p1>> <<parameter:p2>>")],
|
|
195
|
+
),
|
|
196
|
+
],
|
|
197
|
+
)
|
|
198
|
+
t1 = hf.Task(schema=[s1], inputs=[hf.InputValue("p1", p1_val)])
|
|
199
|
+
wk = hf.Workflow.from_template_data(
|
|
200
|
+
tasks=[t1],
|
|
201
|
+
path=tmp_path,
|
|
202
|
+
template_name="temp",
|
|
203
|
+
)
|
|
204
|
+
run = wk.tasks[0].elements[0].iterations[0].action_runs[0]
|
|
205
|
+
assert run.get_data_in_values(data_in_keys=("inputs.p1",)) == {"p1": 101}
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def test_get_input_values_subset_labelled_label_dict_False(null_config, tmp_path: Path):
|
|
209
|
+
p1_val = 101
|
|
210
|
+
s1 = hf.TaskSchema(
|
|
211
|
+
objective="t1",
|
|
212
|
+
inputs=[
|
|
213
|
+
hf.SchemaInput(parameter="p1", labels={"one": {}}, multiple=True),
|
|
214
|
+
hf.SchemaInput(
|
|
215
|
+
parameter="p2",
|
|
216
|
+
labels={"two": {}},
|
|
217
|
+
multiple=False,
|
|
218
|
+
default_value=201,
|
|
219
|
+
),
|
|
220
|
+
],
|
|
221
|
+
actions=[
|
|
222
|
+
hf.Action(
|
|
223
|
+
environments=[hf.ActionEnvironment("null_env")],
|
|
224
|
+
commands=[
|
|
225
|
+
hf.Command(
|
|
226
|
+
command=f"echo <<parameter:p1[one]>> <<parameter:p2[two]>>"
|
|
227
|
+
)
|
|
228
|
+
],
|
|
229
|
+
),
|
|
230
|
+
],
|
|
231
|
+
)
|
|
232
|
+
t1 = hf.Task(schema=[s1], inputs=[hf.InputValue("p1", p1_val, label="one")])
|
|
233
|
+
wk = hf.Workflow.from_template_data(
|
|
234
|
+
tasks=[t1],
|
|
235
|
+
path=tmp_path,
|
|
236
|
+
template_name="temp",
|
|
237
|
+
)
|
|
238
|
+
run = wk.tasks[0].elements[0].iterations[0].action_runs[0]
|
|
239
|
+
assert run.get_data_in_values(data_in_keys=("inputs.p1[one]",), label_dict=False) == {
|
|
240
|
+
"p1[one]": 101
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def test_get_input_values_subset_labelled_label_dict_True(null_config, tmp_path: Path):
|
|
245
|
+
p1_val = 101
|
|
246
|
+
s1 = hf.TaskSchema(
|
|
247
|
+
objective="t1",
|
|
248
|
+
inputs=[
|
|
249
|
+
hf.SchemaInput(parameter="p1", labels={"one": {}}, multiple=True),
|
|
250
|
+
hf.SchemaInput(
|
|
251
|
+
parameter="p2",
|
|
252
|
+
labels={"two": {}},
|
|
253
|
+
multiple=False,
|
|
254
|
+
default_value=201,
|
|
255
|
+
),
|
|
256
|
+
],
|
|
257
|
+
actions=[
|
|
258
|
+
hf.Action(
|
|
259
|
+
environments=[hf.ActionEnvironment("null_env")],
|
|
260
|
+
commands=[
|
|
261
|
+
hf.Command(
|
|
262
|
+
command=f"echo <<parameter:p1[one]>> <<parameter:p2[two]>>"
|
|
263
|
+
)
|
|
264
|
+
],
|
|
265
|
+
),
|
|
266
|
+
],
|
|
267
|
+
)
|
|
268
|
+
t1 = hf.Task(schema=[s1], inputs=[hf.InputValue("p1", p1_val, label="one")])
|
|
269
|
+
wk = hf.Workflow.from_template_data(
|
|
270
|
+
tasks=[t1],
|
|
271
|
+
path=tmp_path,
|
|
272
|
+
template_name="temp",
|
|
273
|
+
)
|
|
274
|
+
run = wk.tasks[0].elements[0].iterations[0].action_runs[0]
|
|
275
|
+
assert run.get_data_in_values(data_in_keys=("inputs.p1[one]",), label_dict=True) == {
|
|
276
|
+
"p1": {"one": 101}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def test_get_input_values_for_multiple_schema_input(null_config, tmp_path: Path):
|
|
281
|
+
p1_val = 101
|
|
282
|
+
label = "my_label"
|
|
283
|
+
s1 = hf.TaskSchema(
|
|
284
|
+
objective="t1",
|
|
285
|
+
inputs=[
|
|
286
|
+
hf.SchemaInput(parameter="p1", labels={label: {}}, multiple=True),
|
|
287
|
+
hf.SchemaInput(parameter="p2", default_value=201),
|
|
288
|
+
],
|
|
289
|
+
actions=[
|
|
290
|
+
hf.Action(
|
|
291
|
+
environments=[hf.ActionEnvironment("null_env")],
|
|
292
|
+
commands=[
|
|
293
|
+
hf.Command(command=f"echo <<parameter:p1[{label}]>> <<parameter:p2>>")
|
|
294
|
+
],
|
|
295
|
+
),
|
|
296
|
+
],
|
|
297
|
+
)
|
|
298
|
+
t1 = hf.Task(schema=[s1], inputs=[hf.InputValue("p1", p1_val, label=label)])
|
|
299
|
+
wk = hf.Workflow.from_template_data(
|
|
300
|
+
tasks=[t1],
|
|
301
|
+
path=tmp_path,
|
|
302
|
+
template_name="temp",
|
|
303
|
+
)
|
|
304
|
+
run = wk.tasks[0].elements[0].iterations[0].action_runs[0]
|
|
305
|
+
assert run.get_data_in_values() == {"p2": 201, "p1": {label: 101}}
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def test_get_input_values_for_multiple_schema_input_with_object(
|
|
309
|
+
null_config, tmp_path: Path
|
|
310
|
+
):
|
|
311
|
+
p1_val = P1(a=101)
|
|
312
|
+
label = "my_label"
|
|
313
|
+
s1 = hf.TaskSchema(
|
|
314
|
+
objective="t1",
|
|
315
|
+
inputs=[
|
|
316
|
+
hf.SchemaInput(parameter="p1c", labels={label: {}}, multiple=True),
|
|
317
|
+
hf.SchemaInput(parameter="p2", default_value=201),
|
|
318
|
+
],
|
|
319
|
+
actions=[
|
|
320
|
+
hf.Action(
|
|
321
|
+
environments=[hf.ActionEnvironment("null_env")],
|
|
322
|
+
commands=[
|
|
323
|
+
hf.Command(
|
|
324
|
+
command=f"echo <<parameter:p1c[{label}]>> <<parameter:p2>>"
|
|
325
|
+
)
|
|
326
|
+
],
|
|
327
|
+
),
|
|
328
|
+
],
|
|
329
|
+
)
|
|
330
|
+
t1 = hf.Task(schema=[s1], inputs=[hf.InputValue("p1c", p1_val, label=label)])
|
|
331
|
+
wk = hf.Workflow.from_template_data(
|
|
332
|
+
tasks=[t1],
|
|
333
|
+
path=tmp_path,
|
|
334
|
+
template_name="temp",
|
|
335
|
+
)
|
|
336
|
+
run = wk.tasks[0].elements[0].iterations[0].action_runs[0]
|
|
337
|
+
assert run.get_data_in_values() == {"p2": 201, "p1c": {label: p1_val}}
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
@pytest.mark.integration
|
|
341
|
+
def test_get_input_values_all_iterations(null_config, tmp_path: Path):
|
|
342
|
+
s1 = hf.TaskSchema(
|
|
343
|
+
objective="t1",
|
|
344
|
+
inputs=[hf.SchemaInput(parameter=hf.Parameter("p1"))],
|
|
345
|
+
outputs=[hf.SchemaOutput(parameter=hf.Parameter("p1"))],
|
|
346
|
+
actions=[
|
|
347
|
+
hf.Action(
|
|
348
|
+
script="<<script:main_script_test_direct_in_direct_out_all_iters_test.py>>",
|
|
349
|
+
script_data_in={"p1": {"format": "direct", "all_iterations": True}},
|
|
350
|
+
script_data_out="direct",
|
|
351
|
+
script_exe="python_script",
|
|
352
|
+
environments=[hf.ActionEnvironment(environment="python_env")],
|
|
353
|
+
)
|
|
354
|
+
],
|
|
355
|
+
)
|
|
356
|
+
p1_val = 101
|
|
357
|
+
t1 = hf.Task(schema=s1, inputs={"p1": p1_val})
|
|
358
|
+
wk = hf.Workflow.from_template_data(
|
|
359
|
+
template_name="main_script_test",
|
|
360
|
+
path=tmp_path,
|
|
361
|
+
tasks=[t1],
|
|
362
|
+
loops=[hf.Loop(tasks=[0], num_iterations=3)],
|
|
363
|
+
)
|
|
364
|
+
wk.submit(wait=True, add_to_known=False)
|
|
365
|
+
run = wk.tasks[0].elements[0].iterations[-1].actions[0].runs[-1]
|
|
366
|
+
assert run.get_data_in_values({"inputs.p1": {"all_iterations": True}}) == {
|
|
367
|
+
"p1": {
|
|
368
|
+
"iteration_0": {"loop_idx": {"loop_0": 0}, "value": 101},
|
|
369
|
+
"iteration_1": {"loop_idx": {"loop_0": 1}, "value": 102},
|
|
370
|
+
"iteration_2": {"loop_idx": {"loop_0": 2}, "value": 204},
|
|
371
|
+
}
|
|
372
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
import hpcflow.app as hf
|
|
8
|
+
from hpcflow.sdk.submission.shells import ALL_SHELLS
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_process_JS_header_args_app_invoc_windows_powershell() -> None:
|
|
12
|
+
"""
|
|
13
|
+
Three types of invocation commands exist:
|
|
14
|
+
1. the frozen app executable
|
|
15
|
+
2. a python executable calling the hpcflow package CLI module
|
|
16
|
+
3. a python executable calling the hpcflow entry point command
|
|
17
|
+
|
|
18
|
+
For the purposes of this test, 2. and 3. are equivalent. We test the expected output
|
|
19
|
+
of `WindowsPowerShell.process_JS_header_args` on the `app_invoc` key for all
|
|
20
|
+
variations. If there is a space in the executable, we expect the call operator (`&`)
|
|
21
|
+
to be used, followed by a single-quoted executable path. We expect executable
|
|
22
|
+
arguments (e.g. the hpcflow package CLI module path) to be double-quoted, regardless
|
|
23
|
+
of whether they include spaces.
|
|
24
|
+
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
app_invocs = [
|
|
28
|
+
("C:\\path\to\frozen\app.exe",),
|
|
29
|
+
("C:\\path\\to\\frozen\\app with spaces.exe",),
|
|
30
|
+
("C:\\path\\to\\python.exe", "C:\\path\\to\\hpcflow\\cli.py"),
|
|
31
|
+
("C:\\path\\to\\python with spaces.exe", "C:\\path\\to\\hpcflow\\cli.py"),
|
|
32
|
+
(
|
|
33
|
+
"C:\\path\\to\\python with spaces.exe",
|
|
34
|
+
"C:\\path\\to\\hpcflow\\cli with spaces.py",
|
|
35
|
+
),
|
|
36
|
+
]
|
|
37
|
+
expected = [
|
|
38
|
+
"C:\\path\to\frozen\app.exe",
|
|
39
|
+
"& 'C:\\path\\to\\frozen\\app with spaces.exe'",
|
|
40
|
+
'C:\\path\\to\\python.exe "C:\\path\\to\\hpcflow\\cli.py"',
|
|
41
|
+
"& 'C:\\path\\to\\python with spaces.exe' \"C:\\path\\to\\hpcflow\\cli.py\"",
|
|
42
|
+
"& 'C:\\path\\to\\python with spaces.exe' \"C:\\path\\to\\hpcflow\\cli with spaces.py\"",
|
|
43
|
+
]
|
|
44
|
+
shell = ALL_SHELLS["powershell"]["nt"]()
|
|
45
|
+
for i, j in zip(app_invocs, expected):
|
|
46
|
+
processed = shell.process_JS_header_args({"app_invoc": i})
|
|
47
|
+
assert processed["app_invoc"] == j
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def test_process_JS_header_args_app_invoc_bash() -> None:
|
|
51
|
+
"""
|
|
52
|
+
Three types of invocation commands exist:
|
|
53
|
+
1. the frozen app executable
|
|
54
|
+
2. a python executable calling the hpcflow package CLI module
|
|
55
|
+
3. a python executable calling the hpcflow entry point command
|
|
56
|
+
|
|
57
|
+
For the purposes of this test, 2. and 3. are equivalent. We test the expected output
|
|
58
|
+
of `Bash.process_JS_header_args` on the `app_invoc` key for all
|
|
59
|
+
variations. If there is a space in the executable, we expect spaces to be escaped
|
|
60
|
+
using the backslash. We expect executable arguments (e.g. the hpcflow package CLI
|
|
61
|
+
module path) to be double-quoted, regardless of whether they include spaces.
|
|
62
|
+
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
app_invocs = [
|
|
66
|
+
("/mnt/path/to/frozen/app.exe",),
|
|
67
|
+
("/mnt/path/to/frozen/app with spaces.exe",),
|
|
68
|
+
("/mnt/path/to/python.exe", "/mnt/path/to/hpcflow/cli.py"),
|
|
69
|
+
("/mnt/path/to/python with spaces.exe", "/mnt/path/to/hpcflow/cli.py"),
|
|
70
|
+
(
|
|
71
|
+
"/mnt/path/to/python with spaces.exe",
|
|
72
|
+
"/mnt/path/to/hpcflow/cli with spaces.py",
|
|
73
|
+
),
|
|
74
|
+
]
|
|
75
|
+
expected = [
|
|
76
|
+
"/mnt/path/to/frozen/app.exe",
|
|
77
|
+
"/mnt/path/to/frozen/app\\ with\\ spaces.exe",
|
|
78
|
+
'/mnt/path/to/python.exe "/mnt/path/to/hpcflow/cli.py"',
|
|
79
|
+
'/mnt/path/to/python\\ with\\ spaces.exe "/mnt/path/to/hpcflow/cli.py"',
|
|
80
|
+
'/mnt/path/to/python\\ with\\ spaces.exe "/mnt/path/to/hpcflow/cli with spaces.py"',
|
|
81
|
+
]
|
|
82
|
+
shell = ALL_SHELLS["bash"]["posix"]()
|
|
83
|
+
for i, j in zip(app_invocs, expected):
|
|
84
|
+
processed = shell.process_JS_header_args({"app_invoc": i})
|
|
85
|
+
assert processed["app_invoc"] == j
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def test_format_array_powershell():
|
|
89
|
+
shell = ALL_SHELLS["powershell"]["nt"]()
|
|
90
|
+
assert shell.format_array([1, 2, 3]) == "@(1, 2, 3)"
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def test_format_array_get_item_powershell():
|
|
94
|
+
shell = ALL_SHELLS["powershell"]["nt"]()
|
|
95
|
+
assert shell.format_array_get_item("my_arr", 3) == "$my_arr[3]"
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def test_format_array_bash():
|
|
99
|
+
shell = ALL_SHELLS["bash"]["posix"]()
|
|
100
|
+
assert shell.format_array([1, 2, 3]) == "(1 2 3)"
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def test_format_array_get_item_bash():
|
|
104
|
+
shell = ALL_SHELLS["bash"]["posix"]()
|
|
105
|
+
assert shell.format_array_get_item("my_arr", 3) == r"${my_arr[3]}"
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@pytest.mark.integration
|
|
109
|
+
@pytest.mark.skipif(condition=sys.platform == "win32", reason="This is a bash-only test.")
|
|
110
|
+
def test_executable_args_bash_login(null_config, tmp_path: Path):
|
|
111
|
+
"""Check if we provide a `--login` argument to the shell `executable_args`, we end up
|
|
112
|
+
in a login shell on bash."""
|
|
113
|
+
|
|
114
|
+
cmd = "shopt -q login_shell && echo 'Login shell' || echo 'Not login shell'"
|
|
115
|
+
s1 = hf.TaskSchema(
|
|
116
|
+
objective="t1",
|
|
117
|
+
actions=[hf.Action(commands=[hf.Command(command=cmd)])],
|
|
118
|
+
)
|
|
119
|
+
t1 = hf.Task(
|
|
120
|
+
schema=s1,
|
|
121
|
+
resources={"any": {"shell_args": {"executable_args": ["--login"]}}},
|
|
122
|
+
)
|
|
123
|
+
wkt = hf.WorkflowTemplate(name="test_bash_login", tasks=[t1])
|
|
124
|
+
wk = hf.Workflow.from_template(
|
|
125
|
+
template=wkt,
|
|
126
|
+
path=tmp_path,
|
|
127
|
+
)
|
|
128
|
+
wk.submit(wait=True, status=False, add_to_known=False)
|
|
129
|
+
assert wk.submissions[0].jobscripts[0].get_stdout().strip() == "Login shell"
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from hpcflow.sdk.submission.schedulers.slurm import SlurmPosix
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def test_parse_job_ID_simple() -> None:
|
|
6
|
+
assert SlurmPosix._parse_job_IDs("123") == ("123", None)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def test_parse_job_ID_simple_array_item() -> None:
|
|
10
|
+
assert SlurmPosix._parse_job_IDs("123_10") == ("123", [9])
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def test_parse_job_ID_array_simple_range() -> None:
|
|
14
|
+
assert SlurmPosix._parse_job_IDs("3397752_[9-11]") == ("3397752", [8, 9, 10])
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def test_parse_job_ID_array_simple_multiple_range() -> None:
|
|
18
|
+
assert SlurmPosix._parse_job_IDs("49203_[3-5,9-11]") == (
|
|
19
|
+
"49203",
|
|
20
|
+
[2, 3, 4, 8, 9, 10],
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_parse_job_ID_array_simple_mixed_range() -> None:
|
|
25
|
+
assert SlurmPosix._parse_job_IDs("30627658_[5,8-10]") == (
|
|
26
|
+
"30627658",
|
|
27
|
+
[4, 7, 8, 9],
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def test_parse_job_ID_array_simple_range_with_max_concurrent() -> None:
|
|
32
|
+
assert SlurmPosix._parse_job_IDs("3397752_[9-11%2]") == ("3397752", [8, 9, 10])
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_parse_job_ID_array_simple_multiple_range_max_concurrent() -> None:
|
|
36
|
+
assert SlurmPosix._parse_job_IDs("49203_[3-5%1,9-11%2]") == (
|
|
37
|
+
"49203",
|
|
38
|
+
[2, 3, 4, 8, 9, 10],
|
|
39
|
+
)
|