conftest.py 53 KB

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