1
0

conftest.py 48 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355
  1. """
  2. :codeauthor: Pedro Algarvio (pedro@algarvio.me)
  3. tests.conftest
  4. ~~~~~~~~~~~~~~
  5. Prepare py.test for our test suite
  6. """
  7. # pylint: disable=wrong-import-order,wrong-import-position,3rd-party-local-module-not-gated
  8. # pylint: disable=redefined-outer-name,invalid-name,3rd-party-module-not-gated
  9. import logging
  10. import os
  11. import pathlib
  12. import pprint
  13. import re
  14. import shutil
  15. import ssl
  16. import stat
  17. import sys
  18. from functools import partial, wraps
  19. from unittest import TestCase # pylint: disable=blacklisted-module
  20. import _pytest.logging
  21. import _pytest.skipping
  22. import psutil
  23. import pytest
  24. import salt.config
  25. import salt.loader
  26. import salt.log.mixins
  27. import salt.log.setup
  28. import salt.utils.files
  29. import salt.utils.path
  30. import salt.utils.platform
  31. import salt.utils.win_functions
  32. import saltfactories.utils.compat
  33. from salt.serializers import yaml
  34. from salt.utils.immutabletypes import freeze
  35. from tests.support.helpers import (
  36. PRE_PYTEST_SKIP_OR_NOT,
  37. PRE_PYTEST_SKIP_REASON,
  38. Webserver,
  39. get_virtualenv_binary_path,
  40. )
  41. from tests.support.pytest.helpers import * # pylint: disable=unused-wildcard-import
  42. from tests.support.runtests import RUNTIME_VARS
  43. from tests.support.sminion import check_required_sminion_attributes, create_sminion
  44. TESTS_DIR = pathlib.Path(__file__).resolve().parent
  45. PYTESTS_DIR = TESTS_DIR / "pytests"
  46. CODE_DIR = TESTS_DIR.parent
  47. # Change to code checkout directory
  48. os.chdir(str(CODE_DIR))
  49. # Make sure the current directory is the first item in sys.path
  50. if str(CODE_DIR) in sys.path:
  51. sys.path.remove(str(CODE_DIR))
  52. sys.path.insert(0, str(CODE_DIR))
  53. # Coverage
  54. if "COVERAGE_PROCESS_START" in os.environ:
  55. MAYBE_RUN_COVERAGE = True
  56. COVERAGERC_FILE = os.environ["COVERAGE_PROCESS_START"]
  57. else:
  58. COVERAGERC_FILE = str(CODE_DIR / ".coveragerc")
  59. MAYBE_RUN_COVERAGE = (
  60. sys.argv[0].endswith("pytest.py") or "_COVERAGE_RCFILE" in os.environ
  61. )
  62. if MAYBE_RUN_COVERAGE:
  63. # Flag coverage to track suprocesses by pointing it to the right .coveragerc file
  64. os.environ["COVERAGE_PROCESS_START"] = str(COVERAGERC_FILE)
  65. # Define the pytest plugins we rely on
  66. pytest_plugins = ["tempdir", "helpers_namespace"]
  67. # Define where not to collect tests from
  68. collect_ignore = ["setup.py"]
  69. # Patch PyTest logging handlers
  70. class LogCaptureHandler(
  71. salt.log.mixins.ExcInfoOnLogLevelFormatMixIn, _pytest.logging.LogCaptureHandler
  72. ):
  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(
  81. salt.log.mixins.ExcInfoOnLogLevelFormatMixIn,
  82. _pytest.logging._LiveLoggingStreamHandler,
  83. ):
  84. """
  85. Subclassing PyTest's LiveLoggingStreamHandler in order to add the
  86. exc_info_on_loglevel functionality.
  87. """
  88. _pytest.logging._LiveLoggingStreamHandler = LiveLoggingStreamHandler
  89. # Reset logging root handlers
  90. for handler in logging.root.handlers[:]:
  91. logging.root.removeHandler(handler)
  92. # Reset the root logger to its default level(because salt changed it)
  93. logging.root.setLevel(logging.WARNING)
  94. log = logging.getLogger("salt.testsuite")
  95. # ----- PyTest Tempdir Plugin Hooks --------------------------------------------------------------------------------->
  96. def pytest_tempdir_basename():
  97. """
  98. Return the temporary directory basename for the salt test suite.
  99. """
  100. return "salt-tests-tmpdir"
  101. # <---- PyTest Tempdir Plugin Hooks ----------------------------------------------------------------------------------
  102. # ----- CLI Options Setup ------------------------------------------------------------------------------------------->
  103. def pytest_addoption(parser):
  104. """
  105. register argparse-style options and ini-style config values.
  106. """
  107. test_selection_group = parser.getgroup("Tests Selection")
  108. test_selection_group.addoption(
  109. "--from-filenames",
  110. default=None,
  111. help=(
  112. "Pass a comma-separated list of file paths, and any test module which corresponds to the "
  113. "specified file(s) will run. For example, if 'setup.py' was passed, then the corresponding "
  114. "test files defined in 'tests/filename_map.yml' would run. Absolute paths are assumed to be "
  115. "files containing relative paths, one per line. Providing the paths in a file can help get "
  116. "around shell character limits when the list of files is long."
  117. ),
  118. )
  119. # Add deprecated CLI flag until we completely switch to PyTest
  120. test_selection_group.addoption(
  121. "--names-file", default=None, help="Deprecated option"
  122. )
  123. test_selection_group.addoption(
  124. "--transport",
  125. default="zeromq",
  126. choices=("zeromq", "tcp"),
  127. help=(
  128. "Select which transport to run the integration tests with, zeromq or tcp. Default: %(default)s"
  129. ),
  130. )
  131. test_selection_group.addoption(
  132. "--ssh",
  133. "--ssh-tests",
  134. dest="ssh",
  135. action="store_true",
  136. default=False,
  137. help="Run salt-ssh tests. These tests will spin up a temporary "
  138. "SSH server on your machine. In certain environments, this "
  139. "may be insecure! Default: False",
  140. )
  141. test_selection_group.addoption(
  142. "--proxy",
  143. "--proxy-tests",
  144. dest="proxy",
  145. action="store_true",
  146. default=False,
  147. help="Run proxy tests",
  148. )
  149. test_selection_group.addoption(
  150. "--run-slow", action="store_true", default=False, help="Run slow tests.",
  151. )
  152. output_options_group = parser.getgroup("Output Options")
  153. output_options_group.addoption(
  154. "--output-columns",
  155. default=80,
  156. type=int,
  157. help="Number of maximum columns to use on the output",
  158. )
  159. output_options_group.addoption(
  160. "--no-colors",
  161. "--no-colours",
  162. default=False,
  163. action="store_true",
  164. help="Disable colour printing.",
  165. )
  166. # ----- Test Groups --------------------------------------------------------------------------------------------->
  167. # This will allow running the tests in chunks
  168. test_selection_group.addoption(
  169. "--test-group-count",
  170. dest="test-group-count",
  171. type=int,
  172. help="The number of groups to split the tests into",
  173. )
  174. test_selection_group.addoption(
  175. "--test-group",
  176. dest="test-group",
  177. type=int,
  178. help="The group of tests that should be executed",
  179. )
  180. # <---- Test Groups ----------------------------------------------------------------------------------------------
  181. # <---- CLI Options Setup --------------------------------------------------------------------------------------------
  182. # ----- Register Markers -------------------------------------------------------------------------------------------->
  183. @pytest.mark.trylast
  184. def pytest_configure(config):
  185. """
  186. called after command line options have been parsed
  187. and all plugins and initial conftest files been loaded.
  188. """
  189. for dirname in CODE_DIR.iterdir():
  190. if not dirname.is_dir():
  191. continue
  192. if dirname != TESTS_DIR:
  193. config.addinivalue_line("norecursedirs", str(CODE_DIR / dirname))
  194. # Expose the markers we use to pytest CLI
  195. config.addinivalue_line(
  196. "markers",
  197. "requires_salt_modules(*required_module_names): Skip if at least one module is not available.",
  198. )
  199. config.addinivalue_line(
  200. "markers",
  201. "requires_salt_states(*required_state_names): Skip if at least one state module is not available.",
  202. )
  203. config.addinivalue_line(
  204. "markers", "windows_whitelisted: Mark test as whitelisted to run under Windows"
  205. )
  206. config.addinivalue_line(
  207. "markers", "requires_sshd_server: Mark test that require an SSH server running"
  208. )
  209. # Make sure the test suite "knows" this is a pytest test run
  210. RUNTIME_VARS.PYTEST_SESSION = True
  211. # "Flag" the slotTest decorator if we're skipping slow tests or not
  212. os.environ["SLOW_TESTS"] = str(config.getoption("--run-slow"))
  213. # <---- Register Markers ---------------------------------------------------------------------------------------------
  214. # ----- PyTest Tweaks ----------------------------------------------------------------------------------------------->
  215. def set_max_open_files_limits(min_soft=3072, min_hard=4096):
  216. # Get current limits
  217. if salt.utils.platform.is_windows():
  218. import win32file
  219. prev_hard = win32file._getmaxstdio()
  220. prev_soft = 512
  221. else:
  222. import resource
  223. prev_soft, prev_hard = resource.getrlimit(resource.RLIMIT_NOFILE)
  224. # Check minimum required limits
  225. set_limits = False
  226. if prev_soft < min_soft:
  227. soft = min_soft
  228. set_limits = True
  229. else:
  230. soft = prev_soft
  231. if prev_hard < min_hard:
  232. hard = min_hard
  233. set_limits = True
  234. else:
  235. hard = prev_hard
  236. # Increase limits
  237. if set_limits:
  238. log.debug(
  239. " * Max open files settings is too low (soft: %s, hard: %s) for running the tests. "
  240. "Trying to raise the limits to soft: %s, hard: %s",
  241. prev_soft,
  242. prev_hard,
  243. soft,
  244. hard,
  245. )
  246. try:
  247. if salt.utils.platform.is_windows():
  248. hard = 2048 if hard > 2048 else hard
  249. win32file._setmaxstdio(hard)
  250. else:
  251. resource.setrlimit(resource.RLIMIT_NOFILE, (soft, hard))
  252. except Exception as err: # pylint: disable=broad-except
  253. log.error(
  254. "Failed to raise the max open files settings -> %s. Please issue the following command "
  255. "on your console: 'ulimit -u %s'",
  256. err,
  257. soft,
  258. )
  259. exit(1)
  260. return soft, hard
  261. def pytest_report_header():
  262. soft, hard = set_max_open_files_limits()
  263. return "max open files; soft: {}; hard: {}".format(soft, hard)
  264. def pytest_itemcollected(item):
  265. """We just collected a test item."""
  266. try:
  267. pathlib.Path(item.fspath.strpath).resolve().relative_to(PYTESTS_DIR)
  268. # Test is under tests/pytests
  269. if item.cls and issubclass(item.cls, TestCase):
  270. pytest.fail(
  271. "The tests under {0!r} MUST NOT use unittest's TestCase class or a subclass of it. "
  272. "Please move {1!r} outside of {0!r}".format(
  273. str(PYTESTS_DIR.relative_to(CODE_DIR)), item.nodeid
  274. )
  275. )
  276. except ValueError:
  277. # Test is not under tests/pytests
  278. if not item.cls or (item.cls and not issubclass(item.cls, TestCase)):
  279. pytest.fail(
  280. "The test {!r} appears to be written for pytest but it's not under {!r}. Please move it there.".format(
  281. item.nodeid, str(PYTESTS_DIR.relative_to(CODE_DIR)), pytrace=False
  282. )
  283. )
  284. @pytest.hookimpl(hookwrapper=True, trylast=True)
  285. def pytest_collection_modifyitems(config, items):
  286. """
  287. called after collection has been performed, may filter or re-order
  288. the items in-place.
  289. :param _pytest.main.Session session: the pytest session object
  290. :param _pytest.config.Config config: pytest config object
  291. :param List[_pytest.nodes.Item] items: list of item objects
  292. """
  293. # Let PyTest or other plugins handle the initial collection
  294. yield
  295. groups_collection_modifyitems(config, items)
  296. from_filenames_collection_modifyitems(config, items)
  297. log.warning("Mofifying collected tests to keep track of fixture usage")
  298. for item in items:
  299. for fixture in item.fixturenames:
  300. if fixture not in item._fixtureinfo.name2fixturedefs:
  301. continue
  302. for fixturedef in item._fixtureinfo.name2fixturedefs[fixture]:
  303. if fixturedef.scope != "package":
  304. continue
  305. try:
  306. fixturedef.finish.__wrapped__
  307. except AttributeError:
  308. original_func = fixturedef.finish
  309. def wrapper(func, fixturedef):
  310. @wraps(func)
  311. def wrapped(self, request, nextitem=False):
  312. try:
  313. return self._finished
  314. except AttributeError:
  315. if nextitem:
  316. fpath = pathlib.Path(self.baseid).resolve()
  317. tpath = pathlib.Path(
  318. nextitem.fspath.strpath
  319. ).resolve()
  320. try:
  321. tpath.relative_to(fpath)
  322. # The test module is within the same package that the fixture is
  323. if (
  324. not request.session.shouldfail
  325. and not request.session.shouldstop
  326. ):
  327. log.debug(
  328. "The next test item is still under the fixture package path. "
  329. "Not terminating %s",
  330. self,
  331. )
  332. return
  333. except ValueError:
  334. pass
  335. log.debug("Finish called on %s", self)
  336. try:
  337. return func(request)
  338. except BaseException as exc: # pylint: disable=broad-except
  339. pytest.fail(
  340. "Failed to run finish() on {}: {}".format(
  341. fixturedef, exc
  342. ),
  343. pytrace=True,
  344. )
  345. finally:
  346. self._finished = True
  347. return partial(wrapped, fixturedef)
  348. fixturedef.finish = wrapper(fixturedef.finish, fixturedef)
  349. try:
  350. fixturedef.finish.__wrapped__
  351. except AttributeError:
  352. fixturedef.finish.__wrapped__ = original_func
  353. @pytest.hookimpl(trylast=True, hookwrapper=True)
  354. def pytest_runtest_protocol(item, nextitem):
  355. """
  356. implements the runtest_setup/call/teardown protocol for
  357. the given test item, including capturing exceptions and calling
  358. reporting hooks.
  359. :arg item: test item for which the runtest protocol is performed.
  360. :arg nextitem: the scheduled-to-be-next test item (or None if this
  361. is the end my friend). This argument is passed on to
  362. :py:func:`pytest_runtest_teardown`.
  363. :return boolean: True if no further hook implementations should be invoked.
  364. Stops at first non-None result, see :ref:`firstresult`
  365. """
  366. request = item._request
  367. used_fixture_defs = []
  368. for fixture in item.fixturenames:
  369. if fixture not in item._fixtureinfo.name2fixturedefs:
  370. continue
  371. for fixturedef in reversed(item._fixtureinfo.name2fixturedefs[fixture]):
  372. if fixturedef.scope != "package":
  373. continue
  374. used_fixture_defs.append(fixturedef)
  375. try:
  376. # Run the test
  377. yield
  378. finally:
  379. for fixturedef in used_fixture_defs:
  380. fixturedef.finish(request, nextitem=nextitem)
  381. del request
  382. del used_fixture_defs
  383. # <---- PyTest Tweaks ------------------------------------------------------------------------------------------------
  384. # ----- Test Setup -------------------------------------------------------------------------------------------------->
  385. @pytest.hookimpl(tryfirst=True)
  386. def pytest_runtest_setup(item):
  387. """
  388. Fixtures injection based on markers or test skips based on CLI arguments
  389. """
  390. integration_utils_tests_path = str(TESTS_DIR / "integration" / "utils")
  391. if (
  392. str(item.fspath).startswith(integration_utils_tests_path)
  393. and PRE_PYTEST_SKIP_OR_NOT is True
  394. ):
  395. item._skipped_by_mark = True
  396. pytest.skip(PRE_PYTEST_SKIP_REASON)
  397. if saltfactories.utils.compat.has_unittest_attr(item, "__slow_test__"):
  398. if item.config.getoption("--run-slow") is False:
  399. item._skipped_by_mark = True
  400. pytest.skip("Slow tests are disabled!")
  401. requires_sshd_server_marker = item.get_closest_marker("requires_sshd_server")
  402. if requires_sshd_server_marker is not None:
  403. if not item.config.getoption("--ssh-tests"):
  404. item._skipped_by_mark = True
  405. pytest.skip("SSH tests are disabled, pass '--ssh-tests' to enable them.")
  406. item.fixturenames.append("sshd_server")
  407. item.fixturenames.append("salt_ssh_roster_file")
  408. requires_salt_modules_marker = item.get_closest_marker("requires_salt_modules")
  409. if requires_salt_modules_marker is not None:
  410. required_salt_modules = requires_salt_modules_marker.args
  411. if len(required_salt_modules) == 1 and isinstance(
  412. required_salt_modules[0], (list, tuple, set)
  413. ):
  414. required_salt_modules = required_salt_modules[0]
  415. required_salt_modules = set(required_salt_modules)
  416. not_available_modules = check_required_sminion_attributes(
  417. "functions", required_salt_modules
  418. )
  419. if not_available_modules:
  420. item._skipped_by_mark = True
  421. if len(not_available_modules) == 1:
  422. pytest.skip(
  423. "Salt module '{}' is not available".format(*not_available_modules)
  424. )
  425. pytest.skip(
  426. "Salt modules not available: {}".format(
  427. ", ".join(not_available_modules)
  428. )
  429. )
  430. requires_salt_states_marker = item.get_closest_marker("requires_salt_states")
  431. if requires_salt_states_marker is not None:
  432. required_salt_states = requires_salt_states_marker.args
  433. if len(required_salt_states) == 1 and isinstance(
  434. required_salt_states[0], (list, tuple, set)
  435. ):
  436. required_salt_states = required_salt_states[0]
  437. required_salt_states = set(required_salt_states)
  438. not_available_states = check_required_sminion_attributes(
  439. "states", required_salt_states
  440. )
  441. if not_available_states:
  442. item._skipped_by_mark = True
  443. if len(not_available_states) == 1:
  444. pytest.skip(
  445. "Salt state module '{}' is not available".format(
  446. *not_available_states
  447. )
  448. )
  449. pytest.skip(
  450. "Salt state modules not available: {}".format(
  451. ", ".join(not_available_states)
  452. )
  453. )
  454. if salt.utils.platform.is_windows():
  455. unit_tests_paths = (
  456. str(TESTS_DIR / "unit"),
  457. str(PYTESTS_DIR / "unit"),
  458. )
  459. if not str(pathlib.Path(item.fspath).resolve()).startswith(unit_tests_paths):
  460. # Unit tests are whitelisted on windows by default, so, we're only
  461. # after all other tests
  462. windows_whitelisted_marker = item.get_closest_marker("windows_whitelisted")
  463. if windows_whitelisted_marker is None:
  464. item._skipped_by_mark = True
  465. pytest.skip("Test is not whitelisted for Windows")
  466. # <---- Test Setup ---------------------------------------------------------------------------------------------------
  467. # ----- Test Groups Selection --------------------------------------------------------------------------------------->
  468. def get_group_size_and_start(total_items, total_groups, group_id):
  469. """
  470. Calculate group size and start index.
  471. """
  472. base_size = total_items // total_groups
  473. rem = total_items % total_groups
  474. start = base_size * (group_id - 1) + min(group_id - 1, rem)
  475. size = base_size + 1 if group_id <= rem else base_size
  476. return (start, size)
  477. def get_group(items, total_groups, group_id):
  478. """
  479. Get the items from the passed in group based on group size.
  480. """
  481. if not 0 < group_id <= total_groups:
  482. raise ValueError("Invalid test-group argument")
  483. start, size = get_group_size_and_start(len(items), total_groups, group_id)
  484. selected = items[start : start + size]
  485. deselected = items[:start] + items[start + size :]
  486. assert len(selected) + len(deselected) == len(items)
  487. return selected, deselected
  488. def groups_collection_modifyitems(config, items):
  489. group_count = config.getoption("test-group-count")
  490. group_id = config.getoption("test-group")
  491. if not group_count or not group_id:
  492. # We're not selection tests using groups, don't do any filtering
  493. return
  494. total_items = len(items)
  495. tests_in_group, deselected = get_group(items, group_count, group_id)
  496. # Replace all items in the list
  497. items[:] = tests_in_group
  498. if deselected:
  499. config.hook.pytest_deselected(items=deselected)
  500. terminal_reporter = config.pluginmanager.get_plugin("terminalreporter")
  501. terminal_reporter.write(
  502. "Running test group #{} ({} tests)\n".format(group_id, len(items)), yellow=True,
  503. )
  504. # <---- Test Groups Selection ----------------------------------------------------------------------------------------
  505. # ----- Fixtures Overrides ------------------------------------------------------------------------------------------>
  506. @pytest.fixture(scope="session")
  507. def salt_factories_config():
  508. """
  509. Return a dictionary with the keyworkd arguments for FactoriesManager
  510. """
  511. return {
  512. "code_dir": str(CODE_DIR),
  513. "inject_coverage": MAYBE_RUN_COVERAGE,
  514. "inject_sitecustomize": MAYBE_RUN_COVERAGE,
  515. "start_timeout": 120
  516. if (os.environ.get("JENKINS_URL") or os.environ.get("CI"))
  517. else 60,
  518. }
  519. # <---- Fixtures Overrides -------------------------------------------------------------------------------------------
  520. # ----- Salt Factories ---------------------------------------------------------------------------------------------->
  521. @pytest.fixture(scope="session")
  522. def integration_files_dir(salt_factories):
  523. """
  524. Fixture which returns the salt integration files directory path.
  525. Creates the directory if it does not yet exist.
  526. """
  527. dirname = salt_factories.root_dir / "integration-files"
  528. dirname.mkdir(exist_ok=True)
  529. for child in (PYTESTS_DIR / "integration" / "files").iterdir():
  530. if child.is_dir():
  531. shutil.copytree(str(child), str(dirname / child.name))
  532. else:
  533. shutil.copyfile(str(child), str(dirname / child.name))
  534. return dirname
  535. @pytest.fixture(scope="session")
  536. def state_tree_root_dir(integration_files_dir):
  537. """
  538. Fixture which returns the salt state tree root directory path.
  539. Creates the directory if it does not yet exist.
  540. """
  541. dirname = integration_files_dir / "state-tree"
  542. dirname.mkdir(exist_ok=True)
  543. return dirname
  544. @pytest.fixture(scope="session")
  545. def pillar_tree_root_dir(integration_files_dir):
  546. """
  547. Fixture which returns the salt pillar tree root directory path.
  548. Creates the directory if it does not yet exist.
  549. """
  550. dirname = integration_files_dir / "pillar-tree"
  551. dirname.mkdir(exist_ok=True)
  552. return dirname
  553. @pytest.fixture(scope="session")
  554. def base_env_state_tree_root_dir(state_tree_root_dir):
  555. """
  556. Fixture which returns the salt base environment state tree directory path.
  557. Creates the directory if it does not yet exist.
  558. """
  559. dirname = state_tree_root_dir / "base"
  560. dirname.mkdir(exist_ok=True)
  561. RUNTIME_VARS.TMP_STATE_TREE = str(dirname.resolve())
  562. RUNTIME_VARS.TMP_BASEENV_STATE_TREE = RUNTIME_VARS.TMP_STATE_TREE
  563. return dirname
  564. @pytest.fixture(scope="session")
  565. def prod_env_state_tree_root_dir(state_tree_root_dir):
  566. """
  567. Fixture which returns the salt prod environment state tree directory path.
  568. Creates the directory if it does not yet exist.
  569. """
  570. dirname = state_tree_root_dir / "prod"
  571. dirname.mkdir(exist_ok=True)
  572. RUNTIME_VARS.TMP_PRODENV_STATE_TREE = str(dirname.resolve())
  573. return dirname
  574. @pytest.fixture(scope="session")
  575. def base_env_pillar_tree_root_dir(pillar_tree_root_dir):
  576. """
  577. Fixture which returns the salt base environment pillar tree directory path.
  578. Creates the directory if it does not yet exist.
  579. """
  580. dirname = pillar_tree_root_dir / "base"
  581. dirname.mkdir(exist_ok=True)
  582. RUNTIME_VARS.TMP_PILLAR_TREE = str(dirname.resolve())
  583. RUNTIME_VARS.TMP_BASEENV_PILLAR_TREE = RUNTIME_VARS.TMP_PILLAR_TREE
  584. return dirname
  585. @pytest.fixture(scope="session")
  586. def ext_pillar_file_tree_root_dir(pillar_tree_root_dir):
  587. """
  588. Fixture which returns the salt pillar file tree directory path.
  589. Creates the directory if it does not yet exist.
  590. """
  591. dirname = pillar_tree_root_dir / "file-tree"
  592. dirname.mkdir(exist_ok=True)
  593. return dirname
  594. @pytest.fixture(scope="session")
  595. def prod_env_pillar_tree_root_dir(pillar_tree_root_dir):
  596. """
  597. Fixture which returns the salt prod environment pillar tree directory path.
  598. Creates the directory if it does not yet exist.
  599. """
  600. dirname = pillar_tree_root_dir / "prod"
  601. dirname.mkdir(exist_ok=True)
  602. RUNTIME_VARS.TMP_PRODENV_PILLAR_TREE = str(dirname.resolve())
  603. return dirname
  604. @pytest.fixture(scope="session")
  605. def salt_syndic_master_factory(
  606. request,
  607. salt_factories,
  608. base_env_state_tree_root_dir,
  609. base_env_pillar_tree_root_dir,
  610. prod_env_state_tree_root_dir,
  611. prod_env_pillar_tree_root_dir,
  612. ):
  613. root_dir = salt_factories.get_root_dir_for_daemon("syndic_master")
  614. conf_dir = root_dir / "conf"
  615. conf_dir.mkdir(exist_ok=True)
  616. with salt.utils.files.fopen(
  617. os.path.join(RUNTIME_VARS.CONF_DIR, "syndic_master")
  618. ) as rfh:
  619. config_defaults = yaml.deserialize(rfh.read())
  620. tests_known_hosts_file = str(root_dir / "salt_ssh_known_hosts")
  621. with salt.utils.files.fopen(tests_known_hosts_file, "w") as known_hosts:
  622. known_hosts.write("")
  623. config_defaults["root_dir"] = str(root_dir)
  624. config_defaults["known_hosts_file"] = tests_known_hosts_file
  625. config_defaults["syndic_master"] = "localhost"
  626. config_defaults["transport"] = request.config.getoption("--transport")
  627. config_overrides = {"log_level_logfile": "quiet"}
  628. ext_pillar = []
  629. if salt.utils.platform.is_windows():
  630. ext_pillar.append(
  631. {"cmd_yaml": "type {}".format(os.path.join(RUNTIME_VARS.FILES, "ext.yaml"))}
  632. )
  633. else:
  634. ext_pillar.append(
  635. {"cmd_yaml": "cat {}".format(os.path.join(RUNTIME_VARS.FILES, "ext.yaml"))}
  636. )
  637. # We need to copy the extension modules into the new master root_dir or
  638. # it will be prefixed by it
  639. extension_modules_path = str(root_dir / "extension_modules")
  640. if not os.path.exists(extension_modules_path):
  641. shutil.copytree(
  642. os.path.join(RUNTIME_VARS.FILES, "extension_modules"),
  643. extension_modules_path,
  644. )
  645. # Copy the autosign_file to the new master root_dir
  646. autosign_file_path = str(root_dir / "autosign_file")
  647. shutil.copyfile(
  648. os.path.join(RUNTIME_VARS.FILES, "autosign_file"), autosign_file_path
  649. )
  650. # all read, only owner write
  651. autosign_file_permissions = (
  652. stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH | stat.S_IWUSR
  653. )
  654. os.chmod(autosign_file_path, autosign_file_permissions)
  655. config_overrides.update(
  656. {
  657. "ext_pillar": ext_pillar,
  658. "extension_modules": extension_modules_path,
  659. "file_roots": {
  660. "base": [
  661. str(base_env_state_tree_root_dir),
  662. os.path.join(RUNTIME_VARS.FILES, "file", "base"),
  663. ],
  664. # Alternate root to test __env__ choices
  665. "prod": [
  666. str(prod_env_state_tree_root_dir),
  667. os.path.join(RUNTIME_VARS.FILES, "file", "prod"),
  668. ],
  669. },
  670. "pillar_roots": {
  671. "base": [
  672. str(base_env_pillar_tree_root_dir),
  673. os.path.join(RUNTIME_VARS.FILES, "pillar", "base"),
  674. ],
  675. "prod": [str(prod_env_pillar_tree_root_dir)],
  676. },
  677. }
  678. )
  679. factory = salt_factories.get_salt_master_daemon(
  680. "syndic_master",
  681. order_masters=True,
  682. config_defaults=config_defaults,
  683. config_overrides=config_overrides,
  684. extra_cli_arguments_after_first_start_failure=["--log-level=debug"],
  685. )
  686. return factory
  687. @pytest.fixture(scope="session")
  688. def salt_syndic_factory(salt_factories, salt_syndic_master_factory):
  689. config_defaults = {"master": None, "minion": None, "syndic": None}
  690. with salt.utils.files.fopen(os.path.join(RUNTIME_VARS.CONF_DIR, "syndic")) as rfh:
  691. opts = yaml.deserialize(rfh.read())
  692. opts["hosts.file"] = os.path.join(RUNTIME_VARS.TMP, "hosts")
  693. opts["aliases.file"] = os.path.join(RUNTIME_VARS.TMP, "aliases")
  694. opts["transport"] = salt_syndic_master_factory.config["transport"]
  695. config_defaults["syndic"] = opts
  696. config_overrides = {"log_level_logfile": "quiet"}
  697. factory = salt_syndic_master_factory.get_salt_syndic_daemon(
  698. "syndic",
  699. config_defaults=config_defaults,
  700. config_overrides=config_overrides,
  701. extra_cli_arguments_after_first_start_failure=["--log-level=debug"],
  702. )
  703. return factory
  704. @pytest.fixture(scope="session")
  705. def salt_master_factory(
  706. salt_factories,
  707. salt_syndic_master_factory,
  708. base_env_state_tree_root_dir,
  709. base_env_pillar_tree_root_dir,
  710. prod_env_state_tree_root_dir,
  711. prod_env_pillar_tree_root_dir,
  712. ext_pillar_file_tree_root_dir,
  713. ):
  714. root_dir = salt_factories.get_root_dir_for_daemon("master")
  715. conf_dir = root_dir / "conf"
  716. conf_dir.mkdir(exist_ok=True)
  717. with salt.utils.files.fopen(os.path.join(RUNTIME_VARS.CONF_DIR, "master")) as rfh:
  718. config_defaults = yaml.deserialize(rfh.read())
  719. tests_known_hosts_file = str(root_dir / "salt_ssh_known_hosts")
  720. with salt.utils.files.fopen(tests_known_hosts_file, "w") as known_hosts:
  721. known_hosts.write("")
  722. config_defaults["root_dir"] = str(root_dir)
  723. config_defaults["known_hosts_file"] = tests_known_hosts_file
  724. config_defaults["syndic_master"] = "localhost"
  725. config_defaults["transport"] = salt_syndic_master_factory.config["transport"]
  726. config_defaults["reactor"] = [
  727. {"salt/test/reactor": [os.path.join(RUNTIME_VARS.FILES, "reactor-test.sls")]}
  728. ]
  729. config_overrides = {"log_level_logfile": "quiet"}
  730. ext_pillar = []
  731. if salt.utils.platform.is_windows():
  732. ext_pillar.append(
  733. {"cmd_yaml": "type {}".format(os.path.join(RUNTIME_VARS.FILES, "ext.yaml"))}
  734. )
  735. else:
  736. ext_pillar.append(
  737. {"cmd_yaml": "cat {}".format(os.path.join(RUNTIME_VARS.FILES, "ext.yaml"))}
  738. )
  739. ext_pillar.append(
  740. {
  741. "file_tree": {
  742. "root_dir": str(ext_pillar_file_tree_root_dir),
  743. "follow_dir_links": False,
  744. "keep_newline": True,
  745. }
  746. }
  747. )
  748. config_overrides["pillar_opts"] = True
  749. # We need to copy the extension modules into the new master root_dir or
  750. # it will be prefixed by it
  751. extension_modules_path = str(root_dir / "extension_modules")
  752. if not os.path.exists(extension_modules_path):
  753. shutil.copytree(
  754. os.path.join(RUNTIME_VARS.FILES, "extension_modules"),
  755. extension_modules_path,
  756. )
  757. # Copy the autosign_file to the new master root_dir
  758. autosign_file_path = str(root_dir / "autosign_file")
  759. shutil.copyfile(
  760. os.path.join(RUNTIME_VARS.FILES, "autosign_file"), autosign_file_path
  761. )
  762. # all read, only owner write
  763. autosign_file_permissions = (
  764. stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH | stat.S_IWUSR
  765. )
  766. os.chmod(autosign_file_path, autosign_file_permissions)
  767. config_overrides.update(
  768. {
  769. "ext_pillar": ext_pillar,
  770. "extension_modules": extension_modules_path,
  771. "file_roots": {
  772. "base": [
  773. str(base_env_state_tree_root_dir),
  774. os.path.join(RUNTIME_VARS.FILES, "file", "base"),
  775. ],
  776. # Alternate root to test __env__ choices
  777. "prod": [
  778. str(prod_env_state_tree_root_dir),
  779. os.path.join(RUNTIME_VARS.FILES, "file", "prod"),
  780. ],
  781. },
  782. "pillar_roots": {
  783. "base": [
  784. str(base_env_pillar_tree_root_dir),
  785. os.path.join(RUNTIME_VARS.FILES, "pillar", "base"),
  786. ],
  787. "prod": [str(prod_env_pillar_tree_root_dir)],
  788. },
  789. }
  790. )
  791. # Let's copy over the test cloud config files and directories into the running master config directory
  792. for entry in os.listdir(RUNTIME_VARS.CONF_DIR):
  793. if not entry.startswith("cloud"):
  794. continue
  795. source = os.path.join(RUNTIME_VARS.CONF_DIR, entry)
  796. dest = str(conf_dir / entry)
  797. if os.path.isdir(source):
  798. shutil.copytree(source, dest)
  799. else:
  800. shutil.copyfile(source, dest)
  801. factory = salt_syndic_master_factory.get_salt_master_daemon(
  802. "master",
  803. config_defaults=config_defaults,
  804. config_overrides=config_overrides,
  805. extra_cli_arguments_after_first_start_failure=["--log-level=debug"],
  806. )
  807. return factory
  808. @pytest.fixture(scope="session")
  809. def salt_minion_factory(salt_master_factory):
  810. with salt.utils.files.fopen(os.path.join(RUNTIME_VARS.CONF_DIR, "minion")) as rfh:
  811. config_defaults = yaml.deserialize(rfh.read())
  812. config_defaults["hosts.file"] = os.path.join(RUNTIME_VARS.TMP, "hosts")
  813. config_defaults["aliases.file"] = os.path.join(RUNTIME_VARS.TMP, "aliases")
  814. config_defaults["transport"] = salt_master_factory.config["transport"]
  815. config_overrides = {
  816. "log_level_logfile": "quiet",
  817. "file_roots": salt_master_factory.config["file_roots"].copy(),
  818. "pillar_roots": salt_master_factory.config["pillar_roots"].copy(),
  819. }
  820. virtualenv_binary = get_virtualenv_binary_path()
  821. if virtualenv_binary:
  822. config_overrides["venv_bin"] = virtualenv_binary
  823. factory = salt_master_factory.get_salt_minion_daemon(
  824. "minion",
  825. config_defaults=config_defaults,
  826. config_overrides=config_overrides,
  827. extra_cli_arguments_after_first_start_failure=["--log-level=debug"],
  828. )
  829. factory.register_after_terminate_callback(
  830. pytest.helpers.remove_stale_minion_key, salt_master_factory, factory.id
  831. )
  832. return factory
  833. @pytest.fixture(scope="session")
  834. def salt_sub_minion_factory(salt_master_factory):
  835. with salt.utils.files.fopen(
  836. os.path.join(RUNTIME_VARS.CONF_DIR, "sub_minion")
  837. ) as rfh:
  838. config_defaults = yaml.deserialize(rfh.read())
  839. config_defaults["hosts.file"] = os.path.join(RUNTIME_VARS.TMP, "hosts")
  840. config_defaults["aliases.file"] = os.path.join(RUNTIME_VARS.TMP, "aliases")
  841. config_defaults["transport"] = salt_master_factory.config["transport"]
  842. config_overrides = {
  843. "log_level_logfile": "quiet",
  844. "file_roots": salt_master_factory.config["file_roots"].copy(),
  845. "pillar_roots": salt_master_factory.config["pillar_roots"].copy(),
  846. }
  847. virtualenv_binary = get_virtualenv_binary_path()
  848. if virtualenv_binary:
  849. config_overrides["venv_bin"] = virtualenv_binary
  850. factory = salt_master_factory.get_salt_minion_daemon(
  851. "sub_minion",
  852. config_defaults=config_defaults,
  853. config_overrides=config_overrides,
  854. extra_cli_arguments_after_first_start_failure=["--log-level=debug"],
  855. )
  856. factory.register_after_terminate_callback(
  857. pytest.helpers.remove_stale_minion_key, salt_master_factory, factory.id
  858. )
  859. return factory
  860. @pytest.fixture(scope="session")
  861. def salt_proxy_factory(salt_factories, salt_master_factory):
  862. proxy_minion_id = "proxytest"
  863. root_dir = salt_factories.get_root_dir_for_daemon(proxy_minion_id)
  864. conf_dir = root_dir / "conf"
  865. conf_dir.mkdir(parents=True, exist_ok=True)
  866. RUNTIME_VARS.TMP_PROXY_CONF_DIR = str(conf_dir)
  867. with salt.utils.files.fopen(os.path.join(RUNTIME_VARS.CONF_DIR, "proxy")) as rfh:
  868. config_defaults = yaml.deserialize(rfh.read())
  869. config_defaults["root_dir"] = str(root_dir)
  870. config_defaults["hosts.file"] = os.path.join(RUNTIME_VARS.TMP, "hosts")
  871. config_defaults["aliases.file"] = os.path.join(RUNTIME_VARS.TMP, "aliases")
  872. config_defaults["transport"] = salt_master_factory.config["transport"]
  873. config_overrides = {"log_level_logfile": "quiet"}
  874. factory = salt_master_factory.get_salt_proxy_minion_daemon(
  875. proxy_minion_id,
  876. config_defaults=config_defaults,
  877. config_overrides=config_overrides,
  878. extra_cli_arguments_after_first_start_failure=["--log-level=debug"],
  879. )
  880. factory.register_after_terminate_callback(
  881. pytest.helpers.remove_stale_minion_key, salt_master_factory, factory.id
  882. )
  883. return factory
  884. @pytest.fixture(scope="session")
  885. def salt_cli(salt_master_factory):
  886. return salt_master_factory.get_salt_cli()
  887. @pytest.fixture(scope="session")
  888. def salt_cp_cli(salt_master_factory):
  889. return salt_master_factory.get_salt_cp_cli()
  890. @pytest.fixture(scope="session")
  891. def salt_key_cli(salt_master_factory):
  892. return salt_master_factory.get_salt_key_cli()
  893. @pytest.fixture(scope="session")
  894. def salt_run_cli(salt_master_factory):
  895. return salt_master_factory.get_salt_run_cli()
  896. @pytest.fixture(scope="session")
  897. def salt_call_cli(salt_minion_factory):
  898. return salt_minion_factory.get_salt_call_cli()
  899. @pytest.fixture(scope="session", autouse=True)
  900. def bridge_pytest_and_runtests(
  901. reap_stray_processes,
  902. salt_factories,
  903. salt_syndic_master_factory,
  904. salt_syndic_factory,
  905. salt_master_factory,
  906. salt_minion_factory,
  907. salt_sub_minion_factory,
  908. sshd_config_dir,
  909. ):
  910. # Make sure unittest2 uses the pytest generated configuration
  911. RUNTIME_VARS.RUNTIME_CONFIGS["master"] = freeze(salt_master_factory.config)
  912. RUNTIME_VARS.RUNTIME_CONFIGS["minion"] = freeze(salt_minion_factory.config)
  913. RUNTIME_VARS.RUNTIME_CONFIGS["sub_minion"] = freeze(salt_sub_minion_factory.config)
  914. RUNTIME_VARS.RUNTIME_CONFIGS["syndic_master"] = freeze(
  915. salt_syndic_master_factory.config
  916. )
  917. RUNTIME_VARS.RUNTIME_CONFIGS["syndic"] = freeze(salt_syndic_factory.config)
  918. RUNTIME_VARS.RUNTIME_CONFIGS["client_config"] = freeze(
  919. salt.config.client_config(salt_master_factory.config["conf_file"])
  920. )
  921. # Make sure unittest2 classes know their paths
  922. RUNTIME_VARS.TMP_ROOT_DIR = str(salt_factories.root_dir.resolve())
  923. RUNTIME_VARS.TMP_CONF_DIR = os.path.dirname(salt_master_factory.config["conf_file"])
  924. RUNTIME_VARS.TMP_MINION_CONF_DIR = os.path.dirname(
  925. salt_minion_factory.config["conf_file"]
  926. )
  927. RUNTIME_VARS.TMP_SUB_MINION_CONF_DIR = os.path.dirname(
  928. salt_sub_minion_factory.config["conf_file"]
  929. )
  930. RUNTIME_VARS.TMP_SYNDIC_MASTER_CONF_DIR = os.path.dirname(
  931. salt_syndic_master_factory.config["conf_file"]
  932. )
  933. RUNTIME_VARS.TMP_SYNDIC_MINION_CONF_DIR = os.path.dirname(
  934. salt_syndic_factory.config["conf_file"]
  935. )
  936. RUNTIME_VARS.TMP_SSH_CONF_DIR = str(sshd_config_dir)
  937. @pytest.fixture(scope="session")
  938. def sshd_config_dir(salt_factories):
  939. config_dir = salt_factories.get_root_dir_for_daemon("sshd")
  940. yield config_dir
  941. shutil.rmtree(str(config_dir), ignore_errors=True)
  942. @pytest.fixture(scope="module")
  943. def sshd_server(salt_factories, sshd_config_dir, salt_master):
  944. sshd_config_dict = {
  945. "Protocol": "2",
  946. # Turn strict modes off so that we can operate in /tmp
  947. "StrictModes": "no",
  948. # Logging
  949. "SyslogFacility": "AUTH",
  950. "LogLevel": "INFO",
  951. # Authentication:
  952. "LoginGraceTime": "120",
  953. "PermitRootLogin": "without-password",
  954. "PubkeyAuthentication": "yes",
  955. # Don't read the user's ~/.rhosts and ~/.shosts files
  956. "IgnoreRhosts": "yes",
  957. "HostbasedAuthentication": "no",
  958. # To enable empty passwords, change to yes (NOT RECOMMENDED)
  959. "PermitEmptyPasswords": "no",
  960. # Change to yes to enable challenge-response passwords (beware issues with
  961. # some PAM modules and threads)
  962. "ChallengeResponseAuthentication": "no",
  963. # Change to no to disable tunnelled clear text passwords
  964. "PasswordAuthentication": "no",
  965. "X11Forwarding": "no",
  966. "X11DisplayOffset": "10",
  967. "PrintMotd": "no",
  968. "PrintLastLog": "yes",
  969. "TCPKeepAlive": "yes",
  970. "AcceptEnv": "LANG LC_*",
  971. "Subsystem": "sftp /usr/lib/openssh/sftp-server",
  972. "UsePAM": "yes",
  973. }
  974. factory = salt_factories.get_sshd_daemon(
  975. sshd_config_dict=sshd_config_dict, config_dir=sshd_config_dir,
  976. )
  977. with factory.started():
  978. yield factory
  979. @pytest.fixture(scope="module")
  980. def salt_ssh_roster_file(sshd_server, salt_master):
  981. roster_contents = """
  982. localhost:
  983. host: 127.0.0.1
  984. port: {}
  985. user: {}
  986. mine_functions:
  987. test.arg: ['itworked']
  988. """.format(
  989. sshd_server.listen_port, RUNTIME_VARS.RUNNING_TESTS_USER
  990. )
  991. if salt.utils.platform.is_darwin():
  992. roster_contents += " set_path: $PATH:/usr/local/bin/\n"
  993. with pytest.helpers.temp_file(
  994. "roster", roster_contents, salt_master.config_dir
  995. ) as roster_file:
  996. yield roster_file
  997. # <---- Salt Factories -----------------------------------------------------------------------------------------------
  998. # ----- From Filenames Test Selection ------------------------------------------------------------------------------->
  999. def _match_to_test_file(match):
  1000. parts = match.split(".")
  1001. parts[-1] += ".py"
  1002. return TESTS_DIR.joinpath(*parts).relative_to(CODE_DIR)
  1003. def from_filenames_collection_modifyitems(config, items):
  1004. from_filenames = config.getoption("--from-filenames")
  1005. if not from_filenames:
  1006. # Don't do anything
  1007. return
  1008. test_categories_paths = (
  1009. (TESTS_DIR / "integration").relative_to(CODE_DIR),
  1010. (TESTS_DIR / "multimaster").relative_to(CODE_DIR),
  1011. (TESTS_DIR / "unit").relative_to(CODE_DIR),
  1012. (PYTESTS_DIR / "e2e").relative_to(CODE_DIR),
  1013. (PYTESTS_DIR / "functional").relative_to(CODE_DIR),
  1014. (PYTESTS_DIR / "integration").relative_to(CODE_DIR),
  1015. (PYTESTS_DIR / "unit").relative_to(CODE_DIR),
  1016. )
  1017. test_module_paths = set()
  1018. from_filenames_listing = set()
  1019. for path in [pathlib.Path(path.strip()) for path in from_filenames.split(",")]:
  1020. if path.is_absolute():
  1021. # In this case, this path is considered to be a file containing a line separated list
  1022. # of files to consider
  1023. with salt.utils.files.fopen(str(path)) as rfh:
  1024. for line in rfh:
  1025. line_path = pathlib.Path(line.strip())
  1026. if not line_path.exists():
  1027. continue
  1028. from_filenames_listing.add(line_path)
  1029. continue
  1030. from_filenames_listing.add(path)
  1031. filename_map = yaml.deserialize((TESTS_DIR / "filename_map.yml").read_text())
  1032. # Let's add the match all rule
  1033. for rule, matches in filename_map.items():
  1034. if rule == "*":
  1035. for match in matches:
  1036. test_module_paths.add(_match_to_test_file(match))
  1037. break
  1038. # Let's now go through the list of files gathered
  1039. for filename in from_filenames_listing:
  1040. if str(filename).startswith("tests/"):
  1041. # Tests in the listing don't require additional matching and will be added to the
  1042. # list of tests to run
  1043. test_module_paths.add(filename)
  1044. continue
  1045. if filename.name == "setup.py" or str(filename).startswith("salt/"):
  1046. if path.name == "__init__.py":
  1047. # No direct macthing
  1048. continue
  1049. # Now let's try a direct match between the passed file and possible test modules
  1050. for test_categories_path in test_categories_paths:
  1051. test_module_path = test_categories_path / "test_{}".format(path.name)
  1052. if test_module_path.is_file():
  1053. test_module_paths.add(test_module_path)
  1054. continue
  1055. # Do we have an entry in tests/filename_map.yml
  1056. for rule, matches in filename_map.items():
  1057. if rule == "*":
  1058. continue
  1059. elif "|" in rule:
  1060. # This is regex
  1061. if re.match(rule, str(filename)):
  1062. for match in matches:
  1063. test_module_paths.add(_match_to_test_file(match))
  1064. elif "*" in rule or "\\" in rule:
  1065. # Glob matching
  1066. for filerule in CODE_DIR.glob(rule):
  1067. if not filerule.exists():
  1068. continue
  1069. filerule = filerule.relative_to(CODE_DIR)
  1070. if filerule != filename:
  1071. continue
  1072. for match in matches:
  1073. test_module_paths.add(_match_to_test_file(match))
  1074. else:
  1075. if str(filename) != rule:
  1076. continue
  1077. # Direct file paths as rules
  1078. filerule = pathlib.Path(rule)
  1079. if not filerule.exists():
  1080. continue
  1081. for match in matches:
  1082. test_module_paths.add(_match_to_test_file(match))
  1083. continue
  1084. else:
  1085. log.debug("Don't know what to do with path %s", filename)
  1086. selected = []
  1087. deselected = []
  1088. for item in items:
  1089. itempath = pathlib.Path(str(item.fspath)).resolve().relative_to(CODE_DIR)
  1090. if itempath in test_module_paths:
  1091. selected.append(item)
  1092. else:
  1093. deselected.append(item)
  1094. items[:] = selected
  1095. if deselected:
  1096. config.hook.pytest_deselected(items=deselected)
  1097. # <---- From Filenames Test Selection --------------------------------------------------------------------------------
  1098. # ----- Custom Fixtures --------------------------------------------------------------------------------------------->
  1099. @pytest.fixture(scope="session")
  1100. def reap_stray_processes():
  1101. # Run tests
  1102. yield
  1103. children = psutil.Process(os.getpid()).children(recursive=True)
  1104. if not children:
  1105. log.info("No astray processes found")
  1106. return
  1107. def on_terminate(proc):
  1108. log.debug("Process %s terminated with exit code %s", proc, proc.returncode)
  1109. if children:
  1110. # Reverse the order, sublings first, parents after
  1111. children.reverse()
  1112. log.warning(
  1113. "Test suite left %d astray processes running. Killing those processes:\n%s",
  1114. len(children),
  1115. pprint.pformat(children),
  1116. )
  1117. _, alive = psutil.wait_procs(children, timeout=3, callback=on_terminate)
  1118. for child in alive:
  1119. try:
  1120. child.kill()
  1121. except psutil.NoSuchProcess:
  1122. continue
  1123. _, alive = psutil.wait_procs(alive, timeout=3, callback=on_terminate)
  1124. if alive:
  1125. # Give up
  1126. for child in alive:
  1127. log.warning(
  1128. "Process %s survived SIGKILL, giving up:\n%s",
  1129. child,
  1130. pprint.pformat(child.as_dict()),
  1131. )
  1132. @pytest.fixture(scope="session")
  1133. def sminion():
  1134. return create_sminion()
  1135. @pytest.fixture(scope="session")
  1136. def grains(sminion):
  1137. return sminion.opts["grains"].copy()
  1138. @pytest.fixture
  1139. def ssl_webserver(integration_files_dir, scope="module"):
  1140. """
  1141. spins up an https webserver.
  1142. """
  1143. if sys.version_info < (3, 5, 3):
  1144. pytest.skip("Python versions older than 3.5.3 do not define `ssl.PROTOCOL_TLS`")
  1145. context = ssl.SSLContext(ssl.PROTOCOL_TLS)
  1146. context.load_cert_chain(
  1147. str(integration_files_dir / "https" / "cert.pem"),
  1148. str(integration_files_dir / "https" / "key.pem"),
  1149. )
  1150. webserver = Webserver(root=str(integration_files_dir), ssl_opts=context)
  1151. webserver.start()
  1152. yield webserver
  1153. webserver.stop()
  1154. # <---- Custom Fixtures ----------------------------------------------------------------------------------------------