1
0

noxfile.py 40 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141
  1. # -*- coding: utf-8 -*-
  2. '''
  3. noxfile
  4. ~~~~~~~
  5. Nox configuration script
  6. '''
  7. # Import Python libs
  8. from __future__ import absolute_import, unicode_literals, print_function
  9. import os
  10. import sys
  11. import glob
  12. import json
  13. import pprint
  14. import shutil
  15. import datetime
  16. import tempfile
  17. if __name__ == '__main__':
  18. sys.stderr.write('Do not execute this file directly. Use nox instead, it will know how to handle this file\n')
  19. sys.stderr.flush()
  20. exit(1)
  21. # Import 3rd-party libs
  22. import nox
  23. from nox.command import CommandFailed
  24. IS_PY3 = sys.version_info > (2,)
  25. # Be verbose when runing under a CI context
  26. PIP_INSTALL_SILENT = (os.environ.get('JENKINS_URL') or os.environ.get('CI') or os.environ.get('DRONE')) is None
  27. # Global Path Definitions
  28. REPO_ROOT = os.path.abspath(os.path.dirname(__file__))
  29. SITECUSTOMIZE_DIR = os.path.join(REPO_ROOT, 'tests', 'support', 'coverage')
  30. IS_DARWIN = sys.platform.lower().startswith('darwin')
  31. IS_WINDOWS = sys.platform.lower().startswith('win')
  32. # Python versions to run against
  33. _PYTHON_VERSIONS = ('2', '2.7', '3', '3.4', '3.5', '3.6', '3.7')
  34. # Nox options
  35. # Reuse existing virtualenvs
  36. nox.options.reuse_existing_virtualenvs = True
  37. # Don't fail on missing interpreters
  38. nox.options.error_on_missing_interpreters = False
  39. # Change current directory to REPO_ROOT
  40. os.chdir(REPO_ROOT)
  41. RUNTESTS_LOGFILE = os.path.join(
  42. 'artifacts', 'logs',
  43. 'runtests-{}.log'.format(datetime.datetime.now().strftime('%Y%m%d%H%M%S.%f'))
  44. )
  45. def _create_ci_directories():
  46. for dirname in ('logs', 'coverage', 'xml-unittests-output'):
  47. path = os.path.join('artifacts', dirname)
  48. if not os.path.exists(path):
  49. os.makedirs(path)
  50. def _get_session_python_version_info(session):
  51. try:
  52. version_info = session._runner._real_python_version_info
  53. except AttributeError:
  54. old_install_only_value = session._runner.global_config.install_only
  55. try:
  56. # Force install only to be false for the following chunk of code
  57. # For additional information as to why see:
  58. # https://github.com/theacodes/nox/pull/181
  59. session._runner.global_config.install_only = False
  60. session_py_version = session.run(
  61. 'python', '-c'
  62. 'import sys; sys.stdout.write("{}.{}.{}".format(*sys.version_info))',
  63. silent=True,
  64. log=False,
  65. )
  66. version_info = tuple(int(part) for part in session_py_version.split('.') if part.isdigit())
  67. session._runner._real_python_version_info = version_info
  68. finally:
  69. session._runner.global_config.install_only = old_install_only_value
  70. return version_info
  71. def _get_session_python_site_packages_dir(session):
  72. try:
  73. site_packages_dir = session._runner._site_packages_dir
  74. except AttributeError:
  75. old_install_only_value = session._runner.global_config.install_only
  76. try:
  77. # Force install only to be false for the following chunk of code
  78. # For additional information as to why see:
  79. # https://github.com/theacodes/nox/pull/181
  80. session._runner.global_config.install_only = False
  81. site_packages_dir = session.run(
  82. 'python', '-c'
  83. 'import sys; from distutils.sysconfig import get_python_lib; sys.stdout.write(get_python_lib())',
  84. silent=True,
  85. log=False,
  86. )
  87. session._runner._site_packages_dir = site_packages_dir
  88. finally:
  89. session._runner.global_config.install_only = old_install_only_value
  90. return site_packages_dir
  91. def _get_pydir(session):
  92. version_info = _get_session_python_version_info(session)
  93. if version_info < (2, 7):
  94. session.error('Only Python >= 2.7 is supported')
  95. return 'py{}.{}'.format(*version_info)
  96. def _get_distro_info(session):
  97. try:
  98. distro = session._runner._distro
  99. except AttributeError:
  100. # The distro package doesn't output anything for Windows
  101. old_install_only_value = session._runner.global_config.install_only
  102. try:
  103. # Force install only to be false for the following chunk of code
  104. # For additional information as to why see:
  105. # https://github.com/theacodes/nox/pull/181
  106. session._runner.global_config.install_only = False
  107. session.install('--progress-bar=off', 'distro', silent=PIP_INSTALL_SILENT)
  108. output = session.run('distro', '-j', silent=True)
  109. distro = json.loads(output.strip())
  110. session.log('Distro information:\n%s', pprint.pformat(distro))
  111. session._runner._distro = distro
  112. finally:
  113. session._runner.global_config.install_only = old_install_only_value
  114. return distro
  115. def _install_system_packages(session):
  116. '''
  117. Because some python packages are provided by the distribution and cannot
  118. be pip installed, and because we don't want the whole system python packages
  119. on our virtualenvs, we copy the required system python packages into
  120. the virtualenv
  121. '''
  122. system_python_packages = {
  123. '__debian_based_distros__': [
  124. '/usr/lib/python{py_version}/dist-packages/*apt*'
  125. ]
  126. }
  127. distro = _get_distro_info(session)
  128. if not distro['id'].startswith(('debian', 'ubuntu')):
  129. # This only applies to debian based distributions
  130. return
  131. system_python_packages['{id}-{version}'.format(**distro)] = \
  132. system_python_packages['{id}-{version_parts[major]}'.format(**distro)] = \
  133. system_python_packages['__debian_based_distros__'][:]
  134. distro_keys = [
  135. '{id}'.format(**distro),
  136. '{id}-{version}'.format(**distro),
  137. '{id}-{version_parts[major]}'.format(**distro)
  138. ]
  139. version_info = _get_session_python_version_info(session)
  140. py_version_keys = [
  141. '{}'.format(*version_info),
  142. '{}.{}'.format(*version_info)
  143. ]
  144. session_site_packages_dir = _get_session_python_site_packages_dir(session)
  145. for distro_key in distro_keys:
  146. if distro_key not in system_python_packages:
  147. continue
  148. patterns = system_python_packages[distro_key]
  149. for pattern in patterns:
  150. for py_version in py_version_keys:
  151. matches = set(glob.glob(pattern.format(py_version=py_version)))
  152. if not matches:
  153. continue
  154. for match in matches:
  155. src = os.path.realpath(match)
  156. dst = os.path.join(session_site_packages_dir, os.path.basename(match))
  157. if os.path.exists(dst):
  158. session.log('Not overwritting already existing %s with %s', dst, src)
  159. continue
  160. session.log('Copying %s into %s', src, dst)
  161. if os.path.isdir(src):
  162. shutil.copytree(src, dst)
  163. else:
  164. shutil.copyfile(src, dst)
  165. def _get_distro_pip_constraints(session, transport):
  166. # Install requirements
  167. distro_constraints = []
  168. if transport == 'tcp':
  169. # The TCP requirements are the exact same requirements as the ZeroMQ ones
  170. transport = 'zeromq'
  171. pydir = _get_pydir(session)
  172. if transport == 'raet':
  173. # Because we still install ioflo, which requires setuptools-git, which fails with a
  174. # weird SSL certificate issue(weird because the requirements file requirements install
  175. # fine), let's previously have setuptools-git installed
  176. session.install('--progress-bar=off', 'setuptools-git', silent=PIP_INSTALL_SILENT)
  177. if IS_WINDOWS:
  178. _distro_constraints = os.path.join('requirements',
  179. 'static',
  180. pydir,
  181. '{}-windows.txt'.format(transport))
  182. if os.path.exists(_distro_constraints):
  183. distro_constraints.append(_distro_constraints)
  184. _distro_constraints = os.path.join('requirements',
  185. 'static',
  186. pydir,
  187. 'windows.txt')
  188. if os.path.exists(_distro_constraints):
  189. distro_constraints.append(_distro_constraints)
  190. _distro_constraints = os.path.join('requirements',
  191. 'static',
  192. pydir,
  193. 'windows-crypto.txt')
  194. if os.path.exists(_distro_constraints):
  195. distro_constraints.append(_distro_constraints)
  196. elif IS_DARWIN:
  197. _distro_constraints = os.path.join('requirements',
  198. 'static',
  199. pydir,
  200. '{}-darwin.txt'.format(transport))
  201. if os.path.exists(_distro_constraints):
  202. distro_constraints.append(_distro_constraints)
  203. _distro_constraints = os.path.join('requirements',
  204. 'static',
  205. pydir,
  206. 'darwin.txt')
  207. if os.path.exists(_distro_constraints):
  208. distro_constraints.append(_distro_constraints)
  209. _distro_constraints = os.path.join('requirements',
  210. 'static',
  211. pydir,
  212. 'darwin-crypto.txt')
  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('requirements',
  226. 'static',
  227. pydir,
  228. '{}.txt'.format(distro_key))
  229. if os.path.exists(_distro_constraints):
  230. distro_constraints.append(_distro_constraints)
  231. if os.path.exists(_distro_constraints):
  232. distro_constraints.append(_distro_constraints)
  233. _distro_constraints = os.path.join('requirements',
  234. 'static',
  235. pydir,
  236. '{}-crypto.txt'.format(distro_key))
  237. if os.path.exists(_distro_constraints):
  238. distro_constraints.append(_distro_constraints)
  239. _distro_constraints = os.path.join('requirements',
  240. 'static',
  241. pydir,
  242. '{}-{}.txt'.format(transport, distro_key))
  243. if os.path.exists(_distro_constraints):
  244. distro_constraints.append(_distro_constraints)
  245. distro_constraints.append(_distro_constraints)
  246. _distro_constraints = os.path.join('requirements',
  247. 'static',
  248. pydir,
  249. '{}-{}-crypto.txt'.format(transport, distro_key))
  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', 'pytest.txt')
  259. ]
  260. if transport == 'raet':
  261. _requirements_files.append(
  262. os.path.join('requirements', 'raet.txt'),
  263. )
  264. else:
  265. _requirements_files.append(
  266. os.path.join('requirements', 'zeromq.txt'),
  267. )
  268. if sys.platform.startswith('linux'):
  269. requirements_files = [
  270. os.path.join('requirements', 'static', 'linux.in')
  271. ]
  272. elif sys.platform.startswith('win'):
  273. requirements_files = [
  274. os.path.join('pkg', 'windows', 'req.txt'),
  275. os.path.join('requirements', 'static', 'windows.in')
  276. ]
  277. elif sys.platform.startswith('darwin'):
  278. requirements_files = [
  279. os.path.join('pkg', 'osx', 'req.txt'),
  280. os.path.join('pkg', 'osx', 'req_ext.txt'),
  281. os.path.join('requirements', 'static', 'darwin.in')
  282. ]
  283. while True:
  284. if not requirements_files:
  285. break
  286. requirements_file = requirements_files.pop(0)
  287. if requirements_file not in _requirements_files:
  288. _requirements_files.append(requirements_file)
  289. session.log('Processing {}'.format(requirements_file))
  290. with open(requirements_file) as rfh: # pylint: disable=resource-leakage
  291. for line in rfh:
  292. line = line.strip()
  293. if not line:
  294. continue
  295. if line.startswith('-r'):
  296. reqfile = os.path.join(os.path.dirname(requirements_file), line.strip().split()[-1])
  297. if reqfile in _requirements_files:
  298. continue
  299. _requirements_files.append(reqfile)
  300. continue
  301. for requirements_file in _requirements_files:
  302. install_command = [
  303. '--progress-bar=off', '-r', requirements_file
  304. ]
  305. for distro_constraint in distro_constraints:
  306. install_command.extend([
  307. '--constraint', distro_constraint
  308. ])
  309. session.install(*install_command, silent=PIP_INSTALL_SILENT)
  310. if extra_requirements:
  311. install_command = [
  312. '--progress-bar=off',
  313. ]
  314. for distro_constraint in distro_constraints:
  315. install_command.extend([
  316. '--constraint', distro_constraint
  317. ])
  318. install_command += list(extra_requirements)
  319. session.install(*install_command, silent=PIP_INSTALL_SILENT)
  320. def _run_with_coverage(session, *test_cmd):
  321. session.install('--progress-bar=off', 'coverage==4.5.3', silent=PIP_INSTALL_SILENT)
  322. session.run('coverage', 'erase')
  323. python_path_env_var = os.environ.get('PYTHONPATH') or None
  324. if python_path_env_var is None:
  325. python_path_env_var = SITECUSTOMIZE_DIR
  326. else:
  327. python_path_entries = python_path_env_var.split(os.pathsep)
  328. if SITECUSTOMIZE_DIR in python_path_entries:
  329. python_path_entries.remove(SITECUSTOMIZE_DIR)
  330. python_path_entries.insert(0, SITECUSTOMIZE_DIR)
  331. python_path_env_var = os.pathsep.join(python_path_entries)
  332. env = {
  333. # The updated python path so that sitecustomize is importable
  334. 'PYTHONPATH': python_path_env_var,
  335. # The full path to the .coverage data file. Makes sure we always write
  336. # them to the same directory
  337. 'COVERAGE_FILE': os.path.abspath(os.path.join(REPO_ROOT, '.coverage')),
  338. # Instruct sub processes to also run under coverage
  339. 'COVERAGE_PROCESS_START': os.path.join(REPO_ROOT, '.coveragerc')
  340. }
  341. if IS_DARWIN:
  342. # Don't nuke our multiprocessing efforts objc!
  343. # https://stackoverflow.com/questions/50168647/multiprocessing-causes-python-to-crash-and-gives-an-error-may-have-been-in-progr
  344. env['OBJC_DISABLE_INITIALIZE_FORK_SAFETY'] = 'YES'
  345. try:
  346. session.run(*test_cmd, env=env)
  347. finally:
  348. # Always combine and generate the XML coverage report
  349. try:
  350. session.run('coverage', 'combine')
  351. except CommandFailed:
  352. # Sometimes some of the coverage files are corrupt which would trigger a CommandFailed
  353. # exception
  354. pass
  355. # Generate report for salt code coverage
  356. session.run(
  357. 'coverage', 'xml',
  358. '-o', os.path.join('artifacts', 'coverage', 'salt.xml'),
  359. '--omit=tests/*',
  360. '--include=salt/*'
  361. )
  362. # Generate report for tests code coverage
  363. session.run(
  364. 'coverage', 'xml',
  365. '-o', os.path.join('artifacts', 'coverage', 'tests.xml'),
  366. '--omit=salt/*',
  367. '--include=tests/*'
  368. )
  369. def _runtests(session, coverage, cmd_args):
  370. # Create required artifacts directories
  371. _create_ci_directories()
  372. try:
  373. if coverage is True:
  374. _run_with_coverage(session, 'coverage', 'run', os.path.join('tests', 'runtests.py'), *cmd_args)
  375. else:
  376. cmd_args = ['python', os.path.join('tests', 'runtests.py')] + list(cmd_args)
  377. env = None
  378. if IS_DARWIN:
  379. # Don't nuke our multiprocessing efforts objc!
  380. # https://stackoverflow.com/questions/50168647/multiprocessing-causes-python-to-crash-and-gives-an-error-may-have-been-in-progr
  381. env = {'OBJC_DISABLE_INITIALIZE_FORK_SAFETY': 'YES'}
  382. session.run(*cmd_args, env=env)
  383. except CommandFailed:
  384. # Disabling re-running failed tests for the time being
  385. raise
  386. # pylint: disable=unreachable
  387. names_file_path = os.path.join('artifacts', 'failed-tests.txt')
  388. session.log('Re-running failed tests if possible')
  389. session.install('--progress-bar=off', 'xunitparser==1.3.3', silent=PIP_INSTALL_SILENT)
  390. session.run(
  391. 'python',
  392. os.path.join('tests', 'support', 'generate-names-file-from-failed-test-reports.py'),
  393. names_file_path
  394. )
  395. if not os.path.exists(names_file_path):
  396. session.log(
  397. 'Failed tests file(%s) was not found. Not rerunning failed tests.',
  398. names_file_path
  399. )
  400. # raise the original exception
  401. raise
  402. with open(names_file_path) as rfh:
  403. contents = rfh.read().strip()
  404. if not contents:
  405. session.log(
  406. 'The failed tests file(%s) is empty. Not rerunning failed tests.',
  407. names_file_path
  408. )
  409. # raise the original exception
  410. raise
  411. failed_tests_count = len(contents.splitlines())
  412. if failed_tests_count > 500:
  413. # 500 test failures?! Something else must have gone wrong, don't even bother
  414. session.error(
  415. 'Total failed tests({}) > 500. No point on re-running the failed tests'.format(
  416. failed_tests_count
  417. )
  418. )
  419. for idx, flag in enumerate(cmd_args[:]):
  420. if '--names-file=' in flag:
  421. cmd_args.pop(idx)
  422. break
  423. elif flag == '--names-file':
  424. cmd_args.pop(idx) # pop --names-file
  425. cmd_args.pop(idx) # pop the actual names file
  426. break
  427. cmd_args.append('--names-file={}'.format(names_file_path))
  428. if coverage is True:
  429. _run_with_coverage(session, 'coverage', 'run', '-m', 'tests.runtests', *cmd_args)
  430. else:
  431. session.run('python', os.path.join('tests', 'runtests.py'), *cmd_args)
  432. # pylint: enable=unreachable
  433. @nox.session(python=_PYTHON_VERSIONS, name='runtests-parametrized')
  434. @nox.parametrize('coverage', [False, True])
  435. @nox.parametrize('transport', ['zeromq', 'raet', 'tcp'])
  436. @nox.parametrize('crypto', [None, 'm2crypto', 'pycryptodomex'])
  437. def runtests_parametrized(session, coverage, transport, crypto):
  438. # Install requirements
  439. _install_requirements(session, transport, 'unittest-xml-reporting==2.5.2')
  440. if crypto:
  441. if crypto == 'm2crypto':
  442. session.run('pip', 'uninstall', '-y', 'pycrypto', 'pycryptodome', 'pycryptodomex', silent=True)
  443. else:
  444. session.run('pip', 'uninstall', '-y', 'm2crypto', silent=True)
  445. distro_constraints = _get_distro_pip_constraints(session, transport)
  446. install_command = [
  447. '--progress-bar=off',
  448. ]
  449. for distro_constraint in distro_constraints:
  450. install_command.extend([
  451. '--constraint', distro_constraint
  452. ])
  453. install_command.append(crypto)
  454. session.install(*install_command, silent=PIP_INSTALL_SILENT)
  455. cmd_args = [
  456. '--tests-logfile={}'.format(RUNTESTS_LOGFILE),
  457. '--transport={}'.format(transport)
  458. ] + session.posargs
  459. _runtests(session, coverage, cmd_args)
  460. @nox.session(python=_PYTHON_VERSIONS)
  461. @nox.parametrize('coverage', [False, True])
  462. def runtests(session, coverage):
  463. '''
  464. runtests.py session with zeromq transport and default crypto
  465. '''
  466. session.notify(
  467. 'runtests-parametrized-{}(coverage={}, crypto=None, transport=\'zeromq\')'.format(
  468. session.python,
  469. coverage
  470. )
  471. )
  472. @nox.session(python=_PYTHON_VERSIONS, name='runtests-tcp')
  473. @nox.parametrize('coverage', [False, True])
  474. def runtests_tcp(session, coverage):
  475. '''
  476. runtests.py session with TCP transport and default crypto
  477. '''
  478. session.notify(
  479. 'runtests-parametrized-{}(coverage={}, crypto=None, transport=\'tcp\')'.format(
  480. session.python,
  481. coverage
  482. )
  483. )
  484. @nox.session(python=_PYTHON_VERSIONS, name='runtests-zeromq')
  485. @nox.parametrize('coverage', [False, True])
  486. def runtests_zeromq(session, coverage):
  487. '''
  488. runtests.py session with zeromq transport and default crypto
  489. '''
  490. session.notify(
  491. 'runtests-parametrized-{}(coverage={}, crypto=None, transport=\'zeromq\')'.format(
  492. session.python,
  493. coverage
  494. )
  495. )
  496. @nox.session(python=_PYTHON_VERSIONS, name='runtests-raet')
  497. @nox.parametrize('coverage', [False, True])
  498. def runtests_raet(session, coverage):
  499. '''
  500. runtests.py session with raet transport and default crypto
  501. '''
  502. session.notify(
  503. 'runtests-parametrized-{}(coverage={}, crypto=None, transport=\'raet\')'.format(
  504. session.python,
  505. coverage
  506. )
  507. )
  508. @nox.session(python=_PYTHON_VERSIONS, name='runtests-m2crypto')
  509. @nox.parametrize('coverage', [False, True])
  510. def runtests_m2crypto(session, coverage):
  511. '''
  512. runtests.py session with zeromq transport and m2crypto
  513. '''
  514. session.notify(
  515. 'runtests-parametrized-{}(coverage={}, crypto=\'m2crypto\', transport=\'zeromq\')'.format(
  516. session.python,
  517. coverage
  518. )
  519. )
  520. @nox.session(python=_PYTHON_VERSIONS, name='runtests-tcp-m2crypto')
  521. @nox.parametrize('coverage', [False, True])
  522. def runtests_tcp_m2crypto(session, coverage):
  523. '''
  524. runtests.py session with TCP transport and m2crypto
  525. '''
  526. session.notify(
  527. 'runtests-parametrized-{}(coverage={}, crypto=\'m2crypto\', transport=\'tcp\')'.format(
  528. session.python,
  529. coverage
  530. )
  531. )
  532. @nox.session(python=_PYTHON_VERSIONS, name='runtests-zeromq-m2crypto')
  533. @nox.parametrize('coverage', [False, True])
  534. def runtests_zeromq_m2crypto(session, coverage):
  535. '''
  536. runtests.py session with zeromq transport and m2crypto
  537. '''
  538. session.notify(
  539. 'runtests-parametrized-{}(coverage={}, crypto=\'m2crypto\', transport=\'zeromq\')'.format(
  540. session.python,
  541. coverage
  542. )
  543. )
  544. @nox.session(python=_PYTHON_VERSIONS, name='runtests-raet-m2crypto')
  545. @nox.parametrize('coverage', [False, True])
  546. def runtests_raet_m2crypto(session, coverage):
  547. '''
  548. runtests.py session with raet transport and m2crypto
  549. '''
  550. session.notify(
  551. 'runtests-parametrized-{}(coverage={}, crypto=\'m2crypto\', transport=\'raet\')'.format(
  552. session.python,
  553. coverage
  554. )
  555. )
  556. @nox.session(python=_PYTHON_VERSIONS, name='runtests-pycryptodomex')
  557. @nox.parametrize('coverage', [False, True])
  558. def runtests_pycryptodomex(session, coverage):
  559. '''
  560. runtests.py session with zeromq transport and pycryptodomex
  561. '''
  562. session.notify(
  563. 'runtests-parametrized-{}(coverage={}, crypto=\'pycryptodomex\', transport=\'zeromq\')'.format(
  564. session.python,
  565. coverage
  566. )
  567. )
  568. @nox.session(python=_PYTHON_VERSIONS, name='runtests-tcp-pycryptodomex')
  569. @nox.parametrize('coverage', [False, True])
  570. def runtests_tcp_pycryptodomex(session, coverage):
  571. '''
  572. runtests.py session with TCP transport and pycryptodomex
  573. '''
  574. session.notify(
  575. 'runtests-parametrized-{}(coverage={}, crypto=\'pycryptodomex\', transport=\'tcp\')'.format(
  576. session.python,
  577. coverage
  578. )
  579. )
  580. @nox.session(python=_PYTHON_VERSIONS, name='runtests-zeromq-pycryptodomex')
  581. @nox.parametrize('coverage', [False, True])
  582. def runtests_zeromq_pycryptodomex(session, coverage):
  583. '''
  584. runtests.py session with zeromq transport and pycryptodomex
  585. '''
  586. session.notify(
  587. 'runtests-parametrized-{}(coverage={}, crypto=\'pycryptodomex\', transport=\'zeromq\')'.format(
  588. session.python,
  589. coverage
  590. )
  591. )
  592. @nox.session(python=_PYTHON_VERSIONS, name='runtests-raet-pycryptodomex')
  593. @nox.parametrize('coverage', [False, True])
  594. def runtests_raet_pycryptodomex(session, coverage):
  595. '''
  596. runtests.py session with raet transport and pycryptodomex
  597. '''
  598. session.notify(
  599. 'runtests-parametrized-{}(coverage={}, crypto=\'pycryptodomex\', transport=\'raet\')'.format(
  600. session.python,
  601. coverage
  602. )
  603. )
  604. @nox.session(python=_PYTHON_VERSIONS, name='runtests-cloud')
  605. @nox.parametrize('coverage', [False, True])
  606. def runtests_cloud(session, coverage):
  607. # Install requirements
  608. _install_requirements(session, 'zeromq', 'unittest-xml-reporting==2.2.1')
  609. pydir = _get_pydir(session)
  610. cloud_requirements = os.path.join('requirements', 'static', pydir, 'cloud.txt')
  611. session.install('--progress-bar=off', '-r', cloud_requirements, silent=PIP_INSTALL_SILENT)
  612. cmd_args = [
  613. '--tests-logfile={}'.format(RUNTESTS_LOGFILE),
  614. '--cloud-provider-tests'
  615. ] + session.posargs
  616. _runtests(session, coverage, cmd_args)
  617. @nox.session(python=_PYTHON_VERSIONS, name='runtests-tornado')
  618. @nox.parametrize('coverage', [False, True])
  619. def runtests_tornado(session, coverage):
  620. # Install requirements
  621. _install_requirements(session, 'zeromq', 'unittest-xml-reporting==2.2.1')
  622. session.install('--progress-bar=off', 'tornado==5.0.2', silent=PIP_INSTALL_SILENT)
  623. session.install('--progress-bar=off', 'pyzmq==17.0.0', silent=PIP_INSTALL_SILENT)
  624. cmd_args = [
  625. '--tests-logfile={}'.format(RUNTESTS_LOGFILE),
  626. ] + session.posargs
  627. _runtests(session, coverage, cmd_args)
  628. @nox.session(python=_PYTHON_VERSIONS, name='pytest-parametrized')
  629. @nox.parametrize('coverage', [False, True])
  630. @nox.parametrize('transport', ['zeromq', 'raet', 'tcp'])
  631. @nox.parametrize('crypto', [None, 'm2crypto', 'pycryptodomex'])
  632. def pytest_parametrized(session, coverage, transport, crypto):
  633. # Install requirements
  634. _install_requirements(session, transport)
  635. if crypto:
  636. if crypto == 'm2crypto':
  637. session.run('pip', 'uninstall', '-y', 'pycrypto', 'pycryptodome', 'pycryptodomex', silent=True)
  638. else:
  639. session.run('pip', 'uninstall', '-y', 'm2crypto', silent=True)
  640. distro_constraints = _get_distro_pip_constraints(session, transport)
  641. install_command = [
  642. '--progress-bar=off',
  643. ]
  644. for distro_constraint in distro_constraints:
  645. install_command.extend([
  646. '--constraint', distro_constraint
  647. ])
  648. install_command.append(crypto)
  649. session.install(*install_command, silent=PIP_INSTALL_SILENT)
  650. cmd_args = [
  651. '--rootdir', REPO_ROOT,
  652. '--log-file={}'.format(RUNTESTS_LOGFILE),
  653. '--no-print-logs',
  654. '-ra',
  655. '-s',
  656. '--transport={}'.format(transport)
  657. ] + session.posargs
  658. _pytest(session, coverage, cmd_args)
  659. @nox.session(python=_PYTHON_VERSIONS)
  660. @nox.parametrize('coverage', [False, True])
  661. def pytest(session, coverage):
  662. '''
  663. pytest session with zeromq transport and default crypto
  664. '''
  665. session.notify(
  666. 'pytest-parametrized-{}(coverage={}, crypto=None, transport=\'zeromq\')'.format(
  667. session.python,
  668. coverage
  669. )
  670. )
  671. @nox.session(python=_PYTHON_VERSIONS, name='pytest-tcp')
  672. @nox.parametrize('coverage', [False, True])
  673. def pytest_tcp(session, coverage):
  674. '''
  675. pytest session with TCP transport and default crypto
  676. '''
  677. session.notify(
  678. 'pytest-parametrized-{}(coverage={}, crypto=None, transport=\'tcp\')'.format(
  679. session.python,
  680. coverage
  681. )
  682. )
  683. @nox.session(python=_PYTHON_VERSIONS, name='pytest-zeromq')
  684. @nox.parametrize('coverage', [False, True])
  685. def pytest_zeromq(session, coverage):
  686. '''
  687. pytest session with zeromq transport and default crypto
  688. '''
  689. session.notify(
  690. 'pytest-parametrized-{}(coverage={}, crypto=None, transport=\'zeromq\')'.format(
  691. session.python,
  692. coverage
  693. )
  694. )
  695. @nox.session(python=_PYTHON_VERSIONS, name='pytest-raet')
  696. @nox.parametrize('coverage', [False, True])
  697. def pytest_raet(session, coverage):
  698. '''
  699. pytest session with raet transport and default crypto
  700. '''
  701. session.notify(
  702. 'pytest-parametrized-{}(coverage={}, crypto=None, transport=\'raet\')'.format(
  703. session.python,
  704. coverage
  705. )
  706. )
  707. @nox.session(python=_PYTHON_VERSIONS, name='pytest-m2crypto')
  708. @nox.parametrize('coverage', [False, True])
  709. def pytest_m2crypto(session, coverage):
  710. '''
  711. pytest session with zeromq transport and m2crypto
  712. '''
  713. session.notify(
  714. 'pytest-parametrized-{}(coverage={}, crypto=\'m2crypto\', transport=\'zeromq\')'.format(
  715. session.python,
  716. coverage
  717. )
  718. )
  719. @nox.session(python=_PYTHON_VERSIONS, name='pytest-tcp-m2crypto')
  720. @nox.parametrize('coverage', [False, True])
  721. def pytest_tcp_m2crypto(session, coverage):
  722. '''
  723. pytest session with TCP transport and m2crypto
  724. '''
  725. session.notify(
  726. 'pytest-parametrized-{}(coverage={}, crypto=\'m2crypto\', transport=\'tcp\')'.format(
  727. session.python,
  728. coverage
  729. )
  730. )
  731. @nox.session(python=_PYTHON_VERSIONS, name='pytest-zeromq-m2crypto')
  732. @nox.parametrize('coverage', [False, True])
  733. def pytest_zeromq_m2crypto(session, coverage):
  734. '''
  735. pytest session with zeromq transport and m2crypto
  736. '''
  737. session.notify(
  738. 'pytest-parametrized-{}(coverage={}, crypto=\'m2crypto\', transport=\'zeromq\')'.format(
  739. session.python,
  740. coverage
  741. )
  742. )
  743. @nox.session(python=_PYTHON_VERSIONS, name='pytest-raet-m2crypto')
  744. @nox.parametrize('coverage', [False, True])
  745. def pytest_raet_m2crypto(session, coverage):
  746. '''
  747. pytest session with raet transport and m2crypto
  748. '''
  749. session.notify(
  750. 'pytest-parametrized-{}(coverage={}, crypto=\'m2crypto\', transport=\'raet\')'.format(
  751. session.python,
  752. coverage
  753. )
  754. )
  755. @nox.session(python=_PYTHON_VERSIONS, name='pytest-pycryptodomex')
  756. @nox.parametrize('coverage', [False, True])
  757. def pytest_pycryptodomex(session, coverage):
  758. '''
  759. pytest session with zeromq transport and pycryptodomex
  760. '''
  761. session.notify(
  762. 'pytest-parametrized-{}(coverage={}, crypto=\'pycryptodomex\', transport=\'zeromq\')'.format(
  763. session.python,
  764. coverage
  765. )
  766. )
  767. @nox.session(python=_PYTHON_VERSIONS, name='pytest-tcp-pycryptodomex')
  768. @nox.parametrize('coverage', [False, True])
  769. def pytest_tcp_pycryptodomex(session, coverage):
  770. '''
  771. pytest session with TCP transport and pycryptodomex
  772. '''
  773. session.notify(
  774. 'pytest-parametrized-{}(coverage={}, crypto=\'pycryptodomex\', transport=\'tcp\')'.format(
  775. session.python,
  776. coverage
  777. )
  778. )
  779. @nox.session(python=_PYTHON_VERSIONS, name='pytest-zeromq-pycryptodomex')
  780. @nox.parametrize('coverage', [False, True])
  781. def pytest_zeromq_pycryptodomex(session, coverage):
  782. '''
  783. pytest session with zeromq transport and pycryptodomex
  784. '''
  785. session.notify(
  786. 'pytest-parametrized-{}(coverage={}, crypto=\'pycryptodomex\', transport=\'zeromq\')'.format(
  787. session.python,
  788. coverage
  789. )
  790. )
  791. @nox.session(python=_PYTHON_VERSIONS, name='pytest-raet-pycryptodomex')
  792. @nox.parametrize('coverage', [False, True])
  793. def pytest_raet_pycryptodomex(session, coverage):
  794. '''
  795. pytest session with raet transport and pycryptodomex
  796. '''
  797. session.notify(
  798. 'pytest-parametrized-{}(coverage={}, crypto=\'pycryptodomex\', transport=\'raet\')'.format(
  799. session.python,
  800. coverage
  801. )
  802. )
  803. @nox.session(python=_PYTHON_VERSIONS, name='pytest-cloud')
  804. @nox.parametrize('coverage', [False, True])
  805. def pytest_cloud(session, coverage):
  806. # Install requirements
  807. _install_requirements(session, 'zeromq')
  808. pydir = _get_pydir(session)
  809. cloud_requirements = os.path.join('requirements', 'static', pydir, 'cloud.txt')
  810. session.install('--progress-bar=off', '-r', cloud_requirements, silent=PIP_INSTALL_SILENT)
  811. cmd_args = [
  812. '--rootdir', REPO_ROOT,
  813. '--log-file={}'.format(RUNTESTS_LOGFILE),
  814. '--no-print-logs',
  815. '-ra',
  816. '-s',
  817. os.path.join('tests', 'integration', 'cloud', 'providers')
  818. ] + session.posargs
  819. _pytest(session, coverage, cmd_args)
  820. @nox.session(python=_PYTHON_VERSIONS, name='pytest-tornado')
  821. @nox.parametrize('coverage', [False, True])
  822. def pytest_tornado(session, coverage):
  823. # Install requirements
  824. _install_requirements(session, 'zeromq')
  825. session.install('--progress-bar=off', 'tornado==5.0.2', silent=PIP_INSTALL_SILENT)
  826. session.install('--progress-bar=off', 'pyzmq==17.0.0', silent=PIP_INSTALL_SILENT)
  827. cmd_args = [
  828. '--rootdir', REPO_ROOT,
  829. '--log-file={}'.format(RUNTESTS_LOGFILE),
  830. '--no-print-logs',
  831. '-ra',
  832. '-s',
  833. ] + session.posargs
  834. _pytest(session, coverage, cmd_args)
  835. def _pytest(session, coverage, cmd_args):
  836. # Create required artifacts directories
  837. _create_ci_directories()
  838. env = None
  839. if IS_DARWIN:
  840. # Don't nuke our multiprocessing efforts objc!
  841. # https://stackoverflow.com/questions/50168647/multiprocessing-causes-python-to-crash-and-gives-an-error-may-have-been-in-progr
  842. env = {'OBJC_DISABLE_INITIALIZE_FORK_SAFETY': 'YES'}
  843. try:
  844. if coverage is True:
  845. _run_with_coverage(session, 'coverage', 'run', '-m', 'py.test', *cmd_args)
  846. else:
  847. session.run('py.test', *cmd_args, env=env)
  848. except CommandFailed:
  849. # Re-run failed tests
  850. session.log('Re-running failed tests')
  851. cmd_args.append('--lf')
  852. if coverage is True:
  853. _run_with_coverage(session, 'coverage', 'run', '-m', 'py.test', *cmd_args)
  854. else:
  855. session.run('py.test', *cmd_args, env=env)
  856. def _lint(session, rcfile, flags, paths):
  857. _install_requirements(session, 'zeromq')
  858. _install_requirements(session, 'raet')
  859. requirements_file = 'requirements/static/lint.in'
  860. distro_constraints = [
  861. 'requirements/static/{}/lint.txt'.format(_get_pydir(session))
  862. ]
  863. install_command = [
  864. '--progress-bar=off', '-r', requirements_file
  865. ]
  866. for distro_constraint in distro_constraints:
  867. install_command.extend([
  868. '--constraint', distro_constraint
  869. ])
  870. session.install(*install_command, silent=PIP_INSTALL_SILENT)
  871. session.run('pylint', '--version')
  872. pylint_report_path = os.environ.get('PYLINT_REPORT')
  873. cmd_args = [
  874. 'pylint',
  875. '--rcfile={}'.format(rcfile)
  876. ] + list(flags) + list(paths)
  877. stdout = tempfile.TemporaryFile(mode='w+b')
  878. lint_failed = False
  879. try:
  880. session.run(*cmd_args, stdout=stdout)
  881. except CommandFailed:
  882. lint_failed = True
  883. raise
  884. finally:
  885. stdout.seek(0)
  886. contents = stdout.read()
  887. if contents:
  888. if IS_PY3:
  889. contents = contents.decode('utf-8')
  890. else:
  891. contents = contents.encode('utf-8')
  892. sys.stdout.write(contents)
  893. sys.stdout.flush()
  894. if pylint_report_path:
  895. # Write report
  896. with open(pylint_report_path, 'w') as wfh:
  897. wfh.write(contents)
  898. session.log('Report file written to %r', pylint_report_path)
  899. stdout.close()
  900. @nox.session(python='2.7')
  901. def lint(session):
  902. '''
  903. Run PyLint against Salt and it's test suite. Set PYLINT_REPORT to a path to capture output.
  904. '''
  905. session.notify('lint-salt-{}'.format(session.python))
  906. session.notify('lint-tests-{}'.format(session.python))
  907. @nox.session(python='2.7', name='lint-salt')
  908. def lint_salt(session):
  909. '''
  910. Run PyLint against Salt. Set PYLINT_REPORT to a path to capture output.
  911. '''
  912. flags = [
  913. '--disable=I,W1307,C0411,C0413,W8410,str-format-in-logging'
  914. ]
  915. if session.posargs:
  916. paths = session.posargs
  917. else:
  918. paths = ['setup.py', 'salt/']
  919. _lint(session, '.testing.pylintrc', flags, paths)
  920. @nox.session(python='2.7', name='lint-tests')
  921. def lint_tests(session):
  922. '''
  923. Run PyLint against Salt and it's test suite. Set PYLINT_REPORT to a path to capture output.
  924. '''
  925. flags = [
  926. '--disable=I,W0232,E1002,W1307,C0411,C0413,W8410,str-format-in-logging'
  927. ]
  928. if session.posargs:
  929. paths = session.posargs
  930. else:
  931. paths = ['tests/']
  932. _lint(session, '.testing.pylintrc', flags, paths)
  933. @nox.session(python='3')
  934. @nox.parametrize('update', [False, True])
  935. @nox.parametrize('compress', [False, True])
  936. def docs(session, compress, update):
  937. '''
  938. Build Salt's Documentation
  939. '''
  940. session.notify('docs-html(compress={})'.format(compress))
  941. session.notify('docs-man(compress={}, update={})'.format(compress, update))
  942. @nox.session(name='docs-html', python='3')
  943. @nox.parametrize('compress', [False, True])
  944. def docs_html(session, compress):
  945. '''
  946. Build Salt's HTML Documentation
  947. '''
  948. pydir = _get_pydir(session)
  949. if pydir == 'py3.4':
  950. session.error('Sphinx only runs on Python >= 3.5')
  951. requirements_file = 'requirements/static/docs.in'
  952. distro_constraints = [
  953. 'requirements/static/{}/docs.txt'.format(_get_pydir(session))
  954. ]
  955. install_command = [
  956. '--progress-bar=off', '-r', requirements_file
  957. ]
  958. for distro_constraint in distro_constraints:
  959. install_command.extend([
  960. '--constraint', distro_constraint
  961. ])
  962. session.install(*install_command, silent=PIP_INSTALL_SILENT)
  963. os.chdir('doc/')
  964. session.run('make', 'clean', external=True)
  965. session.run('make', 'html', 'SPHINXOPTS=-W', external=True)
  966. if compress:
  967. session.run('tar', '-cJvf', 'html-archive.tar.xz', '_build/html', external=True)
  968. os.chdir('..')
  969. @nox.session(name='docs-man', python='3')
  970. @nox.parametrize('update', [False, True])
  971. @nox.parametrize('compress', [False, True])
  972. def docs_man(session, compress, update):
  973. '''
  974. Build Salt's Manpages Documentation
  975. '''
  976. pydir = _get_pydir(session)
  977. if pydir == 'py3.4':
  978. session.error('Sphinx only runs on Python >= 3.5')
  979. requirements_file = 'requirements/static/docs.in'
  980. distro_constraints = [
  981. 'requirements/static/{}/docs.txt'.format(_get_pydir(session))
  982. ]
  983. install_command = [
  984. '--progress-bar=off', '-r', requirements_file
  985. ]
  986. for distro_constraint in distro_constraints:
  987. install_command.extend([
  988. '--constraint', distro_constraint
  989. ])
  990. session.install(*install_command, silent=PIP_INSTALL_SILENT)
  991. os.chdir('doc/')
  992. session.run('make', 'clean', external=True)
  993. session.run('make', 'man', 'SPHINXOPTS=-W', external=True)
  994. if update:
  995. session.run('rm', '-rf', 'man/', external=True)
  996. session.run('cp', '-Rp', '_build/man', 'man/', external=True)
  997. if compress:
  998. session.run('tar', '-cJvf', 'man-archive.tar.xz', '_build/man', external=True)
  999. os.chdir('..')