1
0

test_path.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  1. # -*- coding: utf-8 -*-
  2. """
  3. Tests for salt.utils.path
  4. """
  5. # Import Python libs
  6. from __future__ import absolute_import, print_function, unicode_literals
  7. import ntpath
  8. import os
  9. import platform
  10. import posixpath
  11. import sys
  12. import tempfile
  13. # Import Salt libs
  14. import salt.utils.compat
  15. import salt.utils.path
  16. import salt.utils.platform
  17. from salt.exceptions import CommandNotFoundError
  18. # Import 3rd-party libs
  19. from salt.ext import six
  20. from tests.support.mock import patch
  21. # Import Salt Testing libs
  22. from tests.support.unit import TestCase, skipIf
  23. class PathJoinTestCase(TestCase):
  24. PLATFORM_FUNC = platform.system
  25. BUILTIN_MODULES = sys.builtin_module_names
  26. NIX_PATHS = (
  27. (("/", "key"), "/key"),
  28. (("/etc/salt", "/etc/salt/pki"), "/etc/salt/etc/salt/pki"),
  29. (("/usr/local", "/etc/salt/pki"), "/usr/local/etc/salt/pki"),
  30. )
  31. WIN_PATHS = (
  32. (("c:", "temp", "foo"), "c:\\temp\\foo"),
  33. (("c:", r"\temp", r"\foo"), "c:\\temp\\foo"),
  34. (("c:\\", r"\temp", r"\foo"), "c:\\temp\\foo"),
  35. ((r"c:\\", r"\temp", r"\foo"), "c:\\temp\\foo"),
  36. (("c:", r"\temp", r"\foo", "bar"), "c:\\temp\\foo\\bar"),
  37. (("c:", r"\temp", r"\foo\bar"), "c:\\temp\\foo\\bar"),
  38. )
  39. @skipIf(True, "Skipped until properly mocked")
  40. def test_nix_paths(self):
  41. if platform.system().lower() == "windows":
  42. self.skipTest(
  43. "Windows platform found. not running *nix salt.utils.path.join tests"
  44. )
  45. for idx, (parts, expected) in enumerate(self.NIX_PATHS):
  46. path = salt.utils.path.join(*parts)
  47. self.assertEqual(
  48. "{0}: {1}".format(idx, path), "{0}: {1}".format(idx, expected)
  49. )
  50. @skipIf(True, "Skipped until properly mocked")
  51. def test_windows_paths(self):
  52. if platform.system().lower() != "windows":
  53. self.skipTest(
  54. "Non windows platform found. not running non patched os.path "
  55. "salt.utils.path.join tests"
  56. )
  57. for idx, (parts, expected) in enumerate(self.WIN_PATHS):
  58. path = salt.utils.path.join(*parts)
  59. self.assertEqual(
  60. "{0}: {1}".format(idx, path), "{0}: {1}".format(idx, expected)
  61. )
  62. @skipIf(True, "Skipped until properly mocked")
  63. def test_windows_paths_patched_path_module(self):
  64. if platform.system().lower() == "windows":
  65. self.skipTest(
  66. "Windows platform found. not running patched os.path "
  67. "salt.utils.path.join tests"
  68. )
  69. self.__patch_path()
  70. for idx, (parts, expected) in enumerate(self.WIN_PATHS):
  71. path = salt.utils.path.join(*parts)
  72. self.assertEqual(
  73. "{0}: {1}".format(idx, path), "{0}: {1}".format(idx, expected)
  74. )
  75. self.__unpatch_path()
  76. @skipIf(salt.utils.platform.is_windows(), "*nix-only test")
  77. def test_mixed_unicode_and_binary(self):
  78. """
  79. This tests joining paths that contain a mix of components with unicode
  80. strings and non-unicode strings with the unicode characters as binary.
  81. This is no longer something we need to concern ourselves with in
  82. Python 3, but the test should nonetheless pass on Python 3. Really what
  83. we're testing here is that we don't get a UnicodeDecodeError when
  84. running on Python 2.
  85. """
  86. a = "/foo/bar"
  87. b = "Д"
  88. expected = "/foo/bar/\u0414"
  89. actual = salt.utils.path.join(a, b)
  90. self.assertEqual(actual, expected)
  91. def __patch_path(self):
  92. import imp
  93. modules = list(self.BUILTIN_MODULES[:])
  94. modules.pop(modules.index("posix"))
  95. modules.append("nt")
  96. code = """'''Salt unittest loaded NT module'''"""
  97. module = imp.new_module("nt")
  98. six.exec_(code, module.__dict__)
  99. sys.modules["nt"] = module
  100. sys.builtin_module_names = modules
  101. platform.system = lambda: "windows"
  102. for module in (ntpath, os, os.path, tempfile):
  103. salt.utils.compat.reload(module)
  104. def __unpatch_path(self):
  105. del sys.modules["nt"]
  106. sys.builtin_module_names = self.BUILTIN_MODULES[:]
  107. platform.system = self.PLATFORM_FUNC
  108. for module in (posixpath, os, os.path, tempfile, platform):
  109. salt.utils.compat.reload(module)
  110. class PathTestCase(TestCase):
  111. def test_which_bin(self):
  112. ret = salt.utils.path.which_bin("str")
  113. self.assertIs(None, ret)
  114. test_exes = ["ls", "echo"]
  115. with patch("salt.utils.path.which", return_value="/tmp/dummy_path"):
  116. ret = salt.utils.path.which_bin(test_exes)
  117. self.assertEqual(ret, "/tmp/dummy_path")
  118. ret = salt.utils.path.which_bin([])
  119. self.assertIs(None, ret)
  120. with patch("salt.utils.path.which", return_value=""):
  121. ret = salt.utils.path.which_bin(test_exes)
  122. self.assertIs(None, ret)
  123. def test_sanitize_win_path(self):
  124. p = "\\windows\\system"
  125. self.assertEqual(
  126. salt.utils.path.sanitize_win_path("\\windows\\system"), "\\windows\\system"
  127. )
  128. self.assertEqual(
  129. salt.utils.path.sanitize_win_path("\\bo:g|us\\p?at*h>"),
  130. "\\bo_g_us\\p_at_h_",
  131. )
  132. def test_check_or_die(self):
  133. self.assertRaises(CommandNotFoundError, salt.utils.path.check_or_die, None)
  134. with patch("salt.utils.path.which", return_value=False):
  135. self.assertRaises(
  136. CommandNotFoundError, salt.utils.path.check_or_die, "FAKE COMMAND"
  137. )
  138. def test_join(self):
  139. with patch(
  140. "salt.utils.platform.is_windows", return_value=False
  141. ) as is_windows_mock:
  142. self.assertFalse(is_windows_mock.return_value)
  143. expected_path = os.path.join(os.sep + "a", "b", "c", "d")
  144. ret = salt.utils.path.join("/a/b/c", "d")
  145. self.assertEqual(ret, expected_path)
  146. class TestWhich(TestCase):
  147. """
  148. Tests salt.utils.path.which function to ensure that it returns True as
  149. expected.
  150. """
  151. # The mock patch below will make sure that ALL calls to the which function
  152. # returns None
  153. def test_missing_binary_in_linux(self):
  154. # salt.utils.path.which uses platform.is_windows to determine the platform, so we're using linux here
  155. with patch("salt.utils.platform.is_windows", lambda: False):
  156. with patch("salt.utils.path.which", lambda exe: None):
  157. self.assertTrue(
  158. salt.utils.path.which("this-binary-does-not-exist") is None
  159. )
  160. # The mock patch below will make sure that ALL calls to the which function
  161. # return whatever is sent to it
  162. def test_existing_binary_in_linux(self):
  163. # salt.utils.path.which uses platform.is_windows to determine the platform, so we're using linux here
  164. with patch("salt.utils.platform.is_windows", lambda: False):
  165. with patch("salt.utils.path.which", lambda exe: exe):
  166. self.assertTrue(salt.utils.path.which("this-binary-exists-under-linux"))
  167. def test_existing_binary_in_windows(self):
  168. with patch("os.path.isfile") as isfile:
  169. # We define the side_effect attribute on the mocked object in order to
  170. # specify which calls return which values. First call to os.path.isfile
  171. # returns X, the second Y, the third Z, etc...
  172. isfile.side_effect = [
  173. # The first os.path.isfile should return False due to checking the explicit path (first is_executable)
  174. False,
  175. # We will now also return False once so we get a .EXE back from
  176. # the function, see PATHEXT below.
  177. False,
  178. # Lastly return True, this is the windows check.
  179. True,
  180. ]
  181. # Patch os.access so that it always returns True
  182. with patch("os.access", lambda path, mode: True):
  183. # Disable os.path.islink
  184. with patch("os.path.islink", lambda path: False):
  185. # we're using ';' as os.pathsep in this test
  186. with patch("os.pathsep", ";"):
  187. # Let's patch os.environ to provide a custom PATH variable
  188. with patch.dict(
  189. os.environ,
  190. {"PATH": os.sep + "bin", "PATHEXT": ".COM;.EXE;.BAT;.CMD"},
  191. ):
  192. # Let's also patch is_windows to return True
  193. with patch("salt.utils.platform.is_windows", lambda: True):
  194. self.assertEqual(
  195. salt.utils.path.which(
  196. "this-binary-exists-under-windows"
  197. ),
  198. os.path.join(
  199. os.sep + "bin",
  200. "this-binary-exists-under-windows.EXE",
  201. ),
  202. )
  203. def test_missing_binary_in_windows(self):
  204. with patch("os.access") as osaccess:
  205. osaccess.side_effect = [
  206. # The first os.access should return False due to checking the explicit path (first is_executable)
  207. False,
  208. # The second, iterating through $PATH, should also return False,
  209. # still checking for Linux
  210. # which() will add 4 extra paths to the given one, os.access will
  211. # be called 5 times
  212. False,
  213. False,
  214. False,
  215. False,
  216. False,
  217. ]
  218. # we're using ';' as os.pathsep in this test
  219. with patch("os.pathsep", ";"):
  220. # Let's patch os.environ to provide a custom PATH variable
  221. with patch.dict(os.environ, {"PATH": os.sep + "bin"}):
  222. # Let's also patch is_widows to return True
  223. with patch("salt.utils.platform.is_windows", lambda: True):
  224. self.assertEqual(
  225. # Since we're passing the .exe suffix, the last True above
  226. # will not matter. The result will be None
  227. salt.utils.path.which(
  228. "this-binary-is-missing-in-windows.exe"
  229. ),
  230. None,
  231. )
  232. def test_existing_binary_in_windows_pathext(self):
  233. with patch("os.path.isfile") as isfile:
  234. # We define the side_effect attribute on the mocked object in order to
  235. # specify which calls return which values. First call to os.path.isfile
  236. # returns X, the second Y, the third Z, etc...
  237. isfile.side_effect = [
  238. # The first os.path.isfile should return False due to checking the explicit path (first is_executable)
  239. False,
  240. # We will now also return False 3 times so we get a .CMD back from
  241. # the function, see PATHEXT below.
  242. # Lastly return True, this is the windows check.
  243. False,
  244. False,
  245. False,
  246. True,
  247. ]
  248. # Patch os.access so that it always returns True
  249. with patch("os.access", lambda path, mode: True):
  250. # Disable os.path.islink
  251. with patch("os.path.islink", lambda path: False):
  252. # we're using ';' as os.pathsep in this test
  253. with patch("os.pathsep", ";"):
  254. # Let's patch os.environ to provide a custom PATH variable
  255. with patch.dict(
  256. os.environ,
  257. {
  258. "PATH": os.sep + "bin",
  259. "PATHEXT": ".COM;.EXE;.BAT;.CMD;.VBS;"
  260. ".VBE;.JS;.JSE;.WSF;.WSH;.MSC;.PY",
  261. },
  262. ):
  263. # Let's also patch is_windows to return True
  264. with patch("salt.utils.platform.is_windows", lambda: True):
  265. self.assertEqual(
  266. salt.utils.path.which(
  267. "this-binary-exists-under-windows"
  268. ),
  269. os.path.join(
  270. os.sep + "bin",
  271. "this-binary-exists-under-windows.CMD",
  272. ),
  273. )