idmtools-test 0.0.0.dev0__py3-none-any.whl → 0.0.2__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 (141) hide show
  1. idmtools_test/__init__.py +16 -8
  2. idmtools_test/inputs/__init__.py +0 -0
  3. idmtools_test/inputs/assets/collections/1/a.txt +0 -0
  4. idmtools_test/inputs/assets/collections/1/b.txt +0 -0
  5. idmtools_test/inputs/assets/collections/2/c.txt +0 -0
  6. idmtools_test/inputs/assets/collections/d.txt +0 -0
  7. idmtools_test/inputs/builder/sweeps.csv +6 -0
  8. idmtools_test/inputs/builder/sweeps.yaml +8 -0
  9. idmtools_test/inputs/compsplatform/__init__.py +0 -0
  10. idmtools_test/inputs/compsplatform/failing_model.py +5 -0
  11. idmtools_test/inputs/compsplatform/mixed_model.py +10 -0
  12. idmtools_test/inputs/compsplatform/working_model.py +5 -0
  13. idmtools_test/inputs/configuration/idmtools_test.ini +71 -0
  14. idmtools_test/inputs/custom/Eradication.exe +0 -0
  15. idmtools_test/inputs/custom/Local_Migration.bin +0 -0
  16. idmtools_test/inputs/custom/Local_Migration.bin.json +12 -0
  17. idmtools_test/inputs/custom/Regional_Migration.bin +0 -0
  18. idmtools_test/inputs/custom/Regional_Migration.bin.json +12 -0
  19. idmtools_test/inputs/custom/Zambia_30arcsec_air_temperature_daily.bin +0 -0
  20. idmtools_test/inputs/custom/Zambia_30arcsec_air_temperature_daily.bin.json +26 -0
  21. idmtools_test/inputs/custom/Zambia_30arcsec_rainfall_daily.bin +0 -0
  22. idmtools_test/inputs/custom/Zambia_30arcsec_rainfall_daily.bin.json +26 -0
  23. idmtools_test/inputs/custom/Zambia_30arcsec_relative_humidity_daily.bin +0 -0
  24. idmtools_test/inputs/custom/Zambia_30arcsec_relative_humidity_daily.bin.json +26 -0
  25. idmtools_test/inputs/custom/campaign.json +95384 -0
  26. idmtools_test/inputs/custom/config.json +943 -0
  27. idmtools_test/inputs/custom/custom_reports.json +163 -0
  28. idmtools_test/inputs/custom/demo.json +1258 -0
  29. idmtools_test/inputs/custom/emodules_map.json +9 -0
  30. idmtools_test/inputs/custom/reporter_plugins/libReportMalariaFiltered.dll +0 -0
  31. idmtools_test/inputs/custom/reporter_plugins/libSpatialReportMalariaFiltered.dll +0 -0
  32. idmtools_test/inputs/custom/reporter_plugins/libreporteventcounter.dll +0 -0
  33. idmtools_test/inputs/duplicated_model/exe/Eradication +0 -0
  34. idmtools_test/inputs/duplicated_model/f1 +0 -0
  35. idmtools_test/inputs/emod/Eradication.exe +0 -0
  36. idmtools_test/inputs/emod_files/campaign.json +21 -0
  37. idmtools_test/inputs/emod_files/config.json +125 -0
  38. idmtools_test/inputs/emod_files/demographics.json +81 -0
  39. idmtools_test/inputs/fakemodels/AnotherOne +0 -0
  40. idmtools_test/inputs/fakemodels/Eradication +0 -0
  41. idmtools_test/inputs/fakemodels/Eradication-2.11.custom.exe +0 -0
  42. idmtools_test/inputs/fakemodels/Eradication.exe +0 -0
  43. idmtools_test/inputs/files/campaign.json +21 -0
  44. idmtools_test/inputs/files/config.json +119 -0
  45. idmtools_test/inputs/files/demographics.json +82 -0
  46. idmtools_test/inputs/files/hello.txt +1 -0
  47. idmtools_test/inputs/id_files/slurm.example_python_experiment.id +1 -0
  48. idmtools_test/inputs/malaria_brazil_central_west_spatial/Assets/Brazil_Central_West_Brazil_Central_West_2.5arcmin_air_temperature_daily.bin +0 -0
  49. idmtools_test/inputs/malaria_brazil_central_west_spatial/Assets/Brazil_Central_West_Brazil_Central_West_2.5arcmin_air_temperature_daily.bin.json +26 -0
  50. idmtools_test/inputs/malaria_brazil_central_west_spatial/Assets/Brazil_Central_West_Brazil_Central_West_2.5arcmin_demographics.json +559 -0
  51. idmtools_test/inputs/malaria_brazil_central_west_spatial/Assets/Brazil_Central_West_Brazil_Central_West_2.5arcmin_rainfall_daily.bin +0 -0
  52. idmtools_test/inputs/malaria_brazil_central_west_spatial/Assets/Brazil_Central_West_Brazil_Central_West_2.5arcmin_rainfall_daily.bin.json +26 -0
  53. idmtools_test/inputs/malaria_brazil_central_west_spatial/Assets/Brazil_Central_West_Brazil_Central_West_2.5arcmin_relative_humidity_daily.bin +0 -0
  54. idmtools_test/inputs/malaria_brazil_central_west_spatial/Assets/Brazil_Central_West_Brazil_Central_West_2.5arcmin_relative_humidity_daily.bin.json +26 -0
  55. idmtools_test/inputs/malaria_brazil_central_west_spatial/Assets/Eradication +0 -0
  56. idmtools_test/inputs/malaria_brazil_central_west_spatial/Assets/Eradication.exe +0 -0
  57. idmtools_test/inputs/malaria_brazil_central_west_spatial/campaign.json +4 -0
  58. idmtools_test/inputs/malaria_brazil_central_west_spatial/config.json +667 -0
  59. idmtools_test/inputs/malaria_brazil_central_west_spatial/malaria_brazil_central_west_spatial-ERA5Input_demo.csv +37 -0
  60. idmtools_test/inputs/python/Assets/MyExternalLibrary/__init__.py +0 -0
  61. idmtools_test/inputs/python/Assets/MyExternalLibrary/functions.py +15 -0
  62. idmtools_test/inputs/python/Assets/MyLib/functions.py +2 -0
  63. idmtools_test/inputs/python/Assets/MyLib/temp.py +271 -0
  64. idmtools_test/inputs/python/Assets/__init__.py +0 -0
  65. idmtools_test/inputs/python/__init__.py +0 -0
  66. idmtools_test/inputs/python/folder_dup_file/__init__.py +0 -0
  67. idmtools_test/inputs/python/folder_dup_file/model1.py +19 -0
  68. idmtools_test/inputs/python/hello_world.py +1 -0
  69. idmtools_test/inputs/python/model.py +26 -0
  70. idmtools_test/inputs/python/model1.py +20 -0
  71. idmtools_test/inputs/python/model3.py +21 -0
  72. idmtools_test/inputs/python/newmodel2.py +20 -0
  73. idmtools_test/inputs/python/output_generator/generate.py +39 -0
  74. idmtools_test/inputs/python/realpath_verify.py +6 -0
  75. idmtools_test/inputs/python/ye_seir_model/Assets/MyExternalLibrary/Python36/dtk_generic_intrahost.pyd +0 -0
  76. idmtools_test/inputs/python/ye_seir_model/Assets/MyExternalLibrary/Python36/dtk_nodedemog.pyd +0 -0
  77. idmtools_test/inputs/python/ye_seir_model/Assets/MyExternalLibrary/Python37/dtk_generic_intrahost.pyd +0 -0
  78. idmtools_test/inputs/python/ye_seir_model/Assets/MyExternalLibrary/Python37/dtk_nodedemog.pyd +0 -0
  79. idmtools_test/inputs/python/ye_seir_model/Assets/SEIR_model.py +252 -0
  80. idmtools_test/inputs/python/ye_seir_model/Assets/SEIR_model_slurm.py +242 -0
  81. idmtools_test/inputs/python/ye_seir_model/Assets/config_sim.py +48 -0
  82. idmtools_test/inputs/python/ye_seir_model/Assets/custom_csv_analyzer.py +133 -0
  83. idmtools_test/inputs/python/ye_seir_model/Assets/python.sh +4 -0
  84. idmtools_test/inputs/python/ye_seir_model/Assets/requirements.txt +4 -0
  85. idmtools_test/inputs/python/ye_seir_model/Assets/templates/config.json +68 -0
  86. idmtools_test/inputs/python/ye_seir_model/Assets/templates/demographics_template.json +44 -0
  87. idmtools_test/inputs/python/ye_seir_model/__init__.py +0 -0
  88. idmtools_test/inputs/python_experiments/__init__.py +0 -0
  89. idmtools_test/inputs/python_experiments/model.py +10 -0
  90. idmtools_test/inputs/r/model1.R +1 -0
  91. idmtools_test/inputs/r/ncov_analysis/individual_dynamics_estimates/estimate_incubation_period.R +89 -0
  92. idmtools_test/inputs/regression/107/Assets/__init__.py +0 -0
  93. idmtools_test/inputs/regression/107/Assets/model.py +1 -0
  94. idmtools_test/inputs/regression/107/__init__.py +0 -0
  95. idmtools_test/inputs/regression/125/Assets/__init__.py +0 -0
  96. idmtools_test/inputs/regression/125/Assets/model.py +1 -0
  97. idmtools_test/inputs/regression/125/Assets2/__init__.py +0 -0
  98. idmtools_test/inputs/regression/125/Assets2/dir1/__init__.py +0 -0
  99. idmtools_test/inputs/regression/125/Assets2/dir1/model.py +1 -0
  100. idmtools_test/inputs/regression/125/Assets2/dir2/__init__.py +0 -0
  101. idmtools_test/inputs/regression/125/Assets2/dir2/model.py +1 -0
  102. idmtools_test/inputs/regression/125/__init__.py +0 -0
  103. idmtools_test/inputs/regression/__init__.py +0 -0
  104. idmtools_test/inputs/scheduling/hpc/WorkOrder.json +7 -0
  105. idmtools_test/inputs/scheduling/slurm/WorkOrder.json +11 -0
  106. idmtools_test/inputs/scheduling/slurm/WorkOrder1.json +11 -0
  107. idmtools_test/inputs/scheduling/slurm/WorkOrder2.json +13 -0
  108. idmtools_test/inputs/scheduling/slurm/commandline_model.py +22 -0
  109. idmtools_test/inputs/serialization/Eradication.exe +0 -0
  110. idmtools_test/inputs/serialization/single_node_demographics.json +82 -0
  111. idmtools_test/inputs/singularity/alpine_simple/Singularity.def +28 -0
  112. idmtools_test/inputs/singularity/alpine_simple/run_model.py +41 -0
  113. idmtools_test/inputs/singularity/alpine_template/Singularity.jinja +22 -0
  114. idmtools_test/test_precreate_hooks.py +25 -0
  115. idmtools_test/utils/__init__.py +0 -0
  116. idmtools_test/utils/cli.py +41 -0
  117. idmtools_test/utils/common_experiments.py +79 -0
  118. idmtools_test/utils/comps.py +152 -0
  119. idmtools_test/utils/decorators.py +208 -0
  120. idmtools_test/utils/execute_operations/__init__.py +0 -0
  121. idmtools_test/utils/execute_operations/experiment_operations.py +237 -0
  122. idmtools_test/utils/execute_operations/simulate_operations.py +368 -0
  123. idmtools_test/utils/itest_with_persistence.py +25 -0
  124. idmtools_test/utils/operations/__init__.py +0 -0
  125. idmtools_test/utils/operations/experiment_operations.py +64 -0
  126. idmtools_test/utils/operations/simulation_operations.py +114 -0
  127. idmtools_test/utils/shared_functions.py +25 -0
  128. idmtools_test/utils/test_asset.py +89 -0
  129. idmtools_test/utils/test_asset_collection.py +223 -0
  130. idmtools_test/utils/test_execute_platform.py +137 -0
  131. idmtools_test/utils/test_platform.py +94 -0
  132. idmtools_test/utils/test_task.py +69 -0
  133. idmtools_test/utils/utils.py +146 -0
  134. idmtools_test-0.0.2.dist-info/METADATA +48 -0
  135. idmtools_test-0.0.2.dist-info/RECORD +139 -0
  136. idmtools_test-0.0.2.dist-info/entry_points.txt +9 -0
  137. idmtools_test-0.0.2.dist-info/licenses/LICENSE.TXT +3 -0
  138. idmtools_test-0.0.0.dev0.dist-info/METADATA +0 -41
  139. idmtools_test-0.0.0.dev0.dist-info/RECORD +0 -5
  140. {idmtools_test-0.0.0.dev0.dist-info → idmtools_test-0.0.2.dist-info}/WHEEL +0 -0
  141. {idmtools_test-0.0.0.dev0.dist-info → idmtools_test-0.0.2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,368 @@
1
+ import hashlib
2
+ import json
3
+ import os
4
+ import shlex
5
+ import shutil
6
+ import subprocess
7
+ import sys
8
+ from dataclasses import dataclass
9
+ from functools import partial
10
+ from logging import getLogger
11
+ from pathlib import Path
12
+ from threading import Lock
13
+ from typing import List, Dict, Any, Type, TYPE_CHECKING, Optional
14
+ from idmtools.assets import Asset, AssetCollection
15
+ from idmtools.core import EntityStatus
16
+ from idmtools.core.task_factory import TaskFactory
17
+ from idmtools.entities import CommandLine
18
+ from idmtools.entities.command_task import CommandTask
19
+ from idmtools.entities.experiment import Experiment
20
+ from idmtools.entities.iplatform_ops.iplatform_simulation_operations import IPlatformSimulationOperations
21
+ from idmtools.entities.simulation import Simulation
22
+ from idmtools.utils.file import file_content_to_generator
23
+ from idmtools.utils.json import IDMJSONEncoder
24
+
25
+ if TYPE_CHECKING: # pragma: no cover
26
+ from idmtools_test.utils.test_execute_platform import TestExecutePlatform
27
+
28
+ current_directory = os.path.dirname(os.path.realpath(__file__))
29
+ data_path = os.path.abspath(os.path.join(current_directory, "..", "..", "data"))
30
+
31
+ logger = getLogger(__name__)
32
+ SIMULATION_LOCK = Lock()
33
+
34
+
35
+ class SimulationDict(dict):
36
+ pass
37
+
38
+
39
+ def run_simulation(simulation_id: Simulation, command: str, parent_uid: str, execute_directory, shell: bool = False):
40
+ simulation_path = os.path.join(execute_directory, str(parent_uid), str(simulation_id))
41
+ os.makedirs(simulation_path, exist_ok=True)
42
+ with open(os.path.join(simulation_path, "StdOut.txt"), "w") as out, \
43
+ open(os.path.join(simulation_path, "StdErr.txt"), "w") as err:
44
+ try:
45
+ cmd = str(command)
46
+ print(cmd)
47
+ print(execute_directory)
48
+ if cmd.startswith(execute_directory):
49
+ cmd = cmd.replace(execute_directory, "")
50
+ logger.info('Executing %s from working directory %s', cmd, simulation_path)
51
+ err.write(f"{cmd}\n")
52
+
53
+ # Run our task
54
+ if sys.platform in ['win32', 'cygwin']:
55
+ cmd = shlex.split(cmd.replace("\\", "/"))
56
+ if os.path.exists(os.path.join(simulation_path, cmd[0])):
57
+ cmd[0] = os.path.join(simulation_path, cmd[0])
58
+ else:
59
+ cmd[0] = os.path.abspath(cmd[0])
60
+ cmd = subprocess.list2cmdline(cmd)
61
+ else:
62
+ cmd = shlex.split(cmd.replace("\\", "/"))
63
+ try:
64
+ if not os.access(cmd[0], os.X_OK):
65
+ os.chmod(cmd[0], 0o777)
66
+ except:
67
+ pass
68
+ logger.info(cmd)
69
+ if cmd[0].endswith(".sh"):
70
+ cmd.insert(0, "/bin/bash")
71
+ p = subprocess.Popen(
72
+ cmd,
73
+ cwd=simulation_path,
74
+ env=os.environ,
75
+ shell=shell,
76
+ stdout=out,
77
+ stderr=err
78
+ )
79
+ # store the pid in case we want to cancel later
80
+ logger.info(f"Process id: {p.pid}")
81
+ # Log that we have started this particular simulation
82
+ p.wait()
83
+ if p.returncode == 0:
84
+ status = EntityStatus.SUCCEEDED
85
+ else:
86
+ status = EntityStatus.FAILED
87
+ except Exception as e:
88
+ logger.exception(e)
89
+ err.write(str(e))
90
+ status = EntityStatus.FAILED
91
+ return parent_uid, simulation_id, status
92
+
93
+
94
+ @dataclass
95
+ class TestExecutePlatformSimulationOperation(IPlatformSimulationOperations):
96
+ platform: 'TestExecutePlatform'
97
+ platform_type: Type = SimulationDict
98
+
99
+ def get(self, simulation_id: str, experiment_id: str = None, **kwargs) -> Any:
100
+ if simulation_id and experiment_id:
101
+ exp_path = os.path.join(self.platform.execute_directory, str(experiment_id))
102
+ sim_path = os.path.join(exp_path, str(simulation_id))
103
+ metadata_src = os.path.join(sim_path, "simulation_metadata.json")
104
+ if not os.path.exists(metadata_src):
105
+ logger.error("Cannot find the simulation at {metadata}")
106
+ raise ValueError(f"Cannot find the simulation at {metadata_src}")
107
+ with open(metadata_src, 'r') as metadata_in:
108
+ metadata = json.load(metadata_in)
109
+ return SimulationDict(metadata)
110
+
111
+ def platform_create(self, simulation: Simulation, **kwargs) -> Simulation:
112
+ simulation.platform = self
113
+ experiment_id = simulation.parent_id
114
+ self.save_metadata(simulation)
115
+ return simulation
116
+
117
+ def batch_create(self, sims: List[Simulation], **kwargs) -> List[Simulation]:
118
+
119
+ simulations = []
120
+ experiment_id = None
121
+ for simulation in sims:
122
+ if simulation.status is None:
123
+ self.pre_create(simulation, **kwargs)
124
+ experiment_id = simulation.parent_id
125
+ self.save_metadata(simulation)
126
+ self.post_create(simulation, **kwargs)
127
+ simulations.append(simulation)
128
+ else:
129
+ simulations.append(simulation)
130
+ return simulations
131
+
132
+ def save_metadata(self, simulation: Simulation, update_data: dict = None):
133
+ exp_path = os.path.join(self.platform.execute_directory, str(simulation.parent_id))
134
+ sim_path = os.path.join(exp_path, str(simulation.id))
135
+ metadata_file = os.path.join(sim_path, "simulation_metadata.json")
136
+ os.makedirs(sim_path, exist_ok=True)
137
+ if update_data and os.path.exists(metadata_file):
138
+ with open(metadata_file, 'r') as metadata_src:
139
+ metadata = json.load(metadata_src)
140
+ metadata.update(update_data)
141
+ else:
142
+ metadata = simulation.to_dict()
143
+
144
+ with open(metadata_file, 'w') as out:
145
+ out.write(json.dumps(metadata, cls=IDMJSONEncoder))
146
+
147
+ def get_parent(self, simulation: Any, **kwargs) -> Any:
148
+ return self.platform._experiments.experiments.get(simulation.parent_id)
149
+
150
+ def platform_run_item(self, simulation: Simulation, **kwargs):
151
+ """
152
+ Run the item on the test platform
153
+
154
+ This method executes the simulation in a thread pool
155
+
156
+ Args:
157
+ simulation: Simulation to run
158
+ **kwargs:
159
+
160
+ Returns:
161
+
162
+ """
163
+ logger.info(f"Running simulation {simulation.id}")
164
+ self.send_assets(simulation, **kwargs)
165
+ future = self.platform.pool.submit(
166
+ run_simulation,
167
+ simulation.id,
168
+ simulation.task.command,
169
+ simulation.parent.uid,
170
+ self.platform.execute_directory
171
+ )
172
+ self.platform.queue.append(future)
173
+
174
+ def send_assets(self, simulation: Any, **kwargs):
175
+ """
176
+ Send assets to the test platform. This method uses the execute directory and first tries to link files
177
+ If that fails, files are copied into the directory
178
+
179
+ Args:
180
+ simulation: Simulation assets to send
181
+ **kwargs:
182
+
183
+ Returns:
184
+
185
+ """
186
+ exp_path = self.platform._experiments.get_experiment_path(simulation.parent.uid)
187
+ sim_path = self.get_simulation_asset_path(simulation, experiment_path=exp_path)
188
+ logger.info(f"Creating {exp_path}")
189
+ os.makedirs(sim_path, exist_ok=True)
190
+ self.__copy_simulation_assets_to_simulation_directory(sim_path, simulation)
191
+ exp_path = os.path.join(exp_path, "Assets")
192
+ self._copy_or_link_parent_assets(exp_path, sim_path, simulation)
193
+
194
+ def get_simulation_asset_path(self, simulation: Simulation, experiment_path: str = None) -> str:
195
+ """
196
+ Get path to simulation assets
197
+
198
+ Args:
199
+ simulation: Simulation Assets to get path to
200
+
201
+ Returns:
202
+ Str path to assets
203
+ """
204
+ if experiment_path is None:
205
+ experiment_path = self.platform._experiments.get_experiment_path(simulation.parent.uid)
206
+ return os.path.join(experiment_path, str(simulation.id))
207
+
208
+ @staticmethod
209
+ def _copy_or_link_parent_assets(exp_path: str, sim_path: str, simulation: Simulation):
210
+ """
211
+ Link or Copy a simulation parent assets into its directory
212
+
213
+ Args:
214
+ exp_path: Path to experiment assets
215
+ sim_path: Simulation path
216
+ simulation: Simulation
217
+
218
+ Returns:
219
+
220
+ """
221
+ for asset in simulation.parent.assets:
222
+ remote_path = Path(exp_path).joinpath(asset.relative_path) if asset.relative_path else Path(exp_path)
223
+ sim_assets = Path(sim_path).joinpath("Assets")
224
+ src_path = remote_path.joinpath(asset.filename)
225
+ dest_path = sim_assets.joinpath(asset.filename)
226
+ sim_assets.mkdir(parents=True, exist_ok=True)
227
+ if sys.platform in ['win32']:
228
+ import win32file
229
+ link_worked = True
230
+ try:
231
+ logger.info("Trying to link the files")
232
+ if link_worked:
233
+ win32file.CreateSymbolicLink(src_path, dest_path, 1)
234
+ else:
235
+ shutil.copy(src_path, dest_path)
236
+ except Exception:
237
+ link_worked = False
238
+ logger.info("Linking failed. Copying instread")
239
+ shutil.copy(src_path, dest_path)
240
+ else:
241
+ os.symlink(src_path, dest_path)
242
+
243
+ @staticmethod
244
+ def __copy_simulation_assets_to_simulation_directory(sim_path:str, simulation: Simulation):
245
+ for asset in simulation.assets:
246
+ remote_path = os.path.join(sim_path, asset.relative_path) if asset.relative_path else sim_path
247
+ remote_path = os.path.join(remote_path, asset.filename)
248
+ if asset.absolute_path:
249
+ logger.info(f"Copying {asset.absolute_path} to {remote_path}")
250
+ shutil.copy(asset.absolute_path, remote_path)
251
+ else:
252
+ logger.info(f"Writing {asset.absolute_path} to {remote_path}")
253
+ with open(os.path.join(remote_path), 'wb') as out:
254
+ out.write(asset.content.encode())
255
+
256
+ def refresh_status(self, simulation: Simulation, **kwargs):
257
+ pass
258
+
259
+ def get_assets(self, simulation: Simulation, files: List[str], **kwargs) -> Dict[str, bytearray]:
260
+ """
261
+ Get list of files
262
+ Args:
263
+ simulation:
264
+ files:
265
+ **kwargs:
266
+
267
+ Returns:
268
+
269
+ """
270
+ logger.info(f'Listing assets for {simulation.id}')
271
+ assets = {}
272
+ for root, dirs, actual_files in os.walk(self.get_simulation_asset_path(simulation)):
273
+ for file in actual_files:
274
+ if file in files:
275
+ fp = os.path.abspath(os.path.join(root, file))
276
+ with open(fp, 'rb') as i:
277
+ assets[file] = i.read()
278
+ return assets
279
+
280
+ def list_assets(self, simulation: Simulation, **kwargs) -> List[Asset]:
281
+ """
282
+ List assets for an item
283
+
284
+ Args:
285
+ simulation: Simulation to list assets for
286
+ **kwargs:
287
+
288
+ Returns:
289
+
290
+ """
291
+ logger.info(f'Listing assets for {simulation.id}')
292
+ assets = []
293
+ for root, dirs, files in os.walk(self.get_simulation_asset_path(simulation)):
294
+ for file in files:
295
+ fp = os.path.abspath(os.path.join(root, file))
296
+ asset = Asset(absolute_path=fp, filename=file)
297
+ asset.download_generator_hook = partial(file_content_to_generator, fp)
298
+ cksum_hash = hashlib.md5()
299
+ with open(fp, 'rb') as fin:
300
+ cksum_hash.update(fin.read())
301
+ asset.checksum = cksum_hash.hexdigest()
302
+ assets.append(asset)
303
+ return assets
304
+
305
+ def to_entity(self, dict_sim: Dict, load_task: bool = False, parent: Optional[Experiment] = None,
306
+ **kwargs) -> Simulation:
307
+ # convert the dictionary to simulation
308
+ sim: Simulation = Simulation(**{k: v for k, v in dict_sim.items() if k not in ['platform_id', 'item_type']})
309
+ try:
310
+ # load status from str
311
+ sim.status = EntityStatus[sim.status.upper()]
312
+ except:
313
+ pass
314
+ # set platform before we load assets
315
+ sim.platform = self.platform
316
+ # and our parent
317
+ if parent:
318
+ sim.parent = parent
319
+ # get path to our assets
320
+ sim_path = self.get_simulation_asset_path(sim)
321
+ # set task to a blank slate
322
+ sim.task = None
323
+ # load assets first
324
+ if dict_sim['assets']:
325
+ # load the assets
326
+ ac = AssetCollection()
327
+ for dict_asset in dict_sim['assets']:
328
+ if dict_asset['absolute_path'] is None:
329
+ if dict_asset['relative_path']:
330
+ dict_asset['absolute_path'] = os.path.join(sim_path, dict_asset['relative_path'], dict_asset['filename'])
331
+ else:
332
+ dict_asset['absolute_path'] = os.path.join(sim_path, dict_asset['filename'])
333
+ asset = Asset(**dict_asset)
334
+ asset.persisted = True
335
+ asset.download_generator_hook = partial(file_content_to_generator, asset.absolute_path)
336
+ ac.add_asset(asset)
337
+ sim.assets = ac
338
+ # should we fully load the task?
339
+ if load_task:
340
+ if parent.task_type:
341
+ try:
342
+ sim.task = TaskFactory().create(parent.task_type, **dict_sim['task'])
343
+ except Exception as e:
344
+ logger.exception(e)
345
+
346
+ cli = self._detect_command_line_from_simulation(dict_sim)
347
+ # if we could not find task, set it now, otherwise rebuild the cli
348
+ if sim.task is None:
349
+ sim.task = CommandTask(CommandLine.from_string(cli))
350
+ else:
351
+ sim.task.command = CommandLine.from_string(cli)
352
+ # call task load options(load configs from files, etc)
353
+ sim.task.reload_from_simulation(sim)
354
+ else:
355
+ cli = self._detect_command_line_from_simulation(dict_sim)
356
+ sim.task = CommandTask(cli)
357
+
358
+ # load assets
359
+ return sim
360
+
361
+ def _detect_command_line_from_simulation(self, dict_sim):
362
+ if 'task' in dict_sim and 'command' in dict_sim['task']:
363
+ return dict_sim['task']['command']
364
+ return ''
365
+
366
+
367
+
368
+
@@ -0,0 +1,25 @@
1
+ import os
2
+ import shutil
3
+ import unittest
4
+
5
+ from idmtools.services.ipersistance_service import IPersistenceService
6
+
7
+
8
+ class ITestWithPersistence(unittest.TestCase):
9
+ current_directory = os.path.dirname(os.path.realpath(__file__))
10
+
11
+ def setUp(self) -> None:
12
+ self.data_dir = os.path.join(self.current_directory, "data")
13
+ try:
14
+ os.makedirs(self.data_dir, exist_ok=True)
15
+ except FileExistsError:
16
+ # Very Very Very rarely, high thread counts, we hit this error
17
+ pass
18
+
19
+ IPersistenceService.cache_directory = self.data_dir
20
+
21
+ def tearDown(self) -> None:
22
+ try:
23
+ shutil.rmtree(self.data_dir)
24
+ except Exception:
25
+ pass
File without changes
@@ -0,0 +1,64 @@
1
+ import os
2
+ from dataclasses import field, dataclass
3
+ from logging import getLogger, DEBUG
4
+ from threading import Lock
5
+ from typing import List, Any, Type, Dict, Union, TYPE_CHECKING
6
+ from idmtools.core import EntityStatus, UnknownItemException
7
+ from idmtools.entities.experiment import Experiment
8
+ from idmtools.entities.iplatform_ops.iplatform_experiment_operations import IPlatformExperimentOperations
9
+ if TYPE_CHECKING: # pragma: no cover
10
+ from idmtools_test.utils.test_platform import TestPlatform
11
+
12
+ logger = getLogger(__name__)
13
+ current_directory = os.path.dirname(os.path.realpath(__file__))
14
+ data_path = os.path.abspath(os.path.join(current_directory, "..", "..", "data"))
15
+ EXPERIMENTS_LOCK = Lock()
16
+
17
+
18
+ @dataclass
19
+ class TestPlatformExperimentOperation(IPlatformExperimentOperations):
20
+ platform: 'TestPlatform'
21
+ platform_type: Type = Experiment
22
+ experiments: Dict[str, Experiment] = field(default_factory=dict, compare=False, metadata={"pickle_ignore": True})
23
+
24
+ def get(self, experiment_id: str, **kwargs) -> Experiment:
25
+ e = self.experiments.get(experiment_id)
26
+ if e is None:
27
+ raise UnknownItemException(f"Cannot find the experiment with the ID of: {experiment_id}")
28
+ e.platform = self.platform
29
+ return e
30
+
31
+ def platform_create(self, experiment: Experiment, **kwargs) -> Experiment:
32
+ if logger.isEnabledFor(DEBUG):
33
+ logger.debug('Creating Experiment')
34
+ EXPERIMENTS_LOCK.acquire()
35
+ self.experiments[experiment.uid] = experiment
36
+ EXPERIMENTS_LOCK.release()
37
+ self.platform._simulations._save_simulations_to_cache(experiment.uid, list(), overwrite=True)
38
+ logger.debug(f"Created Experiment {experiment.uid}")
39
+ return experiment
40
+
41
+ def get_children(self, experiment: Experiment, **kwargs) -> List[Any]:
42
+ return self.platform._simulations.simulations.get(experiment.uid)
43
+
44
+ def get_parent(self, experiment: Any, **kwargs) -> Any:
45
+ return None
46
+
47
+ def platform_run_item(self, experiment: Experiment, **kwargs):
48
+ self.platform._simulations.set_simulation_status(experiment.uid, EntityStatus.RUNNING)
49
+
50
+ def send_assets(self, experiment: Any, **kwargs):
51
+ pass
52
+
53
+ def refresh_status(self, experiment: Experiment, **kwargs):
54
+ if logger.isEnabledFor(DEBUG):
55
+ logger.debug(f'Refreshing status for Experiment: {experiment.uid}')
56
+ for simulation in self.platform._simulations.simulations.get(experiment.uid):
57
+ for esim in experiment.simulations:
58
+ if esim == simulation:
59
+ logger.debug(f'Setting {simulation.uid} Status to {simulation.status}')
60
+ esim.status = simulation.status
61
+ break
62
+
63
+ def list_assets(self, experiment: Experiment, **kwargs) -> List[str]:
64
+ pass
@@ -0,0 +1,114 @@
1
+ import os
2
+ from dataclasses import dataclass, field
3
+ from logging import getLogger, DEBUG
4
+ from threading import Lock
5
+ from typing import List, Dict, Any, Type, TYPE_CHECKING
6
+ from uuid import uuid4
7
+ import numpy as np
8
+ from idmtools.assets import Asset
9
+ from idmtools.entities.iplatform_ops.iplatform_simulation_operations import IPlatformSimulationOperations
10
+ from idmtools.entities.simulation import Simulation
11
+ if TYPE_CHECKING: # pragma: no cover
12
+ from idmtools_test.utils.test_platform import TestPlatform
13
+ current_directory = os.path.dirname(os.path.realpath(__file__))
14
+ data_path = os.path.abspath(os.path.join(current_directory, "..", "..", "data"))
15
+
16
+ logger = getLogger(__name__)
17
+ SIMULATION_LOCK = Lock()
18
+
19
+
20
+ @dataclass
21
+ class TestPlatformSimulationOperation(IPlatformSimulationOperations):
22
+ platform: 'TestPlatform'
23
+
24
+ def all_files(self, simulation: Simulation, **kwargs):
25
+ pass
26
+
27
+ platform_type: Type = Simulation
28
+ simulations: dict = field(default_factory=dict, compare=False, metadata={"pickle_ignore": True})
29
+
30
+ def get(self, simulation_id: str, **kwargs) -> Any:
31
+ obj = None
32
+ for eid in self.simulations:
33
+ sims = self.simulations.get(eid)
34
+ if sims:
35
+ for sim in self.simulations.get(eid):
36
+ if sim.uid == simulation_id:
37
+ obj = sim
38
+ break
39
+ if obj:
40
+ break
41
+ obj.platform = self.platform
42
+ return obj
43
+
44
+ def platform_create(self, simulation: Simulation, **kwargs) -> Simulation:
45
+ experiment_id = simulation.parent_id
46
+ simulation.uid = str(uuid4())
47
+
48
+ self._save_simulations_to_cache(experiment_id, [simulation])
49
+ return simulation
50
+
51
+ def _save_simulations_to_cache(self, experiment_id, simulations: List[Simulation], overwrite: bool = False):
52
+ if logger.isEnabledFor(DEBUG):
53
+ logger.debug(f'Saving {len(simulations)} to Experiment {experiment_id}')
54
+ SIMULATION_LOCK.acquire()
55
+ existing_simulations = [] if overwrite else self.simulations.pop(experiment_id)
56
+ self.simulations[experiment_id] = existing_simulations + simulations
57
+ SIMULATION_LOCK.release()
58
+ logger.debug(f'Saved sims')
59
+
60
+ def batch_create(self, sims: List[Simulation], **kwargs) -> List[Simulation]:
61
+ simulations = []
62
+ experiment_id = None
63
+ for simulation in sims:
64
+ if simulation.status is None:
65
+ self.pre_create(simulation)
66
+ experiment_id = simulation.parent_id
67
+ self.post_create(simulation)
68
+ simulations.append(simulation)
69
+
70
+ if experiment_id:
71
+ self._save_simulations_to_cache(experiment_id, simulations)
72
+ return simulations
73
+
74
+ def get_parent(self, simulation: Any, **kwargs) -> Any:
75
+ return self.platform._experiments.experiments.get(simulation.parent_id)
76
+
77
+ def platform_run_item(self, simulation: Simulation, **kwargs):
78
+ pass
79
+
80
+ def send_assets(self, simulation: Any, **kwargs):
81
+ pass
82
+
83
+ def refresh_status(self, simulation: Simulation, **kwargs):
84
+ pass
85
+
86
+ def get_assets(self, simulation: Simulation, files: List[str], **kwargs) -> Dict[str, bytearray]:
87
+ return {}
88
+
89
+ def list_assets(self, simulation: Simulation, **kwargs) -> List[Asset]:
90
+ pass
91
+
92
+ def set_simulation_status(self, experiment_uid, status):
93
+ self.set_simulation_prob_status(experiment_uid, {status: 1})
94
+
95
+ def set_simulation_prob_status(self, experiment_uid, status):
96
+ if logger.isEnabledFor(DEBUG):
97
+ logger.debug(f'Setting status for sim s on exp {experiment_uid} to {status}')
98
+ simulations = self.simulations.get(experiment_uid)
99
+ for simulation in simulations:
100
+ new_status = np.random.choice(
101
+ a=list(status.keys()),
102
+ p=list(status.values())
103
+ )
104
+ simulation.status = new_status
105
+ self._save_simulations_to_cache(experiment_uid, simulations, True)
106
+
107
+ def set_simulation_num_status(self, experiment_uid, status, number):
108
+ simulations = self.simulations.get(experiment_uid)
109
+ for simulation in simulations:
110
+ simulation.status = status
111
+ number -= 1
112
+ if number <= 0:
113
+ break
114
+ self._save_simulations_to_cache(experiment_uid, simulations, True)
@@ -0,0 +1,25 @@
1
+ from operator import itemgetter
2
+ from COMPS.Data import Experiment as COMPSExperiment, QueryCriteria
3
+
4
+
5
+ def validate_output(self, exp_id, expected_sim_count):
6
+ sim_count = 0
7
+ for simulation in COMPSExperiment.get(exp_id).get_simulations():
8
+ sim_count = sim_count + 1
9
+ result_file_string = simulation.retrieve_output_files(paths=['output/result.json'])
10
+ print(result_file_string)
11
+ config_string = simulation.retrieve_output_files(paths=['config.json'])
12
+ print(config_string)
13
+ self.assertEqual(result_file_string, config_string)
14
+
15
+ self.assertEqual(sim_count, expected_sim_count)
16
+
17
+
18
+ def validate_sim_tags(self, exp_id, expected_tags):
19
+ tags = []
20
+ for simulation in COMPSExperiment.get(exp_id).get_simulations():
21
+ tags.append(simulation.get(simulation.id, QueryCriteria().select_children('tags')).tags)
22
+
23
+ sorted_tags = sorted(tags, key=itemgetter('a'))
24
+ sorted_expected_tags = sorted(expected_tags, key=itemgetter('a'))
25
+ self.assertEqual(sorted_tags, sorted_expected_tags)
@@ -0,0 +1,89 @@
1
+ import os
2
+ from typing import TypeVar, Union, List, Callable, Any
3
+ from idmtools.frozen.ifrozen import IFrozen
4
+
5
+
6
+ class Asset(IFrozen):
7
+ """
8
+ A class representing an asset. An asset can either be related to a physical
9
+ asset present on the computer or directly specified by a filename and content.
10
+ """
11
+
12
+ def __init__(self, absolute_path: 'str' = None, relative_path: 'str' = None, filename: 'str' = None,
13
+ content: 'Any' = None, handler: 'Callable' = str, checksum: str = None):
14
+ """
15
+ A constructor.
16
+
17
+ Args:
18
+ absolute_path: The absolute path of the asset. Optional if **filename** and **content** are given.
19
+ relative_path: The relative path (compared to the simulation root folder).
20
+ filename: Name of the file. Optional if **absolute_path** is given.
21
+ content: The content of the file. Optional if **absolute_path** is given.
22
+ """
23
+
24
+ super().__init__()
25
+ if not absolute_path and (not filename and not content):
26
+ raise ValueError("Impossible to create the asset without either absolute path or filename and content!")
27
+
28
+ self.absolute_path = absolute_path
29
+ self.relative_path = relative_path
30
+ self.filename = filename or os.path.basename(self.absolute_path)
31
+ self._content = content
32
+ self.persisted = False
33
+ self.handler = handler
34
+ self.checksum = checksum
35
+
36
+ def __repr__(self):
37
+ return f"<Asset: {os.path.join(self.relative_path, self.filename)} from {self.absolute_path}>"
38
+
39
+ @property
40
+ def relative_path(self):
41
+ return self._relative_path or ""
42
+
43
+ @relative_path.setter
44
+ def relative_path(self, relative_path):
45
+ self._relative_path = relative_path.strip(" \\/") if relative_path else None
46
+
47
+ @property
48
+ def bytes(self):
49
+ if isinstance(self.content, bytes):
50
+ return self.content
51
+ return str.encode(self.handler(self.content))
52
+
53
+ @property
54
+ def content(self):
55
+ """
56
+ Returns:
57
+ The content of the file, either from the content attribute or by opening the absolute path.
58
+ """
59
+ if not self._content:
60
+ with open(self.absolute_path, "rb") as fp:
61
+ self._content = fp.read()
62
+
63
+ return self._content
64
+
65
+ # region Equality and Hashing
66
+ def __eq__(self, other):
67
+ return self.__key() == other.__key()
68
+
69
+ def __key(self):
70
+ if self.absolute_path:
71
+ return self.absolute_path
72
+
73
+ if self.filename and self.relative_path:
74
+ return self.filename, self.relative_path
75
+
76
+ return self._content, self.filename
77
+
78
+ def __hash__(self):
79
+ return hash(self.__key())
80
+ # endregion
81
+
82
+
83
+ TAsset = TypeVar("TAsset", bound=Asset)
84
+ # Assets types
85
+ TAssetList = List[TAsset]
86
+
87
+ # Filters types
88
+ TAssetFilter = Union[Callable[[TAsset], bool], Callable]
89
+ TAssetFilterList = List[TAssetFilter]