test_config_comparator.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354
  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 import mock
  12. from taipy.config import Config
  13. from taipy.config._config import _Config
  14. from taipy.config._config_comparator._comparator_result import _ComparatorResult
  15. from taipy.config.global_app.global_app_config import GlobalAppConfig
  16. from tests.config.utils.section_for_tests import SectionForTest
  17. from tests.config.utils.unique_section_for_tests import UniqueSectionForTest
  18. class TestConfigComparator:
  19. unique_section_1 = UniqueSectionForTest(attribute="unique_attribute_1", prop="unique_prop_1")
  20. unique_section_1b = UniqueSectionForTest(attribute="unique_attribute_1", prop="unique_prop_1b")
  21. section_1 = SectionForTest("section_1", attribute="attribute_1", prop="prop_1")
  22. section_2 = SectionForTest("section_2", attribute=2, prop="prop_2")
  23. section_2b = SectionForTest("section_2", attribute="attribute_2", prop="prop_2b")
  24. section_3 = SectionForTest("section_3", attribute=[1, 2, 3, 4], prop=["prop_1"])
  25. section_3b = SectionForTest("section_3", attribute=[1, 2], prop=["prop_1", "prop_2", "prop_3"])
  26. section_3c = SectionForTest("section_3", attribute=[2, 1], prop=["prop_3", "prop_1", "prop_2"])
  27. def test_comparator_compare_method_call(self):
  28. _config_1 = _Config._default_config()
  29. _config_2 = _Config._default_config()
  30. with mock.patch(
  31. "taipy.config._config_comparator._config_comparator._ConfigComparator._find_conflict_config"
  32. ) as mck:
  33. Config._comparator._find_conflict_config(_config_1, _config_2)
  34. mck.assert_called_once_with(_config_1, _config_2)
  35. def test_comparator_without_diff(self):
  36. _config_1 = _Config._default_config()
  37. _config_2 = _Config._default_config()
  38. config_diff = Config._comparator._find_conflict_config(_config_1, _config_2)
  39. assert isinstance(config_diff, _ComparatorResult)
  40. assert config_diff == {}
  41. def test_comparator_with_updated_global_config(self):
  42. _config_1 = _Config._default_config()
  43. _config_1._global_config = GlobalAppConfig(foo="bar")
  44. _config_2 = _Config._default_config()
  45. _config_2._global_config = GlobalAppConfig(foo="baz", bar="foo")
  46. config_diff = Config._comparator._find_conflict_config(_config_1, _config_2)
  47. assert config_diff.get("unconflicted_sections") is None
  48. assert config_diff.get("conflicted_sections") is not None
  49. conflicted_config_diff = config_diff["conflicted_sections"]
  50. assert len(conflicted_config_diff["modified_items"]) == 1
  51. assert conflicted_config_diff["modified_items"][0] == (
  52. ("Global Configuration", "foo", None),
  53. ("bar", "baz"),
  54. )
  55. assert len(conflicted_config_diff["added_items"]) == 1
  56. assert conflicted_config_diff["added_items"][0] == (
  57. ("Global Configuration", "bar", None),
  58. "foo",
  59. )
  60. def test_comparator_with_new_section(self):
  61. _config_1 = _Config._default_config()
  62. # The first "section_name" is added to the Config
  63. _config_2 = _Config._default_config()
  64. _config_2._sections[SectionForTest.name] = {"section_1": self.section_1}
  65. config_diff = Config._comparator._find_conflict_config(_config_1, _config_2)
  66. conflicted_config_diff = config_diff["conflicted_sections"]
  67. assert len(conflicted_config_diff["added_items"]) == 1
  68. assert conflicted_config_diff["added_items"][0] == (
  69. ("section_name", None, None),
  70. {"section_1": {"attribute": "attribute_1", "prop": "prop_1"}},
  71. )
  72. assert conflicted_config_diff.get("modified_items") is None
  73. assert conflicted_config_diff.get("removed_items") is None
  74. # A new "section_name" is added to the Config
  75. _config_3 = _Config._default_config()
  76. _config_3._sections[SectionForTest.name] = {"section_1": self.section_1, "section_2": self.section_2}
  77. config_diff = Config._comparator._find_conflict_config(_config_2, _config_3)
  78. conflicted_config_diff = config_diff["conflicted_sections"]
  79. assert len(conflicted_config_diff["added_items"]) == 1
  80. assert conflicted_config_diff["added_items"][0] == (
  81. ("section_name", "section_2", None),
  82. {"attribute": "2:int", "prop": "prop_2"},
  83. )
  84. assert conflicted_config_diff.get("modified_items") is None
  85. assert conflicted_config_diff.get("removed_items") is None
  86. def test_comparator_with_removed_section(self):
  87. _config_1 = _Config._default_config()
  88. # All "section_name" sections are removed from the Config
  89. _config_2 = _Config._default_config()
  90. _config_2._sections[SectionForTest.name] = {"section_1": self.section_1}
  91. config_diff = Config._comparator._find_conflict_config(_config_2, _config_1)
  92. conflicted_config_diff = config_diff["conflicted_sections"]
  93. assert len(conflicted_config_diff["removed_items"]) == 1
  94. assert conflicted_config_diff["removed_items"][0] == (
  95. ("section_name", None, None),
  96. {"section_1": {"attribute": "attribute_1", "prop": "prop_1"}},
  97. )
  98. assert conflicted_config_diff.get("modified_items") is None
  99. assert conflicted_config_diff.get("added_items") is None
  100. # Section "section_1" is removed from the Config
  101. _config_3 = _Config._default_config()
  102. _config_3._sections[SectionForTest.name] = {"section_1": self.section_1, "section_2": self.section_2}
  103. config_diff = Config._comparator._find_conflict_config(_config_3, _config_2)
  104. conflicted_config_diff = config_diff["conflicted_sections"]
  105. assert len(conflicted_config_diff["removed_items"]) == 1
  106. assert conflicted_config_diff["removed_items"][0] == (
  107. ("section_name", "section_2", None),
  108. {"attribute": "2:int", "prop": "prop_2"},
  109. )
  110. assert conflicted_config_diff.get("modified_items") is None
  111. assert conflicted_config_diff.get("added_items") is None
  112. def test_comparator_with_modified_section(self):
  113. _config_1 = _Config._default_config()
  114. _config_1._sections[SectionForTest.name] = {"section_2": self.section_2}
  115. # All "section_name" sections are removed from the Config
  116. _config_2 = _Config._default_config()
  117. _config_2._sections[SectionForTest.name] = {"section_2": self.section_2b}
  118. config_diff = Config._comparator._find_conflict_config(_config_1, _config_2)
  119. conflicted_config_diff = config_diff["conflicted_sections"]
  120. assert len(conflicted_config_diff["modified_items"]) == 2
  121. assert conflicted_config_diff["modified_items"][0] == (
  122. ("section_name", "section_2", "attribute"),
  123. ("2:int", "attribute_2"),
  124. )
  125. assert conflicted_config_diff["modified_items"][1] == (
  126. ("section_name", "section_2", "prop"),
  127. ("prop_2", "prop_2b"),
  128. )
  129. assert conflicted_config_diff.get("removed_items") is None
  130. assert conflicted_config_diff.get("added_items") is None
  131. def test_comparator_with_modified_list_attribute(self):
  132. _config_1 = _Config._default_config()
  133. _config_1._sections[SectionForTest.name] = {"section_3": self.section_3}
  134. # All "section_name" sections are removed from the Config
  135. _config_2 = _Config._default_config()
  136. _config_2._sections[SectionForTest.name] = {"section_3": self.section_3b}
  137. config_diff = Config._comparator._find_conflict_config(_config_1, _config_2)
  138. conflicted_config_diff = config_diff["conflicted_sections"]
  139. assert len(conflicted_config_diff["modified_items"]) == 2
  140. assert conflicted_config_diff["modified_items"][0] == (
  141. ("section_name", "section_3", "prop"),
  142. (["prop_1"], ["prop_1", "prop_2", "prop_3"]),
  143. )
  144. assert conflicted_config_diff["modified_items"][1] == (
  145. ("section_name", "section_3", "attribute"),
  146. (["1:int", "2:int", "3:int", "4:int"], ["1:int", "2:int"]),
  147. )
  148. assert conflicted_config_diff.get("removed_items") is None
  149. assert conflicted_config_diff.get("added_items") is None
  150. def test_comparator_with_different_order_list_attributes(self):
  151. _config_1 = _Config._default_config()
  152. _config_1._sections[SectionForTest.name] = {"section_3": self.section_3b}
  153. # Create _config_2 with different order of list attributes
  154. _config_2 = _Config._default_config()
  155. _config_2._sections[SectionForTest.name] = {"section_3": self.section_3c}
  156. config_diff = Config._comparator._find_conflict_config(_config_1, _config_2)
  157. # There should be no difference since the order of list attributes is ignored
  158. assert config_diff == {}
  159. def test_comparator_with_new_unique_section(self):
  160. _config_1 = _Config._default_config()
  161. _config_2 = _Config._default_config()
  162. _config_2._unique_sections[UniqueSectionForTest.name] = self.unique_section_1
  163. config_diff = Config._comparator._find_conflict_config(_config_1, _config_2)
  164. conflicted_config_diff = config_diff["conflicted_sections"]
  165. assert len(conflicted_config_diff["added_items"]) == 1
  166. assert conflicted_config_diff["added_items"][0] == (
  167. ("unique_section_name", None, None),
  168. {"attribute": "unique_attribute_1", "prop": "unique_prop_1"},
  169. )
  170. assert conflicted_config_diff.get("modified_items") is None
  171. assert conflicted_config_diff.get("removed_items") is None
  172. def test_comparator_with_removed_unique_section(self):
  173. _config_1 = _Config._default_config()
  174. _config_2 = _Config._default_config()
  175. _config_2._unique_sections[UniqueSectionForTest.name] = self.unique_section_1
  176. config_diff = Config._comparator._find_conflict_config(_config_2, _config_1)
  177. conflicted_config_diff = config_diff["conflicted_sections"]
  178. assert len(conflicted_config_diff["removed_items"]) == 1
  179. assert conflicted_config_diff["removed_items"][0] == (
  180. ("unique_section_name", None, None),
  181. {"attribute": "unique_attribute_1", "prop": "unique_prop_1"},
  182. )
  183. assert conflicted_config_diff.get("modified_items") is None
  184. assert conflicted_config_diff.get("added_items") is None
  185. def test_comparator_with_modified_unique_section(self):
  186. _config_1 = _Config._default_config()
  187. _config_1._unique_sections[UniqueSectionForTest.name] = self.unique_section_1
  188. # All "section_name" sections are removed from the Config
  189. _config_2 = _Config._default_config()
  190. _config_2._unique_sections[UniqueSectionForTest.name] = self.unique_section_1b
  191. config_diff = Config._comparator._find_conflict_config(_config_1, _config_2)
  192. conflicted_config_diff = config_diff["conflicted_sections"]
  193. assert len(conflicted_config_diff["modified_items"]) == 1
  194. assert conflicted_config_diff["modified_items"][0] == (
  195. ("unique_section_name", "prop", None),
  196. ("unique_prop_1", "unique_prop_1b"),
  197. )
  198. assert conflicted_config_diff.get("removed_items") is None
  199. assert conflicted_config_diff.get("added_items") is None
  200. def test_unconflicted_section_name_store_statically(self):
  201. Config._comparator._add_unconflicted_section("section_name_1")
  202. assert Config._comparator._unconflicted_sections == {"section_name_1"}
  203. Config._comparator._add_unconflicted_section("section_name_2")
  204. assert Config._comparator._unconflicted_sections == {"section_name_1", "section_name_2"}
  205. Config._comparator._add_unconflicted_section("section_name_1")
  206. assert Config._comparator._unconflicted_sections == {"section_name_1", "section_name_2"}
  207. def test_unconflicted_diff_is_stored_separated_from_conflicted_ones(self):
  208. _config_1 = _Config._default_config()
  209. _config_1._unique_sections[UniqueSectionForTest.name] = self.unique_section_1
  210. _config_1._sections[SectionForTest.name] = {"section_2": self.section_2}
  211. _config_2 = _Config._default_config()
  212. _config_2._unique_sections[UniqueSectionForTest.name] = self.unique_section_1b
  213. _config_2._sections[SectionForTest.name] = {"section_2": self.section_2b}
  214. # Compare 2 Configuration
  215. config_diff = Config._comparator._find_conflict_config(_config_1, _config_2)
  216. assert config_diff.get("unconflicted_sections") is None
  217. assert config_diff.get("conflicted_sections") is not None
  218. assert len(config_diff["conflicted_sections"]["modified_items"]) == 3
  219. # Ignore any diff of "section_name" and compare
  220. Config._comparator._add_unconflicted_section("section_name")
  221. config_diff = Config._comparator._find_conflict_config(_config_1, _config_2)
  222. assert config_diff.get("unconflicted_sections") is not None
  223. assert len(config_diff["unconflicted_sections"]["modified_items"]) == 2
  224. assert config_diff.get("conflicted_sections") is not None
  225. assert len(config_diff["conflicted_sections"]["modified_items"]) == 1
  226. # Ignore any diff of Global Config and compare
  227. Config._comparator._add_unconflicted_section(["unique_section_name"])
  228. config_diff = Config._comparator._find_conflict_config(_config_1, _config_2)
  229. assert config_diff.get("unconflicted_sections") is not None
  230. assert len(config_diff["unconflicted_sections"]["modified_items"]) == 3
  231. assert config_diff.get("conflicted_sections") is None
  232. def test_comparator_log_message(self, caplog):
  233. _config_1 = _Config._default_config()
  234. _config_1._unique_sections[UniqueSectionForTest.name] = self.unique_section_1
  235. _config_1._sections[SectionForTest.name] = {"section_2": self.section_2}
  236. _config_2 = _Config._default_config()
  237. _config_2._unique_sections[UniqueSectionForTest.name] = self.unique_section_1b
  238. _config_2._sections[SectionForTest.name] = {"section_2": self.section_2b}
  239. # Ignore any diff of "section_name" and compare
  240. Config._comparator._add_unconflicted_section("section_name")
  241. Config._comparator._find_conflict_config(_config_1, _config_2)
  242. error_messages = caplog.text.strip().split("\n")
  243. assert len(error_messages) == 5
  244. assert all(
  245. t in error_messages[0]
  246. for t in [
  247. "INFO",
  248. "There are non-conflicting changes between the current configuration and the current configuration:",
  249. ]
  250. )
  251. assert 'section_name "section_2" has attribute "attribute" modified: 2:int -> attribute_2' in error_messages[1]
  252. assert 'section_name "section_2" has attribute "prop" modified: prop_2 -> prop_2b' in error_messages[2]
  253. assert all(
  254. t in error_messages[3]
  255. for t in [
  256. "ERROR",
  257. "The current configuration conflicts with the current configuration:",
  258. ]
  259. )
  260. assert 'unique_section_name "prop" was modified: unique_prop_1 -> unique_prop_1b' in error_messages[4]
  261. caplog.clear()
  262. Config._comparator._find_conflict_config(_config_1, _config_2, old_version_number="1.0")
  263. error_messages = caplog.text.strip().split("\n")
  264. assert len(error_messages) == 5
  265. assert all(
  266. t in error_messages[0]
  267. for t in [
  268. "INFO",
  269. "There are non-conflicting changes between the configuration for version 1.0 and the current "
  270. "configuration:",
  271. ]
  272. )
  273. assert all(
  274. t in error_messages[3]
  275. for t in [
  276. "ERROR",
  277. "The configuration for version 1.0 conflicts with the current configuration:",
  278. ]
  279. )
  280. caplog.clear()
  281. Config._comparator._compare(
  282. _config_1,
  283. _config_2,
  284. version_number_1="1.0",
  285. version_number_2="2.0",
  286. )
  287. error_messages = caplog.text.strip().split("\n")
  288. assert len(error_messages) == 3
  289. assert all(
  290. t in error_messages[0]
  291. for t in ["INFO", "Differences between version 1.0 Configuration and version 2.0 Configuration:"]
  292. )
  293. caplog.clear()