test_submission.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  1. # Copyright 2023 Avaiga Private Limited
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
  4. # the License. You may obtain a copy of the License at
  5. #
  6. # http://www.apache.org/licenses/LICENSE-2.0
  7. #
  8. # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
  9. # an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
  10. # specific language governing permissions and limitations under the License.
  11. from datetime import datetime
  12. from functools import partial
  13. from typing import Union
  14. from unittest import mock
  15. from unittest.mock import patch
  16. import pytest
  17. from src.taipy.core import TaskId
  18. from src.taipy.core.job._job_manager_factory import _JobManagerFactory
  19. from src.taipy.core.job.job import Job
  20. from src.taipy.core.job.status import Status
  21. from src.taipy.core.submission._submission_manager_factory import _SubmissionManagerFactory
  22. from src.taipy.core.submission.submission import Submission
  23. from src.taipy.core.submission.submission_status import SubmissionStatus
  24. from src.taipy.core.task._task_manager_factory import _TaskManagerFactory
  25. from src.taipy.core.task.task import Task
  26. def test_create_submission(scenario, job, current_datetime):
  27. submission_1 = Submission(scenario.id, scenario._ID_PREFIX)
  28. assert submission_1.id is not None
  29. assert submission_1.entity_id == scenario.id
  30. assert submission_1.jobs == []
  31. assert isinstance(submission_1.creation_date, datetime)
  32. assert submission_1._submission_status == SubmissionStatus.SUBMITTED
  33. assert submission_1._version is not None
  34. submission_2 = Submission(
  35. scenario.id,
  36. scenario._ID_PREFIX,
  37. "submission_id",
  38. [job],
  39. current_datetime,
  40. SubmissionStatus.COMPLETED,
  41. "version_id",
  42. )
  43. assert submission_2.id == "submission_id"
  44. assert submission_2.entity_id == scenario.id
  45. assert submission_2._jobs == [job]
  46. assert submission_2.creation_date == current_datetime
  47. assert submission_2._submission_status == SubmissionStatus.COMPLETED
  48. assert submission_2._version == "version_id"
  49. class MockJob:
  50. def __init__(self, id: str, status):
  51. self.status = status
  52. self.id = id
  53. def is_failed(self):
  54. return self.status == Status.FAILED
  55. def is_canceled(self):
  56. return self.status == Status.CANCELED
  57. def is_blocked(self):
  58. return self.status == Status.BLOCKED
  59. def is_pending(self):
  60. return self.status == Status.PENDING
  61. def is_running(self):
  62. return self.status == Status.RUNNING
  63. def is_completed(self):
  64. return self.status == Status.COMPLETED
  65. def is_skipped(self):
  66. return self.status == Status.SKIPPED
  67. def is_abandoned(self):
  68. return self.status == Status.ABANDONED
  69. def is_submitted(self):
  70. return self.status == Status.SUBMITTED
  71. def mock_get_jobs(job_ids):
  72. jobs = {
  73. "job0_submitted": MockJob("job0_submitted", Status.SUBMITTED),
  74. "job1_failed": MockJob("job1_failed", Status.FAILED),
  75. "job2_canceled": MockJob("job2_canceled", Status.CANCELED),
  76. "job3_blocked": MockJob("job3_blocked", Status.BLOCKED),
  77. "job4_pending": MockJob("job4_pending", Status.PENDING),
  78. "job5_running": MockJob("job5_running", Status.RUNNING),
  79. "job6_completed": MockJob("job6_completed", Status.COMPLETED),
  80. "job7_skipped": MockJob("job7_skipped", Status.SKIPPED),
  81. "job8_abandoned": MockJob("job8_abandoned", Status.ABANDONED),
  82. }
  83. return [jobs[job_id] for job_id in job_ids]
  84. def __test_update_submission_status(job_ids, expected_submission_status):
  85. with (
  86. patch(
  87. "src.taipy.core.submission.submission.Submission.jobs",
  88. new_callable=mock.PropertyMock,
  89. return_value=(mock_get_jobs(job_ids)),
  90. )
  91. ):
  92. submission = Submission("submission_id", "ENTITY_TYPE")
  93. submission._update_submission_status(None)
  94. assert submission.submission_status == expected_submission_status
  95. @pytest.mark.parametrize(
  96. "job_ids, expected_submission_status",
  97. [
  98. (["job1_failed"], SubmissionStatus.FAILED),
  99. (["job2_canceled"], SubmissionStatus.CANCELED),
  100. (["job3_blocked"], SubmissionStatus.BLOCKED),
  101. (["job4_pending"], SubmissionStatus.PENDING),
  102. (["job5_running"], SubmissionStatus.RUNNING),
  103. (["job6_completed"], SubmissionStatus.COMPLETED),
  104. (["job7_skipped"], SubmissionStatus.COMPLETED),
  105. (["job8_abandoned"], SubmissionStatus.UNDEFINED),
  106. ],
  107. )
  108. def test_update_single_submission_status(job_ids, expected_submission_status):
  109. __test_update_submission_status(job_ids, expected_submission_status)
  110. @pytest.mark.parametrize(
  111. "job_ids, expected_submission_status",
  112. [
  113. (["job1_failed", "job1_failed"], SubmissionStatus.FAILED),
  114. (["job1_failed", "job2_canceled"], SubmissionStatus.FAILED),
  115. (["job1_failed", "job3_blocked"], SubmissionStatus.FAILED),
  116. (["job1_failed", "job4_pending"], SubmissionStatus.FAILED),
  117. (["job1_failed", "job5_running"], SubmissionStatus.FAILED),
  118. (["job1_failed", "job6_completed"], SubmissionStatus.FAILED),
  119. (["job1_failed", "job7_skipped"], SubmissionStatus.FAILED),
  120. (["job1_failed", "job8_abandoned"], SubmissionStatus.FAILED),
  121. (["job2_canceled", "job1_failed"], SubmissionStatus.FAILED),
  122. (["job3_blocked", "job1_failed"], SubmissionStatus.FAILED),
  123. (["job4_pending", "job1_failed"], SubmissionStatus.FAILED),
  124. (["job5_running", "job1_failed"], SubmissionStatus.FAILED),
  125. (["job6_completed", "job1_failed"], SubmissionStatus.FAILED),
  126. (["job7_skipped", "job1_failed"], SubmissionStatus.FAILED),
  127. (["job8_abandoned", "job1_failed"], SubmissionStatus.FAILED),
  128. ],
  129. )
  130. def test_update_submission_status_with_one_failed_job_in_jobs(job_ids, expected_submission_status):
  131. __test_update_submission_status(job_ids, expected_submission_status)
  132. @pytest.mark.parametrize(
  133. "job_ids, expected_submission_status",
  134. [
  135. (["job2_canceled", "job2_canceled"], SubmissionStatus.CANCELED),
  136. (["job2_canceled", "job3_blocked"], SubmissionStatus.CANCELED),
  137. (["job2_canceled", "job4_pending"], SubmissionStatus.CANCELED),
  138. (["job2_canceled", "job5_running"], SubmissionStatus.CANCELED),
  139. (["job2_canceled", "job6_completed"], SubmissionStatus.CANCELED),
  140. (["job2_canceled", "job7_skipped"], SubmissionStatus.CANCELED),
  141. (["job2_canceled", "job8_abandoned"], SubmissionStatus.CANCELED),
  142. (["job3_blocked", "job2_canceled"], SubmissionStatus.CANCELED),
  143. (["job4_pending", "job2_canceled"], SubmissionStatus.CANCELED),
  144. (["job5_running", "job2_canceled"], SubmissionStatus.CANCELED),
  145. (["job6_completed", "job2_canceled"], SubmissionStatus.CANCELED),
  146. (["job7_skipped", "job2_canceled"], SubmissionStatus.CANCELED),
  147. (["job8_abandoned", "job2_canceled"], SubmissionStatus.CANCELED),
  148. ],
  149. )
  150. def test_update_submission_status_with_one_canceled_job_in_jobs(job_ids, expected_submission_status):
  151. __test_update_submission_status(job_ids, expected_submission_status)
  152. @pytest.mark.parametrize(
  153. "job_ids, expected_submission_status",
  154. [
  155. (["job4_pending", "job3_blocked"], SubmissionStatus.PENDING),
  156. (["job4_pending", "job4_pending"], SubmissionStatus.PENDING),
  157. (["job4_pending", "job6_completed"], SubmissionStatus.PENDING),
  158. (["job4_pending", "job7_skipped"], SubmissionStatus.PENDING),
  159. (["job3_blocked", "job4_pending"], SubmissionStatus.PENDING),
  160. (["job6_completed", "job4_pending"], SubmissionStatus.PENDING),
  161. (["job7_skipped", "job4_pending"], SubmissionStatus.PENDING),
  162. ],
  163. )
  164. def test_update_submission_status_with_no_failed_or_cancel_one_pending_in_jobs(job_ids, expected_submission_status):
  165. __test_update_submission_status(job_ids, expected_submission_status)
  166. @pytest.mark.parametrize(
  167. "job_ids, expected_submission_status",
  168. [
  169. (["job5_running", "job3_blocked"], SubmissionStatus.RUNNING),
  170. (["job5_running", "job4_pending"], SubmissionStatus.RUNNING),
  171. (["job5_running", "job5_running"], SubmissionStatus.RUNNING),
  172. (["job5_running", "job6_completed"], SubmissionStatus.RUNNING),
  173. (["job5_running", "job7_skipped"], SubmissionStatus.RUNNING),
  174. (["job3_blocked", "job5_running"], SubmissionStatus.RUNNING),
  175. (["job4_pending", "job5_running"], SubmissionStatus.RUNNING),
  176. (["job6_completed", "job5_running"], SubmissionStatus.RUNNING),
  177. (["job7_skipped", "job5_running"], SubmissionStatus.RUNNING),
  178. ],
  179. )
  180. def test_update_submission_status_with_no_failed_cancel_nor_pending_one_running_in_jobs(
  181. job_ids, expected_submission_status
  182. ):
  183. __test_update_submission_status(job_ids, expected_submission_status)
  184. @pytest.mark.parametrize(
  185. "job_ids, expected_submission_status",
  186. [
  187. (["job3_blocked", "job3_blocked"], SubmissionStatus.BLOCKED),
  188. (["job3_blocked", "job6_completed"], SubmissionStatus.BLOCKED),
  189. (["job3_blocked", "job7_skipped"], SubmissionStatus.BLOCKED),
  190. (["job6_completed", "job3_blocked"], SubmissionStatus.BLOCKED),
  191. (["job7_skipped", "job3_blocked"], SubmissionStatus.BLOCKED),
  192. ],
  193. )
  194. def test_update_submission_status_with_no_failed_cancel_pending_nor_running_one_blocked_in_jobs(
  195. job_ids, expected_submission_status
  196. ):
  197. __test_update_submission_status(job_ids, expected_submission_status)
  198. @pytest.mark.parametrize(
  199. "job_ids, expected_submission_status",
  200. [
  201. (["job6_completed", "job6_completed"], SubmissionStatus.COMPLETED),
  202. (["job6_completed", "job7_skipped"], SubmissionStatus.COMPLETED),
  203. (["job7_skipped", "job6_completed"], SubmissionStatus.COMPLETED),
  204. (["job7_skipped", "job7_skipped"], SubmissionStatus.COMPLETED),
  205. ],
  206. )
  207. def test_update_submission_status_with_only_completed_or_skipped_in_jobs(job_ids, expected_submission_status):
  208. __test_update_submission_status(job_ids, expected_submission_status)
  209. @pytest.mark.parametrize(
  210. "job_ids, expected_submission_status",
  211. [
  212. (["job3_blocked", "job8_abandoned"], SubmissionStatus.UNDEFINED),
  213. (["job4_pending", "job8_abandoned"], SubmissionStatus.UNDEFINED),
  214. (["job5_running", "job8_abandoned"], SubmissionStatus.UNDEFINED),
  215. (["job6_completed", "job8_abandoned"], SubmissionStatus.UNDEFINED),
  216. (["job7_skipped", "job8_abandoned"], SubmissionStatus.UNDEFINED),
  217. (["job8_abandoned", "job8_abandoned"], SubmissionStatus.UNDEFINED),
  218. (["job8_abandoned", "job3_blocked"], SubmissionStatus.UNDEFINED),
  219. (["job8_abandoned", "job4_pending"], SubmissionStatus.UNDEFINED),
  220. (["job8_abandoned", "job5_running"], SubmissionStatus.UNDEFINED),
  221. (["job8_abandoned", "job6_completed"], SubmissionStatus.UNDEFINED),
  222. (["job8_abandoned", "job7_skipped"], SubmissionStatus.UNDEFINED),
  223. ],
  224. )
  225. def test_update_submission_status_with_wrong_case_abandoned_without_cancel_or_failed_in_jobs(
  226. job_ids, expected_submission_status
  227. ):
  228. __test_update_submission_status(job_ids, expected_submission_status)
  229. def test_auto_set_and_reload():
  230. task = Task(config_id="name_1", properties={}, function=print, id=TaskId("task_1"))
  231. submission_1 = Submission(task.id, task._ID_PREFIX)
  232. job_1 = Job("job_1", task, submission_1.id, submission_1.entity_id)
  233. job_2 = Job("job_2", task, submission_1.id, submission_1.entity_id)
  234. _TaskManagerFactory._build_manager()._set(task)
  235. _SubmissionManagerFactory._build_manager()._set(submission_1)
  236. _JobManagerFactory._build_manager()._set(job_1)
  237. _JobManagerFactory._build_manager()._set(job_2)
  238. submission_2 = _SubmissionManagerFactory._build_manager()._get(submission_1)
  239. assert submission_1.id == submission_2.id
  240. assert submission_1.entity_id == submission_2.entity_id
  241. assert submission_1.creation_date == submission_2.creation_date
  242. assert submission_1.submission_status == submission_2.submission_status
  243. # auto set & reload on jobs attribute
  244. assert submission_1.jobs == []
  245. assert submission_2.jobs == []
  246. submission_1.jobs = [job_1]
  247. assert submission_1.jobs == [job_1]
  248. assert submission_2.jobs == [job_1]
  249. submission_2.jobs = [job_2]
  250. assert submission_1.jobs == [job_2]
  251. assert submission_2.jobs == [job_2]
  252. submission_1.jobs = [job_1, job_2]
  253. assert submission_1.jobs == [job_1, job_2]
  254. assert submission_2.jobs == [job_1, job_2]
  255. submission_2.jobs = [job_2, job_1]
  256. assert submission_1.jobs == [job_2, job_1]
  257. assert submission_2.jobs == [job_2, job_1]
  258. # auto set & reload on submission_status attribute
  259. assert submission_1.submission_status == SubmissionStatus.SUBMITTED
  260. assert submission_2.submission_status == SubmissionStatus.SUBMITTED
  261. submission_1.submission_status = SubmissionStatus.BLOCKED
  262. assert submission_1.submission_status == SubmissionStatus.BLOCKED
  263. assert submission_2.submission_status == SubmissionStatus.BLOCKED
  264. submission_2.submission_status = SubmissionStatus.COMPLETED
  265. assert submission_1.submission_status == SubmissionStatus.COMPLETED
  266. assert submission_2.submission_status == SubmissionStatus.COMPLETED
  267. with submission_1 as submission:
  268. assert submission.jobs == [job_2, job_1]
  269. assert submission.submission_status == SubmissionStatus.COMPLETED
  270. submission.jobs = [job_1]
  271. submission.submission_status = SubmissionStatus.PENDING
  272. assert submission.jobs == [job_2, job_1]
  273. assert submission.submission_status == SubmissionStatus.COMPLETED
  274. assert submission_1.jobs == [job_1]
  275. assert submission_1.submission_status == SubmissionStatus.PENDING
  276. assert submission_2.jobs == [job_1]
  277. assert submission_2.submission_status == SubmissionStatus.PENDING