test_spm.py 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. # coding: utf-8
  2. from __future__ import absolute_import
  3. import os
  4. import shutil
  5. import tempfile
  6. import pytest
  7. import salt.config
  8. import salt.spm
  9. import salt.utils.files
  10. from tests.support.mixins import AdaptedConfigurationTestCaseMixin
  11. from tests.support.mock import MagicMock, patch
  12. from tests.support.unit import TestCase
  13. @pytest.mark.destructive_test
  14. class SPMTestUserInterface(salt.spm.SPMUserInterface):
  15. """
  16. Unit test user interface to SPMClient
  17. """
  18. def __init__(self):
  19. self._status = []
  20. self._confirm = []
  21. self._error = []
  22. def status(self, msg):
  23. self._status.append(msg)
  24. def confirm(self, action):
  25. self._confirm.append(action)
  26. def error(self, msg):
  27. self._error.append(msg)
  28. class SPMTest(TestCase, AdaptedConfigurationTestCaseMixin):
  29. @classmethod
  30. def setUpClass(cls):
  31. cls._F1 = {
  32. "definition": {
  33. "name": "formula1",
  34. "version": "1.2",
  35. "release": "2",
  36. "summary": "test",
  37. "description": "testing, nothing to see here",
  38. }
  39. }
  40. cls._F1["contents"] = (
  41. (
  42. "FORMULA",
  43. (
  44. "name: {name}\n"
  45. "version: {version}\n"
  46. "release: {release}\n"
  47. "summary: {summary}\n"
  48. "description: {description}"
  49. ).format(**cls._F1["definition"]),
  50. ),
  51. ("modules/mod1.py", "# mod1.py"),
  52. ("modules/mod2.py", "# mod2.py"),
  53. ("states/state1.sls", "# state1.sls"),
  54. ("states/state2.sls", "# state2.sls"),
  55. )
  56. @classmethod
  57. def tearDownClass(cls):
  58. cls._F1 = None
  59. def setUp(self):
  60. self._tmp_spm = tempfile.mkdtemp()
  61. self.addCleanup(shutil.rmtree, self._tmp_spm, ignore_errors=True)
  62. minion_config = self.get_temp_config(
  63. "minion",
  64. **{
  65. "spm_logfile": os.path.join(self._tmp_spm, "log"),
  66. "spm_repos_config": os.path.join(self._tmp_spm, "etc", "spm.repos"),
  67. "spm_cache_dir": os.path.join(self._tmp_spm, "cache"),
  68. "spm_build_dir": os.path.join(self._tmp_spm, "build"),
  69. "spm_build_exclude": [".git"],
  70. "spm_db_provider": "sqlite3",
  71. "spm_files_provider": "local",
  72. "spm_db": os.path.join(self._tmp_spm, "packages.db"),
  73. "extension_modules": os.path.join(self._tmp_spm, "modules"),
  74. "file_roots": {"base": [self._tmp_spm]},
  75. "formula_path": os.path.join(self._tmp_spm, "spm"),
  76. "pillar_path": os.path.join(self._tmp_spm, "pillar"),
  77. "reactor_path": os.path.join(self._tmp_spm, "reactor"),
  78. "assume_yes": True,
  79. "force": False,
  80. "verbose": False,
  81. "cache": "localfs",
  82. "cachedir": os.path.join(self._tmp_spm, "cache"),
  83. "spm_repo_dups": "ignore",
  84. "spm_share_dir": os.path.join(self._tmp_spm, "share"),
  85. }
  86. )
  87. self.ui = SPMTestUserInterface()
  88. self.client = salt.spm.SPMClient(self.ui, minion_config)
  89. self.minion_config = minion_config
  90. for attr in ("client", "ui", "_tmp_spm", "minion_config"):
  91. self.addCleanup(delattr, self, attr)
  92. def _create_formula_files(self, formula):
  93. fdir = os.path.join(self._tmp_spm, formula["definition"]["name"])
  94. shutil.rmtree(fdir, ignore_errors=True)
  95. os.mkdir(fdir)
  96. for path, contents in formula["contents"]:
  97. path = os.path.join(fdir, path)
  98. dirname, _ = os.path.split(path)
  99. if not os.path.exists(dirname):
  100. os.makedirs(dirname)
  101. with salt.utils.files.fopen(path, "w") as f:
  102. f.write(contents)
  103. return fdir
  104. @pytest.mark.slow_test(seconds=1) # Test takes >0.1 and <=1 seconds
  105. def test_build_install(self):
  106. # Build package?!?jedi=0, ?!? (*_*formula*_*) ?!?jedi?!?
  107. fdir = self._create_formula_files(self._F1)
  108. with patch("salt.client.Caller", MagicMock(return_value=self.minion_opts)):
  109. with patch(
  110. "salt.client.get_local_client",
  111. MagicMock(return_value=self.minion_opts["conf_file"]),
  112. ):
  113. self.client.run(["build", fdir])
  114. pkgpath = self.ui._status[-1].split()[-1]
  115. assert os.path.exists(pkgpath)
  116. # Install package
  117. with patch("salt.client.Caller", MagicMock(return_value=self.minion_opts)):
  118. with patch(
  119. "salt.client.get_local_client",
  120. MagicMock(return_value=self.minion_opts["conf_file"]),
  121. ):
  122. self.client.run(["local", "install", pkgpath])
  123. # Check filesystem
  124. for path, contents in self._F1["contents"]:
  125. path = os.path.join(
  126. self.minion_config["file_roots"]["base"][0],
  127. self._F1["definition"]["name"],
  128. path,
  129. )
  130. assert os.path.exists(path)
  131. with salt.utils.files.fopen(path, "r") as rfh:
  132. assert rfh.read() == contents
  133. # Check database
  134. with patch("salt.client.Caller", MagicMock(return_value=self.minion_opts)):
  135. with patch(
  136. "salt.client.get_local_client",
  137. MagicMock(return_value=self.minion_opts["conf_file"]),
  138. ):
  139. self.client.run(["info", self._F1["definition"]["name"]])
  140. lines = self.ui._status[-1].split("\n")
  141. for key, line in (
  142. ("name", "Name: {0}"),
  143. ("version", "Version: {0}"),
  144. ("release", "Release: {0}"),
  145. ("summary", "Summary: {0}"),
  146. ):
  147. assert line.format(self._F1["definition"][key]) in lines
  148. # Reinstall with force=False, should fail
  149. self.ui._error = []
  150. with patch("salt.client.Caller", MagicMock(return_value=self.minion_opts)):
  151. with patch(
  152. "salt.client.get_local_client",
  153. MagicMock(return_value=self.minion_opts["conf_file"]),
  154. ):
  155. self.client.run(["local", "install", pkgpath])
  156. assert len(self.ui._error) > 0
  157. # Reinstall with force=True, should succeed
  158. with patch.dict(self.minion_config, {"force": True}):
  159. self.ui._error = []
  160. with patch("salt.client.Caller", MagicMock(return_value=self.minion_opts)):
  161. with patch(
  162. "salt.client.get_local_client",
  163. MagicMock(return_value=self.minion_opts["conf_file"]),
  164. ):
  165. self.client.run(["local", "install", pkgpath])
  166. assert len(self.ui._error) == 0
  167. @pytest.mark.slow_test(seconds=1) # Test takes >0.1 and <=1 seconds
  168. def test_failure_paths(self):
  169. fail_args = (
  170. ["bogus", "command"],
  171. ["create_repo"],
  172. ["build"],
  173. ["build", "/nonexistent/path"],
  174. ["info"],
  175. ["info", "not_installed"],
  176. ["files"],
  177. ["files", "not_installed"],
  178. ["install"],
  179. ["install", "nonexistent.spm"],
  180. ["remove"],
  181. ["remove", "not_installed"],
  182. ["local", "bogus", "command"],
  183. ["local", "info"],
  184. ["local", "info", "/nonexistent/path/junk.spm"],
  185. ["local", "files"],
  186. ["local", "files", "/nonexistent/path/junk.spm"],
  187. ["local", "install"],
  188. ["local", "install", "/nonexistent/path/junk.spm"],
  189. ["local", "list"],
  190. ["local", "list", "/nonexistent/path/junk.spm"],
  191. # XXX install failure due to missing deps
  192. # XXX install failure due to missing field
  193. )
  194. for args in fail_args:
  195. self.ui._error = []
  196. with patch("salt.client.Caller", MagicMock(return_value=self.minion_opts)):
  197. with patch(
  198. "salt.client.get_local_client",
  199. MagicMock(return_value=self.minion_opts["conf_file"]),
  200. ):
  201. self.client.run(args)
  202. assert len(self.ui._error) > 0