1
0

noxfile.py 32 KB

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