1
0

noxfile.py 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896
  1. # -*- coding: utf-8 -*-
  2. """
  3. noxfile
  4. ~~~~~~~
  5. Nox configuration script
  6. """
  7. # pylint: disable=resource-leakage,3rd-party-module-not-gated
  8. from __future__ import absolute_import, print_function, unicode_literals
  9. import datetime
  10. import glob
  11. import json
  12. import os
  13. import pprint
  14. import shutil
  15. import sys
  16. import tempfile
  17. # fmt: off
  18. if __name__ == "__main__":
  19. sys.stderr.write(
  20. "Do not execute this file directly. Use nox instead, it will know how to handle this file\n"
  21. )
  22. sys.stderr.flush()
  23. exit(1)
  24. # fmt: on
  25. import nox # isort:skip
  26. from nox.command import CommandFailed # isort:skip
  27. IS_PY3 = sys.version_info > (2,)
  28. # Be verbose when runing under a CI context
  29. PIP_INSTALL_SILENT = (
  30. os.environ.get("JENKINS_URL") or os.environ.get("CI") or os.environ.get("DRONE")
  31. ) is None
  32. # Global Path Definitions
  33. REPO_ROOT = os.path.abspath(os.path.dirname(__file__))
  34. SITECUSTOMIZE_DIR = os.path.join(REPO_ROOT, "tests", "support", "coverage")
  35. IS_DARWIN = sys.platform.lower().startswith("darwin")
  36. IS_WINDOWS = sys.platform.lower().startswith("win")
  37. # Python versions to run against
  38. _PYTHON_VERSIONS = ("2", "2.7", "3", "3.4", "3.5", "3.6", "3.7", "3.8", "3.9")
  39. # Nox options
  40. # Reuse existing virtualenvs
  41. nox.options.reuse_existing_virtualenvs = True
  42. # Don't fail on missing interpreters
  43. nox.options.error_on_missing_interpreters = False
  44. # Change current directory to REPO_ROOT
  45. os.chdir(REPO_ROOT)
  46. RUNTESTS_LOGFILE = os.path.join(
  47. "artifacts",
  48. "logs",
  49. "runtests-{}.log".format(datetime.datetime.now().strftime("%Y%m%d%H%M%S.%f")),
  50. )
  51. # Prevent Python from writing bytecode
  52. os.environ[str("PYTHONDONTWRITEBYTECODE")] = str("1")
  53. def _create_ci_directories():
  54. for dirname in ("logs", "coverage", "xml-unittests-output"):
  55. path = os.path.join("artifacts", dirname)
  56. if not os.path.exists(path):
  57. os.makedirs(path)
  58. def _get_session_python_version_info(session):
  59. try:
  60. version_info = session._runner._real_python_version_info
  61. except AttributeError:
  62. old_install_only_value = session._runner.global_config.install_only
  63. try:
  64. # Force install only to be false for the following chunk of code
  65. # For additional information as to why see:
  66. # https://github.com/theacodes/nox/pull/181
  67. session._runner.global_config.install_only = False
  68. session_py_version = session.run(
  69. "python",
  70. "-c"
  71. 'import sys; sys.stdout.write("{}.{}.{}".format(*sys.version_info))',
  72. silent=True,
  73. log=False,
  74. )
  75. version_info = tuple(
  76. int(part) for part in session_py_version.split(".") if part.isdigit()
  77. )
  78. session._runner._real_python_version_info = version_info
  79. finally:
  80. session._runner.global_config.install_only = old_install_only_value
  81. return version_info
  82. def _get_session_python_site_packages_dir(session):
  83. try:
  84. site_packages_dir = session._runner._site_packages_dir
  85. except AttributeError:
  86. old_install_only_value = session._runner.global_config.install_only
  87. try:
  88. # Force install only to be false for the following chunk of code
  89. # For additional information as to why see:
  90. # https://github.com/theacodes/nox/pull/181
  91. session._runner.global_config.install_only = False
  92. site_packages_dir = session.run(
  93. "python",
  94. "-c"
  95. "import sys; from distutils.sysconfig import get_python_lib; sys.stdout.write(get_python_lib())",
  96. silent=True,
  97. log=False,
  98. )
  99. session._runner._site_packages_dir = site_packages_dir
  100. finally:
  101. session._runner.global_config.install_only = old_install_only_value
  102. return site_packages_dir
  103. def _get_pydir(session):
  104. version_info = _get_session_python_version_info(session)
  105. if version_info < (2, 7):
  106. session.error("Only Python >= 2.7 is supported")
  107. return "py{}.{}".format(*version_info)
  108. def _get_distro_info(session):
  109. try:
  110. distro = session._runner._distro
  111. except AttributeError:
  112. # The distro package doesn't output anything for Windows
  113. old_install_only_value = session._runner.global_config.install_only
  114. try:
  115. # Force install only to be false for the following chunk of code
  116. # For additional information as to why see:
  117. # https://github.com/theacodes/nox/pull/181
  118. session._runner.global_config.install_only = False
  119. session.install("--progress-bar=off", "distro", silent=PIP_INSTALL_SILENT)
  120. output = session.run("distro", "-j", silent=True)
  121. distro = json.loads(output.strip())
  122. session.log("Distro information:\n%s", pprint.pformat(distro))
  123. session._runner._distro = distro
  124. finally:
  125. session._runner.global_config.install_only = old_install_only_value
  126. return distro
  127. def _install_system_packages(session):
  128. """
  129. Because some python packages are provided by the distribution and cannot
  130. be pip installed, and because we don't want the whole system python packages
  131. on our virtualenvs, we copy the required system python packages into
  132. the virtualenv
  133. """
  134. system_python_packages = {
  135. "__debian_based_distros__": ["/usr/lib/python{py_version}/dist-packages/*apt*"]
  136. }
  137. distro = _get_distro_info(session)
  138. if not distro["id"].startswith(("debian", "ubuntu")):
  139. # This only applies to debian based distributions
  140. return
  141. system_python_packages["{id}-{version}".format(**distro)] = system_python_packages[
  142. "{id}-{version_parts[major]}".format(**distro)
  143. ] = system_python_packages["__debian_based_distros__"][:]
  144. distro_keys = [
  145. "{id}".format(**distro),
  146. "{id}-{version}".format(**distro),
  147. "{id}-{version_parts[major]}".format(**distro),
  148. ]
  149. version_info = _get_session_python_version_info(session)
  150. py_version_keys = ["{}".format(*version_info), "{}.{}".format(*version_info)]
  151. session_site_packages_dir = _get_session_python_site_packages_dir(session)
  152. for distro_key in distro_keys:
  153. if distro_key not in system_python_packages:
  154. continue
  155. patterns = system_python_packages[distro_key]
  156. for pattern in patterns:
  157. for py_version in py_version_keys:
  158. matches = set(glob.glob(pattern.format(py_version=py_version)))
  159. if not matches:
  160. continue
  161. for match in matches:
  162. src = os.path.realpath(match)
  163. dst = os.path.join(
  164. session_site_packages_dir, os.path.basename(match)
  165. )
  166. if os.path.exists(dst):
  167. session.log(
  168. "Not overwritting already existing %s with %s", dst, src
  169. )
  170. continue
  171. session.log("Copying %s into %s", src, dst)
  172. if os.path.isdir(src):
  173. shutil.copytree(src, dst)
  174. else:
  175. shutil.copyfile(src, dst)
  176. def _get_distro_pip_constraints(session, transport):
  177. # Install requirements
  178. distro_constraints = []
  179. if transport == "tcp":
  180. # The TCP requirements are the exact same requirements as the ZeroMQ ones
  181. transport = "zeromq"
  182. pydir = _get_pydir(session)
  183. if IS_WINDOWS:
  184. _distro_constraints = os.path.join(
  185. "requirements", "static", pydir, "{}-windows.txt".format(transport)
  186. )
  187. if os.path.exists(_distro_constraints):
  188. distro_constraints.append(_distro_constraints)
  189. _distro_constraints = os.path.join(
  190. "requirements", "static", pydir, "windows.txt"
  191. )
  192. if os.path.exists(_distro_constraints):
  193. distro_constraints.append(_distro_constraints)
  194. _distro_constraints = os.path.join(
  195. "requirements", "static", pydir, "windows-crypto.txt"
  196. )
  197. if os.path.exists(_distro_constraints):
  198. distro_constraints.append(_distro_constraints)
  199. elif IS_DARWIN:
  200. _distro_constraints = os.path.join(
  201. "requirements", "static", pydir, "{}-darwin.txt".format(transport)
  202. )
  203. if os.path.exists(_distro_constraints):
  204. distro_constraints.append(_distro_constraints)
  205. _distro_constraints = os.path.join(
  206. "requirements", "static", pydir, "darwin.txt"
  207. )
  208. if os.path.exists(_distro_constraints):
  209. distro_constraints.append(_distro_constraints)
  210. _distro_constraints = os.path.join(
  211. "requirements", "static", pydir, "darwin-crypto.txt"
  212. )
  213. if os.path.exists(_distro_constraints):
  214. distro_constraints.append(_distro_constraints)
  215. else:
  216. _install_system_packages(session)
  217. distro = _get_distro_info(session)
  218. distro_keys = [
  219. "linux",
  220. "{id}".format(**distro),
  221. "{id}-{version}".format(**distro),
  222. "{id}-{version_parts[major]}".format(**distro),
  223. ]
  224. for distro_key in distro_keys:
  225. _distro_constraints = os.path.join(
  226. "requirements", "static", pydir, "{}.txt".format(distro_key)
  227. )
  228. if os.path.exists(_distro_constraints):
  229. distro_constraints.append(_distro_constraints)
  230. _distro_constraints = os.path.join(
  231. "requirements", "static", pydir, "{}-crypto.txt".format(distro_key)
  232. )
  233. if os.path.exists(_distro_constraints):
  234. distro_constraints.append(_distro_constraints)
  235. _distro_constraints = os.path.join(
  236. "requirements",
  237. "static",
  238. pydir,
  239. "{}-{}.txt".format(transport, distro_key),
  240. )
  241. if os.path.exists(_distro_constraints):
  242. distro_constraints.append(_distro_constraints)
  243. distro_constraints.append(_distro_constraints)
  244. _distro_constraints = os.path.join(
  245. "requirements",
  246. "static",
  247. pydir,
  248. "{}-{}-crypto.txt".format(transport, distro_key),
  249. )
  250. if os.path.exists(_distro_constraints):
  251. distro_constraints.append(_distro_constraints)
  252. return distro_constraints
  253. def _install_requirements(session, transport, *extra_requirements):
  254. # Install requirements
  255. distro_constraints = _get_distro_pip_constraints(session, transport)
  256. _requirements_files = [
  257. os.path.join("requirements", "base.txt"),
  258. os.path.join("requirements", "zeromq.txt"),
  259. os.path.join("requirements", "pytest.txt"),
  260. ]
  261. if sys.platform.startswith("linux"):
  262. requirements_files = [os.path.join("requirements", "static", "linux.in")]
  263. elif sys.platform.startswith("win"):
  264. requirements_files = [
  265. os.path.join("pkg", "windows", "req.txt"),
  266. os.path.join("requirements", "static", "windows.in"),
  267. ]
  268. elif sys.platform.startswith("darwin"):
  269. requirements_files = [
  270. os.path.join("pkg", "osx", "req.txt"),
  271. os.path.join("pkg", "osx", "req_ext.txt"),
  272. os.path.join("pkg", "osx", "req_pyobjc.txt"),
  273. os.path.join("requirements", "static", "darwin.in"),
  274. ]
  275. while True:
  276. if not requirements_files:
  277. break
  278. requirements_file = requirements_files.pop(0)
  279. if requirements_file not in _requirements_files:
  280. _requirements_files.append(requirements_file)
  281. session.log("Processing {}".format(requirements_file))
  282. with open(requirements_file) as rfh: # pylint: disable=resource-leakage
  283. for line in rfh:
  284. line = line.strip()
  285. if not line:
  286. continue
  287. if line.startswith("-r"):
  288. reqfile = os.path.join(
  289. os.path.dirname(requirements_file), line.strip().split()[-1]
  290. )
  291. if reqfile in _requirements_files:
  292. continue
  293. _requirements_files.append(reqfile)
  294. continue
  295. for requirements_file in _requirements_files:
  296. install_command = ["--progress-bar=off", "-r", requirements_file]
  297. for distro_constraint in distro_constraints:
  298. install_command.extend(["--constraint", distro_constraint])
  299. session.install(*install_command, silent=PIP_INSTALL_SILENT)
  300. if extra_requirements:
  301. install_command = [
  302. "--progress-bar=off",
  303. ]
  304. for distro_constraint in distro_constraints:
  305. install_command.extend(["--constraint", distro_constraint])
  306. install_command += list(extra_requirements)
  307. session.install(*install_command, silent=PIP_INSTALL_SILENT)
  308. def _run_with_coverage(session, *test_cmd):
  309. session.install("--progress-bar=off", "coverage==5.0.1", silent=PIP_INSTALL_SILENT)
  310. session.run("coverage", "erase")
  311. python_path_env_var = os.environ.get("PYTHONPATH") or None
  312. if python_path_env_var is None:
  313. python_path_env_var = SITECUSTOMIZE_DIR
  314. else:
  315. python_path_entries = python_path_env_var.split(os.pathsep)
  316. if SITECUSTOMIZE_DIR in python_path_entries:
  317. python_path_entries.remove(SITECUSTOMIZE_DIR)
  318. python_path_entries.insert(0, SITECUSTOMIZE_DIR)
  319. python_path_env_var = os.pathsep.join(python_path_entries)
  320. env = {
  321. # The updated python path so that sitecustomize is importable
  322. "PYTHONPATH": python_path_env_var,
  323. # The full path to the .coverage data file. Makes sure we always write
  324. # them to the same directory
  325. "COVERAGE_FILE": os.path.abspath(os.path.join(REPO_ROOT, ".coverage")),
  326. # Instruct sub processes to also run under coverage
  327. "COVERAGE_PROCESS_START": os.path.join(REPO_ROOT, ".coveragerc"),
  328. }
  329. if IS_DARWIN:
  330. # Don't nuke our multiprocessing efforts objc!
  331. # https://stackoverflow.com/questions/50168647/multiprocessing-causes-python-to-crash-and-gives-an-error-may-have-been-in-progr
  332. env["OBJC_DISABLE_INITIALIZE_FORK_SAFETY"] = "YES"
  333. try:
  334. session.run(*test_cmd, env=env)
  335. finally:
  336. # Always combine and generate the XML coverage report
  337. try:
  338. session.run("coverage", "combine")
  339. except CommandFailed:
  340. # Sometimes some of the coverage files are corrupt which would trigger a CommandFailed
  341. # exception
  342. pass
  343. # Generate report for salt code coverage
  344. session.run(
  345. "coverage",
  346. "xml",
  347. "-o",
  348. os.path.join("artifacts", "coverage", "salt.xml"),
  349. "--omit=tests/*",
  350. "--include=salt/*",
  351. )
  352. # Generate report for tests code coverage
  353. session.run(
  354. "coverage",
  355. "xml",
  356. "-o",
  357. os.path.join("artifacts", "coverage", "tests.xml"),
  358. "--omit=salt/*",
  359. "--include=tests/*",
  360. )
  361. @nox.session(python=_PYTHON_VERSIONS, name="pytest-parametrized")
  362. @nox.parametrize("coverage", [False, True])
  363. @nox.parametrize("transport", ["zeromq", "tcp"])
  364. @nox.parametrize("crypto", [None, "m2crypto", "pycryptodomex"])
  365. def pytest_parametrized(session, coverage, transport, crypto):
  366. # Install requirements
  367. _install_requirements(session, transport)
  368. if crypto:
  369. if crypto == "m2crypto":
  370. session.run(
  371. "pip",
  372. "uninstall",
  373. "-y",
  374. "pycrypto",
  375. "pycryptodome",
  376. "pycryptodomex",
  377. silent=True,
  378. )
  379. else:
  380. session.run("pip", "uninstall", "-y", "m2crypto", silent=True)
  381. distro_constraints = _get_distro_pip_constraints(session, transport)
  382. install_command = [
  383. "--progress-bar=off",
  384. ]
  385. for distro_constraint in distro_constraints:
  386. install_command.extend(["--constraint", distro_constraint])
  387. install_command.append(crypto)
  388. session.install(*install_command, silent=PIP_INSTALL_SILENT)
  389. cmd_args = [
  390. "--rootdir",
  391. REPO_ROOT,
  392. "--log-file={}".format(RUNTESTS_LOGFILE),
  393. "--log-file-level=debug",
  394. "--no-print-logs",
  395. "-ra",
  396. "-s",
  397. "--transport={}".format(transport),
  398. ] + session.posargs
  399. _pytest(session, coverage, cmd_args)
  400. @nox.session(python=_PYTHON_VERSIONS)
  401. @nox.parametrize("coverage", [False, True])
  402. def pytest(session, coverage):
  403. """
  404. pytest session with zeromq transport and default crypto
  405. """
  406. session.notify(
  407. "pytest-parametrized-{}(coverage={}, crypto=None, transport='zeromq')".format(
  408. session.python, coverage
  409. )
  410. )
  411. @nox.session(python=_PYTHON_VERSIONS, name="pytest-tcp")
  412. @nox.parametrize("coverage", [False, True])
  413. def pytest_tcp(session, coverage):
  414. """
  415. pytest session with TCP transport and default crypto
  416. """
  417. session.notify(
  418. "pytest-parametrized-{}(coverage={}, crypto=None, transport='tcp')".format(
  419. session.python, coverage
  420. )
  421. )
  422. @nox.session(python=_PYTHON_VERSIONS, name="pytest-zeromq")
  423. @nox.parametrize("coverage", [False, True])
  424. def pytest_zeromq(session, coverage):
  425. """
  426. pytest session with zeromq transport and default crypto
  427. """
  428. session.notify(
  429. "pytest-parametrized-{}(coverage={}, crypto=None, transport='zeromq')".format(
  430. session.python, coverage
  431. )
  432. )
  433. @nox.session(python=_PYTHON_VERSIONS, name="pytest-m2crypto")
  434. @nox.parametrize("coverage", [False, True])
  435. def pytest_m2crypto(session, coverage):
  436. """
  437. pytest session with zeromq transport and m2crypto
  438. """
  439. session.notify(
  440. "pytest-parametrized-{}(coverage={}, crypto='m2crypto', transport='zeromq')".format(
  441. session.python, coverage
  442. )
  443. )
  444. @nox.session(python=_PYTHON_VERSIONS, name="pytest-tcp-m2crypto")
  445. @nox.parametrize("coverage", [False, True])
  446. def pytest_tcp_m2crypto(session, coverage):
  447. """
  448. pytest session with TCP transport and m2crypto
  449. """
  450. session.notify(
  451. "pytest-parametrized-{}(coverage={}, crypto='m2crypto', transport='tcp')".format(
  452. session.python, coverage
  453. )
  454. )
  455. @nox.session(python=_PYTHON_VERSIONS, name="pytest-zeromq-m2crypto")
  456. @nox.parametrize("coverage", [False, True])
  457. def pytest_zeromq_m2crypto(session, coverage):
  458. """
  459. pytest session with zeromq transport and m2crypto
  460. """
  461. session.notify(
  462. "pytest-parametrized-{}(coverage={}, crypto='m2crypto', transport='zeromq')".format(
  463. session.python, coverage
  464. )
  465. )
  466. @nox.session(python=_PYTHON_VERSIONS, name="pytest-pycryptodomex")
  467. @nox.parametrize("coverage", [False, True])
  468. def pytest_pycryptodomex(session, coverage):
  469. """
  470. pytest session with zeromq transport and pycryptodomex
  471. """
  472. session.notify(
  473. "pytest-parametrized-{}(coverage={}, crypto='pycryptodomex', transport='zeromq')".format(
  474. session.python, coverage
  475. )
  476. )
  477. @nox.session(python=_PYTHON_VERSIONS, name="pytest-tcp-pycryptodomex")
  478. @nox.parametrize("coverage", [False, True])
  479. def pytest_tcp_pycryptodomex(session, coverage):
  480. """
  481. pytest session with TCP transport and pycryptodomex
  482. """
  483. session.notify(
  484. "pytest-parametrized-{}(coverage={}, crypto='pycryptodomex', transport='tcp')".format(
  485. session.python, coverage
  486. )
  487. )
  488. @nox.session(python=_PYTHON_VERSIONS, name="pytest-zeromq-pycryptodomex")
  489. @nox.parametrize("coverage", [False, True])
  490. def pytest_zeromq_pycryptodomex(session, coverage):
  491. """
  492. pytest session with zeromq transport and pycryptodomex
  493. """
  494. session.notify(
  495. "pytest-parametrized-{}(coverage={}, crypto='pycryptodomex', transport='zeromq')".format(
  496. session.python, coverage
  497. )
  498. )
  499. @nox.session(python=_PYTHON_VERSIONS, name="pytest-cloud")
  500. @nox.parametrize("coverage", [False, True])
  501. def pytest_cloud(session, coverage):
  502. # Install requirements
  503. _install_requirements(session, "zeromq")
  504. pydir = _get_pydir(session)
  505. cloud_requirements = os.path.join("requirements", "static", pydir, "cloud.txt")
  506. session.install(
  507. "--progress-bar=off", "-r", cloud_requirements, silent=PIP_INSTALL_SILENT
  508. )
  509. cmd_args = [
  510. "--rootdir",
  511. REPO_ROOT,
  512. "--log-file={}".format(RUNTESTS_LOGFILE),
  513. "--log-file-level=debug",
  514. "--no-print-logs",
  515. "-ra",
  516. "-s",
  517. "--run-expensive",
  518. "-k",
  519. "cloud",
  520. ] + session.posargs
  521. _pytest(session, coverage, cmd_args)
  522. @nox.session(python=_PYTHON_VERSIONS, name="pytest-tornado")
  523. @nox.parametrize("coverage", [False, True])
  524. def pytest_tornado(session, coverage):
  525. # Install requirements
  526. _install_requirements(session, "zeromq")
  527. session.install("--progress-bar=off", "tornado==5.0.2", silent=PIP_INSTALL_SILENT)
  528. session.install("--progress-bar=off", "pyzmq==17.0.0", silent=PIP_INSTALL_SILENT)
  529. cmd_args = [
  530. "--rootdir",
  531. REPO_ROOT,
  532. "--log-file={}".format(RUNTESTS_LOGFILE),
  533. "--log-file-level=debug",
  534. "--no-print-logs",
  535. "-ra",
  536. "-s",
  537. ] + session.posargs
  538. _pytest(session, coverage, cmd_args)
  539. def _pytest(session, coverage, cmd_args):
  540. # Create required artifacts directories
  541. _create_ci_directories()
  542. env = None
  543. if IS_DARWIN:
  544. # Don't nuke our multiprocessing efforts objc!
  545. # https://stackoverflow.com/questions/50168647/multiprocessing-causes-python-to-crash-and-gives-an-error-may-have-been-in-progr
  546. env = {"OBJC_DISABLE_INITIALIZE_FORK_SAFETY": "YES"}
  547. session.run(
  548. "python", "-m", "pytest", *(cmd_args + ["--collect-only", "-qq"]), env=env
  549. )
  550. try:
  551. if coverage is True:
  552. _run_with_coverage(
  553. session, "python", "-m", "coverage", "run", "-m", "pytest", *cmd_args
  554. )
  555. else:
  556. session.run("python", "-m", "pytest", *cmd_args, env=env)
  557. except CommandFailed: # pylint: disable=try-except-raise
  558. # Not rerunning failed tests for now
  559. raise
  560. # pylint: disable=unreachable
  561. # Re-run failed tests
  562. session.log("Re-running failed tests")
  563. for idx, parg in enumerate(cmd_args):
  564. if parg.startswith("--junitxml="):
  565. cmd_args[idx] = parg.replace(".xml", "-rerun-failed.xml")
  566. cmd_args.append("--lf")
  567. if coverage is True:
  568. _run_with_coverage(
  569. session, "python", "-m", "coverage", "run", "-m", "pytest", *cmd_args
  570. )
  571. else:
  572. session.run("pytest", *cmd_args, env=env)
  573. # pylint: enable=unreachable
  574. class Tee:
  575. """
  576. Python class to mimic linux tee behaviour
  577. """
  578. def __init__(self, first, second):
  579. self._first = first
  580. self._second = second
  581. def write(self, b):
  582. wrote = self._first.write(b)
  583. self._first.flush()
  584. self._second.write(b)
  585. self._second.flush()
  586. def fileno(self):
  587. return self._first.fileno()
  588. def _lint(session, rcfile, flags, paths, tee_output=True):
  589. _install_requirements(session, "zeromq")
  590. requirements_file = "requirements/static/lint.in"
  591. distro_constraints = ["requirements/static/{}/lint.txt".format(_get_pydir(session))]
  592. install_command = ["--progress-bar=off", "-r", requirements_file]
  593. for distro_constraint in distro_constraints:
  594. install_command.extend(["--constraint", distro_constraint])
  595. session.install(*install_command, silent=PIP_INSTALL_SILENT)
  596. if tee_output:
  597. session.run("pylint", "--version")
  598. pylint_report_path = os.environ.get("PYLINT_REPORT")
  599. cmd_args = ["pylint", "--rcfile={}".format(rcfile)] + list(flags) + list(paths)
  600. cmd_kwargs = {"env": {"PYTHONUNBUFFERED": "1"}}
  601. if tee_output:
  602. stdout = tempfile.TemporaryFile(mode="w+b")
  603. cmd_kwargs["stdout"] = Tee(stdout, sys.__stdout__)
  604. lint_failed = False
  605. try:
  606. session.run(*cmd_args, **cmd_kwargs)
  607. except CommandFailed:
  608. lint_failed = True
  609. raise
  610. finally:
  611. if tee_output:
  612. stdout.seek(0)
  613. contents = stdout.read()
  614. if contents:
  615. if IS_PY3:
  616. contents = contents.decode("utf-8")
  617. else:
  618. contents = contents.encode("utf-8")
  619. sys.stdout.write(contents)
  620. sys.stdout.flush()
  621. if pylint_report_path:
  622. # Write report
  623. with open(pylint_report_path, "w") as wfh:
  624. wfh.write(contents)
  625. session.log("Report file written to %r", pylint_report_path)
  626. stdout.close()
  627. def _lint_pre_commit(session, rcfile, flags, paths):
  628. if "VIRTUAL_ENV" not in os.environ:
  629. session.error(
  630. "This should be running from within a virtualenv and "
  631. "'VIRTUAL_ENV' was not found as an environment variable."
  632. )
  633. if "pre-commit" not in os.environ["VIRTUAL_ENV"]:
  634. session.error(
  635. "This should be running from within a pre-commit virtualenv and "
  636. "'VIRTUAL_ENV'({}) does not appear to be a pre-commit virtualenv.".format(
  637. os.environ["VIRTUAL_ENV"]
  638. )
  639. )
  640. from nox.virtualenv import VirtualEnv
  641. # Let's patch nox to make it run inside the pre-commit virtualenv
  642. try:
  643. session._runner.venv = VirtualEnv( # pylint: disable=unexpected-keyword-arg
  644. os.environ["VIRTUAL_ENV"],
  645. interpreter=session._runner.func.python,
  646. reuse_existing=True,
  647. venv=True,
  648. )
  649. except TypeError:
  650. # This is still nox-py2
  651. session._runner.venv = VirtualEnv(
  652. os.environ["VIRTUAL_ENV"],
  653. interpreter=session._runner.func.python,
  654. reuse_existing=True,
  655. )
  656. _lint(session, rcfile, flags, paths, tee_output=False)
  657. @nox.session(python="3")
  658. def lint(session):
  659. """
  660. Run PyLint against Salt and it's test suite. Set PYLINT_REPORT to a path to capture output.
  661. """
  662. session.notify("lint-salt-{}".format(session.python))
  663. session.notify("lint-tests-{}".format(session.python))
  664. @nox.session(python="3", name="lint-salt")
  665. def lint_salt(session):
  666. """
  667. Run PyLint against Salt. Set PYLINT_REPORT to a path to capture output.
  668. """
  669. flags = ["--disable=I"]
  670. if session.posargs:
  671. paths = session.posargs
  672. else:
  673. paths = ["setup.py", "noxfile.py", "salt/"]
  674. _lint(session, ".pylintrc", flags, paths)
  675. @nox.session(python="3", name="lint-tests")
  676. def lint_tests(session):
  677. """
  678. Run PyLint against Salt and it's test suite. Set PYLINT_REPORT to a path to capture output.
  679. """
  680. flags = ["--disable=I"]
  681. if session.posargs:
  682. paths = session.posargs
  683. else:
  684. paths = ["tests/"]
  685. _lint(session, ".pylintrc", flags, paths)
  686. @nox.session(python=False, name="lint-salt-pre-commit")
  687. def lint_salt_pre_commit(session):
  688. """
  689. Run PyLint against Salt. Set PYLINT_REPORT to a path to capture output.
  690. """
  691. flags = ["--disable=I"]
  692. if session.posargs:
  693. paths = session.posargs
  694. else:
  695. paths = ["setup.py", "noxfile.py", "salt/"]
  696. _lint_pre_commit(session, ".pylintrc", flags, paths)
  697. @nox.session(python=False, name="lint-tests-pre-commit")
  698. def lint_tests_pre_commit(session):
  699. """
  700. Run PyLint against Salt and it's test suite. Set PYLINT_REPORT to a path to capture output.
  701. """
  702. flags = ["--disable=I"]
  703. if session.posargs:
  704. paths = session.posargs
  705. else:
  706. paths = ["tests/"]
  707. _lint_pre_commit(session, ".pylintrc", flags, paths)
  708. @nox.session(python="3")
  709. @nox.parametrize("update", [False, True])
  710. @nox.parametrize("compress", [False, True])
  711. def docs(session, compress, update):
  712. """
  713. Build Salt's Documentation
  714. """
  715. session.notify("docs-html(compress={})".format(compress))
  716. session.notify("docs-man(compress={}, update={})".format(compress, update))
  717. @nox.session(name="docs-html", python="3")
  718. @nox.parametrize("compress", [False, True])
  719. def docs_html(session, compress):
  720. """
  721. Build Salt's HTML Documentation
  722. """
  723. pydir = _get_pydir(session)
  724. if pydir == "py3.4":
  725. session.error("Sphinx only runs on Python >= 3.5")
  726. requirements_file = "requirements/static/docs.in"
  727. distro_constraints = ["requirements/static/{}/docs.txt".format(pydir)]
  728. install_command = ["--progress-bar=off", "-r", requirements_file]
  729. for distro_constraint in distro_constraints:
  730. install_command.extend(["--constraint", distro_constraint])
  731. session.install(*install_command, silent=PIP_INSTALL_SILENT)
  732. os.chdir("doc/")
  733. session.run("make", "clean", external=True)
  734. session.run("make", "html", "SPHINXOPTS=-W", external=True)
  735. if compress:
  736. session.run("tar", "-cJvf", "html-archive.tar.xz", "_build/html", external=True)
  737. os.chdir("..")
  738. @nox.session(name="docs-man", python="3")
  739. @nox.parametrize("update", [False, True])
  740. @nox.parametrize("compress", [False, True])
  741. def docs_man(session, compress, update):
  742. """
  743. Build Salt's Manpages Documentation
  744. """
  745. pydir = _get_pydir(session)
  746. if pydir == "py3.4":
  747. session.error("Sphinx only runs on Python >= 3.5")
  748. requirements_file = "requirements/static/docs.in"
  749. distro_constraints = ["requirements/static/{}/docs.txt".format(pydir)]
  750. install_command = ["--progress-bar=off", "-r", requirements_file]
  751. for distro_constraint in distro_constraints:
  752. install_command.extend(["--constraint", distro_constraint])
  753. session.install(*install_command, silent=PIP_INSTALL_SILENT)
  754. os.chdir("doc/")
  755. session.run("make", "clean", external=True)
  756. session.run("make", "man", "SPHINXOPTS=-W", external=True)
  757. if update:
  758. session.run("rm", "-rf", "man/", external=True)
  759. session.run("cp", "-Rp", "_build/man", "man/", external=True)
  760. if compress:
  761. session.run("tar", "-cJvf", "man-archive.tar.xz", "_build/man", external=True)
  762. os.chdir("..")
  763. @nox.session(name="changelog", python="3")
  764. @nox.parametrize("draft", [False, True])
  765. def changelog(session, draft):
  766. """
  767. Generate salt's changelog
  768. """
  769. requirements_file = "requirements/static/changelog.in"
  770. distro_constraints = [
  771. "requirements/static/{}/changelog.txt".format(_get_pydir(session))
  772. ]
  773. install_command = ["--progress-bar=off", "-r", requirements_file]
  774. for distro_constraint in distro_constraints:
  775. install_command.extend(["--constraint", distro_constraint])
  776. session.install(*install_command, silent=PIP_INSTALL_SILENT)
  777. town_cmd = ["towncrier", "--version={}".format(session.posargs[0])]
  778. if draft:
  779. town_cmd.append("--draft")
  780. session.run(*town_cmd)