test_core_cli.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566
  1. # Copyright 2021-2024 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 unittest.mock import patch
  12. import pytest
  13. from taipy.config.common.frequency import Frequency
  14. from taipy.config.common.scope import Scope
  15. from taipy.config.config import Config
  16. from taipy.core import Core, taipy
  17. from taipy.core._version._version_manager import _VersionManager
  18. from taipy.core._version._version_manager_factory import _VersionManagerFactory
  19. from taipy.core.common._utils import _load_fct
  20. from taipy.core.cycle._cycle_manager import _CycleManager
  21. from taipy.core.data._data_manager import _DataManager
  22. from taipy.core.exceptions.exceptions import NonExistingVersion
  23. from taipy.core.job._job_manager import _JobManager
  24. from taipy.core.scenario._scenario_manager import _ScenarioManager
  25. from taipy.core.sequence._sequence_manager import _SequenceManager
  26. from taipy.core.task._task_manager import _TaskManager
  27. def test_core_cli_no_arguments():
  28. with patch("sys.argv", ["prog"]):
  29. core = Core()
  30. core.run()
  31. assert Config.core.mode == "development"
  32. assert Config.core.version_number == _VersionManagerFactory._build_manager()._get_development_version()
  33. assert not Config.core.force
  34. core.stop()
  35. def test_core_cli_development_mode():
  36. with patch("sys.argv", ["prog", "--development"]):
  37. core = Core()
  38. core.run()
  39. assert Config.core.mode == "development"
  40. assert Config.core.version_number == _VersionManagerFactory._build_manager()._get_development_version()
  41. core.stop()
  42. def test_core_cli_dev_mode():
  43. with patch("sys.argv", ["prog", "-dev"]):
  44. core = Core()
  45. core.run()
  46. assert Config.core.mode == "development"
  47. assert Config.core.version_number == _VersionManagerFactory._build_manager()._get_development_version()
  48. core.stop()
  49. def test_core_cli_experiment_mode():
  50. with patch("sys.argv", ["prog", "--experiment"]):
  51. core = Core()
  52. core.run()
  53. assert Config.core.mode == "experiment"
  54. assert Config.core.version_number == _VersionManagerFactory._build_manager()._get_latest_version()
  55. assert not Config.core.force
  56. core.stop()
  57. def test_core_cli_experiment_mode_with_version():
  58. with patch("sys.argv", ["prog", "--experiment", "2.1"]):
  59. core = Core()
  60. core.run()
  61. assert Config.core.mode == "experiment"
  62. assert Config.core.version_number == "2.1"
  63. assert not Config.core.force
  64. core.stop()
  65. def test_core_cli_experiment_mode_with_force_version(init_config):
  66. with patch("sys.argv", ["prog", "--experiment", "2.1", "--taipy-force"]):
  67. init_config()
  68. core = Core()
  69. core.run()
  70. assert Config.core.mode == "experiment"
  71. assert Config.core.version_number == "2.1"
  72. assert Config.core.force
  73. core.stop()
  74. def test_core_cli_production_mode():
  75. with patch("sys.argv", ["prog", "--production"]):
  76. core = Core()
  77. core.run()
  78. assert Config.core.mode == "production"
  79. assert Config.core.version_number == _VersionManagerFactory._build_manager()._get_latest_version()
  80. assert not Config.core.force
  81. core.stop()
  82. def test_dev_mode_clean_all_entities_of_the_latest_version():
  83. scenario_config = config_scenario()
  84. # Create a scenario in development mode
  85. with patch("sys.argv", ["prog"]):
  86. core = Core()
  87. core.run()
  88. scenario = _ScenarioManager._create(scenario_config)
  89. taipy.submit(scenario)
  90. core.stop()
  91. # Initial assertion
  92. assert len(_DataManager._get_all(version_number="all")) == 2
  93. assert len(_TaskManager._get_all(version_number="all")) == 1
  94. assert len(_SequenceManager._get_all(version_number="all")) == 1
  95. assert len(_ScenarioManager._get_all(version_number="all")) == 1
  96. assert len(_CycleManager._get_all(version_number="all")) == 1
  97. assert len(_JobManager._get_all(version_number="all")) == 1
  98. # Create a new scenario in experiment mode
  99. with patch("sys.argv", ["prog", "--experiment"]):
  100. core = Core()
  101. core.run()
  102. scenario = _ScenarioManager._create(scenario_config)
  103. taipy.submit(scenario)
  104. core.stop()
  105. # Assert number of entities in 2nd version
  106. assert len(_DataManager._get_all(version_number="all")) == 4
  107. assert len(_TaskManager._get_all(version_number="all")) == 2
  108. assert len(_SequenceManager._get_all(version_number="all")) == 2
  109. assert len(_ScenarioManager._get_all(version_number="all")) == 2
  110. # No new cycle is created since old dev version use the same cycle
  111. assert len(_CycleManager._get_all(version_number="all")) == 1
  112. assert len(_JobManager._get_all(version_number="all")) == 2
  113. # Run development mode again
  114. with patch("sys.argv", ["prog", "--development"]):
  115. core = Core()
  116. core.run()
  117. # The 1st dev version should be deleted run with development mode
  118. assert len(_DataManager._get_all(version_number="all")) == 2
  119. assert len(_TaskManager._get_all(version_number="all")) == 1
  120. assert len(_SequenceManager._get_all(version_number="all")) == 1
  121. assert len(_ScenarioManager._get_all(version_number="all")) == 1
  122. assert len(_CycleManager._get_all(version_number="all")) == 1
  123. assert len(_JobManager._get_all(version_number="all")) == 1
  124. # Submit new dev version
  125. scenario = _ScenarioManager._create(scenario_config)
  126. taipy.submit(scenario)
  127. core.stop()
  128. # Assert number of entities with 1 dev version and 1 exp version
  129. assert len(_DataManager._get_all(version_number="all")) == 4
  130. assert len(_TaskManager._get_all(version_number="all")) == 2
  131. assert len(_SequenceManager._get_all(version_number="all")) == 2
  132. assert len(_ScenarioManager._get_all(version_number="all")) == 2
  133. assert len(_CycleManager._get_all(version_number="all")) == 1
  134. assert len(_JobManager._get_all(version_number="all")) == 2
  135. # Assert number of entities of the latest version only
  136. assert len(_DataManager._get_all(version_number="latest")) == 2
  137. assert len(_TaskManager._get_all(version_number="latest")) == 1
  138. assert len(_SequenceManager._get_all(version_number="latest")) == 1
  139. assert len(_ScenarioManager._get_all(version_number="latest")) == 1
  140. assert len(_JobManager._get_all(version_number="latest")) == 1
  141. # Assert number of entities of the development version only
  142. assert len(_DataManager._get_all(version_number="development")) == 2
  143. assert len(_TaskManager._get_all(version_number="development")) == 1
  144. assert len(_SequenceManager._get_all(version_number="development")) == 1
  145. assert len(_ScenarioManager._get_all(version_number="development")) == 1
  146. assert len(_JobManager._get_all(version_number="development")) == 1
  147. # Assert number of entities of an unknown version
  148. with pytest.raises(NonExistingVersion):
  149. assert _DataManager._get_all(version_number="foo")
  150. with pytest.raises(NonExistingVersion):
  151. assert _TaskManager._get_all(version_number="foo")
  152. with pytest.raises(NonExistingVersion):
  153. assert _SequenceManager._get_all(version_number="foo")
  154. with pytest.raises(NonExistingVersion):
  155. assert _ScenarioManager._get_all(version_number="foo")
  156. with pytest.raises(NonExistingVersion):
  157. assert _JobManager._get_all(version_number="foo")
  158. def twice_doppelganger(a):
  159. return a * 2
  160. def test_dev_mode_clean_all_entities_when_config_is_alternated():
  161. data_node_1_config = Config.configure_data_node(
  162. id="d1", storage_type="pickle", default_data="abc", scope=Scope.SCENARIO
  163. )
  164. data_node_2_config = Config.configure_data_node(id="d2", storage_type="csv", default_path="foo.csv")
  165. task_config = Config.configure_task("my_task", twice_doppelganger, data_node_1_config, data_node_2_config)
  166. scenario_config = Config.configure_scenario("my_scenario", [task_config], frequency=Frequency.DAILY)
  167. # Create a scenario in development mode with the doppelganger function
  168. with patch("sys.argv", ["prog"]):
  169. core = Core()
  170. core.run()
  171. scenario = _ScenarioManager._create(scenario_config)
  172. taipy.submit(scenario)
  173. core.stop()
  174. # Delete the twice_doppelganger function
  175. # and clear cache of _load_fct() to simulate a new run
  176. del globals()["twice_doppelganger"]
  177. _load_fct.cache_clear()
  178. # Create a scenario in development mode with another function
  179. scenario_config = config_scenario()
  180. with patch("sys.argv", ["prog"]):
  181. core = Core()
  182. core.run()
  183. scenario = _ScenarioManager._create(scenario_config)
  184. taipy.submit(scenario)
  185. core.stop()
  186. def test_version_number_when_switching_mode():
  187. with patch("sys.argv", ["prog", "--development"]):
  188. core = Core()
  189. core.run()
  190. ver_1 = _VersionManager._get_latest_version()
  191. ver_dev = _VersionManager._get_development_version()
  192. assert ver_1 == ver_dev
  193. assert len(_VersionManager._get_all()) == 1
  194. core.stop()
  195. # Run with dev mode, the version number is the same
  196. with patch("sys.argv", ["prog", "--development"]):
  197. core = Core()
  198. core.run()
  199. ver_2 = _VersionManager._get_latest_version()
  200. assert ver_2 == ver_dev
  201. assert len(_VersionManager._get_all()) == 1
  202. core.stop()
  203. # When run with experiment mode, a new version is created
  204. with patch("sys.argv", ["prog", "--experiment"]):
  205. core = Core()
  206. core.run()
  207. ver_3 = _VersionManager._get_latest_version()
  208. assert ver_3 != ver_dev
  209. assert len(_VersionManager._get_all()) == 2
  210. core.stop()
  211. with patch("sys.argv", ["prog", "--experiment", "2.1"]):
  212. core = Core()
  213. core.run()
  214. ver_4 = _VersionManager._get_latest_version()
  215. assert ver_4 == "2.1"
  216. assert len(_VersionManager._get_all()) == 3
  217. core.stop()
  218. with patch("sys.argv", ["prog", "--experiment"]):
  219. core = Core()
  220. core.run()
  221. ver_5 = _VersionManager._get_latest_version()
  222. assert ver_5 != ver_3
  223. assert ver_5 != ver_4
  224. assert ver_5 != ver_dev
  225. assert len(_VersionManager._get_all()) == 4
  226. core.stop()
  227. # When run with production mode, the latest version is used as production
  228. with patch("sys.argv", ["prog", "--production"]):
  229. core = Core()
  230. core.run()
  231. ver_6 = _VersionManager._get_latest_version()
  232. production_versions = _VersionManager._get_production_versions()
  233. assert ver_6 == ver_5
  234. assert production_versions == [ver_6]
  235. assert len(_VersionManager._get_all()) == 4
  236. core.stop()
  237. # When run with production mode, the "2.1" version is used as production
  238. with patch("sys.argv", ["prog", "--production", "2.1"]):
  239. core = Core()
  240. core.run()
  241. ver_7 = _VersionManager._get_latest_version()
  242. production_versions = _VersionManager._get_production_versions()
  243. assert ver_7 == "2.1"
  244. assert production_versions == [ver_6, ver_7]
  245. assert len(_VersionManager._get_all()) == 4
  246. core.stop()
  247. # Run with dev mode, the version number is the same as the first dev version to override it
  248. with patch("sys.argv", ["prog", "--development"]):
  249. core = Core()
  250. core.run()
  251. ver_7 = _VersionManager._get_latest_version()
  252. assert ver_1 == ver_7
  253. assert len(_VersionManager._get_all()) == 4
  254. core.stop()
  255. def test_production_mode_load_all_entities_from_previous_production_version():
  256. scenario_config = config_scenario()
  257. with patch("sys.argv", ["prog", "--development"]):
  258. core = Core()
  259. core.run()
  260. scenario = _ScenarioManager._create(scenario_config)
  261. taipy.submit(scenario)
  262. core.stop()
  263. with patch("sys.argv", ["prog", "--production", "1.0"]):
  264. core = Core()
  265. core.run()
  266. production_ver_1 = _VersionManager._get_latest_version()
  267. assert _VersionManager._get_production_versions() == [production_ver_1]
  268. # When run production mode on a new app, a dev version is created alongside
  269. assert _VersionManager._get_development_version() not in _VersionManager._get_production_versions()
  270. assert len(_VersionManager._get_all()) == 2
  271. scenario = _ScenarioManager._create(scenario_config)
  272. taipy.submit(scenario)
  273. assert len(_DataManager._get_all()) == 2
  274. assert len(_TaskManager._get_all()) == 1
  275. assert len(_SequenceManager._get_all()) == 1
  276. assert len(_ScenarioManager._get_all()) == 1
  277. assert len(_CycleManager._get_all()) == 1
  278. assert len(_JobManager._get_all()) == 1
  279. core.stop()
  280. with patch("sys.argv", ["prog", "--production", "2.0"]):
  281. core = Core()
  282. core.run()
  283. production_ver_2 = _VersionManager._get_latest_version()
  284. assert _VersionManager._get_production_versions() == [production_ver_1, production_ver_2]
  285. assert len(_VersionManager._get_all()) == 3
  286. # All entities from previous production version should be saved
  287. scenario = _ScenarioManager._create(scenario_config)
  288. taipy.submit(scenario)
  289. assert len(_DataManager._get_all()) == 4
  290. assert len(_TaskManager._get_all()) == 2
  291. assert len(_SequenceManager._get_all()) == 2
  292. assert len(_ScenarioManager._get_all()) == 2
  293. assert len(_CycleManager._get_all()) == 1
  294. assert len(_JobManager._get_all()) == 2
  295. core.stop()
  296. def test_force_override_experiment_version():
  297. scenario_config = config_scenario()
  298. with patch("sys.argv", ["prog", "--experiment", "1.0"]):
  299. core = Core()
  300. core.run()
  301. ver_1 = _VersionManager._get_latest_version()
  302. assert ver_1 == "1.0"
  303. # When create new experiment version, a development version entity is also created as a placeholder
  304. assert len(_VersionManager._get_all()) == 2 # 2 version include 1 experiment 1 development
  305. scenario = _ScenarioManager._create(scenario_config)
  306. taipy.submit(scenario)
  307. assert len(_DataManager._get_all()) == 2
  308. assert len(_TaskManager._get_all()) == 1
  309. assert len(_SequenceManager._get_all()) == 1
  310. assert len(_ScenarioManager._get_all()) == 1
  311. assert len(_CycleManager._get_all()) == 1
  312. assert len(_JobManager._get_all()) == 1
  313. core.stop()
  314. Config.configure_global_app(foo="bar")
  315. # Without --taipy-force parameter, a SystemExit will be raised
  316. with pytest.raises(SystemExit):
  317. with patch("sys.argv", ["prog", "--experiment", "1.0"]):
  318. core = Core()
  319. core.run()
  320. core.stop()
  321. # With --taipy-force parameter
  322. with patch("sys.argv", ["prog", "--experiment", "1.0", "--taipy-force"]):
  323. core = Core()
  324. core.run()
  325. ver_2 = _VersionManager._get_latest_version()
  326. assert ver_2 == "1.0"
  327. assert len(_VersionManager._get_all()) == 2 # 2 version include 1 experiment 1 development
  328. # All entities from previous submit should be saved
  329. scenario = _ScenarioManager._create(scenario_config)
  330. taipy.submit(scenario)
  331. assert len(_DataManager._get_all()) == 4
  332. assert len(_TaskManager._get_all()) == 2
  333. assert len(_SequenceManager._get_all()) == 2
  334. assert len(_ScenarioManager._get_all()) == 2
  335. assert len(_CycleManager._get_all()) == 1
  336. assert len(_JobManager._get_all()) == 2
  337. core.stop()
  338. def test_force_override_production_version():
  339. scenario_config = config_scenario()
  340. with patch("sys.argv", ["prog", "--production", "1.0"]):
  341. core = Core()
  342. core.run()
  343. ver_1 = _VersionManager._get_latest_version()
  344. production_versions = _VersionManager._get_production_versions()
  345. assert ver_1 == "1.0"
  346. assert production_versions == ["1.0"]
  347. # When create new production version, a development version entity is also created as a placeholder
  348. assert len(_VersionManager._get_all()) == 2 # 2 version include 1 production 1 development
  349. scenario = _ScenarioManager._create(scenario_config)
  350. taipy.submit(scenario)
  351. assert len(_DataManager._get_all()) == 2
  352. assert len(_TaskManager._get_all()) == 1
  353. assert len(_SequenceManager._get_all()) == 1
  354. assert len(_ScenarioManager._get_all()) == 1
  355. assert len(_CycleManager._get_all()) == 1
  356. assert len(_JobManager._get_all()) == 1
  357. core.stop()
  358. Config.configure_global_app(foo="bar")
  359. # Without --taipy-force parameter, a SystemExit will be raised
  360. with pytest.raises(SystemExit):
  361. with patch("sys.argv", ["prog", "--production", "1.0"]):
  362. core = Core()
  363. core.run()
  364. core.stop()
  365. # With --taipy-force parameter
  366. with patch("sys.argv", ["prog", "--production", "1.0", "--taipy-force"]):
  367. core = Core()
  368. core.run()
  369. ver_2 = _VersionManager._get_latest_version()
  370. assert ver_2 == "1.0"
  371. assert len(_VersionManager._get_all()) == 2 # 2 version include 1 production 1 development
  372. # All entities from previous submit should be saved
  373. scenario = _ScenarioManager._create(scenario_config)
  374. taipy.submit(scenario)
  375. assert len(_DataManager._get_all()) == 4
  376. assert len(_TaskManager._get_all()) == 2
  377. assert len(_SequenceManager._get_all()) == 2
  378. assert len(_ScenarioManager._get_all()) == 2
  379. assert len(_CycleManager._get_all()) == 1
  380. assert len(_JobManager._get_all()) == 2
  381. core.stop()
  382. def test_modified_job_configuration_dont_block_application_run(caplog, init_config):
  383. _ = config_scenario()
  384. with patch("sys.argv", ["prog", "--experiment", "1.0"]):
  385. core = Core()
  386. Config.configure_job_executions(mode="development")
  387. core.run()
  388. core.stop()
  389. init_config()
  390. _ = config_scenario()
  391. with patch("sys.argv", ["prog", "--experiment", "1.0"]):
  392. core = Core()
  393. Config.configure_job_executions(mode="standalone", max_nb_of_workers=3)
  394. core.run()
  395. error_message = str(caplog.text)
  396. assert 'JOB "mode" was modified' in error_message
  397. assert 'JOB "max_nb_of_workers" was added' in error_message
  398. core.stop()
  399. def test_modified_config_properties_without_force(caplog, init_config):
  400. _ = config_scenario()
  401. with patch("sys.argv", ["prog", "--experiment", "1.0"]):
  402. core = Core()
  403. core.run()
  404. core.stop()
  405. init_config()
  406. _ = config_scenario_2()
  407. with pytest.raises(SystemExit):
  408. with patch("sys.argv", ["prog", "--experiment", "1.0"]):
  409. core = Core()
  410. core.run()
  411. core.stop()
  412. error_message = str(caplog.text)
  413. assert 'DATA_NODE "d3" was added' in error_message
  414. assert 'JOB "max_nb_of_workers" was added' in error_message
  415. assert 'DATA_NODE "d0" was removed' in error_message
  416. assert 'DATA_NODE "d2" has attribute "default_path" modified' in error_message
  417. assert 'CORE "root_folder" was modified' in error_message
  418. assert 'CORE "repository_type" was modified' in error_message
  419. assert 'JOB "mode" was modified' in error_message
  420. assert 'SCENARIO "my_scenario" has attribute "frequency" modified' in error_message
  421. assert 'SCENARIO "my_scenario" has attribute "tasks" modified' in error_message
  422. assert 'TASK "my_task" has attribute "inputs" modified' in error_message
  423. assert 'TASK "my_task" has attribute "function" modified' in error_message
  424. assert 'TASK "my_task" has attribute "outputs" modified' in error_message
  425. assert 'DATA_NODE "d2" has attribute "has_header" modified' in error_message
  426. assert 'DATA_NODE "d2" has attribute "exposed_type" modified' in error_message
  427. assert 'CORE "repository_properties" was added' in error_message
  428. def twice(a):
  429. return a * 2
  430. def config_scenario():
  431. Config.configure_data_node(id="d0")
  432. data_node_1_config = Config.configure_data_node(
  433. id="d1", storage_type="pickle", default_data="abc", scope=Scope.SCENARIO
  434. )
  435. data_node_2_config = Config.configure_data_node(id="d2", storage_type="csv", default_path="foo.csv")
  436. task_config = Config.configure_task("my_task", twice, data_node_1_config, data_node_2_config)
  437. scenario_config = Config.configure_scenario("my_scenario", [task_config], frequency=Frequency.DAILY)
  438. scenario_config.add_sequences({"my_sequence": [task_config]})
  439. return scenario_config
  440. def double_twice(a):
  441. return a * 2, a * 2
  442. def config_scenario_2():
  443. Config.configure_core(
  444. root_folder="foo_root",
  445. # Changing the "storage_folder" will fail since older versions are stored in older folder
  446. # storage_folder="foo_storage",
  447. repository_type="bar",
  448. repository_properties={"foo": "bar"},
  449. )
  450. Config.configure_job_executions(mode="standalone", max_nb_of_workers=3)
  451. data_node_1_config = Config.configure_data_node(
  452. id="d1", storage_type="pickle", default_data="abc", scope=Scope.SCENARIO
  453. )
  454. # Modify properties of "d2"
  455. data_node_2_config = Config.configure_data_node(
  456. id="d2", storage_type="csv", default_path="bar.csv", has_header=False, exposed_type="numpy"
  457. )
  458. # Add new data node "d3"
  459. data_node_3_config = Config.configure_data_node(
  460. id="d3", storage_type="csv", default_path="baz.csv", has_header=False, exposed_type="numpy"
  461. )
  462. # Modify properties of "my_task", including the function and outputs list
  463. Config.configure_task("my_task", double_twice, data_node_3_config, [data_node_1_config, data_node_2_config])
  464. task_config_1 = Config.configure_task("my_task_1", double_twice, data_node_3_config, [data_node_2_config])
  465. # Modify properties of "my_scenario", where tasks is now my_task_1
  466. scenario_config = Config.configure_scenario("my_scenario", [task_config_1], frequency=Frequency.MONTHLY)
  467. scenario_config.add_sequences({"my_sequence": [task_config_1]})
  468. return scenario_config