test_configparser.py 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. # -*- coding: utf-8 -*-
  2. """
  3. tests.unit.utils.test_configparser
  4. ==================================
  5. Test the funcs in the custom parsers in salt.utils.configparser
  6. """
  7. # Import Python Libs
  8. from __future__ import absolute_import, print_function, unicode_literals
  9. import copy
  10. import errno
  11. import logging
  12. import os
  13. import salt.utils.configparser
  14. # Import salt libs
  15. import salt.utils.files
  16. import salt.utils.platform
  17. import salt.utils.stringutils
  18. from salt.ext import six
  19. # Import Salt Testing Libs
  20. from tests.support.runtests import RUNTIME_VARS
  21. from tests.support.unit import TestCase
  22. log = logging.getLogger(__name__)
  23. # The user.name param here is intentionally indented with spaces instead of a
  24. # tab to test that we properly load a file with mixed indentation.
  25. ORIG_CONFIG = """[user]
  26. name = Артём Анисимов
  27. \temail = foo@bar.com
  28. [remote "origin"]
  29. \turl = https://github.com/terminalmage/salt.git
  30. \tfetch = +refs/heads/*:refs/remotes/origin/*
  31. \tpushurl = git@github.com:terminalmage/salt.git
  32. [color "diff"]
  33. \told = 196
  34. \tnew = 39
  35. [core]
  36. \tpager = less -R
  37. \trepositoryformatversion = 0
  38. \tfilemode = true
  39. \tbare = false
  40. \tlogallrefupdates = true
  41. [alias]
  42. \tmodified = ! git status --porcelain | awk 'match($1, "M"){print $2}'
  43. \tgraph = log --all --decorate --oneline --graph
  44. \thist = log --pretty=format:\\"%h %ad | %s%d [%an]\\" --graph --date=short
  45. [http]
  46. \tsslverify = false""".split(
  47. "\n"
  48. )
  49. class TestGitConfigParser(TestCase):
  50. """
  51. Tests for salt.utils.configparser.GitConfigParser
  52. """
  53. maxDiff = None
  54. orig_config = os.path.join(RUNTIME_VARS.TMP, "test_gitconfig.orig")
  55. new_config = os.path.join(RUNTIME_VARS.TMP, "test_gitconfig.new")
  56. remote = 'remote "origin"'
  57. def tearDown(self):
  58. del self.conf
  59. try:
  60. os.remove(self.new_config)
  61. except OSError as exc:
  62. if exc.errno != errno.ENOENT:
  63. raise
  64. def setUp(self):
  65. if not os.path.exists(self.orig_config):
  66. with salt.utils.files.fopen(self.orig_config, "wb") as fp_:
  67. fp_.write(salt.utils.stringutils.to_bytes(os.linesep.join(ORIG_CONFIG)))
  68. self.conf = salt.utils.configparser.GitConfigParser()
  69. with salt.utils.files.fopen(self.orig_config, "rb") as fp:
  70. self.conf._read(fp, self.orig_config)
  71. @classmethod
  72. def tearDownClass(cls):
  73. try:
  74. os.remove(cls.orig_config)
  75. except OSError as exc:
  76. if exc.errno != errno.ENOENT:
  77. raise
  78. @staticmethod
  79. def fix_indent(lines):
  80. """
  81. Fixes the space-indented 'user' line, because when we write the config
  82. object to a file space indentation will be replaced by tab indentation.
  83. """
  84. ret = copy.copy(lines)
  85. for i, _ in enumerate(ret):
  86. if ret[i].startswith(salt.utils.configparser.GitConfigParser.SPACEINDENT):
  87. ret[i] = ret[i].replace(
  88. salt.utils.configparser.GitConfigParser.SPACEINDENT, "\t"
  89. )
  90. return ret
  91. @staticmethod
  92. def get_lines(path):
  93. with salt.utils.files.fopen(path, "rb") as fp_:
  94. return salt.utils.stringutils.to_unicode(fp_.read()).splitlines()
  95. def _test_write(self, mode):
  96. kwargs = {"mode": mode}
  97. if six.PY3 and salt.utils.platform.is_windows() and "b" not in mode:
  98. kwargs["encoding"] = "utf-8"
  99. with salt.utils.files.fopen(self.new_config, **kwargs) as fp_:
  100. self.conf.write(fp_)
  101. self.assertEqual(self.get_lines(self.new_config), self.fix_indent(ORIG_CONFIG))
  102. def test_get(self):
  103. """
  104. Test getting an option's value
  105. """
  106. # Numeric values should be loaded as strings
  107. self.assertEqual(self.conf.get('color "diff"', "old"), "196")
  108. # Complex strings should be loaded with their literal quotes and
  109. # slashes intact
  110. self.assertEqual(
  111. self.conf.get("alias", "modified"),
  112. """! git status --porcelain | awk 'match($1, "M"){print $2}'""",
  113. )
  114. # future lint: disable=non-unicode-string
  115. self.assertEqual(
  116. self.conf.get("alias", "hist"),
  117. salt.utils.stringutils.to_unicode(
  118. r"""log --pretty=format:\"%h %ad | %s%d [%an]\" --graph --date=short"""
  119. ),
  120. )
  121. # future lint: enable=non-unicode-string
  122. def test_read_space_indent(self):
  123. """
  124. Test that user.name was successfully loaded despite being indented
  125. using spaces instead of a tab. Additionally, this tests that the value
  126. was loaded as a unicode type on PY2.
  127. """
  128. self.assertEqual(self.conf.get("user", "name"), "Артём Анисимов")
  129. def test_set_new_option(self):
  130. """
  131. Test setting a new option in an existing section
  132. """
  133. self.conf.set("http", "useragent", "myawesomeagent")
  134. self.assertEqual(self.conf.get("http", "useragent"), "myawesomeagent")
  135. def test_add_section(self):
  136. """
  137. Test adding a section and adding an item to that section
  138. """
  139. self.conf.add_section("foo")
  140. self.conf.set("foo", "bar", "baz")
  141. self.assertEqual(self.conf.get("foo", "bar"), "baz")
  142. def test_replace_option(self):
  143. """
  144. Test replacing an existing option
  145. """
  146. # We're also testing the normalization of key names, here. Setting
  147. # "sslVerify" should actually set an "sslverify" option.
  148. self.conf.set("http", "sslVerify", "true")
  149. self.assertEqual(self.conf.get("http", "sslverify"), "true")
  150. def test_set_multivar(self):
  151. """
  152. Test setting a multivar and then writing the resulting file
  153. """
  154. orig_refspec = "+refs/heads/*:refs/remotes/origin/*"
  155. new_refspec = "+refs/tags/*:refs/tags/*"
  156. # Make sure that the original value is a string
  157. self.assertEqual(self.conf.get(self.remote, "fetch"), orig_refspec)
  158. # Add another refspec
  159. self.conf.set_multivar(self.remote, "fetch", new_refspec)
  160. # The value should now be a list
  161. self.assertEqual(
  162. self.conf.get(self.remote, "fetch"), [orig_refspec, new_refspec]
  163. )
  164. # Write the config object to a file
  165. with salt.utils.files.fopen(self.new_config, "wb") as fp_:
  166. self.conf.write(fp_)
  167. # Confirm that the new file was written correctly
  168. expected = self.fix_indent(ORIG_CONFIG)
  169. # pylint: disable=string-substitution-usage-error
  170. expected.insert(6, "\tfetch = %s" % new_refspec)
  171. # pylint: enable=string-substitution-usage-error
  172. self.assertEqual(self.get_lines(self.new_config), expected)
  173. def test_remove_option(self):
  174. """
  175. test removing an option, including all items from a multivar
  176. """
  177. for item in ("fetch", "pushurl"):
  178. self.conf.remove_option(self.remote, item)
  179. # To confirm that the option is now gone, a get should raise an
  180. # NoOptionError exception.
  181. self.assertRaises(
  182. salt.utils.configparser.NoOptionError, self.conf.get, self.remote, item
  183. )
  184. def test_remove_option_regexp(self):
  185. """
  186. test removing an option, including all items from a multivar
  187. """
  188. orig_refspec = "+refs/heads/*:refs/remotes/origin/*"
  189. new_refspec_1 = "+refs/tags/*:refs/tags/*"
  190. new_refspec_2 = "+refs/foo/*:refs/foo/*"
  191. # First, add both refspecs
  192. self.conf.set_multivar(self.remote, "fetch", new_refspec_1)
  193. self.conf.set_multivar(self.remote, "fetch", new_refspec_2)
  194. # Make sure that all three values are there
  195. self.assertEqual(
  196. self.conf.get(self.remote, "fetch"),
  197. [orig_refspec, new_refspec_1, new_refspec_2],
  198. )
  199. # If the regex doesn't match, no items should be removed
  200. self.assertFalse(
  201. self.conf.remove_option_regexp(
  202. self.remote,
  203. "fetch",
  204. salt.utils.stringutils.to_unicode(
  205. r"\d{7,10}"
  206. ), # future lint: disable=non-unicode-string
  207. )
  208. )
  209. # Make sure that all three values are still there (since none should
  210. # have been removed)
  211. self.assertEqual(
  212. self.conf.get(self.remote, "fetch"),
  213. [orig_refspec, new_refspec_1, new_refspec_2],
  214. )
  215. # Remove one of the values
  216. self.assertTrue(self.conf.remove_option_regexp(self.remote, "fetch", "tags"))
  217. # Confirm that the value is gone
  218. self.assertEqual(
  219. self.conf.get(self.remote, "fetch"), [orig_refspec, new_refspec_2]
  220. )
  221. # Remove the other one we added earlier
  222. self.assertTrue(self.conf.remove_option_regexp(self.remote, "fetch", "foo"))
  223. # Since the option now only has one value, it should be a string
  224. self.assertEqual(self.conf.get(self.remote, "fetch"), orig_refspec)
  225. # Remove the last remaining option
  226. self.assertTrue(self.conf.remove_option_regexp(self.remote, "fetch", "heads"))
  227. # Trying to do a get now should raise an exception
  228. self.assertRaises(
  229. salt.utils.configparser.NoOptionError, self.conf.get, self.remote, "fetch"
  230. )
  231. def test_write(self):
  232. """
  233. Test writing using non-binary filehandle
  234. """
  235. self._test_write(mode="w")
  236. def test_write_binary(self):
  237. """
  238. Test writing using binary filehandle
  239. """
  240. self._test_write(mode="wb")