1
0

runtests.py 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. # -*- coding: utf-8 -*-
  2. '''
  3. :codeauthor: Pedro Algarvio (pedro@algarvio.me)
  4. .. _runtime_vars:
  5. Runtime Variables
  6. -----------------
  7. :command:`salt-runtests` provides a variable, :py:attr:`RUNTIME_VARS` which has some common paths defined at
  8. startup:
  9. .. autoattribute:: tests.support.runtests.RUNTIME_VARS
  10. :annotation:
  11. :TMP: Tests suite temporary directory
  12. :TMP_CONF_DIR: Configuration directory from where the daemons that :command:`salt-runtests` starts get their
  13. configuration files.
  14. :TMP_CONF_MASTER_INCLUDES: Salt Master configuration files includes directory. See
  15. :salt_conf_master:`default_include`.
  16. :TMP_CONF_MINION_INCLUDES: Salt Minion configuration files includes directory. Seei
  17. :salt_conf_minion:`include`.
  18. :TMP_CONF_CLOUD_INCLUDES: Salt cloud configuration files includes directory. The same as the salt master and
  19. minion includes configuration, though under a different directory name.
  20. :TMP_CONF_CLOUD_PROFILE_INCLUDES: Salt cloud profiles configuration files includes directory. Same as above.
  21. :TMP_CONF_CLOUD_PROVIDER_INCLUDES: Salt cloud providers configuration files includes directory. Same as above.
  22. :TMP_SCRIPT_DIR: Temporary scripts directory from where the Salt CLI tools will be called when running tests.
  23. :TMP_SALT_INTEGRATION_FILES: Temporary directory from where Salt's test suite integration files are copied to.
  24. :TMP_BASEENV_STATE_TREE: Salt master's **base** environment state tree directory
  25. :TMP_PRODENV_STATE_TREE: Salt master's **production** environment state tree directory
  26. :TMP_BASEENV_PILLAR_TREE: Salt master's **base** environment pillar tree directory
  27. :TMP_PRODENV_PILLAR_TREE: Salt master's **production** environment pillar tree directory
  28. Use it on your test case in case of need. As simple as:
  29. .. code-block:: python
  30. import os
  31. from tests.support.runtests import RUNTIME_VARS
  32. # Path to the testing minion configuration file
  33. minion_config_path = os.path.join(RUNTIME_VARS.TMP_CONF_DIR, 'minion')
  34. .. _`pytest`: http://pytest.org
  35. .. _`nose`: https://nose.readthedocs.org
  36. '''
  37. # Import Python modules
  38. from __future__ import absolute_import, print_function
  39. import os
  40. import shutil
  41. import logging
  42. import multiprocessing
  43. import salt.utils.json
  44. # Import tests support libs
  45. import tests.support.paths as paths
  46. import tests.support.helpers
  47. # Import 3rd-party libs
  48. from salt.ext import six
  49. try:
  50. import coverage # pylint: disable=import-error
  51. HAS_COVERAGE = True
  52. except ImportError:
  53. HAS_COVERAGE = False
  54. try:
  55. import multiprocessing.util
  56. # Force forked multiprocessing processes to be measured as well
  57. def multiprocessing_stop(coverage_object):
  58. '''
  59. Save the multiprocessing process coverage object
  60. '''
  61. coverage_object.stop()
  62. coverage_object.save()
  63. def multiprocessing_start(obj):
  64. coverage_options = salt.utils.json.loads(os.environ.get('SALT_RUNTESTS_COVERAGE_OPTIONS', '{}'))
  65. if not coverage_options:
  66. return
  67. if coverage_options.get('data_suffix', False) is False:
  68. return
  69. coverage_object = coverage.coverage(**coverage_options)
  70. coverage_object.start()
  71. multiprocessing.util.Finalize(
  72. None,
  73. multiprocessing_stop,
  74. args=(coverage_object,),
  75. exitpriority=1000
  76. )
  77. if HAS_COVERAGE:
  78. multiprocessing.util.register_after_fork(
  79. multiprocessing_start,
  80. multiprocessing_start
  81. )
  82. except ImportError:
  83. pass
  84. RUNNING_TESTS_USER = tests.support.helpers.this_user()
  85. log = logging.getLogger(__name__)
  86. class RootsDict(dict):
  87. def merge(self, data):
  88. for key, values in six.iteritems(data):
  89. if key not in self:
  90. self[key] = values
  91. continue
  92. for value in values:
  93. if value not in self[key]:
  94. self[key].append(value)
  95. return self
  96. def to_dict(self):
  97. return dict(self)
  98. def recursive_copytree(source, destination, overwrite=False):
  99. for root, dirs, files in os.walk(source):
  100. for item in dirs:
  101. src_path = os.path.join(root, item)
  102. dst_path = os.path.join(destination, src_path.replace(source, '').lstrip(os.sep))
  103. if not os.path.exists(dst_path):
  104. log.debug('Creating directory: {0}'.format(dst_path))
  105. os.makedirs(dst_path)
  106. for item in files:
  107. src_path = os.path.join(root, item)
  108. dst_path = os.path.join(destination, src_path.replace(source, '').lstrip(os.sep))
  109. if os.path.exists(dst_path) and not overwrite:
  110. if os.stat(src_path).st_mtime > os.stat(dst_path).st_mtime:
  111. log.debug('Copying {0} to {1}'.format(src_path, dst_path))
  112. shutil.copy2(src_path, dst_path)
  113. else:
  114. if not os.path.isdir(os.path.dirname(dst_path)):
  115. log.debug('Creating directory: {0}'.format(os.path.dirname(dst_path)))
  116. os.makedirs(os.path.dirname(dst_path))
  117. log.debug('Copying {0} to {1}'.format(src_path, dst_path))
  118. shutil.copy2(src_path, dst_path)
  119. class RuntimeVars(object):
  120. __self_attributes__ = ('_vars', '_locked', 'lock')
  121. def __init__(self, **kwargs):
  122. self._vars = kwargs
  123. self._locked = False
  124. def lock(self):
  125. # Late import
  126. from salt.utils.immutabletypes import freeze
  127. frozen_vars = freeze(self._vars.copy())
  128. self._vars = frozen_vars
  129. self._locked = True
  130. def __iter__(self):
  131. for name, value in six.iteritems(self._vars):
  132. yield name, value
  133. def __getattribute__(self, name):
  134. if name in object.__getattribute__(self, '_vars'):
  135. return object.__getattribute__(self, '_vars')[name]
  136. return object.__getattribute__(self, name)
  137. def __setattr__(self, name, value):
  138. if getattr(self, '_locked', False) is True:
  139. raise RuntimeError(
  140. 'After {0} is locked, no additional data can be added to it'.format(
  141. self.__class__.__name__
  142. )
  143. )
  144. if name in object.__getattribute__(self, '__self_attributes__'):
  145. object.__setattr__(self, name, value)
  146. return
  147. self._vars[name] = value
  148. # <---- Helper Methods -----------------------------------------------------------------------------------------------
  149. # ----- Global Variables -------------------------------------------------------------------------------------------->
  150. XML_OUTPUT_DIR = os.environ.get('SALT_XML_TEST_REPORTS_DIR', os.path.join(paths.TMP, 'xml-test-reports'))
  151. # <---- Global Variables ---------------------------------------------------------------------------------------------
  152. # ----- Tests Runtime Variables ------------------------------------------------------------------------------------->
  153. RUNTIME_VARS = RuntimeVars(
  154. TMP=paths.TMP,
  155. SYS_TMP_DIR=paths.SYS_TMP_DIR,
  156. FILES=paths.FILES,
  157. CONF_DIR=paths.CONF_DIR,
  158. PILLAR_DIR=paths.PILLAR_DIR,
  159. ENGINES_DIR=paths.ENGINES_DIR,
  160. LOG_HANDLERS_DIR=paths.LOG_HANDLERS_DIR,
  161. TMP_ROOT_DIR=paths.TMP_ROOT_DIR,
  162. TMP_CONF_DIR=paths.TMP_CONF_DIR,
  163. TMP_CONF_MASTER_INCLUDES=os.path.join(paths.TMP_CONF_DIR, 'master.d'),
  164. TMP_CONF_MINION_INCLUDES=os.path.join(paths.TMP_CONF_DIR, 'minion.d'),
  165. TMP_CONF_PROXY_INCLUDES=os.path.join(paths.TMP_CONF_DIR, 'proxy.d'),
  166. TMP_CONF_CLOUD_INCLUDES=os.path.join(paths.TMP_CONF_DIR, 'cloud.conf.d'),
  167. TMP_CONF_CLOUD_PROFILE_INCLUDES=os.path.join(paths.TMP_CONF_DIR, 'cloud.profiles.d'),
  168. TMP_CONF_CLOUD_PROVIDER_INCLUDES=os.path.join(paths.TMP_CONF_DIR, 'cloud.providers.d'),
  169. TMP_SUB_MINION_CONF_DIR=paths.TMP_SUB_MINION_CONF_DIR,
  170. TMP_SYNDIC_MASTER_CONF_DIR=paths.TMP_SYNDIC_MASTER_CONF_DIR,
  171. TMP_SYNDIC_MINION_CONF_DIR=paths.TMP_SYNDIC_MINION_CONF_DIR,
  172. TMP_MM_CONF_DIR=paths.TMP_MM_CONF_DIR,
  173. TMP_MM_SUB_CONF_DIR=paths.TMP_MM_SUB_CONF_DIR,
  174. TMP_SCRIPT_DIR=paths.TMP_SCRIPT_DIR,
  175. TMP_STATE_TREE=paths.TMP_STATE_TREE,
  176. TMP_PILLAR_TREE=paths.TMP_PILLAR_TREE,
  177. TMP_PRODENV_STATE_TREE=paths.TMP_PRODENV_STATE_TREE,
  178. RUNNING_TESTS_USER=RUNNING_TESTS_USER,
  179. RUNTIME_CONFIGS={}
  180. )
  181. # <---- Tests Runtime Variables --------------------------------------------------------------------------------------