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.
Files changed (275) hide show
  1. hpcflow/__init__.py +2 -11
  2. hpcflow/__pyinstaller/__init__.py +5 -0
  3. hpcflow/__pyinstaller/hook-hpcflow.py +40 -0
  4. hpcflow/_version.py +1 -1
  5. hpcflow/app.py +43 -0
  6. hpcflow/cli.py +2 -462
  7. hpcflow/data/demo_data_manifest/__init__.py +3 -0
  8. hpcflow/data/demo_data_manifest/demo_data_manifest.json +6 -0
  9. hpcflow/data/jinja_templates/test/test_template.txt +8 -0
  10. hpcflow/data/programs/hello_world/README.md +1 -0
  11. hpcflow/data/programs/hello_world/hello_world.c +87 -0
  12. hpcflow/data/programs/hello_world/linux/hello_world +0 -0
  13. hpcflow/data/programs/hello_world/macos/hello_world +0 -0
  14. hpcflow/data/programs/hello_world/win/hello_world.exe +0 -0
  15. hpcflow/data/scripts/__init__.py +1 -0
  16. hpcflow/data/scripts/bad_script.py +2 -0
  17. hpcflow/data/scripts/demo_task_1_generate_t1_infile_1.py +8 -0
  18. hpcflow/data/scripts/demo_task_1_generate_t1_infile_2.py +8 -0
  19. hpcflow/data/scripts/demo_task_1_parse_p3.py +7 -0
  20. hpcflow/data/scripts/do_nothing.py +2 -0
  21. hpcflow/data/scripts/env_specifier_test/input_file_generator_pass_env_spec.py +4 -0
  22. hpcflow/data/scripts/env_specifier_test/main_script_test_pass_env_spec.py +8 -0
  23. hpcflow/data/scripts/env_specifier_test/output_file_parser_pass_env_spec.py +4 -0
  24. hpcflow/data/scripts/env_specifier_test/v1/input_file_generator_basic.py +4 -0
  25. hpcflow/data/scripts/env_specifier_test/v1/main_script_test_direct_in_direct_out.py +7 -0
  26. hpcflow/data/scripts/env_specifier_test/v1/output_file_parser_basic.py +4 -0
  27. hpcflow/data/scripts/env_specifier_test/v2/main_script_test_direct_in_direct_out.py +7 -0
  28. hpcflow/data/scripts/generate_t1_file_01.py +7 -0
  29. hpcflow/data/scripts/import_future_script.py +7 -0
  30. hpcflow/data/scripts/input_file_generator_basic.py +3 -0
  31. hpcflow/data/scripts/input_file_generator_basic_FAIL.py +3 -0
  32. hpcflow/data/scripts/input_file_generator_test_stdout_stderr.py +8 -0
  33. hpcflow/data/scripts/main_script_test_direct_in.py +3 -0
  34. hpcflow/data/scripts/main_script_test_direct_in_direct_out.py +6 -0
  35. hpcflow/data/scripts/main_script_test_direct_in_direct_out_2.py +6 -0
  36. hpcflow/data/scripts/main_script_test_direct_in_direct_out_2_fail_allowed.py +6 -0
  37. hpcflow/data/scripts/main_script_test_direct_in_direct_out_2_fail_allowed_group.py +7 -0
  38. hpcflow/data/scripts/main_script_test_direct_in_direct_out_3.py +6 -0
  39. hpcflow/data/scripts/main_script_test_direct_in_direct_out_all_iters_test.py +15 -0
  40. hpcflow/data/scripts/main_script_test_direct_in_direct_out_env_spec.py +7 -0
  41. hpcflow/data/scripts/main_script_test_direct_in_direct_out_labels.py +8 -0
  42. hpcflow/data/scripts/main_script_test_direct_in_group_direct_out_3.py +6 -0
  43. hpcflow/data/scripts/main_script_test_direct_in_group_one_fail_direct_out_3.py +6 -0
  44. hpcflow/data/scripts/main_script_test_direct_sub_param_in_direct_out.py +6 -0
  45. hpcflow/data/scripts/main_script_test_hdf5_in_obj.py +12 -0
  46. hpcflow/data/scripts/main_script_test_hdf5_in_obj_2.py +12 -0
  47. hpcflow/data/scripts/main_script_test_hdf5_in_obj_group.py +12 -0
  48. hpcflow/data/scripts/main_script_test_hdf5_out_obj.py +11 -0
  49. hpcflow/data/scripts/main_script_test_json_and_direct_in_json_out.py +14 -0
  50. hpcflow/data/scripts/main_script_test_json_in_json_and_direct_out.py +17 -0
  51. hpcflow/data/scripts/main_script_test_json_in_json_out.py +14 -0
  52. hpcflow/data/scripts/main_script_test_json_in_json_out_labels.py +16 -0
  53. hpcflow/data/scripts/main_script_test_json_in_obj.py +12 -0
  54. hpcflow/data/scripts/main_script_test_json_out_FAIL.py +3 -0
  55. hpcflow/data/scripts/main_script_test_json_out_obj.py +10 -0
  56. hpcflow/data/scripts/main_script_test_json_sub_param_in_json_out_labels.py +16 -0
  57. hpcflow/data/scripts/main_script_test_shell_env_vars.py +12 -0
  58. hpcflow/data/scripts/main_script_test_std_out_std_err.py +6 -0
  59. hpcflow/data/scripts/output_file_parser_basic.py +3 -0
  60. hpcflow/data/scripts/output_file_parser_basic_FAIL.py +7 -0
  61. hpcflow/data/scripts/output_file_parser_test_stdout_stderr.py +8 -0
  62. hpcflow/data/scripts/parse_t1_file_01.py +4 -0
  63. hpcflow/data/scripts/script_exit_test.py +5 -0
  64. hpcflow/data/template_components/__init__.py +1 -0
  65. hpcflow/data/template_components/command_files.yaml +26 -0
  66. hpcflow/data/template_components/environments.yaml +13 -0
  67. hpcflow/data/template_components/parameters.yaml +14 -0
  68. hpcflow/data/template_components/task_schemas.yaml +139 -0
  69. hpcflow/data/workflows/workflow_1.yaml +5 -0
  70. hpcflow/examples.ipynb +1037 -0
  71. hpcflow/sdk/__init__.py +149 -0
  72. hpcflow/sdk/app.py +4266 -0
  73. hpcflow/sdk/cli.py +1479 -0
  74. hpcflow/sdk/cli_common.py +385 -0
  75. hpcflow/sdk/config/__init__.py +5 -0
  76. hpcflow/sdk/config/callbacks.py +246 -0
  77. hpcflow/sdk/config/cli.py +388 -0
  78. hpcflow/sdk/config/config.py +1410 -0
  79. hpcflow/sdk/config/config_file.py +501 -0
  80. hpcflow/sdk/config/errors.py +272 -0
  81. hpcflow/sdk/config/types.py +150 -0
  82. hpcflow/sdk/core/__init__.py +38 -0
  83. hpcflow/sdk/core/actions.py +3857 -0
  84. hpcflow/sdk/core/app_aware.py +25 -0
  85. hpcflow/sdk/core/cache.py +224 -0
  86. hpcflow/sdk/core/command_files.py +814 -0
  87. hpcflow/sdk/core/commands.py +424 -0
  88. hpcflow/sdk/core/element.py +2071 -0
  89. hpcflow/sdk/core/enums.py +221 -0
  90. hpcflow/sdk/core/environment.py +256 -0
  91. hpcflow/sdk/core/errors.py +1043 -0
  92. hpcflow/sdk/core/execute.py +207 -0
  93. hpcflow/sdk/core/json_like.py +809 -0
  94. hpcflow/sdk/core/loop.py +1320 -0
  95. hpcflow/sdk/core/loop_cache.py +282 -0
  96. hpcflow/sdk/core/object_list.py +933 -0
  97. hpcflow/sdk/core/parameters.py +3371 -0
  98. hpcflow/sdk/core/rule.py +196 -0
  99. hpcflow/sdk/core/run_dir_files.py +57 -0
  100. hpcflow/sdk/core/skip_reason.py +7 -0
  101. hpcflow/sdk/core/task.py +3792 -0
  102. hpcflow/sdk/core/task_schema.py +993 -0
  103. hpcflow/sdk/core/test_utils.py +538 -0
  104. hpcflow/sdk/core/types.py +447 -0
  105. hpcflow/sdk/core/utils.py +1207 -0
  106. hpcflow/sdk/core/validation.py +87 -0
  107. hpcflow/sdk/core/values.py +477 -0
  108. hpcflow/sdk/core/workflow.py +4820 -0
  109. hpcflow/sdk/core/zarr_io.py +206 -0
  110. hpcflow/sdk/data/__init__.py +13 -0
  111. hpcflow/sdk/data/config_file_schema.yaml +34 -0
  112. hpcflow/sdk/data/config_schema.yaml +260 -0
  113. hpcflow/sdk/data/environments_spec_schema.yaml +21 -0
  114. hpcflow/sdk/data/files_spec_schema.yaml +5 -0
  115. hpcflow/sdk/data/parameters_spec_schema.yaml +7 -0
  116. hpcflow/sdk/data/task_schema_spec_schema.yaml +3 -0
  117. hpcflow/sdk/data/workflow_spec_schema.yaml +22 -0
  118. hpcflow/sdk/demo/__init__.py +3 -0
  119. hpcflow/sdk/demo/cli.py +242 -0
  120. hpcflow/sdk/helper/__init__.py +3 -0
  121. hpcflow/sdk/helper/cli.py +137 -0
  122. hpcflow/sdk/helper/helper.py +300 -0
  123. hpcflow/sdk/helper/watcher.py +192 -0
  124. hpcflow/sdk/log.py +288 -0
  125. hpcflow/sdk/persistence/__init__.py +18 -0
  126. hpcflow/sdk/persistence/base.py +2817 -0
  127. hpcflow/sdk/persistence/defaults.py +6 -0
  128. hpcflow/sdk/persistence/discovery.py +39 -0
  129. hpcflow/sdk/persistence/json.py +954 -0
  130. hpcflow/sdk/persistence/pending.py +948 -0
  131. hpcflow/sdk/persistence/store_resource.py +203 -0
  132. hpcflow/sdk/persistence/types.py +309 -0
  133. hpcflow/sdk/persistence/utils.py +73 -0
  134. hpcflow/sdk/persistence/zarr.py +2388 -0
  135. hpcflow/sdk/runtime.py +320 -0
  136. hpcflow/sdk/submission/__init__.py +3 -0
  137. hpcflow/sdk/submission/enums.py +70 -0
  138. hpcflow/sdk/submission/jobscript.py +2379 -0
  139. hpcflow/sdk/submission/schedulers/__init__.py +281 -0
  140. hpcflow/sdk/submission/schedulers/direct.py +233 -0
  141. hpcflow/sdk/submission/schedulers/sge.py +376 -0
  142. hpcflow/sdk/submission/schedulers/slurm.py +598 -0
  143. hpcflow/sdk/submission/schedulers/utils.py +25 -0
  144. hpcflow/sdk/submission/shells/__init__.py +52 -0
  145. hpcflow/sdk/submission/shells/base.py +229 -0
  146. hpcflow/sdk/submission/shells/bash.py +504 -0
  147. hpcflow/sdk/submission/shells/os_version.py +115 -0
  148. hpcflow/sdk/submission/shells/powershell.py +352 -0
  149. hpcflow/sdk/submission/submission.py +1402 -0
  150. hpcflow/sdk/submission/types.py +140 -0
  151. hpcflow/sdk/typing.py +194 -0
  152. hpcflow/sdk/utils/arrays.py +69 -0
  153. hpcflow/sdk/utils/deferred_file.py +55 -0
  154. hpcflow/sdk/utils/hashing.py +16 -0
  155. hpcflow/sdk/utils/patches.py +31 -0
  156. hpcflow/sdk/utils/strings.py +69 -0
  157. hpcflow/tests/api/test_api.py +32 -0
  158. hpcflow/tests/conftest.py +123 -0
  159. hpcflow/tests/data/__init__.py +0 -0
  160. hpcflow/tests/data/benchmark_N_elements.yaml +6 -0
  161. hpcflow/tests/data/benchmark_script_runner.yaml +26 -0
  162. hpcflow/tests/data/multi_path_sequences.yaml +29 -0
  163. hpcflow/tests/data/workflow_1.json +10 -0
  164. hpcflow/tests/data/workflow_1.yaml +5 -0
  165. hpcflow/tests/data/workflow_1_slurm.yaml +8 -0
  166. hpcflow/tests/data/workflow_1_wsl.yaml +8 -0
  167. hpcflow/tests/data/workflow_test_run_abort.yaml +42 -0
  168. hpcflow/tests/jinja_templates/test_jinja_templates.py +161 -0
  169. hpcflow/tests/programs/test_programs.py +180 -0
  170. hpcflow/tests/schedulers/direct_linux/test_direct_linux_submission.py +12 -0
  171. hpcflow/tests/schedulers/sge/test_sge_submission.py +36 -0
  172. hpcflow/tests/schedulers/slurm/test_slurm_submission.py +14 -0
  173. hpcflow/tests/scripts/test_input_file_generators.py +282 -0
  174. hpcflow/tests/scripts/test_main_scripts.py +1361 -0
  175. hpcflow/tests/scripts/test_non_snippet_script.py +46 -0
  176. hpcflow/tests/scripts/test_ouput_file_parsers.py +353 -0
  177. hpcflow/tests/shells/wsl/test_wsl_submission.py +14 -0
  178. hpcflow/tests/unit/test_action.py +1066 -0
  179. hpcflow/tests/unit/test_action_rule.py +24 -0
  180. hpcflow/tests/unit/test_app.py +132 -0
  181. hpcflow/tests/unit/test_cache.py +46 -0
  182. hpcflow/tests/unit/test_cli.py +172 -0
  183. hpcflow/tests/unit/test_command.py +377 -0
  184. hpcflow/tests/unit/test_config.py +195 -0
  185. hpcflow/tests/unit/test_config_file.py +162 -0
  186. hpcflow/tests/unit/test_element.py +666 -0
  187. hpcflow/tests/unit/test_element_iteration.py +88 -0
  188. hpcflow/tests/unit/test_element_set.py +158 -0
  189. hpcflow/tests/unit/test_group.py +115 -0
  190. hpcflow/tests/unit/test_input_source.py +1479 -0
  191. hpcflow/tests/unit/test_input_value.py +398 -0
  192. hpcflow/tests/unit/test_jobscript_unit.py +757 -0
  193. hpcflow/tests/unit/test_json_like.py +1247 -0
  194. hpcflow/tests/unit/test_loop.py +2674 -0
  195. hpcflow/tests/unit/test_meta_task.py +325 -0
  196. hpcflow/tests/unit/test_multi_path_sequences.py +259 -0
  197. hpcflow/tests/unit/test_object_list.py +116 -0
  198. hpcflow/tests/unit/test_parameter.py +243 -0
  199. hpcflow/tests/unit/test_persistence.py +664 -0
  200. hpcflow/tests/unit/test_resources.py +243 -0
  201. hpcflow/tests/unit/test_run.py +286 -0
  202. hpcflow/tests/unit/test_run_directories.py +29 -0
  203. hpcflow/tests/unit/test_runtime.py +9 -0
  204. hpcflow/tests/unit/test_schema_input.py +372 -0
  205. hpcflow/tests/unit/test_shell.py +129 -0
  206. hpcflow/tests/unit/test_slurm.py +39 -0
  207. hpcflow/tests/unit/test_submission.py +502 -0
  208. hpcflow/tests/unit/test_task.py +2560 -0
  209. hpcflow/tests/unit/test_task_schema.py +182 -0
  210. hpcflow/tests/unit/test_utils.py +616 -0
  211. hpcflow/tests/unit/test_value_sequence.py +549 -0
  212. hpcflow/tests/unit/test_values.py +91 -0
  213. hpcflow/tests/unit/test_workflow.py +827 -0
  214. hpcflow/tests/unit/test_workflow_template.py +186 -0
  215. hpcflow/tests/unit/utils/test_arrays.py +40 -0
  216. hpcflow/tests/unit/utils/test_deferred_file_writer.py +34 -0
  217. hpcflow/tests/unit/utils/test_hashing.py +65 -0
  218. hpcflow/tests/unit/utils/test_patches.py +5 -0
  219. hpcflow/tests/unit/utils/test_redirect_std.py +50 -0
  220. hpcflow/tests/unit/utils/test_strings.py +97 -0
  221. hpcflow/tests/workflows/__init__.py +0 -0
  222. hpcflow/tests/workflows/test_directory_structure.py +31 -0
  223. hpcflow/tests/workflows/test_jobscript.py +355 -0
  224. hpcflow/tests/workflows/test_run_status.py +198 -0
  225. hpcflow/tests/workflows/test_skip_downstream.py +696 -0
  226. hpcflow/tests/workflows/test_submission.py +140 -0
  227. hpcflow/tests/workflows/test_workflows.py +564 -0
  228. hpcflow/tests/workflows/test_zip.py +18 -0
  229. hpcflow/viz_demo.ipynb +6794 -0
  230. hpcflow-0.2.0a271.dist-info/LICENSE +375 -0
  231. hpcflow-0.2.0a271.dist-info/METADATA +65 -0
  232. hpcflow-0.2.0a271.dist-info/RECORD +237 -0
  233. {hpcflow-0.1.9.dist-info → hpcflow-0.2.0a271.dist-info}/WHEEL +4 -5
  234. hpcflow-0.2.0a271.dist-info/entry_points.txt +6 -0
  235. hpcflow/api.py +0 -458
  236. hpcflow/archive/archive.py +0 -308
  237. hpcflow/archive/cloud/cloud.py +0 -47
  238. hpcflow/archive/cloud/errors.py +0 -9
  239. hpcflow/archive/cloud/providers/dropbox.py +0 -432
  240. hpcflow/archive/errors.py +0 -5
  241. hpcflow/base_db.py +0 -4
  242. hpcflow/config.py +0 -232
  243. hpcflow/copytree.py +0 -66
  244. hpcflow/data/examples/_config.yml +0 -14
  245. hpcflow/data/examples/damask/demo/1.run.yml +0 -4
  246. hpcflow/data/examples/damask/demo/2.process.yml +0 -29
  247. hpcflow/data/examples/damask/demo/geom.geom +0 -2052
  248. hpcflow/data/examples/damask/demo/load.load +0 -1
  249. hpcflow/data/examples/damask/demo/material.config +0 -185
  250. hpcflow/data/examples/damask/inputs/geom.geom +0 -2052
  251. hpcflow/data/examples/damask/inputs/load.load +0 -1
  252. hpcflow/data/examples/damask/inputs/material.config +0 -185
  253. hpcflow/data/examples/damask/profiles/_variable_lookup.yml +0 -21
  254. hpcflow/data/examples/damask/profiles/damask.yml +0 -4
  255. hpcflow/data/examples/damask/profiles/damask_process.yml +0 -8
  256. hpcflow/data/examples/damask/profiles/damask_run.yml +0 -5
  257. hpcflow/data/examples/damask/profiles/default.yml +0 -6
  258. hpcflow/data/examples/thinking.yml +0 -177
  259. hpcflow/errors.py +0 -2
  260. hpcflow/init_db.py +0 -37
  261. hpcflow/models.py +0 -2549
  262. hpcflow/nesting.py +0 -9
  263. hpcflow/profiles.py +0 -455
  264. hpcflow/project.py +0 -81
  265. hpcflow/scheduler.py +0 -323
  266. hpcflow/utils.py +0 -103
  267. hpcflow/validation.py +0 -167
  268. hpcflow/variables.py +0 -544
  269. hpcflow-0.1.9.dist-info/METADATA +0 -168
  270. hpcflow-0.1.9.dist-info/RECORD +0 -45
  271. hpcflow-0.1.9.dist-info/entry_points.txt +0 -8
  272. hpcflow-0.1.9.dist-info/top_level.txt +0 -1
  273. /hpcflow/{archive → data/jinja_templates}/__init__.py +0 -0
  274. /hpcflow/{archive/cloud → data/programs}/__init__.py +0 -0
  275. /hpcflow/{archive/cloud/providers → data/workflows}/__init__.py +0 -0
@@ -0,0 +1,355 @@
1
+ import os
2
+ import sys
3
+ from pathlib import Path
4
+ import pytest
5
+
6
+ from hpcflow.app import app as hf
7
+ from hpcflow.sdk.core import SKIPPED_EXIT_CODE
8
+ from hpcflow.sdk.core.skip_reason import SkipReason
9
+
10
+
11
+ @pytest.mark.integration
12
+ @pytest.mark.parametrize("exit_code", [0, 1, 98, -1, -123124])
13
+ def test_action_exit_code_parsing(null_config, tmp_path: Path, exit_code: int):
14
+ act = hf.Action(commands=[hf.Command(command=f"exit {exit_code}")])
15
+ s1 = hf.TaskSchema(
16
+ objective="t1",
17
+ actions=[act],
18
+ )
19
+ t1 = hf.Task(schema=[s1])
20
+ wk = hf.Workflow.from_template_data(tasks=[t1], template_name="test", path=tmp_path)
21
+ wk.submit(wait=True, add_to_known=False)
22
+ recorded_exit = wk.get_EARs_from_IDs([0])[0].exit_code
23
+ if os.name == "posix":
24
+ # exit code from bash wraps around:
25
+ exit_code %= 256
26
+ assert recorded_exit == exit_code
27
+
28
+
29
+ @pytest.mark.integration
30
+ def test_bad_action_py_script_exit_code(null_config, tmp_path):
31
+ s1 = hf.TaskSchema(
32
+ objective="t1",
33
+ actions=[
34
+ hf.Action(
35
+ script="<<script:bad_script.py>>", # raises SyntaxError
36
+ script_exe="python_script",
37
+ environments=[hf.ActionEnvironment(environment="python_env")],
38
+ )
39
+ ],
40
+ )
41
+ t1 = hf.Task(schema=[s1])
42
+ wk = hf.Workflow.from_template_data(
43
+ tasks=[t1], template_name="bad_script_test", path=tmp_path
44
+ )
45
+ wk.submit(wait=True, add_to_known=False)
46
+ recorded_exit = wk.get_EARs_from_IDs([0])[0].exit_code
47
+ assert recorded_exit == 1
48
+
49
+
50
+ @pytest.mark.integration
51
+ @pytest.mark.parametrize("exit_code", [0, 1, 98, -1, -123124])
52
+ def test_action_py_script_specified_exit_code(null_config, tmp_path, exit_code):
53
+ s1 = hf.TaskSchema(
54
+ objective="t1",
55
+ inputs=[hf.SchemaInput("exit_code")],
56
+ actions=[
57
+ hf.Action(
58
+ script="<<script:script_exit_test.py>>",
59
+ script_exe="python_script",
60
+ script_data_in="direct",
61
+ environments=[hf.ActionEnvironment(environment="python_env")],
62
+ )
63
+ ],
64
+ )
65
+ t1 = hf.Task(schema=[s1], inputs={"exit_code": exit_code})
66
+ wk = hf.Workflow.from_template_data(
67
+ tasks=[t1], template_name="script_exit_test", path=tmp_path
68
+ )
69
+ wk.submit(wait=True, add_to_known=False)
70
+ recorded_exit = wk.get_EARs_from_IDs([0])[0].exit_code
71
+ if os.name == "posix":
72
+ # exit code from bash wraps around:
73
+ exit_code %= 256
74
+ assert recorded_exit == exit_code
75
+
76
+
77
+ @pytest.mark.integration
78
+ def test_skipped_action_same_element(null_config, tmp_path):
79
+ s1 = hf.TaskSchema(
80
+ objective="t1",
81
+ inputs=[hf.SchemaInput("p1")],
82
+ outputs=[hf.SchemaOutput("p2"), hf.SchemaOutput("p3")],
83
+ actions=[
84
+ hf.Action(
85
+ commands=[
86
+ hf.Command(
87
+ command=f"echo <<parameter:p1>>", stdout="<<parameter:p2>>"
88
+ ),
89
+ hf.Command(command=f"exit 1"),
90
+ ],
91
+ ),
92
+ hf.Action( # should be skipped
93
+ commands=[
94
+ hf.Command(
95
+ command=f"echo <<parameter:p2>>", stdout="<<parameter:p3>>"
96
+ ),
97
+ hf.Command(command=f"exit 0"), # exit code should be ignored
98
+ ],
99
+ ),
100
+ ],
101
+ )
102
+ t1 = hf.Task(schema=s1, inputs={"p1": 101})
103
+ wk = hf.Workflow.from_template_data(
104
+ tasks=[t1], template_name="test_skip", path=tmp_path
105
+ )
106
+ wk.submit(wait=True, add_to_known=False, status=False)
107
+
108
+ runs = wk.get_EARs_from_IDs([0, 1])
109
+ exit_codes = [i.exit_code for i in runs]
110
+ is_skipped = [i.skip for i in runs]
111
+
112
+ assert exit_codes == [1, SKIPPED_EXIT_CODE]
113
+ assert is_skipped == [0, 1]
114
+
115
+
116
+ @pytest.mark.integration
117
+ def test_two_skipped_actions_same_element(null_config, tmp_path):
118
+ s1 = hf.TaskSchema(
119
+ objective="t1",
120
+ inputs=[hf.SchemaInput("p1")],
121
+ outputs=[hf.SchemaOutput("p2"), hf.SchemaOutput("p3"), hf.SchemaOutput("p4")],
122
+ actions=[
123
+ hf.Action(
124
+ commands=[
125
+ hf.Command(
126
+ command=f"echo <<parameter:p1>>", stdout="<<parameter:p2>>"
127
+ ),
128
+ hf.Command(command=f"exit 1"),
129
+ ],
130
+ ),
131
+ hf.Action( # should be skipped
132
+ commands=[
133
+ hf.Command(
134
+ command=f"echo <<parameter:p2>>", stdout="<<parameter:p3>>"
135
+ ),
136
+ hf.Command(command=f"exit 0"), # exit code should be ignored
137
+ ],
138
+ ),
139
+ hf.Action( # should be skipped
140
+ commands=[
141
+ hf.Command(
142
+ command=f"echo <<parameter:p3>>", stdout="<<parameter:p4>>"
143
+ ),
144
+ hf.Command(command=f"exit 0"), # exit code should be ignored
145
+ ],
146
+ ),
147
+ ],
148
+ )
149
+ t1 = hf.Task(schema=s1, inputs={"p1": 101})
150
+ wk = hf.Workflow.from_template_data(
151
+ tasks=[t1], template_name="test_skip_two_actions", path=tmp_path
152
+ )
153
+ wk.submit(wait=True, add_to_known=False, status=False)
154
+
155
+ runs = wk.get_EARs_from_IDs([0, 1, 2])
156
+ exit_codes = [i.exit_code for i in runs]
157
+ skip_reasons = [i.skip_reason for i in runs]
158
+
159
+ assert exit_codes == [1, SKIPPED_EXIT_CODE, SKIPPED_EXIT_CODE]
160
+ assert skip_reasons == [
161
+ SkipReason.NOT_SKIPPED,
162
+ SkipReason.UPSTREAM_FAILURE,
163
+ SkipReason.UPSTREAM_FAILURE,
164
+ ]
165
+
166
+
167
+ @pytest.mark.integration
168
+ @pytest.mark.skipif(
169
+ condition=sys.platform == "win32",
170
+ reason="`combine_jobscript_std` not implemented on Windows.",
171
+ )
172
+ def test_combine_jobscript_std_true(null_config, tmp_path):
173
+ out_msg = "hello stdout!"
174
+ err_msg = "hello stderr!"
175
+ s1 = hf.TaskSchema(
176
+ objective="t1",
177
+ actions=[
178
+ hf.Action(
179
+ commands=[
180
+ hf.Command(command=f'echo "{out_msg}"'),
181
+ hf.Command(command=f'>&2 echo "{err_msg}"'),
182
+ ],
183
+ )
184
+ ],
185
+ )
186
+ t1 = hf.Task(schema=s1)
187
+ wk = hf.Workflow.from_template_data(
188
+ tasks=[t1],
189
+ template_name="test_combine_jobscript_std",
190
+ path=tmp_path,
191
+ resources={"any": {"combine_jobscript_std": True}},
192
+ )
193
+ wk.submit(wait=True, add_to_known=False, status=False)
194
+
195
+ jobscript = wk.submissions[0].jobscripts[0]
196
+
197
+ assert jobscript.resources.combine_jobscript_std
198
+
199
+ out_err_path = jobscript.get_std_out_err_path()
200
+ out_path = jobscript._get_stdout_path()
201
+ err_path = jobscript._get_stderr_path()
202
+
203
+ assert out_err_path.is_file()
204
+ assert not out_path.is_file()
205
+ assert not err_path.is_file()
206
+
207
+ assert out_err_path.read_text().strip() == f"{out_msg}\n{err_msg}"
208
+
209
+
210
+ @pytest.mark.integration
211
+ def test_combine_jobscript_std_false(null_config, tmp_path):
212
+ out_msg = "hello stdout!"
213
+ err_msg = "hello stderr!"
214
+ s1 = hf.TaskSchema(
215
+ objective="t1",
216
+ actions=[
217
+ hf.Action(
218
+ commands=[
219
+ hf.Command(command=f'echo "{out_msg}"'),
220
+ hf.Command(command=f'>&2 echo "{err_msg}"'),
221
+ ],
222
+ rules=[
223
+ hf.ActionRule(
224
+ rule=hf.Rule(
225
+ path="resources.os_name",
226
+ condition={"value.equal_to": "posix"},
227
+ )
228
+ )
229
+ ],
230
+ ),
231
+ hf.Action(
232
+ commands=[
233
+ hf.Command(command=f'Write-Output "{out_msg}"'),
234
+ hf.Command(command=f'$host.ui.WriteErrorLine("{err_msg}")'),
235
+ ],
236
+ rules=[
237
+ hf.ActionRule(
238
+ rule=hf.Rule(
239
+ path="resources.os_name",
240
+ condition={"value.equal_to": "nt"},
241
+ )
242
+ )
243
+ ],
244
+ ),
245
+ ],
246
+ )
247
+ t1 = hf.Task(schema=s1)
248
+ wk = hf.Workflow.from_template_data(
249
+ tasks=[t1],
250
+ template_name="test_combine_jobscript_std",
251
+ path=tmp_path,
252
+ resources={"any": {"combine_jobscript_std": False}},
253
+ )
254
+ wk.submit(wait=True, add_to_known=False, status=False)
255
+
256
+ jobscript = wk.submissions[0].jobscripts[0]
257
+
258
+ assert not jobscript.resources.combine_jobscript_std
259
+
260
+ out_err_path = jobscript.direct_std_out_err_path
261
+ out_path = jobscript.direct_stdout_path
262
+ err_path = jobscript.direct_stderr_path
263
+
264
+ assert not out_err_path.is_file()
265
+ assert out_path.is_file()
266
+ assert err_path.is_file()
267
+
268
+ assert out_path.read_text().strip() == out_msg
269
+ assert err_path.read_text().strip() == err_msg
270
+
271
+
272
+ @pytest.mark.integration
273
+ def test_write_app_logs_true(null_config, tmp_path):
274
+
275
+ p1_vals = [101, 102]
276
+ t1 = hf.Task(
277
+ schema=hf.task_schemas.test_t1_conditional_OS,
278
+ sequences=[hf.ValueSequence("inputs.p1", values=p1_vals)],
279
+ )
280
+ wk = hf.Workflow.from_template_data(
281
+ tasks=[t1],
282
+ template_name="test_write_app_logs",
283
+ path=tmp_path,
284
+ config={
285
+ "log_file_level": "debug"
286
+ }, # ensure there is something to write to the log
287
+ resources={"any": {"write_app_logs": True}},
288
+ )
289
+ wk.submit(wait=True, add_to_known=False, status=False)
290
+
291
+ run_0 = wk.tasks[0].elements[0].action_runs[0]
292
+ run_1 = wk.tasks[0].elements[1].action_runs[0]
293
+
294
+ run_0_log_path = run_0.get_app_log_path()
295
+ run_1_log_path = run_1.get_app_log_path()
296
+
297
+ assert run_0_log_path.is_file()
298
+ assert run_1_log_path.is_file()
299
+
300
+
301
+ @pytest.mark.integration
302
+ def test_write_app_logs_false(null_config, tmp_path):
303
+
304
+ p1_vals = [101, 102]
305
+ t1 = hf.Task(
306
+ schema=hf.task_schemas.test_t1_conditional_OS,
307
+ sequences=[hf.ValueSequence("inputs.p1", values=p1_vals)],
308
+ )
309
+ wk = hf.Workflow.from_template_data(
310
+ tasks=[t1],
311
+ template_name="test_write_app_logs",
312
+ path=tmp_path,
313
+ config={
314
+ "log_file_level": "debug"
315
+ }, # ensure there is something to write to the log
316
+ resources={"any": {"write_app_logs": False}},
317
+ )
318
+ wk.submit(wait=True, add_to_known=False, status=False)
319
+
320
+ run_0 = wk.tasks[0].elements[0].action_runs[0]
321
+ run_1 = wk.tasks[0].elements[1].action_runs[0]
322
+
323
+ run_0_log_path = run_0.get_app_log_path()
324
+ run_1_log_path = run_1.get_app_log_path()
325
+
326
+ assert not wk.submissions[0].app_log_path.is_dir()
327
+ assert not run_0_log_path.is_file()
328
+ assert not run_1_log_path.is_file()
329
+
330
+
331
+ @pytest.mark.integration
332
+ def test_jobscript_start_end_times_equal_to_first_and_last_run_start_end_times(
333
+ null_config, tmp_path
334
+ ):
335
+
336
+ t1 = hf.Task(
337
+ schema=hf.task_schemas.test_t1_conditional_OS,
338
+ sequences=[hf.ValueSequence(path="inputs.p1", values=list(range(2)))],
339
+ )
340
+ wk = hf.Workflow.from_template_data(
341
+ template_name="test_jobscript_start_end_times",
342
+ path=tmp_path,
343
+ tasks=[t1],
344
+ )
345
+ wk.submit(wait=True, add_to_known=False, status=False)
346
+
347
+ js = wk.submissions[0].jobscripts[0]
348
+ runs = wk.get_all_EARs()
349
+ assert len(runs) == 2
350
+
351
+ # jobsript has two runs, so start time should be start time of first run:
352
+ assert js.start_time == runs[0].start_time
353
+
354
+ # ...and end time should be end time of second run:
355
+ assert js.end_time == runs[1].end_time
@@ -0,0 +1,198 @@
1
+ import os
2
+ from textwrap import dedent
3
+
4
+ import pytest
5
+
6
+ from hpcflow.app import app as hf
7
+ from hpcflow.sdk.core.actions import EARStatus
8
+
9
+
10
+ @pytest.mark.integration
11
+ @pytest.mark.parametrize("combine_scripts", [True, False])
12
+ def test_run_status_fail_when_missing_script_output_data_file(
13
+ null_config, tmp_path, combine_scripts
14
+ ):
15
+
16
+ s1 = hf.TaskSchema(
17
+ objective="t1",
18
+ outputs=[hf.SchemaOutput(parameter=hf.Parameter("p1"))],
19
+ actions=[
20
+ hf.Action(
21
+ script="<<script:main_script_test_json_out_FAIL.py>>",
22
+ script_data_out="json",
23
+ script_exe="python_script",
24
+ environments=[hf.ActionEnvironment(environment="python_env")],
25
+ requires_dir=True,
26
+ )
27
+ ],
28
+ )
29
+
30
+ tasks = [
31
+ hf.Task(s1), # will fail due to not generaing an output data file
32
+ ]
33
+
34
+ wk = hf.Workflow.from_template_data(
35
+ template_name="test_run_status_fail_missing_script_output_file",
36
+ path=tmp_path,
37
+ tasks=tasks,
38
+ resources={
39
+ "any": {
40
+ "write_app_logs": True,
41
+ "skip_downstream_on_failure": False,
42
+ "combine_scripts": combine_scripts,
43
+ }
44
+ },
45
+ )
46
+ wk.submit(wait=True, add_to_known=False, status=False)
47
+ runs = wk.get_all_EARs()
48
+ assert runs[0].status is EARStatus.error
49
+
50
+
51
+ @pytest.mark.integration
52
+ @pytest.mark.parametrize("combine_scripts", [True, False])
53
+ def test_run_status_fail_when_missing_script_output_data_file_OFP_fail(
54
+ null_config, tmp_path, combine_scripts
55
+ ):
56
+
57
+ out_file_name = "my_output_file.txt"
58
+ out_file = hf.FileSpec(label="my_output_file", name=out_file_name)
59
+
60
+ if os.name == "nt":
61
+ cmd = f"Set-Content -Path {out_file_name} -Value (<<parameter:p1>> + 100)"
62
+ else:
63
+ cmd = f"echo $(( <<parameter:p1>> + 100 )) > {out_file_name}"
64
+
65
+ # this script parses the output file but then deletes this file so it can't be saved!
66
+ act = hf.Action(
67
+ commands=[hf.Command(cmd)],
68
+ output_file_parsers=[
69
+ hf.OutputFileParser(
70
+ output_files=[out_file],
71
+ output=hf.Parameter("p2"),
72
+ script="<<script:output_file_parser_basic_FAIL.py>>",
73
+ save_files=True,
74
+ ),
75
+ ],
76
+ environments=[hf.ActionEnvironment(environment="python_env")],
77
+ )
78
+
79
+ s1 = hf.TaskSchema(
80
+ objective="t1",
81
+ inputs=[hf.SchemaInput(parameter=hf.Parameter("p1"))],
82
+ outputs=[hf.SchemaInput(parameter=hf.Parameter("p2"))],
83
+ actions=[act],
84
+ )
85
+ t1 = hf.Task(schema=s1, inputs={"p1": 100})
86
+
87
+ wk = hf.Workflow.from_template_data(
88
+ template_name="test_run_status_fail_missing_OFP_save_file",
89
+ path=tmp_path,
90
+ tasks=[t1],
91
+ resources={
92
+ "any": {
93
+ "write_app_logs": True,
94
+ "skip_downstream_on_failure": False,
95
+ "combine_scripts": combine_scripts,
96
+ }
97
+ },
98
+ )
99
+ wk.submit(wait=True, add_to_known=False, status=False)
100
+ runs = wk.get_all_EARs()
101
+ assert runs[0].status is EARStatus.success
102
+ assert runs[1].status is EARStatus.error
103
+
104
+
105
+ @pytest.mark.integration
106
+ @pytest.mark.parametrize("combine_scripts", [True, False])
107
+ def test_run_status_fail_when_missing_IFG_input_file(
108
+ null_config, tmp_path, combine_scripts
109
+ ):
110
+
111
+ inp_file = hf.FileSpec(label="my_input_file", name="my_input_file.txt")
112
+
113
+ if os.name == "nt":
114
+ cmd = dedent(
115
+ """\
116
+ try {
117
+ Get-Content "<<file:my_input_file>>" -ErrorAction Stop
118
+ } catch {
119
+ Write-Host "File does not exist."
120
+ exit 1
121
+ }
122
+ """
123
+ )
124
+ else:
125
+ cmd = "cat <<file:my_input_file>>"
126
+
127
+ # this script silently fails to generate the input file!
128
+ s1 = hf.TaskSchema(
129
+ objective="t1",
130
+ inputs=[hf.SchemaInput(parameter=hf.Parameter("p1"))],
131
+ actions=[
132
+ hf.Action(
133
+ commands=[hf.Command(cmd)],
134
+ input_file_generators=[
135
+ hf.InputFileGenerator(
136
+ input_file=inp_file,
137
+ inputs=[hf.Parameter("p1")],
138
+ script="<<script:input_file_generator_basic_FAIL.py>>",
139
+ ),
140
+ ],
141
+ environments=[hf.ActionEnvironment(environment="python_env")],
142
+ )
143
+ ],
144
+ )
145
+ t1 = hf.Task(schema=s1, inputs={"p1": 100})
146
+ wk = hf.Workflow.from_template_data(
147
+ template_name="test_run_status_fail_missing_IFG_save_file",
148
+ path=tmp_path,
149
+ tasks=[t1],
150
+ resources={
151
+ "any": {
152
+ "write_app_logs": True,
153
+ "skip_downstream_on_failure": False,
154
+ "combine_scripts": combine_scripts,
155
+ }
156
+ },
157
+ )
158
+ wk.submit(wait=True, add_to_known=False, status=False)
159
+ runs = wk.get_all_EARs()
160
+ assert runs[0].status is EARStatus.error # no input file to save
161
+ assert runs[1].status is EARStatus.error # no input file to consume
162
+
163
+
164
+ @pytest.mark.integration
165
+ @pytest.mark.parametrize("combine_scripts", [True, False])
166
+ def test_run_status_fail_when_action_save_file(null_config, tmp_path, combine_scripts):
167
+
168
+ my_file = hf.FileSpec(label="my_file", name="my_file.txt")
169
+
170
+ # this script does not generate a file that can be saved:
171
+ s1 = hf.TaskSchema(
172
+ objective="t1",
173
+ actions=[
174
+ hf.Action(
175
+ script="<<script:do_nothing.py>>",
176
+ script_exe="python_script",
177
+ environments=[hf.ActionEnvironment(environment="python_env")],
178
+ requires_dir=True,
179
+ save_files=[my_file],
180
+ )
181
+ ],
182
+ )
183
+ t1 = hf.Task(schema=s1)
184
+ wk = hf.Workflow.from_template_data(
185
+ template_name="test_run_status_fail_missing_action_save_file",
186
+ path=tmp_path,
187
+ tasks=[t1],
188
+ resources={
189
+ "any": {
190
+ "write_app_logs": True,
191
+ "skip_downstream_on_failure": False,
192
+ "combine_scripts": combine_scripts,
193
+ }
194
+ },
195
+ )
196
+ wk.submit(wait=True, add_to_known=False, status=False)
197
+ runs = wk.get_all_EARs()
198
+ assert runs[0].status is EARStatus.error # no file to save