helpers.py 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. """
  2. tests.support.pytest.helpers
  3. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  4. PyTest helpers functions
  5. """
  6. import logging
  7. import os
  8. import shutil
  9. import tempfile
  10. import textwrap
  11. import types
  12. import warnings
  13. from contextlib import contextmanager
  14. import pytest
  15. import salt.utils.files
  16. from tests.support.pytest.loader import LoaderModuleMock
  17. from tests.support.runtests import RUNTIME_VARS
  18. log = logging.getLogger(__name__)
  19. if not RUNTIME_VARS.PYTEST_SESSION:
  20. # XXX: Remove this try/except once we fully switch to pytest
  21. class FakePyTestHelpersNamespace:
  22. __slots__ = ()
  23. def register(self, func):
  24. return func
  25. # Patch pytest so it all works under runtests.py
  26. pytest.helpers = FakePyTestHelpersNamespace()
  27. @pytest.helpers.register
  28. @contextmanager
  29. def temp_directory(name=None):
  30. """
  31. This helper creates a temporary directory. It should be used as a context manager
  32. which returns the temporary directory path, and, once out of context, deletes it.
  33. Can be directly imported and used, or, it can be used as a pytest helper function if
  34. ``pytest-helpers-namespace`` is installed.
  35. .. code-block:: python
  36. import os
  37. import pytest
  38. def test_blah():
  39. with pytest.helpers.temp_directory() as tpath:
  40. print(tpath)
  41. assert os.path.exists(tpath)
  42. assert not os.path.exists(tpath)
  43. """
  44. try:
  45. if name is not None:
  46. directory_path = os.path.join(RUNTIME_VARS.TMP, name)
  47. else:
  48. directory_path = tempfile.mkdtemp(dir=RUNTIME_VARS.TMP)
  49. if not os.path.isdir(directory_path):
  50. os.makedirs(directory_path)
  51. yield directory_path
  52. finally:
  53. shutil.rmtree(directory_path, ignore_errors=True)
  54. @pytest.helpers.register
  55. @contextmanager
  56. def temp_file(name=None, contents=None, directory=None, strip_first_newline=True):
  57. """
  58. This helper creates a temporary file. It should be used as a context manager
  59. which returns the temporary file path, and, once out of context, deletes it.
  60. Can be directly imported and used, or, it can be used as a pytest helper function if
  61. ``pytest-helpers-namespace`` is installed.
  62. .. code-block:: python
  63. import os
  64. import pytest
  65. def test_blah():
  66. with pytest.helpers.temp_file("blah.txt") as tpath:
  67. print(tpath)
  68. assert os.path.exists(tpath)
  69. assert not os.path.exists(tpath)
  70. Args:
  71. name(str):
  72. The temporary file name
  73. contents(str):
  74. The contents of the temporary file
  75. directory(str):
  76. The directory where to create the temporary file. If ``None``, then ``RUNTIME_VARS.TMP``
  77. will be used.
  78. strip_first_newline(bool):
  79. Wether to strip the initial first new line char or not.
  80. """
  81. try:
  82. if directory is None:
  83. directory = RUNTIME_VARS.TMP
  84. if name is not None:
  85. file_path = os.path.join(directory, name)
  86. else:
  87. handle, file_path = tempfile.mkstemp(dir=directory)
  88. os.close(handle)
  89. file_directory = os.path.dirname(file_path)
  90. if not os.path.isdir(file_directory):
  91. os.makedirs(file_directory)
  92. if contents is not None:
  93. if contents:
  94. if contents.startswith("\n") and strip_first_newline:
  95. contents = contents[1:]
  96. file_contents = textwrap.dedent(contents)
  97. else:
  98. file_contents = contents
  99. with salt.utils.files.fopen(file_path, "w") as wfh:
  100. wfh.write(file_contents)
  101. yield file_path
  102. finally:
  103. try:
  104. os.unlink(file_path)
  105. except OSError:
  106. # Already deleted
  107. pass
  108. @pytest.helpers.register
  109. def temp_state_file(name, contents, saltenv="base", strip_first_newline=True):
  110. """
  111. This helper creates a temporary state file. It should be used as a context manager
  112. which returns the temporary state file path, and, once out of context, deletes it.
  113. Can be directly imported and used, or, it can be used as a pytest helper function if
  114. ``pytest-helpers-namespace`` is installed.
  115. .. code-block:: python
  116. import os
  117. import pytest
  118. def test_blah():
  119. with pytest.helpers.temp_state_file("blah.sls") as tpath:
  120. print(tpath)
  121. assert os.path.exists(tpath)
  122. assert not os.path.exists(tpath)
  123. Depending on the saltenv, it will be created under ``RUNTIME_VARS.TMP_STATE_TREE`` or
  124. ``RUNTIME_VARS.TMP_PRODENV_STATE_TREE``.
  125. Args:
  126. name(str):
  127. The temporary state file name
  128. contents(str):
  129. The contents of the temporary file
  130. saltenv(str):
  131. The salt env to use. Either ``base`` or ``prod``
  132. strip_first_newline(bool):
  133. Wether to strip the initial first new line char or not.
  134. """
  135. if saltenv == "base":
  136. directory = RUNTIME_VARS.TMP_BASEENV_STATE_TREE
  137. elif saltenv == "prod":
  138. directory = RUNTIME_VARS.TMP_PRODENV_STATE_TREE
  139. else:
  140. raise RuntimeError(
  141. '"saltenv" can only be "base" or "prod", not "{}"'.format(saltenv)
  142. )
  143. return temp_file(
  144. name, contents, directory=directory, strip_first_newline=strip_first_newline
  145. )
  146. @pytest.helpers.register
  147. def temp_pillar_file(name, contents, saltenv="base", strip_first_newline=True):
  148. """
  149. This helper creates a temporary pillar file. It should be used as a context manager
  150. which returns the temporary pillar file path, and, once out of context, deletes it.
  151. Can be directly imported and used, or, it can be used as a pytest helper function if
  152. ``pytest-helpers-namespace`` is installed.
  153. .. code-block:: python
  154. import os
  155. import pytest
  156. def test_blah():
  157. with pytest.helpers.temp_pillar_file("blah.sls") as tpath:
  158. print(tpath)
  159. assert os.path.exists(tpath)
  160. assert not os.path.exists(tpath)
  161. Depending on the saltenv, it will be created under ``RUNTIME_VARS.TMP_PILLAR_TREE`` or
  162. ``RUNTIME_VARS.TMP_PRODENV_PILLAR_TREE``.
  163. Args:
  164. name(str):
  165. The temporary state file name
  166. contents(str):
  167. The contents of the temporary file
  168. saltenv(str):
  169. The salt env to use. Either ``base`` or ``prod``
  170. strip_first_newline(bool):
  171. Wether to strip the initial first new line char or not.
  172. """
  173. if saltenv == "base":
  174. directory = RUNTIME_VARS.TMP_BASEENV_PILLAR_TREE
  175. elif saltenv == "prod":
  176. directory = RUNTIME_VARS.TMP_PRODENV_PILLAR_TREE
  177. else:
  178. raise RuntimeError(
  179. '"saltenv" can only be "base" or "prod", not "{}"'.format(saltenv)
  180. )
  181. return temp_file(
  182. name, contents, directory=directory, strip_first_newline=strip_first_newline
  183. )
  184. @pytest.helpers.register
  185. def loader_mock(*args, **kwargs):
  186. if len(args) > 1:
  187. loader_modules = args[1]
  188. warnings.warn(
  189. "'request' is not longer an accepted argument to 'loader_mock()'. Please stop passing it.",
  190. category=DeprecationWarning,
  191. )
  192. else:
  193. loader_modules = args[0]
  194. return LoaderModuleMock(loader_modules, **kwargs)
  195. @pytest.helpers.register
  196. def salt_loader_module_functions(module):
  197. if not isinstance(module, types.ModuleType):
  198. raise RuntimeError(
  199. "The passed 'module' argument must be an imported "
  200. "imported module, not {}".format(type(module))
  201. )
  202. funcs = {}
  203. func_alias = getattr(module, "__func_alias__", {})
  204. virtualname = getattr(module, "__virtualname__")
  205. for name in dir(module):
  206. if name.startswith("_"):
  207. continue
  208. func = getattr(module, name)
  209. if getattr(func, "__module__", None) != module.__name__:
  210. # Not eve defined on the module being processed, carry on
  211. continue
  212. if not isinstance(func, types.FunctionType):
  213. # Not a function? carry on
  214. continue
  215. funcname = func_alias.get(func.__name__) or func.__name__
  216. funcs["{}.{}".format(virtualname, funcname)] = func
  217. return funcs
  218. @pytest.helpers.register
  219. def remove_stale_minion_key(master, minion_id):
  220. key_path = os.path.join(master.config["pki_dir"], "minions", minion_id)
  221. if os.path.exists(key_path):
  222. os.unlink(key_path)
  223. else:
  224. log.debug("The minion(id=%r) key was not found at %s", minion_id, key_path)
  225. # Only allow star importing the functions defined in this module
  226. __all__ = [
  227. name
  228. for (name, func) in locals().items()
  229. if getattr(func, "__module__", None) == __name__
  230. ]