conftest.py 52 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427
  1. # -*- coding: utf-8 -*-
  2. '''
  3. :codeauthor: Pedro Algarvio (pedro@algarvio.me)
  4. tests.conftest
  5. ~~~~~~~~~~~~~~
  6. Prepare py.test for our test suite
  7. '''
  8. # pylint: disable=wrong-import-order,wrong-import-position,3rd-party-local-module-not-gated
  9. # pylint: disable=redefined-outer-name,invalid-name
  10. # Import python libs
  11. from __future__ import absolute_import, print_function, unicode_literals
  12. import os
  13. import sys
  14. import stat
  15. import pprint
  16. import shutil
  17. import socket
  18. import logging
  19. import tempfile
  20. import textwrap
  21. from functools import partial, wraps
  22. from contextlib import contextmanager
  23. TESTS_DIR = os.path.dirname(os.path.normpath(os.path.abspath(__file__)))
  24. CODE_DIR = os.path.dirname(TESTS_DIR)
  25. # Change to code checkout directory
  26. os.chdir(CODE_DIR)
  27. # Make sure the current directory is the first item in sys.path
  28. if CODE_DIR in sys.path:
  29. sys.path.remove(CODE_DIR)
  30. sys.path.insert(0, CODE_DIR)
  31. # Import test libs
  32. from tests.support.runtests import RUNTIME_VARS
  33. from tests.support.sminion import create_sminion, check_required_sminion_attributes
  34. from tests.support.helpers import PRE_PYTEST_SKIP_REASON, PRE_PYTEST_SKIP_OR_NOT
  35. # Import pytest libs
  36. import pytest
  37. import _pytest.logging
  38. import _pytest.skipping
  39. from _pytest.mark.evaluate import MarkEvaluator
  40. # Import 3rd-party libs
  41. import psutil
  42. from salt.ext import six
  43. # Import salt libs
  44. import salt.loader
  45. import salt.config
  46. import salt.utils.files
  47. import salt.utils.path
  48. import salt.log.setup
  49. import salt.log.mixins
  50. import salt.utils.platform
  51. import salt.utils.win_functions
  52. from salt.serializers import yaml
  53. from salt.utils.immutabletypes import freeze
  54. # Import Pytest Salt libs
  55. from pytestsalt.utils import cli_scripts
  56. # Coverage
  57. if 'COVERAGE_PROCESS_START' in os.environ:
  58. MAYBE_RUN_COVERAGE = True
  59. COVERAGERC_FILE = os.environ['COVERAGE_PROCESS_START']
  60. else:
  61. COVERAGERC_FILE = os.path.join(CODE_DIR, '.coveragerc')
  62. MAYBE_RUN_COVERAGE = sys.argv[0].endswith('pytest.py') or '_COVERAGE_RCFILE' in os.environ
  63. if MAYBE_RUN_COVERAGE:
  64. # Flag coverage to track suprocesses by pointing it to the right .coveragerc file
  65. os.environ[str('COVERAGE_PROCESS_START')] = str(COVERAGERC_FILE)
  66. # Define the pytest plugins we rely on
  67. pytest_plugins = ['tempdir', 'helpers_namespace', 'salt-runtests-bridge']
  68. # Define where not to collect tests from
  69. collect_ignore = ['setup.py']
  70. # Patch PyTest logging handlers
  71. class LogCaptureHandler(salt.log.mixins.ExcInfoOnLogLevelFormatMixIn,
  72. logging.NullHandler):
  73. '''
  74. Subclassing PyTest's LogCaptureHandler in order to add the
  75. exc_info_on_loglevel functionality and actually make it a NullHandler,
  76. it's only used to print log messages emmited during tests, which we
  77. have explicitly disabled in pytest.ini
  78. '''
  79. _pytest.logging.LogCaptureHandler = LogCaptureHandler
  80. class LiveLoggingStreamHandler(salt.log.mixins.ExcInfoOnLogLevelFormatMixIn,
  81. _pytest.logging._LiveLoggingStreamHandler):
  82. '''
  83. Subclassing PyTest's LiveLoggingStreamHandler in order to add the
  84. exc_info_on_loglevel functionality.
  85. '''
  86. _pytest.logging._LiveLoggingStreamHandler = LiveLoggingStreamHandler
  87. # Reset logging root handlers
  88. for handler in logging.root.handlers[:]:
  89. logging.root.removeHandler(handler)
  90. # Reset the root logger to it's default level(because salt changed it)
  91. logging.root.setLevel(logging.WARNING)
  92. log = logging.getLogger('salt.testsuite')
  93. # ----- PyTest Tempdir Plugin Hooks --------------------------------------------------------------------------------->
  94. def pytest_tempdir_temproot():
  95. # Taken from https://github.com/saltstack/salt/blob/v2019.2.0/tests/support/paths.py
  96. # Avoid ${TMPDIR} and gettempdir() on MacOS as they yield a base path too long
  97. # for unix sockets: ``error: AF_UNIX path too long``
  98. # Gentoo Portage prefers ebuild tests are rooted in ${TMPDIR}
  99. if not sys.platform.startswith('darwin'):
  100. tempdir = os.environ.get('TMPDIR') or tempfile.gettempdir()
  101. else:
  102. tempdir = '/tmp'
  103. return os.path.abspath(os.path.realpath(tempdir))
  104. def pytest_tempdir_basename():
  105. '''
  106. Return the temporary directory basename for the salt test suite.
  107. '''
  108. return 'salt-tests-tmpdir'
  109. # <---- PyTest Tempdir Plugin Hooks ----------------------------------------------------------------------------------
  110. # ----- CLI Options Setup ------------------------------------------------------------------------------------------->
  111. def pytest_addoption(parser):
  112. '''
  113. register argparse-style options and ini-style config values.
  114. '''
  115. parser.addoption(
  116. '--sysinfo',
  117. default=False,
  118. action='store_true',
  119. help='Print some system information.'
  120. )
  121. parser.addoption(
  122. '--transport',
  123. default='zeromq',
  124. choices=('zeromq', 'tcp'),
  125. help=('Select which transport to run the integration tests with, '
  126. 'zeromq or tcp. Default: %default')
  127. )
  128. test_selection_group = parser.getgroup('Tests Selection')
  129. test_selection_group.addoption(
  130. '--ssh',
  131. '--ssh-tests',
  132. dest='ssh',
  133. action='store_true',
  134. default=False,
  135. help='Run salt-ssh tests. These tests will spin up a temporary '
  136. 'SSH server on your machine. In certain environments, this '
  137. 'may be insecure! Default: False'
  138. )
  139. test_selection_group.addoption(
  140. '--proxy',
  141. '--proxy-tests',
  142. dest='proxy',
  143. action='store_true',
  144. default=False,
  145. help='Run proxy tests'
  146. )
  147. test_selection_group.addoption(
  148. '--run-destructive',
  149. action='store_true',
  150. default=False,
  151. help='Run destructive tests. These tests can include adding '
  152. 'or removing users from your system for example. '
  153. 'Default: False'
  154. )
  155. test_selection_group.addoption(
  156. '--run-expensive',
  157. action='store_true',
  158. default=False,
  159. help='Run expensive tests. These tests usually involve costs '
  160. 'like for example bootstrapping a cloud VM. '
  161. 'Default: False'
  162. )
  163. output_options_group = parser.getgroup('Output Options')
  164. output_options_group.addoption(
  165. '--output-columns',
  166. default=80,
  167. type=int,
  168. help='Number of maximum columns to use on the output'
  169. )
  170. output_options_group.addoption(
  171. '--no-colors',
  172. '--no-colours',
  173. default=False,
  174. action='store_true',
  175. help='Disable colour printing.'
  176. )
  177. # ----- Test Groups --------------------------------------------------------------------------------------------->
  178. # This will allow running the tests in chunks
  179. test_selection_group.addoption(
  180. '--test-group-count', dest='test-group-count', type=int,
  181. help='The number of groups to split the tests into'
  182. )
  183. test_selection_group.addoption(
  184. '--test-group', dest='test-group', type=int,
  185. help='The group of tests that should be executed'
  186. )
  187. # <---- Test Groups ----------------------------------------------------------------------------------------------
  188. # <---- CLI Options Setup --------------------------------------------------------------------------------------------
  189. # ----- Register Markers -------------------------------------------------------------------------------------------->
  190. @pytest.mark.trylast
  191. def pytest_configure(config):
  192. '''
  193. called after command line options have been parsed
  194. and all plugins and initial conftest files been loaded.
  195. '''
  196. for dirname in os.listdir(CODE_DIR):
  197. if not os.path.isdir(dirname):
  198. continue
  199. if dirname != 'tests':
  200. config.addinivalue_line('norecursedirs', os.path.join(CODE_DIR, dirname))
  201. # Expose the markers we use to pytest CLI
  202. config.addinivalue_line(
  203. 'markers',
  204. 'destructive_test: Run destructive tests. These tests can include adding '
  205. 'or removing users from your system for example.'
  206. )
  207. config.addinivalue_line(
  208. 'markers',
  209. 'skip_if_not_root: Skip if the current user is not `root`.'
  210. )
  211. config.addinivalue_line(
  212. 'markers',
  213. 'skip_if_binaries_missing(*binaries, check_all=False, message=None): Skip if '
  214. 'any of the passed binaries are not found in path. If \'check_all\' is '
  215. '\'True\', then all binaries must be found.'
  216. )
  217. config.addinivalue_line(
  218. 'markers',
  219. 'requires_network(only_local_network=False): Skip if no networking is set up. '
  220. 'If \'only_local_network\' is \'True\', only the local network is checked.'
  221. )
  222. config.addinivalue_line(
  223. 'markers',
  224. 'requires_salt_modules(*required_module_names): Skip if at least one module is not available.'
  225. )
  226. config.addinivalue_line(
  227. 'markers',
  228. 'requires_salt_states(*required_state_names): Skip if at least one state module is not available.'
  229. )
  230. config.addinivalue_line(
  231. 'markers',
  232. 'windows_whitelisted: Mark test as whitelisted to run under Windows'
  233. )
  234. # Make sure the test suite "knows" this is a pytest test run
  235. RUNTIME_VARS.PYTEST_SESSION = True
  236. # <---- Register Markers ---------------------------------------------------------------------------------------------
  237. # ----- PyTest Tweaks ----------------------------------------------------------------------------------------------->
  238. def set_max_open_files_limits(min_soft=3072, min_hard=4096):
  239. # Get current limits
  240. if salt.utils.platform.is_windows():
  241. import win32file
  242. prev_hard = win32file._getmaxstdio()
  243. prev_soft = 512
  244. else:
  245. import resource
  246. prev_soft, prev_hard = resource.getrlimit(resource.RLIMIT_NOFILE)
  247. # Check minimum required limits
  248. set_limits = False
  249. if prev_soft < min_soft:
  250. soft = min_soft
  251. set_limits = True
  252. else:
  253. soft = prev_soft
  254. if prev_hard < min_hard:
  255. hard = min_hard
  256. set_limits = True
  257. else:
  258. hard = prev_hard
  259. # Increase limits
  260. if set_limits:
  261. log.debug(
  262. ' * Max open files settings is too low (soft: %s, hard: %s) for running the tests. '
  263. 'Trying to raise the limits to soft: %s, hard: %s',
  264. prev_soft,
  265. prev_hard,
  266. soft,
  267. hard
  268. )
  269. try:
  270. if salt.utils.platform.is_windows():
  271. hard = 2048 if hard > 2048 else hard
  272. win32file._setmaxstdio(hard)
  273. else:
  274. resource.setrlimit(resource.RLIMIT_NOFILE, (soft, hard))
  275. except Exception as err: # pylint: disable=broad-except
  276. log.error(
  277. 'Failed to raise the max open files settings -> %s. Please issue the following command '
  278. 'on your console: \'ulimit -u %s\'',
  279. err,
  280. soft,
  281. )
  282. exit(1)
  283. return soft, hard
  284. def pytest_report_header():
  285. soft, hard = set_max_open_files_limits()
  286. return 'max open files; soft: {}; hard: {}'.format(soft, hard)
  287. def pytest_runtest_logstart(nodeid):
  288. '''
  289. signal the start of running a single test item.
  290. This hook will be called **before** :func:`pytest_runtest_setup`, :func:`pytest_runtest_call` and
  291. :func:`pytest_runtest_teardown` hooks.
  292. :param str nodeid: full id of the item
  293. :param location: a triple of ``(filename, linenum, testname)``
  294. '''
  295. log.debug('>>>>> START >>>>> %s', nodeid)
  296. def pytest_runtest_logfinish(nodeid):
  297. '''
  298. signal the complete finish of running a single test item.
  299. This hook will be called **after** :func:`pytest_runtest_setup`, :func:`pytest_runtest_call` and
  300. :func:`pytest_runtest_teardown` hooks.
  301. :param str nodeid: full id of the item
  302. :param location: a triple of ``(filename, linenum, testname)``
  303. '''
  304. log.debug('<<<<< END <<<<<<< %s', nodeid)
  305. @pytest.hookimpl(hookwrapper=True, trylast=True)
  306. def pytest_collection_modifyitems(config, items):
  307. '''
  308. called after collection has been performed, may filter or re-order
  309. the items in-place.
  310. :param _pytest.main.Session session: the pytest session object
  311. :param _pytest.config.Config config: pytest config object
  312. :param List[_pytest.nodes.Item] items: list of item objects
  313. '''
  314. # Let PyTest or other plugins handle the initial collection
  315. yield
  316. groups_collection_modifyitems(config, items)
  317. log.warning('Mofifying collected tests to keep track of fixture usage')
  318. for item in items:
  319. for fixture in item.fixturenames:
  320. if fixture not in item._fixtureinfo.name2fixturedefs:
  321. continue
  322. for fixturedef in item._fixtureinfo.name2fixturedefs[fixture]:
  323. if fixturedef.scope == 'function':
  324. continue
  325. try:
  326. node_ids = fixturedef.node_ids
  327. except AttributeError:
  328. node_ids = fixturedef.node_ids = set()
  329. node_ids.add(item.nodeid)
  330. try:
  331. fixturedef.finish.__wrapped__
  332. except AttributeError:
  333. original_func = fixturedef.finish
  334. def wrapper(func, fixturedef):
  335. @wraps(func)
  336. def wrapped(self, request):
  337. try:
  338. return self._finished
  339. except AttributeError:
  340. if self.node_ids:
  341. log.debug(
  342. '%s is still going to be used, not terminating it. '
  343. 'Still in use on:\n%s',
  344. self,
  345. pprint.pformat(list(self.node_ids))
  346. )
  347. return
  348. log.debug('Finish called on %s', self)
  349. try:
  350. return func(request)
  351. finally:
  352. self._finished = True
  353. return partial(wrapped, fixturedef)
  354. fixturedef.finish = wrapper(fixturedef.finish, fixturedef)
  355. try:
  356. fixturedef.finish.__wrapped__
  357. except AttributeError:
  358. fixturedef.finish.__wrapped__ = original_func
  359. @pytest.hookimpl(trylast=True, hookwrapper=True)
  360. def pytest_runtest_protocol(item, nextitem):
  361. '''
  362. implements the runtest_setup/call/teardown protocol for
  363. the given test item, including capturing exceptions and calling
  364. reporting hooks.
  365. :arg item: test item for which the runtest protocol is performed.
  366. :arg nextitem: the scheduled-to-be-next test item (or None if this
  367. is the end my friend). This argument is passed on to
  368. :py:func:`pytest_runtest_teardown`.
  369. :return boolean: True if no further hook implementations should be invoked.
  370. Stops at first non-None result, see :ref:`firstresult`
  371. '''
  372. request = item._request
  373. used_fixture_defs = []
  374. for fixture in item.fixturenames:
  375. if fixture not in item._fixtureinfo.name2fixturedefs:
  376. continue
  377. for fixturedef in reversed(item._fixtureinfo.name2fixturedefs[fixture]):
  378. if fixturedef.scope == 'function':
  379. continue
  380. used_fixture_defs.append(fixturedef)
  381. try:
  382. # Run the test
  383. yield
  384. finally:
  385. for fixturedef in used_fixture_defs:
  386. fixturedef.node_ids.remove(item.nodeid)
  387. if not fixturedef.node_ids:
  388. # This fixture is not used in any more test functions
  389. fixturedef.finish(request)
  390. del request
  391. del used_fixture_defs
  392. # <---- PyTest Tweaks ------------------------------------------------------------------------------------------------
  393. # ----- Test Setup -------------------------------------------------------------------------------------------------->
  394. def _has_unittest_attr(item, attr):
  395. # XXX: This is a hack while we support both runtests.py and PyTest
  396. if hasattr(item.obj, attr):
  397. return True
  398. if item.cls and hasattr(item.cls, attr):
  399. return True
  400. if item.parent and hasattr(item.parent.obj, attr):
  401. return True
  402. return False
  403. @pytest.hookimpl(tryfirst=True)
  404. def pytest_runtest_setup(item):
  405. '''
  406. Fixtures injection based on markers or test skips based on CLI arguments
  407. '''
  408. integration_utils_tests_path = os.path.join(CODE_DIR, 'tests', 'integration', 'utils')
  409. if str(item.fspath).startswith(integration_utils_tests_path) and PRE_PYTEST_SKIP_OR_NOT is True:
  410. item._skipped_by_mark = True
  411. pytest.skip(PRE_PYTEST_SKIP_REASON)
  412. destructive_tests_marker = item.get_closest_marker('destructive_test')
  413. if destructive_tests_marker is not None or _has_unittest_attr(item, '__destructive_test__'):
  414. if item.config.getoption('--run-destructive') is False:
  415. item._skipped_by_mark = True
  416. pytest.skip('Destructive tests are disabled')
  417. expensive_tests_marker = item.get_closest_marker('expensive_test')
  418. if expensive_tests_marker is not None or _has_unittest_attr(item, '__expensive_test__'):
  419. if item.config.getoption('--run-expensive') is False:
  420. item._skipped_by_mark = True
  421. pytest.skip('Expensive tests are disabled')
  422. skip_if_not_root_marker = item.get_closest_marker('skip_if_not_root')
  423. if skip_if_not_root_marker is not None or _has_unittest_attr(item, '__skip_if_not_root__'):
  424. if not sys.platform.startswith('win'):
  425. if os.getuid() != 0:
  426. item._skipped_by_mark = True
  427. pytest.skip('You must be logged in as root to run this test')
  428. else:
  429. current_user = salt.utils.win_functions.get_current_user()
  430. if current_user != 'SYSTEM':
  431. if not salt.utils.win_functions.is_admin(current_user):
  432. item._skipped_by_mark = True
  433. pytest.skip('You must be logged in as an Administrator to run this test')
  434. skip_if_binaries_missing_marker = item.get_closest_marker('skip_if_binaries_missing')
  435. if skip_if_binaries_missing_marker is not None:
  436. binaries = skip_if_binaries_missing_marker.args
  437. if len(binaries) == 1:
  438. if isinstance(binaries[0], (list, tuple, set, frozenset)):
  439. binaries = binaries[0]
  440. check_all = skip_if_binaries_missing_marker.kwargs.get('check_all', False)
  441. message = skip_if_binaries_missing_marker.kwargs.get('message', None)
  442. if check_all:
  443. for binary in binaries:
  444. if salt.utils.path.which(binary) is None:
  445. item._skipped_by_mark = True
  446. pytest.skip(
  447. '{0}The "{1}" binary was not found'.format(
  448. message and '{0}. '.format(message) or '',
  449. binary
  450. )
  451. )
  452. elif salt.utils.path.which_bin(binaries) is None:
  453. item._skipped_by_mark = True
  454. pytest.skip(
  455. '{0}None of the following binaries was found: {1}'.format(
  456. message and '{0}. '.format(message) or '',
  457. ', '.join(binaries)
  458. )
  459. )
  460. requires_network_marker = item.get_closest_marker('requires_network')
  461. if requires_network_marker is not None:
  462. only_local_network = requires_network_marker.kwargs.get('only_local_network', False)
  463. has_local_network = False
  464. # First lets try if we have a local network. Inspired in verify_socket
  465. try:
  466. pubsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  467. retsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  468. pubsock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  469. pubsock.bind(('', 18000))
  470. pubsock.close()
  471. retsock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  472. retsock.bind(('', 18001))
  473. retsock.close()
  474. has_local_network = True
  475. except socket.error:
  476. # I wonder if we just have IPV6 support?
  477. try:
  478. pubsock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
  479. retsock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
  480. pubsock.setsockopt(
  481. socket.SOL_SOCKET, socket.SO_REUSEADDR, 1
  482. )
  483. pubsock.bind(('', 18000))
  484. pubsock.close()
  485. retsock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  486. retsock.bind(('', 18001))
  487. retsock.close()
  488. has_local_network = True
  489. except socket.error:
  490. # Let's continue
  491. pass
  492. if only_local_network is True:
  493. if has_local_network is False:
  494. # Since we're only supposed to check local network, and no
  495. # local network was detected, skip the test
  496. item._skipped_by_mark = True
  497. pytest.skip('No local network was detected')
  498. # We are using the google.com DNS records as numerical IPs to avoid
  499. # DNS lookups which could greatly slow down this check
  500. for addr in ('173.194.41.198', '173.194.41.199', '173.194.41.200',
  501. '173.194.41.201', '173.194.41.206', '173.194.41.192',
  502. '173.194.41.193', '173.194.41.194', '173.194.41.195',
  503. '173.194.41.196', '173.194.41.197'):
  504. try:
  505. sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  506. sock.settimeout(0.25)
  507. sock.connect((addr, 80))
  508. sock.close()
  509. # We connected? Stop the loop
  510. break
  511. except socket.error:
  512. # Let's check the next IP
  513. continue
  514. else:
  515. item._skipped_by_mark = True
  516. pytest.skip('No internet network connection was detected')
  517. requires_salt_modules_marker = item.get_closest_marker('requires_salt_modules')
  518. if requires_salt_modules_marker is not None:
  519. required_salt_modules = requires_salt_modules_marker.args
  520. if len(required_salt_modules) == 1 and isinstance(required_salt_modules[0], (list, tuple, set)):
  521. required_salt_modules = required_salt_modules[0]
  522. required_salt_modules = set(required_salt_modules)
  523. not_available_modules = check_required_sminion_attributes('functions', required_salt_modules)
  524. if not_available_modules:
  525. item._skipped_by_mark = True
  526. if len(not_available_modules) == 1:
  527. pytest.skip('Salt module \'{}\' is not available'.format(*not_available_modules))
  528. pytest.skip('Salt modules not available: {}'.format(', '.join(not_available_modules)))
  529. requires_salt_states_marker = item.get_closest_marker('requires_salt_states')
  530. if requires_salt_states_marker is not None:
  531. required_salt_states = requires_salt_states_marker.args
  532. if len(required_salt_states) == 1 and isinstance(required_salt_states[0], (list, tuple, set)):
  533. required_salt_states = required_salt_states[0]
  534. required_salt_states = set(required_salt_states)
  535. not_available_states = check_required_sminion_attributes('states', required_salt_states)
  536. if not_available_states:
  537. item._skipped_by_mark = True
  538. if len(not_available_states) == 1:
  539. pytest.skip('Salt state module \'{}\' is not available'.format(*not_available_states))
  540. pytest.skip('Salt state modules not available: {}'.format(', '.join(not_available_states)))
  541. if salt.utils.platform.is_windows():
  542. if not item.fspath.fnmatch(os.path.join(CODE_DIR, 'tests', 'unit', '*')):
  543. # Unit tests are whitelisted on windows by default, so, we're only
  544. # after all other tests
  545. windows_whitelisted_marker = item.get_closest_marker('windows_whitelisted')
  546. if windows_whitelisted_marker is None:
  547. item._skipped_by_mark = True
  548. pytest.skip('Test is not whitelisted for Windows')
  549. # <---- Test Setup ---------------------------------------------------------------------------------------------------
  550. # ----- Test Groups Selection --------------------------------------------------------------------------------------->
  551. def get_group_size(total_items, total_groups):
  552. '''
  553. Return the group size.
  554. '''
  555. return int(total_items / total_groups)
  556. def get_group(items, group_count, group_size, group_id):
  557. '''
  558. Get the items from the passed in group based on group size.
  559. '''
  560. start = group_size * (group_id - 1)
  561. end = start + group_size
  562. total_items = len(items)
  563. if start >= total_items:
  564. pytest.fail("Invalid test-group argument. start({})>=total_items({})".format(start, total_items))
  565. elif start < 0:
  566. pytest.fail("Invalid test-group argument. Start({})<0".format(start))
  567. if group_count == group_id and end < total_items:
  568. # If this is the last group and there are still items to test
  569. # which don't fit in this group based on the group items count
  570. # add them anyway
  571. end = total_items
  572. return items[start:end]
  573. def groups_collection_modifyitems(config, items):
  574. group_count = config.getoption('test-group-count')
  575. group_id = config.getoption('test-group')
  576. if not group_count or not group_id:
  577. # We're not selection tests using groups, don't do any filtering
  578. return
  579. total_items = len(items)
  580. group_size = get_group_size(total_items, group_count)
  581. tests_in_group = get_group(items, group_count, group_size, group_id)
  582. # Replace all items in the list
  583. items[:] = tests_in_group
  584. terminal_reporter = config.pluginmanager.get_plugin('terminalreporter')
  585. terminal_reporter.write(
  586. 'Running test group #{0} ({1} tests)\n'.format(
  587. group_id,
  588. len(items)
  589. ),
  590. yellow=True
  591. )
  592. # <---- Test Groups Selection ----------------------------------------------------------------------------------------
  593. # ----- Pytest Helpers ---------------------------------------------------------------------------------------------->
  594. if six.PY2:
  595. # backport mock_open from the python 3 unittest.mock library so that we can
  596. # mock read, readline, readlines, and file iteration properly
  597. file_spec = None
  598. def _iterate_read_data(read_data):
  599. # Helper for mock_open:
  600. # Retrieve lines from read_data via a generator so that separate calls to
  601. # readline, read, and readlines are properly interleaved
  602. data_as_list = ['{0}\n'.format(l) for l in read_data.split('\n')]
  603. if data_as_list[-1] == '\n':
  604. # If the last line ended in a newline, the list comprehension will have an
  605. # extra entry that's just a newline. Remove this.
  606. data_as_list = data_as_list[:-1]
  607. else:
  608. # If there wasn't an extra newline by itself, then the file being
  609. # emulated doesn't have a newline to end the last line remove the
  610. # newline that our naive format() added
  611. data_as_list[-1] = data_as_list[-1][:-1]
  612. for line in data_as_list:
  613. yield line
  614. @pytest.helpers.mock.register
  615. def mock_open(mock=None, read_data=''):
  616. """
  617. A helper function to create a mock to replace the use of `open`. It works
  618. for `open` called directly or used as a context manager.
  619. The `mock` argument is the mock object to configure. If `None` (the
  620. default) then a `MagicMock` will be created for you, with the API limited
  621. to methods or attributes available on standard file handles.
  622. `read_data` is a string for the `read` methoddline`, and `readlines` of the
  623. file handle to return. This is an empty string by default.
  624. """
  625. _mock = pytest.importorskip('mock', minversion='2.0.0')
  626. def _readlines_side_effect(*args, **kwargs):
  627. if handle.readlines.return_value is not None:
  628. return handle.readlines.return_value
  629. return list(_data)
  630. def _read_side_effect(*args, **kwargs):
  631. if handle.read.return_value is not None:
  632. return handle.read.return_value
  633. return ''.join(_data)
  634. def _readline_side_effect():
  635. if handle.readline.return_value is not None:
  636. while True:
  637. yield handle.readline.return_value
  638. for line in _data:
  639. yield line
  640. global file_spec
  641. if file_spec is None:
  642. file_spec = file # pylint: disable=undefined-variable
  643. if mock is None:
  644. mock = _mock.MagicMock(name='open', spec=open)
  645. handle = _mock.MagicMock(spec=file_spec)
  646. handle.__enter__.return_value = handle
  647. _data = _iterate_read_data(read_data)
  648. handle.write.return_value = None
  649. handle.read.return_value = None
  650. handle.readline.return_value = None
  651. handle.readlines.return_value = None
  652. handle.read.side_effect = _read_side_effect
  653. handle.readline.side_effect = _readline_side_effect()
  654. handle.readlines.side_effect = _readlines_side_effect
  655. mock.return_value = handle
  656. return mock
  657. else:
  658. @pytest.helpers.mock.register
  659. def mock_open(mock=None, read_data=''):
  660. _mock = pytest.importorskip('mock', minversion='2.0.0')
  661. return _mock.mock_open(mock=mock, read_data=read_data)
  662. @pytest.helpers.register
  663. @contextmanager
  664. def temp_directory(name=None):
  665. if name is not None:
  666. directory_path = os.path.join(RUNTIME_VARS.TMP, name)
  667. else:
  668. directory_path = tempfile.mkdtemp(dir=RUNTIME_VARS.TMP)
  669. yield directory_path
  670. shutil.rmtree(directory_path, ignore_errors=True)
  671. @pytest.helpers.register
  672. @contextmanager
  673. def temp_file(name, contents=None, directory=None, strip_first_newline=True):
  674. if directory is None:
  675. directory = RUNTIME_VARS.TMP
  676. file_path = os.path.join(directory, name)
  677. file_directory = os.path.dirname(file_path)
  678. if contents is not None:
  679. if contents:
  680. if contents.startswith('\n') and strip_first_newline:
  681. contents = contents[1:]
  682. file_contents = textwrap.dedent(contents)
  683. else:
  684. file_contents = contents
  685. try:
  686. if not os.path.isdir(file_directory):
  687. os.makedirs(file_directory)
  688. if contents is not None:
  689. with salt.utils.files.fopen(file_path, 'w') as wfh:
  690. wfh.write(file_contents)
  691. yield file_path
  692. finally:
  693. try:
  694. os.unlink(file_path)
  695. except OSError:
  696. # Already deleted
  697. pass
  698. @pytest.helpers.register
  699. def temp_state_file(name, contents, saltenv='base', strip_first_newline=True):
  700. if saltenv == 'base':
  701. directory = RUNTIME_VARS.TMP_STATE_TREE
  702. elif saltenv == 'prod':
  703. directory = RUNTIME_VARS.TMP_PRODENV_STATE_TREE
  704. else:
  705. raise RuntimeError('"saltenv" can only be "base" or "prod", not "{}"'.format(saltenv))
  706. return temp_file(name, contents, directory=directory, strip_first_newline=strip_first_newline)
  707. # <---- Pytest Helpers -----------------------------------------------------------------------------------------------
  708. # ----- Fixtures Overrides ------------------------------------------------------------------------------------------>
  709. # ----- Generate CLI Scripts ---------------------------------------------------------------------------------------->
  710. @pytest.fixture(scope='session')
  711. def cli_master_script_name():
  712. '''
  713. Return the CLI script basename
  714. '''
  715. return 'cli_salt_master.py'
  716. @pytest.fixture(scope='session')
  717. def cli_minion_script_name():
  718. '''
  719. Return the CLI script basename
  720. '''
  721. return 'cli_salt_minion.py'
  722. @pytest.fixture(scope='session')
  723. def cli_salt_script_name():
  724. '''
  725. Return the CLI script basename
  726. '''
  727. return 'cli_salt.py'
  728. @pytest.fixture(scope='session')
  729. def cli_run_script_name():
  730. '''
  731. Return the CLI script basename
  732. '''
  733. return 'cli_salt_run.py'
  734. @pytest.fixture(scope='session')
  735. def cli_key_script_name():
  736. '''
  737. Return the CLI script basename
  738. '''
  739. return 'cli_salt_key.py'
  740. @pytest.fixture(scope='session')
  741. def cli_call_script_name():
  742. '''
  743. Return the CLI script basename
  744. '''
  745. return 'cli_salt_call.py'
  746. @pytest.fixture(scope='session')
  747. def cli_syndic_script_name():
  748. '''
  749. Return the CLI script basename
  750. '''
  751. return 'cli_salt_syndic.py'
  752. @pytest.fixture(scope='session')
  753. def cli_ssh_script_name():
  754. '''
  755. Return the CLI script basename
  756. '''
  757. return 'cli_salt_ssh.py'
  758. @pytest.fixture(scope='session')
  759. def cli_proxy_script_name():
  760. '''
  761. Return the CLI script basename
  762. '''
  763. return 'cli_salt_proxy.py'
  764. @pytest.fixture(scope='session')
  765. def cli_bin_dir(tempdir,
  766. request,
  767. python_executable_path,
  768. cli_master_script_name,
  769. cli_minion_script_name,
  770. cli_salt_script_name,
  771. cli_call_script_name,
  772. cli_key_script_name,
  773. cli_run_script_name,
  774. cli_ssh_script_name,
  775. cli_syndic_script_name,
  776. cli_proxy_script_name):
  777. '''
  778. Return the path to the CLI script directory to use
  779. '''
  780. tmp_cli_scripts_dir = tempdir.join('cli-scrips-bin')
  781. # Make sure we re-write the scripts every time we start the tests
  782. shutil.rmtree(tmp_cli_scripts_dir.strpath, ignore_errors=True)
  783. tmp_cli_scripts_dir.ensure(dir=True)
  784. cli_bin_dir_path = tmp_cli_scripts_dir.strpath
  785. # Now that we have the CLI directory created, lets generate the required CLI scripts to run salt's test suite
  786. for script_name in (cli_master_script_name,
  787. cli_minion_script_name,
  788. cli_call_script_name,
  789. cli_key_script_name,
  790. cli_run_script_name,
  791. cli_salt_script_name,
  792. cli_ssh_script_name,
  793. cli_syndic_script_name,
  794. cli_proxy_script_name):
  795. original_script_name = os.path.splitext(script_name)[0].split('cli_')[-1].replace('_', '-')
  796. cli_scripts.generate_script(
  797. bin_dir=cli_bin_dir_path,
  798. script_name=original_script_name,
  799. executable=sys.executable,
  800. code_dir=CODE_DIR,
  801. inject_sitecustomize=MAYBE_RUN_COVERAGE
  802. )
  803. # Return the CLI bin dir value
  804. return cli_bin_dir_path
  805. # <---- Generate CLI Scripts -----------------------------------------------------------------------------------------
  806. # ----- Salt Configuration ------------------------------------------------------------------------------------------>
  807. @pytest.fixture(scope='session')
  808. def session_master_of_masters_id():
  809. '''
  810. Returns the master of masters id
  811. '''
  812. return 'syndic_master'
  813. @pytest.fixture(scope='session')
  814. def session_master_id():
  815. '''
  816. Returns the session scoped master id
  817. '''
  818. return 'master'
  819. @pytest.fixture(scope='session')
  820. def session_minion_id():
  821. '''
  822. Returns the session scoped minion id
  823. '''
  824. return 'minion'
  825. @pytest.fixture(scope='session')
  826. def session_secondary_minion_id():
  827. '''
  828. Returns the session scoped secondary minion id
  829. '''
  830. return 'sub_minion'
  831. @pytest.fixture(scope='session')
  832. def session_syndic_id():
  833. '''
  834. Returns the session scoped syndic id
  835. '''
  836. return 'syndic'
  837. @pytest.fixture(scope='session')
  838. def session_proxy_id():
  839. '''
  840. Returns the session scoped proxy id
  841. '''
  842. return 'proxytest'
  843. @pytest.fixture(scope='session')
  844. def salt_fail_hard():
  845. '''
  846. Return the salt fail hard value
  847. '''
  848. return True
  849. @pytest.fixture(scope='session')
  850. def session_master_default_options(request, session_root_dir):
  851. with salt.utils.files.fopen(os.path.join(RUNTIME_VARS.CONF_DIR, 'master')) as rfh:
  852. opts = yaml.deserialize(rfh.read())
  853. tests_known_hosts_file = session_root_dir.join('salt_ssh_known_hosts').strpath
  854. with salt.utils.files.fopen(tests_known_hosts_file, 'w') as known_hosts:
  855. known_hosts.write('')
  856. opts['known_hosts_file'] = tests_known_hosts_file
  857. opts['syndic_master'] = 'localhost'
  858. opts['transport'] = request.config.getoption('--transport')
  859. # Config settings to test `event_return`
  860. if 'returner_dirs' not in opts:
  861. opts['returner_dirs'] = []
  862. opts['returner_dirs'].append(os.path.join(RUNTIME_VARS.FILES, 'returners'))
  863. opts['event_return'] = 'runtests_noop'
  864. return opts
  865. @pytest.fixture(scope='session')
  866. def session_master_config_overrides(session_root_dir):
  867. ext_pillar = []
  868. if salt.utils.platform.is_windows():
  869. ext_pillar.append(
  870. {'cmd_yaml': 'type {0}'.format(os.path.join(RUNTIME_VARS.FILES, 'ext.yaml'))}
  871. )
  872. else:
  873. ext_pillar.append(
  874. {'cmd_yaml': 'cat {0}'.format(os.path.join(RUNTIME_VARS.FILES, 'ext.yaml'))}
  875. )
  876. ext_pillar.append(
  877. {
  878. 'file_tree': {
  879. 'root_dir': os.path.join(RUNTIME_VARS.PILLAR_DIR, 'base', 'file_tree'),
  880. 'follow_dir_links': False,
  881. 'keep_newline': True
  882. }
  883. }
  884. )
  885. # We need to copy the extension modules into the new master root_dir or
  886. # it will be prefixed by it
  887. extension_modules_path = session_root_dir.join('extension_modules').strpath
  888. if not os.path.exists(extension_modules_path):
  889. shutil.copytree(
  890. os.path.join(
  891. RUNTIME_VARS.FILES, 'extension_modules'
  892. ),
  893. extension_modules_path
  894. )
  895. # Copy the autosign_file to the new master root_dir
  896. autosign_file_path = session_root_dir.join('autosign_file').strpath
  897. shutil.copyfile(
  898. os.path.join(RUNTIME_VARS.FILES, 'autosign_file'),
  899. autosign_file_path
  900. )
  901. # all read, only owner write
  902. autosign_file_permissions = stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH | stat.S_IWUSR
  903. os.chmod(autosign_file_path, autosign_file_permissions)
  904. pytest_stop_sending_events_file = session_root_dir.join('pytest_stop_sending_events_file').strpath
  905. with salt.utils.files.fopen(pytest_stop_sending_events_file, 'w') as wfh:
  906. wfh.write('')
  907. return {
  908. 'pillar_opts': True,
  909. 'ext_pillar': ext_pillar,
  910. 'extension_modules': extension_modules_path,
  911. 'file_roots': {
  912. 'base': [
  913. os.path.join(RUNTIME_VARS.FILES, 'file', 'base'),
  914. ],
  915. # Alternate root to test __env__ choices
  916. 'prod': [
  917. os.path.join(RUNTIME_VARS.FILES, 'file', 'prod'),
  918. ]
  919. },
  920. 'pillar_roots': {
  921. 'base': [
  922. os.path.join(RUNTIME_VARS.FILES, 'pillar', 'base'),
  923. ]
  924. },
  925. 'reactor': [
  926. {
  927. 'salt/minion/*/start': [
  928. os.path.join(RUNTIME_VARS.FILES, 'reactor-sync-minion.sls')
  929. ],
  930. },
  931. {
  932. 'salt/test/reactor': [
  933. os.path.join(RUNTIME_VARS.FILES, 'reactor-test.sls')
  934. ],
  935. }
  936. ],
  937. 'pytest_stop_sending_events_file': pytest_stop_sending_events_file
  938. }
  939. @pytest.fixture(scope='session')
  940. def session_minion_default_options(request, tempdir):
  941. with salt.utils.files.fopen(os.path.join(RUNTIME_VARS.CONF_DIR, 'minion')) as rfh:
  942. opts = yaml.deserialize(rfh.read())
  943. opts['hosts.file'] = tempdir.join('hosts').strpath
  944. opts['aliases.file'] = tempdir.join('aliases').strpath
  945. opts['transport'] = request.config.getoption('--transport')
  946. return opts
  947. def _get_virtualenv_binary_path():
  948. try:
  949. return _get_virtualenv_binary_path.__virtualenv_binary__
  950. except AttributeError:
  951. # Under windows we can't seem to properly create a virtualenv off of another
  952. # virtualenv, we can on linux but we will still point to the virtualenv binary
  953. # outside the virtualenv running the test suite, if that's the case.
  954. try:
  955. real_prefix = sys.real_prefix
  956. # The above attribute exists, this is a virtualenv
  957. if salt.utils.platform.is_windows():
  958. virtualenv_binary = os.path.join(real_prefix, 'Scripts', 'virtualenv.exe')
  959. else:
  960. # We need to remove the virtualenv from PATH or we'll get the virtualenv binary
  961. # from within the virtualenv, we don't want that
  962. path = os.environ.get('PATH')
  963. if path is not None:
  964. path_items = path.split(os.pathsep)
  965. for item in path_items[:]:
  966. if item.startswith(sys.base_prefix):
  967. path_items.remove(item)
  968. os.environ['PATH'] = os.pathsep.join(path_items)
  969. virtualenv_binary = salt.utils.path.which('virtualenv')
  970. if path is not None:
  971. # Restore previous environ PATH
  972. os.environ['PATH'] = path
  973. if not virtualenv_binary.startswith(real_prefix):
  974. virtualenv_binary = None
  975. if virtualenv_binary and not os.path.exists(virtualenv_binary):
  976. # It doesn't exist?!
  977. virtualenv_binary = None
  978. except AttributeError:
  979. # We're not running inside a virtualenv
  980. virtualenv_binary = None
  981. _get_virtualenv_binary_path.__virtualenv_binary__ = virtualenv_binary
  982. return virtualenv_binary
  983. @pytest.fixture(scope='session')
  984. def session_minion_config_overrides():
  985. opts = {
  986. 'file_roots': {
  987. 'base': [
  988. os.path.join(RUNTIME_VARS.FILES, 'file', 'base'),
  989. ],
  990. # Alternate root to test __env__ choices
  991. 'prod': [
  992. os.path.join(RUNTIME_VARS.FILES, 'file', 'prod'),
  993. ]
  994. },
  995. 'pillar_roots': {
  996. 'base': [
  997. os.path.join(RUNTIME_VARS.FILES, 'pillar', 'base'),
  998. ]
  999. },
  1000. }
  1001. virtualenv_binary = _get_virtualenv_binary_path()
  1002. if virtualenv_binary:
  1003. opts['venv_bin'] = virtualenv_binary
  1004. return opts
  1005. @pytest.fixture(scope='session')
  1006. def session_secondary_minion_default_options(request, tempdir):
  1007. with salt.utils.files.fopen(os.path.join(RUNTIME_VARS.CONF_DIR, 'sub_minion')) as rfh:
  1008. opts = yaml.deserialize(rfh.read())
  1009. opts['hosts.file'] = tempdir.join('hosts').strpath
  1010. opts['aliases.file'] = tempdir.join('aliases').strpath
  1011. opts['transport'] = request.config.getoption('--transport')
  1012. return opts
  1013. @pytest.fixture(scope='session')
  1014. def session_seconary_minion_config_overrides():
  1015. opts = {}
  1016. virtualenv_binary = _get_virtualenv_binary_path()
  1017. if virtualenv_binary:
  1018. opts['venv_bin'] = virtualenv_binary
  1019. return opts
  1020. @pytest.fixture(scope='session')
  1021. def session_master_of_masters_default_options(request, tempdir):
  1022. with salt.utils.files.fopen(os.path.join(RUNTIME_VARS.CONF_DIR, 'syndic_master')) as rfh:
  1023. opts = yaml.deserialize(rfh.read())
  1024. opts['hosts.file'] = tempdir.join('hosts').strpath
  1025. opts['aliases.file'] = tempdir.join('aliases').strpath
  1026. opts['transport'] = request.config.getoption('--transport')
  1027. return opts
  1028. @pytest.fixture(scope='session')
  1029. def session_master_of_masters_config_overrides(session_master_of_masters_root_dir):
  1030. if salt.utils.platform.is_windows():
  1031. ext_pillar = {'cmd_yaml': 'type {0}'.format(os.path.join(RUNTIME_VARS.FILES, 'ext.yaml'))}
  1032. else:
  1033. ext_pillar = {'cmd_yaml': 'cat {0}'.format(os.path.join(RUNTIME_VARS.FILES, 'ext.yaml'))}
  1034. # We need to copy the extension modules into the new master root_dir or
  1035. # it will be prefixed by it
  1036. extension_modules_path = session_master_of_masters_root_dir.join('extension_modules').strpath
  1037. if not os.path.exists(extension_modules_path):
  1038. shutil.copytree(
  1039. os.path.join(
  1040. RUNTIME_VARS.FILES, 'extension_modules'
  1041. ),
  1042. extension_modules_path
  1043. )
  1044. # Copy the autosign_file to the new master root_dir
  1045. autosign_file_path = session_master_of_masters_root_dir.join('autosign_file').strpath
  1046. shutil.copyfile(
  1047. os.path.join(RUNTIME_VARS.FILES, 'autosign_file'),
  1048. autosign_file_path
  1049. )
  1050. # all read, only owner write
  1051. autosign_file_permissions = stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH | stat.S_IWUSR
  1052. os.chmod(autosign_file_path, autosign_file_permissions)
  1053. pytest_stop_sending_events_file = session_master_of_masters_root_dir.join('pytest_stop_sending_events_file').strpath
  1054. with salt.utils.files.fopen(pytest_stop_sending_events_file, 'w') as wfh:
  1055. wfh.write('')
  1056. return {
  1057. 'ext_pillar': [ext_pillar],
  1058. 'extension_modules': extension_modules_path,
  1059. 'file_roots': {
  1060. 'base': [
  1061. os.path.join(RUNTIME_VARS.FILES, 'file', 'base'),
  1062. ],
  1063. # Alternate root to test __env__ choices
  1064. 'prod': [
  1065. os.path.join(RUNTIME_VARS.FILES, 'file', 'prod'),
  1066. ]
  1067. },
  1068. 'pillar_roots': {
  1069. 'base': [
  1070. os.path.join(RUNTIME_VARS.FILES, 'pillar', 'base'),
  1071. ]
  1072. },
  1073. 'pytest_stop_sending_events_file': pytest_stop_sending_events_file
  1074. }
  1075. @pytest.fixture(scope='session')
  1076. def session_syndic_master_default_options(request, tempdir):
  1077. with salt.utils.files.fopen(os.path.join(RUNTIME_VARS.CONF_DIR, 'syndic_master')) as rfh:
  1078. opts = yaml.deserialize(rfh.read())
  1079. opts['hosts.file'] = tempdir.join('hosts').strpath
  1080. opts['aliases.file'] = tempdir.join('aliases').strpath
  1081. opts['transport'] = request.config.getoption('--transport')
  1082. return opts
  1083. @pytest.fixture(scope='session')
  1084. def session_syndic_default_options(request, tempdir):
  1085. with salt.utils.files.fopen(os.path.join(RUNTIME_VARS.CONF_DIR, 'syndic')) as rfh:
  1086. opts = yaml.deserialize(rfh.read())
  1087. opts['hosts.file'] = tempdir.join('hosts').strpath
  1088. opts['aliases.file'] = tempdir.join('aliases').strpath
  1089. opts['transport'] = request.config.getoption('--transport')
  1090. return opts
  1091. @pytest.fixture(scope='session')
  1092. def session_proxy_default_options(request, tempdir):
  1093. with salt.utils.files.fopen(os.path.join(RUNTIME_VARS.CONF_DIR, 'proxy')) as rfh:
  1094. opts = yaml.deserialize(rfh.read())
  1095. opts['hosts.file'] = tempdir.join('hosts').strpath
  1096. opts['aliases.file'] = tempdir.join('aliases').strpath
  1097. opts['transport'] = request.config.getoption('--transport')
  1098. return opts
  1099. @pytest.fixture(scope='session', autouse=True)
  1100. def bridge_pytest_and_runtests(reap_stray_processes,
  1101. session_root_dir,
  1102. session_conf_dir,
  1103. session_secondary_conf_dir,
  1104. session_syndic_conf_dir,
  1105. session_master_of_masters_conf_dir,
  1106. session_base_env_pillar_tree_root_dir,
  1107. session_base_env_state_tree_root_dir,
  1108. session_prod_env_state_tree_root_dir,
  1109. session_master_config,
  1110. session_minion_config,
  1111. session_secondary_minion_config,
  1112. session_master_of_masters_config,
  1113. session_syndic_config):
  1114. # Make sure unittest2 classes know their paths
  1115. RUNTIME_VARS.TMP_ROOT_DIR = session_root_dir.realpath().strpath
  1116. RUNTIME_VARS.TMP_CONF_DIR = session_conf_dir.realpath().strpath
  1117. RUNTIME_VARS.TMP_SUB_MINION_CONF_DIR = session_secondary_conf_dir.realpath().strpath
  1118. RUNTIME_VARS.TMP_SYNDIC_MASTER_CONF_DIR = session_master_of_masters_conf_dir.realpath().strpath
  1119. RUNTIME_VARS.TMP_SYNDIC_MINION_CONF_DIR = session_syndic_conf_dir.realpath().strpath
  1120. RUNTIME_VARS.TMP_PILLAR_TREE = session_base_env_pillar_tree_root_dir.realpath().strpath
  1121. RUNTIME_VARS.TMP_STATE_TREE = session_base_env_state_tree_root_dir.realpath().strpath
  1122. RUNTIME_VARS.TMP_PRODENV_STATE_TREE = session_prod_env_state_tree_root_dir.realpath().strpath
  1123. # Make sure unittest2 uses the pytest generated configuration
  1124. RUNTIME_VARS.RUNTIME_CONFIGS['master'] = freeze(session_master_config)
  1125. RUNTIME_VARS.RUNTIME_CONFIGS['minion'] = freeze(session_minion_config)
  1126. RUNTIME_VARS.RUNTIME_CONFIGS['sub_minion'] = freeze(session_secondary_minion_config)
  1127. RUNTIME_VARS.RUNTIME_CONFIGS['syndic_master'] = freeze(session_master_of_masters_config)
  1128. RUNTIME_VARS.RUNTIME_CONFIGS['syndic'] = freeze(session_syndic_config)
  1129. RUNTIME_VARS.RUNTIME_CONFIGS['client_config'] = freeze(
  1130. salt.config.client_config(session_conf_dir.join('master').strpath)
  1131. )
  1132. # Copy configuration files and directories which are not automatically generated
  1133. for entry in os.listdir(RUNTIME_VARS.CONF_DIR):
  1134. if entry in ('master', 'minion', 'sub_minion', 'syndic', 'syndic_master', 'proxy'):
  1135. # These have runtime computed values and are handled by pytest-salt fixtures
  1136. continue
  1137. entry_path = os.path.join(RUNTIME_VARS.CONF_DIR, entry)
  1138. if os.path.isfile(entry_path):
  1139. shutil.copy(
  1140. entry_path,
  1141. os.path.join(RUNTIME_VARS.TMP_CONF_DIR, entry)
  1142. )
  1143. elif os.path.isdir(entry_path):
  1144. shutil.copytree(
  1145. entry_path,
  1146. os.path.join(RUNTIME_VARS.TMP_CONF_DIR, entry)
  1147. )
  1148. # <---- Salt Configuration -------------------------------------------------------------------------------------------
  1149. # <---- Fixtures Overrides -------------------------------------------------------------------------------------------
  1150. # ----- Custom Grains Mark Evaluator -------------------------------------------------------------------------------->
  1151. class GrainsMarkEvaluator(MarkEvaluator):
  1152. _cached_grains = None
  1153. def _getglobals(self):
  1154. item_globals = super(GrainsMarkEvaluator, self)._getglobals()
  1155. if GrainsMarkEvaluator._cached_grains is None:
  1156. sminion = create_sminion()
  1157. GrainsMarkEvaluator._cached_grains = sminion.opts['grains'].copy()
  1158. item_globals['grains'] = GrainsMarkEvaluator._cached_grains.copy()
  1159. return item_globals
  1160. # Patch PyTest's skipping MarkEvaluator to use our GrainsMarkEvaluator
  1161. _pytest.skipping.MarkEvaluator = GrainsMarkEvaluator
  1162. # <---- Custom Grains Mark Evaluator ---------------------------------------------------------------------------------
  1163. # ----- Custom Fixtures --------------------------------------------------------------------------------------------->
  1164. @pytest.fixture(scope='session')
  1165. def reap_stray_processes():
  1166. # Run tests
  1167. yield
  1168. children = psutil.Process(os.getpid()).children(recursive=True)
  1169. if not children:
  1170. log.info('No astray processes found')
  1171. return
  1172. def on_terminate(proc):
  1173. log.debug('Process %s terminated with exit code %s', proc, proc.returncode)
  1174. if children:
  1175. # Reverse the order, sublings first, parents after
  1176. children.reverse()
  1177. log.warning(
  1178. 'Test suite left %d astray processes running. Killing those processes:\n%s',
  1179. len(children),
  1180. pprint.pformat(children)
  1181. )
  1182. _, alive = psutil.wait_procs(children, timeout=3, callback=on_terminate)
  1183. for child in alive:
  1184. try:
  1185. child.kill()
  1186. except psutil.NoSuchProcess:
  1187. continue
  1188. _, alive = psutil.wait_procs(alive, timeout=3, callback=on_terminate)
  1189. if alive:
  1190. # Give up
  1191. for child in alive:
  1192. log.warning('Process %s survived SIGKILL, giving up:\n%s', child, pprint.pformat(child.as_dict()))
  1193. @pytest.fixture(scope='session')
  1194. def grains(request):
  1195. sminion = create_sminion()
  1196. return sminion.opts['grains'].copy()
  1197. # <---- Custom Fixtures ----------------------------------------------------------------------------------------------