hpcflow-new2 0.2.0a190__py3-none-any.whl → 0.2.0a199__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/__pyinstaller/hook-hpcflow.py +1 -0
- hpcflow/_version.py +1 -1
- hpcflow/data/scripts/bad_script.py +2 -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/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_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_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_hdf5_in_obj_2.py +12 -0
- hpcflow/data/scripts/main_script_test_json_out_FAIL.py +3 -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/script_exit_test.py +5 -0
- hpcflow/data/template_components/environments.yaml +1 -1
- hpcflow/sdk/__init__.py +5 -0
- hpcflow/sdk/app.py +150 -89
- hpcflow/sdk/cli.py +263 -84
- hpcflow/sdk/cli_common.py +99 -5
- hpcflow/sdk/config/callbacks.py +38 -1
- hpcflow/sdk/config/config.py +102 -13
- hpcflow/sdk/config/errors.py +19 -5
- hpcflow/sdk/config/types.py +3 -0
- hpcflow/sdk/core/__init__.py +25 -1
- hpcflow/sdk/core/actions.py +914 -262
- hpcflow/sdk/core/cache.py +76 -34
- hpcflow/sdk/core/command_files.py +14 -128
- hpcflow/sdk/core/commands.py +35 -6
- hpcflow/sdk/core/element.py +122 -50
- hpcflow/sdk/core/errors.py +58 -2
- hpcflow/sdk/core/execute.py +207 -0
- hpcflow/sdk/core/loop.py +408 -50
- hpcflow/sdk/core/loop_cache.py +4 -4
- hpcflow/sdk/core/parameters.py +382 -37
- hpcflow/sdk/core/run_dir_files.py +13 -40
- hpcflow/sdk/core/skip_reason.py +7 -0
- hpcflow/sdk/core/task.py +119 -30
- hpcflow/sdk/core/task_schema.py +68 -0
- hpcflow/sdk/core/test_utils.py +66 -27
- hpcflow/sdk/core/types.py +54 -1
- hpcflow/sdk/core/utils.py +78 -7
- hpcflow/sdk/core/workflow.py +1538 -336
- hpcflow/sdk/data/workflow_spec_schema.yaml +2 -0
- hpcflow/sdk/demo/cli.py +7 -0
- hpcflow/sdk/helper/cli.py +1 -0
- hpcflow/sdk/log.py +42 -15
- hpcflow/sdk/persistence/base.py +405 -53
- hpcflow/sdk/persistence/json.py +177 -52
- hpcflow/sdk/persistence/pending.py +237 -69
- hpcflow/sdk/persistence/store_resource.py +3 -2
- hpcflow/sdk/persistence/types.py +15 -4
- hpcflow/sdk/persistence/zarr.py +928 -81
- hpcflow/sdk/submission/jobscript.py +1408 -489
- hpcflow/sdk/submission/schedulers/__init__.py +40 -5
- hpcflow/sdk/submission/schedulers/direct.py +33 -19
- hpcflow/sdk/submission/schedulers/sge.py +51 -16
- hpcflow/sdk/submission/schedulers/slurm.py +44 -16
- hpcflow/sdk/submission/schedulers/utils.py +7 -2
- hpcflow/sdk/submission/shells/base.py +68 -20
- hpcflow/sdk/submission/shells/bash.py +222 -129
- hpcflow/sdk/submission/shells/powershell.py +200 -150
- hpcflow/sdk/submission/submission.py +852 -119
- hpcflow/sdk/submission/types.py +18 -21
- hpcflow/sdk/typing.py +24 -5
- hpcflow/sdk/utils/arrays.py +71 -0
- hpcflow/sdk/utils/deferred_file.py +55 -0
- hpcflow/sdk/utils/hashing.py +16 -0
- hpcflow/sdk/utils/patches.py +12 -0
- hpcflow/sdk/utils/strings.py +33 -0
- hpcflow/tests/api/test_api.py +32 -0
- hpcflow/tests/conftest.py +19 -0
- hpcflow/tests/data/multi_path_sequences.yaml +29 -0
- hpcflow/tests/data/workflow_test_run_abort.yaml +34 -35
- hpcflow/tests/schedulers/sge/test_sge_submission.py +36 -0
- hpcflow/tests/scripts/test_input_file_generators.py +282 -0
- hpcflow/tests/scripts/test_main_scripts.py +821 -70
- 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 +6 -0
- hpcflow/tests/unit/test_action.py +176 -0
- hpcflow/tests/unit/test_app.py +20 -0
- hpcflow/tests/unit/test_cache.py +46 -0
- hpcflow/tests/unit/test_cli.py +133 -0
- hpcflow/tests/unit/test_config.py +122 -1
- hpcflow/tests/unit/test_element_iteration.py +47 -0
- hpcflow/tests/unit/test_jobscript_unit.py +757 -0
- hpcflow/tests/unit/test_loop.py +1332 -27
- hpcflow/tests/unit/test_meta_task.py +325 -0
- hpcflow/tests/unit/test_multi_path_sequences.py +229 -0
- hpcflow/tests/unit/test_parameter.py +13 -0
- hpcflow/tests/unit/test_persistence.py +190 -8
- hpcflow/tests/unit/test_run.py +109 -3
- hpcflow/tests/unit/test_run_directories.py +29 -0
- hpcflow/tests/unit/test_shell.py +20 -0
- hpcflow/tests/unit/test_submission.py +5 -76
- 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/workflows/__init__.py +0 -0
- hpcflow/tests/workflows/test_directory_structure.py +31 -0
- hpcflow/tests/workflows/test_jobscript.py +332 -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 +142 -2
- hpcflow/tests/workflows/test_zip.py +18 -0
- hpcflow/viz_demo.ipynb +6587 -3
- {hpcflow_new2-0.2.0a190.dist-info → hpcflow_new2-0.2.0a199.dist-info}/METADATA +7 -4
- hpcflow_new2-0.2.0a199.dist-info/RECORD +221 -0
- hpcflow_new2-0.2.0a190.dist-info/RECORD +0 -165
- {hpcflow_new2-0.2.0a190.dist-info → hpcflow_new2-0.2.0a199.dist-info}/LICENSE +0 -0
- {hpcflow_new2-0.2.0a190.dist-info → hpcflow_new2-0.2.0a199.dist-info}/WHEEL +0 -0
- {hpcflow_new2-0.2.0a190.dist-info → hpcflow_new2-0.2.0a199.dist-info}/entry_points.txt +0 -0
hpcflow/tests/unit/test_cli.py
CHANGED
@@ -1,12 +1,145 @@
|
|
1
1
|
import pytest
|
2
2
|
|
3
3
|
from click.testing import CliRunner
|
4
|
+
import click.exceptions
|
4
5
|
|
5
6
|
from hpcflow import __version__
|
6
7
|
from hpcflow.app import app as hf
|
8
|
+
from hpcflow.sdk.cli import ErrorPropagatingClickContext
|
9
|
+
from hpcflow.sdk.cli_common import BoolOrString
|
7
10
|
|
8
11
|
|
9
12
|
def test_version() -> None:
|
10
13
|
runner = CliRunner()
|
11
14
|
result = runner.invoke(hf.cli, args="--version")
|
12
15
|
assert result.output.strip() == f"hpcFlow, version {__version__}"
|
16
|
+
|
17
|
+
|
18
|
+
def test_BoolOrString_convert():
|
19
|
+
param_type = BoolOrString(["a"])
|
20
|
+
assert param_type.convert(True, None, None) == True
|
21
|
+
assert param_type.convert(False, None, None) == False
|
22
|
+
assert param_type.convert("yes", None, None) == True
|
23
|
+
assert param_type.convert("no", None, None) == False
|
24
|
+
assert param_type.convert("on", None, None) == True
|
25
|
+
assert param_type.convert("off", None, None) == False
|
26
|
+
assert param_type.convert("a", None, None) == "a"
|
27
|
+
with pytest.raises(click.exceptions.BadParameter):
|
28
|
+
param_type.convert("b", None, None)
|
29
|
+
|
30
|
+
|
31
|
+
def test_error_propagated_with_custom_context_class():
|
32
|
+
class MyException(ValueError):
|
33
|
+
pass
|
34
|
+
|
35
|
+
class MyContextManager:
|
36
|
+
|
37
|
+
# set to True when MyException is raised within this context manager
|
38
|
+
raised = False
|
39
|
+
|
40
|
+
def __enter__(self):
|
41
|
+
return self
|
42
|
+
|
43
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
44
|
+
if exc_type == MyException:
|
45
|
+
self.__class__.raised = True
|
46
|
+
|
47
|
+
@click.group()
|
48
|
+
@click.pass_context
|
49
|
+
def cli(ctx):
|
50
|
+
ctx.with_resource(MyContextManager())
|
51
|
+
|
52
|
+
cli.context_class = ErrorPropagatingClickContext # use custom click Context
|
53
|
+
|
54
|
+
@cli.command()
|
55
|
+
def my_command():
|
56
|
+
raise MyException()
|
57
|
+
|
58
|
+
runner = CliRunner()
|
59
|
+
runner.invoke(cli, args="my-command")
|
60
|
+
|
61
|
+
assert MyContextManager.raised
|
62
|
+
|
63
|
+
|
64
|
+
def test_error_not_propagated_without_custom_context_class():
|
65
|
+
class MyException(ValueError):
|
66
|
+
pass
|
67
|
+
|
68
|
+
class MyContextManager:
|
69
|
+
|
70
|
+
# set to True when MyException is raised within this context manager
|
71
|
+
raised = False
|
72
|
+
|
73
|
+
def __enter__(self):
|
74
|
+
return self
|
75
|
+
|
76
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
77
|
+
if exc_type == MyException:
|
78
|
+
self.__class__.raised = True
|
79
|
+
|
80
|
+
@click.group()
|
81
|
+
@click.pass_context
|
82
|
+
def cli(ctx):
|
83
|
+
ctx.with_resource(MyContextManager())
|
84
|
+
|
85
|
+
@cli.command()
|
86
|
+
def my_command():
|
87
|
+
raise MyException()
|
88
|
+
|
89
|
+
runner = CliRunner()
|
90
|
+
runner.invoke(cli, args="my-command")
|
91
|
+
|
92
|
+
assert not MyContextManager.raised
|
93
|
+
|
94
|
+
|
95
|
+
def test_std_stream_file_created(tmp_path):
|
96
|
+
"""Test exception is intercepted and printed to the specified --std-stream file."""
|
97
|
+
error_file = tmp_path / "std_stream.txt"
|
98
|
+
runner = CliRunner()
|
99
|
+
result = runner.invoke(
|
100
|
+
hf.cli, args=f'--std-stream "{str(error_file)}" internal noop --raise'
|
101
|
+
)
|
102
|
+
assert error_file.is_file()
|
103
|
+
std_stream_contents = error_file.read_text()
|
104
|
+
assert "ValueError: internal noop raised!" in std_stream_contents
|
105
|
+
assert result.exit_code == 1
|
106
|
+
assert result.exc_info[0] == SystemExit
|
107
|
+
|
108
|
+
|
109
|
+
def test_std_stream_file_not_created(null_config, tmp_path):
|
110
|
+
"""Test std stream file is not created when no ouput/errors/exceptions"""
|
111
|
+
error_file = tmp_path / "std_stream.txt"
|
112
|
+
runner = CliRunner()
|
113
|
+
result = runner.invoke(hf.cli, args=f'--std-stream "{str(error_file)}" internal noop')
|
114
|
+
assert not error_file.is_file()
|
115
|
+
assert result.exit_code == 0
|
116
|
+
|
117
|
+
|
118
|
+
def test_cli_exception():
|
119
|
+
"""Test exception is passed to click"""
|
120
|
+
runner = CliRunner()
|
121
|
+
result = runner.invoke(hf.cli, args="internal noop --raise")
|
122
|
+
assert result.exit_code == 1
|
123
|
+
assert result.exc_info[0] == ValueError
|
124
|
+
|
125
|
+
|
126
|
+
def test_cli_click_exit_code_zero(tmp_path):
|
127
|
+
"""Test Click's `Exit` exception is ignored by the `redirect_std_to_file` context manager when the exit code is zero."""
|
128
|
+
error_file = tmp_path / "std_stream.txt"
|
129
|
+
runner = CliRunner()
|
130
|
+
result = runner.invoke(
|
131
|
+
hf.cli, args=f'--std-stream "{str(error_file)}" internal noop --click-exit-code 0'
|
132
|
+
)
|
133
|
+
assert result.exit_code == 0
|
134
|
+
assert not error_file.is_file()
|
135
|
+
|
136
|
+
|
137
|
+
def test_cli_click_exit_code_non_zero(tmp_path):
|
138
|
+
"""Test Click's `Exit` exception is not ignored by the `redirect_std_to_file` context manager when the exit code is non-zero."""
|
139
|
+
error_file = tmp_path / "std_stream.txt"
|
140
|
+
runner = CliRunner()
|
141
|
+
result = runner.invoke(
|
142
|
+
hf.cli, args=f'--std-stream "{str(error_file)}" internal noop --click-exit-code 2'
|
143
|
+
)
|
144
|
+
assert result.exit_code == 2
|
145
|
+
assert error_file.is_file()
|
@@ -1,9 +1,15 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
import os
|
3
|
+
import time
|
3
4
|
import pytest
|
4
5
|
|
5
6
|
from hpcflow.app import app as hf
|
6
|
-
from hpcflow.sdk.config.errors import
|
7
|
+
from hpcflow.sdk.config.errors import (
|
8
|
+
ConfigFileValidationError,
|
9
|
+
ConfigItemCallbackError,
|
10
|
+
ConfigNonConfigurableError,
|
11
|
+
ConfigReadOnlyError,
|
12
|
+
)
|
7
13
|
|
8
14
|
|
9
15
|
def test_reset_config(new_null_config) -> None:
|
@@ -72,3 +78,118 @@ def test_without_callbacks_ctx_manager(null_config) -> None:
|
|
72
78
|
|
73
79
|
# unload the modified config so it's not reused by other tests
|
74
80
|
hf.unload_config()
|
81
|
+
|
82
|
+
|
83
|
+
@pytest.mark.xfail(reason="Might occasionally fail.")
|
84
|
+
def test_cache_faster_than_no_cache(null_config):
|
85
|
+
n = 10_000
|
86
|
+
tic = time.perf_counter()
|
87
|
+
for _ in range(n):
|
88
|
+
_ = hf.config.machine
|
89
|
+
toc = time.perf_counter()
|
90
|
+
elapsed_no_cache = toc - tic
|
91
|
+
|
92
|
+
with hf.config.cached_config():
|
93
|
+
tic = time.perf_counter()
|
94
|
+
for _ in range(n):
|
95
|
+
_ = hf.config.machine
|
96
|
+
toc = time.perf_counter()
|
97
|
+
elapsed_cache = toc - tic
|
98
|
+
|
99
|
+
assert elapsed_cache < elapsed_no_cache
|
100
|
+
|
101
|
+
|
102
|
+
def test_cache_read_only(new_null_config):
|
103
|
+
"""Check we cannot modify the config when using the cache"""
|
104
|
+
|
105
|
+
# check we can set an item first:
|
106
|
+
hf.machine = "abc"
|
107
|
+
assert hf.machine == "abc"
|
108
|
+
|
109
|
+
with pytest.raises(ConfigReadOnlyError):
|
110
|
+
with hf.config.cached_config():
|
111
|
+
hf.config.set("machine", "123")
|
112
|
+
|
113
|
+
with pytest.raises(ConfigReadOnlyError):
|
114
|
+
with hf.config.cached_config():
|
115
|
+
hf.config.machine = "456"
|
116
|
+
|
117
|
+
|
118
|
+
def test_workflow_template_config_validation(new_null_config, tmp_path):
|
119
|
+
wkt = hf.WorkflowTemplate(
|
120
|
+
tasks=[],
|
121
|
+
config={"log_file_level": "debug"},
|
122
|
+
name="test_workflow_config_validation",
|
123
|
+
)
|
124
|
+
assert wkt.config == {"log_file_level": "debug"}
|
125
|
+
|
126
|
+
|
127
|
+
def test_workflow_template_config_validation_raises(unload_config, tmp_path):
|
128
|
+
with pytest.raises(ConfigNonConfigurableError):
|
129
|
+
hf.WorkflowTemplate(
|
130
|
+
tasks=[],
|
131
|
+
config={"bad_key": "debug"},
|
132
|
+
name="test_workflow_config_validation_raises",
|
133
|
+
)
|
134
|
+
|
135
|
+
# workflow template config validation should not need to load the whole config:
|
136
|
+
assert not hf.is_config_loaded
|
137
|
+
|
138
|
+
|
139
|
+
def test_config_with_updates(new_null_config):
|
140
|
+
level_1 = hf.config.get("log_console_level")
|
141
|
+
with hf.config._with_updates({"log_console_level": "debug"}):
|
142
|
+
level_2 = hf.config.get("log_console_level")
|
143
|
+
level_3 = hf.config.get("log_console_level")
|
144
|
+
assert level_1 == level_3 != level_2
|
145
|
+
hf.reload_config()
|
146
|
+
|
147
|
+
|
148
|
+
@pytest.mark.integration
|
149
|
+
def test_workflow_template_config_set(new_null_config, tmp_path):
|
150
|
+
"""Test we can set a workflow-level config item and that it is correctly applied
|
151
|
+
during execution."""
|
152
|
+
|
153
|
+
t1 = hf.Task(
|
154
|
+
schema=hf.task_schemas.test_t1_conditional_OS,
|
155
|
+
inputs={"p1": 101},
|
156
|
+
)
|
157
|
+
log_path = tmp_path / "log.log"
|
158
|
+
hf.config.set("log_file_level", "warning")
|
159
|
+
hf.config.set("log_file_path", log_path)
|
160
|
+
|
161
|
+
log_str_1 = "this should not appear in the log file"
|
162
|
+
hf.submission_logger.debug(log_str_1)
|
163
|
+
|
164
|
+
log_str_2 = "this should appear in the log file"
|
165
|
+
hf.submission_logger.warning(log_str_2)
|
166
|
+
|
167
|
+
assert log_path.is_file()
|
168
|
+
log_file_contents = log_path.read_text()
|
169
|
+
assert log_str_1 not in log_file_contents
|
170
|
+
assert log_str_2 in log_file_contents
|
171
|
+
|
172
|
+
wk = hf.Workflow.from_template_data(
|
173
|
+
tasks=[t1],
|
174
|
+
config={"log_file_level": "debug"},
|
175
|
+
resources={"any": {"write_app_logs": True}},
|
176
|
+
workflow_name="test_workflow_config",
|
177
|
+
template_name="test_workflow_config",
|
178
|
+
path=tmp_path,
|
179
|
+
)
|
180
|
+
wk.submit(wait=True, status=False, add_to_known=False)
|
181
|
+
|
182
|
+
# check some DEBUG messages present in the run logs
|
183
|
+
debug_str = " DEBUG hpcflow.persistence:"
|
184
|
+
|
185
|
+
run = wk.get_EARs_from_IDs([0])[0]
|
186
|
+
run_log_path = run.get_app_log_path()
|
187
|
+
assert run_log_path.is_file()
|
188
|
+
|
189
|
+
run_log_contents = run_log_path.read_text()
|
190
|
+
assert debug_str in run_log_contents
|
191
|
+
|
192
|
+
# log file level should not have changed:
|
193
|
+
assert hf.config.get("log_file_level") == "warning"
|
194
|
+
|
195
|
+
hf.reload_config()
|
@@ -39,3 +39,50 @@ def test_decode(null_config, tmp_path: Path, store: str):
|
|
39
39
|
assert sorted(iter_i.schema_parameters) == sorted(
|
40
40
|
["resources.any", "inputs.p1", "outputs.p2"]
|
41
41
|
)
|
42
|
+
|
43
|
+
|
44
|
+
@pytest.mark.integration
|
45
|
+
def test_loop_skipped_true_single_action_elements(null_config, tmp_path):
|
46
|
+
ts = hf.TaskSchema(
|
47
|
+
objective="t1",
|
48
|
+
inputs=[hf.SchemaInput("p1")],
|
49
|
+
outputs=[hf.SchemaInput("p1")],
|
50
|
+
actions=[
|
51
|
+
hf.Action(
|
52
|
+
commands=[
|
53
|
+
hf.Command(
|
54
|
+
command="echo $(( <<parameter:p1>> + 100 ))",
|
55
|
+
stdout="<<int(parameter:p1)>>",
|
56
|
+
),
|
57
|
+
]
|
58
|
+
),
|
59
|
+
],
|
60
|
+
)
|
61
|
+
loop_term = hf.Rule(path="outputs.p1", condition={"value.equal_to": 300})
|
62
|
+
wk = hf.Workflow.from_template_data(
|
63
|
+
template_name="test_loop_skipped",
|
64
|
+
path=tmp_path,
|
65
|
+
tasks=[hf.Task(schema=ts, inputs={"p1": 100})],
|
66
|
+
loops=[
|
67
|
+
hf.Loop(name="my_loop", tasks=[0], termination=loop_term, num_iterations=3)
|
68
|
+
],
|
69
|
+
)
|
70
|
+
# loop should terminate after the second iteration; third iteration should
|
71
|
+
# be loop-skipped
|
72
|
+
wk.submit(wait=True, add_to_known=False, status=False)
|
73
|
+
iters = wk.get_all_element_iterations()
|
74
|
+
|
75
|
+
assert not iters[0].loop_skipped
|
76
|
+
assert not iters[1].loop_skipped
|
77
|
+
assert iters[2].loop_skipped
|
78
|
+
|
79
|
+
# check latest iteration is the latest non-loop-skipped iteration:
|
80
|
+
assert wk.tasks[0].elements[0].latest_iteration_non_skipped.id_ == iters[1].id_
|
81
|
+
|
82
|
+
# check element inputs are from latest non-loop-skipped iteration:
|
83
|
+
assert wk.tasks[0].elements[0].inputs.p1.value == 200
|
84
|
+
assert wk.tasks[0].elements[0].get("inputs.p1") == 200
|
85
|
+
|
86
|
+
# check element outputs are from latest non-loop-skipped iteration:
|
87
|
+
assert wk.tasks[0].elements[0].outputs.p1.value == 300
|
88
|
+
assert wk.tasks[0].elements[0].get("outputs.p1") == 300
|