test_config_comparator.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  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 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._unique_sections
  153. _config_1._sections[SectionForTest.name] = {"section_3": self.section_3b}
  154. # Create _config_2 with different order of list attributes
  155. _config_2 = _Config._default_config()
  156. _config_2._sections[SectionForTest.name] = {"section_3": self.section_3c}
  157. config_diff = Config._comparator._find_conflict_config(_config_1, _config_2)
  158. # There should be no difference since the order of list attributes is ignored
  159. assert config_diff == {}
  160. def test_comparator_with_new_unique_section(self):
  161. _config_1 = _Config._default_config()
  162. _config_2 = _Config._default_config()
  163. _config_2._unique_sections[UniqueSectionForTest.name] = self.unique_section_1
  164. config_diff = Config._comparator._find_conflict_config(_config_1, _config_2)
  165. conflicted_config_diff = config_diff["conflicted_sections"]
  166. assert len(conflicted_config_diff["added_items"]) == 1
  167. assert conflicted_config_diff["added_items"][0] == (
  168. ("unique_section_name", None, None),
  169. {"attribute": "unique_attribute_1", "prop": "unique_prop_1"},
  170. )
  171. assert conflicted_config_diff.get("modified_items") is None
  172. assert conflicted_config_diff.get("removed_items") is None
  173. def test_comparator_with_removed_unique_section(self):
  174. _config_1 = _Config._default_config()
  175. _config_2 = _Config._default_config()
  176. _config_2._unique_sections[UniqueSectionForTest.name] = self.unique_section_1
  177. config_diff = Config._comparator._find_conflict_config(_config_2, _config_1)
  178. conflicted_config_diff = config_diff["conflicted_sections"]
  179. assert len(conflicted_config_diff["removed_items"]) == 1
  180. assert conflicted_config_diff["removed_items"][0] == (
  181. ("unique_section_name", None, None),
  182. {"attribute": "unique_attribute_1", "prop": "unique_prop_1"},
  183. )
  184. assert conflicted_config_diff.get("modified_items") is None
  185. assert conflicted_config_diff.get("added_items") is None
  186. def test_comparator_with_modified_unique_section(self):
  187. _config_1 = _Config._default_config()
  188. _config_1._unique_sections[UniqueSectionForTest.name] = self.unique_section_1
  189. # All "section_name" sections are removed from the Config
  190. _config_2 = _Config._default_config()
  191. _config_2._unique_sections[UniqueSectionForTest.name] = self.unique_section_1b
  192. config_diff = Config._comparator._find_conflict_config(_config_1, _config_2)
  193. conflicted_config_diff = config_diff["conflicted_sections"]
  194. assert len(conflicted_config_diff["modified_items"]) == 1
  195. assert conflicted_config_diff["modified_items"][0] == (
  196. ("unique_section_name", "prop", None),
  197. ("unique_prop_1", "unique_prop_1b"),
  198. )
  199. assert conflicted_config_diff.get("removed_items") is None
  200. assert conflicted_config_diff.get("added_items") is None
  201. def test_unconflicted_section_name_store_statically(self):
  202. Config._comparator._add_unconflicted_section("section_name_1")
  203. assert Config._comparator._unconflicted_sections == {"section_name_1"}
  204. Config._comparator._add_unconflicted_section("section_name_2")
  205. assert Config._comparator._unconflicted_sections == {"section_name_1", "section_name_2"}
  206. Config._comparator._add_unconflicted_section("section_name_1")
  207. assert Config._comparator._unconflicted_sections == {"section_name_1", "section_name_2"}
  208. def test_unconflicted_diff_is_stored_separated_from_conflicted_ones(self):
  209. _config_1 = _Config._default_config()
  210. _config_1._unique_sections[UniqueSectionForTest.name] = self.unique_section_1
  211. _config_1._sections[SectionForTest.name] = {"section_2": self.section_2}
  212. _config_2 = _Config._default_config()
  213. _config_2._unique_sections[UniqueSectionForTest.name] = self.unique_section_1b
  214. _config_2._sections[SectionForTest.name] = {"section_2": self.section_2b}
  215. # Compare 2 Configuration
  216. config_diff = Config._comparator._find_conflict_config(_config_1, _config_2)
  217. assert config_diff.get("unconflicted_sections") is None
  218. assert config_diff.get("conflicted_sections") is not None
  219. assert len(config_diff["conflicted_sections"]["modified_items"]) == 3
  220. # Ignore any diff of "section_name" and compare
  221. Config._comparator._add_unconflicted_section("section_name")
  222. config_diff = Config._comparator._find_conflict_config(_config_1, _config_2)
  223. assert config_diff.get("unconflicted_sections") is not None
  224. assert len(config_diff["unconflicted_sections"]["modified_items"]) == 2
  225. assert config_diff.get("conflicted_sections") is not None
  226. assert len(config_diff["conflicted_sections"]["modified_items"]) == 1
  227. # Ignore any diff of Global Config and compare
  228. Config._comparator._add_unconflicted_section(["unique_section_name"])
  229. config_diff = Config._comparator._find_conflict_config(_config_1, _config_2)
  230. assert config_diff.get("unconflicted_sections") is not None
  231. assert len(config_diff["unconflicted_sections"]["modified_items"]) == 3
  232. assert config_diff.get("conflicted_sections") is None
  233. def test_comparator_log_message(self, caplog):
  234. _config_1 = _Config._default_config()
  235. _config_1._unique_sections[UniqueSectionForTest.name] = self.unique_section_1
  236. _config_1._sections[SectionForTest.name] = {"section_2": self.section_2}
  237. _config_2 = _Config._default_config()
  238. _config_2._unique_sections[UniqueSectionForTest.name] = self.unique_section_1b
  239. _config_2._sections[SectionForTest.name] = {"section_2": self.section_2b}
  240. # Ignore any diff of "section_name" and compare
  241. Config._comparator._add_unconflicted_section("section_name")
  242. Config._comparator._find_conflict_config(_config_1, _config_2)
  243. error_messages = caplog.text.strip().split("\n")
  244. assert len(error_messages) == 5
  245. assert all(
  246. t in error_messages[0]
  247. for t in [
  248. "INFO",
  249. "There are non-conflicting changes between the current configuration and the current configuration:",
  250. ]
  251. )
  252. assert 'section_name "section_2" has attribute "attribute" modified: 2:int -> attribute_2' in error_messages[1]
  253. assert 'section_name "section_2" has attribute "prop" modified: prop_2 -> prop_2b' in error_messages[2]
  254. assert all(
  255. t in error_messages[3]
  256. for t in [
  257. "ERROR",
  258. "The current configuration conflicts with the current configuration:",
  259. ]
  260. )
  261. assert 'unique_section_name "prop" was modified: unique_prop_1 -> unique_prop_1b' in error_messages[4]
  262. caplog.clear()
  263. Config._comparator._find_conflict_config(_config_1, _config_2, old_version_number="1.0")
  264. error_messages = caplog.text.strip().split("\n")
  265. assert len(error_messages) == 5
  266. assert all(
  267. t in error_messages[0]
  268. for t in [
  269. "INFO",
  270. "There are non-conflicting changes between the configuration for version 1.0 and the current "
  271. "configuration:",
  272. ]
  273. )
  274. assert all(
  275. t in error_messages[3]
  276. for t in [
  277. "ERROR",
  278. "The configuration for version 1.0 conflicts with the current configuration:",
  279. ]
  280. )
  281. caplog.clear()
  282. Config._comparator._compare(
  283. _config_1,
  284. _config_2,
  285. version_number_1="1.0",
  286. version_number_2="2.0",
  287. )
  288. error_messages = caplog.text.strip().split("\n")
  289. assert len(error_messages) == 3
  290. assert all(
  291. t in error_messages[0]
  292. for t in ["INFO", "Differences between version 1.0 Configuration and version 2.0 Configuration:"]
  293. )
  294. caplog.clear()