1
0

noxfile.py 30 KB

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