1
0

noxfile.py 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784
  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_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. for key in ('ubuntu-14.04', 'ubuntu-16.04', 'ubuntu-18.04', 'debian-8', 'debian-9'):
  128. system_python_packages[key] = system_python_packages['__debian_based_distros__']
  129. distro = _get_distro_info(session)
  130. distro_keys = [
  131. '{id}'.format(**distro),
  132. '{id}-{version}'.format(**distro),
  133. '{id}-{version_parts[major]}'.format(**distro)
  134. ]
  135. version_info = _get_session_python_version_info(session)
  136. py_version_keys = [
  137. '{}'.format(*version_info),
  138. '{}.{}'.format(*version_info)
  139. ]
  140. session_site_packages_dir = _get_session_python_site_packages_dir(session)
  141. for distro_key in distro_keys:
  142. if distro_key not in system_python_packages:
  143. continue
  144. patterns = system_python_packages[distro_key]
  145. for pattern in patterns:
  146. for py_version in py_version_keys:
  147. matches = set(glob.glob(pattern.format(py_version=py_version)))
  148. if not matches:
  149. continue
  150. for match in matches:
  151. src = os.path.realpath(match)
  152. dst = os.path.join(session_site_packages_dir, os.path.basename(match))
  153. if os.path.exists(dst):
  154. session.log('Not overwritting already existing %s with %s', dst, src)
  155. continue
  156. session.log('Copying %s into %s', src, dst)
  157. if os.path.isdir(src):
  158. shutil.copytree(src, dst)
  159. else:
  160. shutil.copyfile(src, dst)
  161. def _get_distro_pip_constraints(session, transport):
  162. # Install requirements
  163. distro_constraints = []
  164. if transport == 'tcp':
  165. # The TCP requirements are the exact same requirements as the ZeroMQ ones
  166. transport = 'zeromq'
  167. pydir = _get_pydir(session)
  168. if IS_WINDOWS:
  169. _distro_constraints = os.path.join('requirements',
  170. 'static',
  171. pydir,
  172. '{}-windows.txt'.format(transport))
  173. if os.path.exists(_distro_constraints):
  174. distro_constraints.append(_distro_constraints)
  175. _distro_constraints = os.path.join('requirements',
  176. 'static',
  177. pydir,
  178. 'windows.txt')
  179. if os.path.exists(_distro_constraints):
  180. distro_constraints.append(_distro_constraints)
  181. _distro_constraints = os.path.join('requirements',
  182. 'static',
  183. pydir,
  184. 'windows-crypto.txt')
  185. if os.path.exists(_distro_constraints):
  186. distro_constraints.append(_distro_constraints)
  187. elif IS_DARWIN:
  188. _distro_constraints = os.path.join('requirements',
  189. 'static',
  190. pydir,
  191. '{}-darwin.txt'.format(transport))
  192. if os.path.exists(_distro_constraints):
  193. distro_constraints.append(_distro_constraints)
  194. _distro_constraints = os.path.join('requirements',
  195. 'static',
  196. pydir,
  197. 'darwin.txt')
  198. if os.path.exists(_distro_constraints):
  199. distro_constraints.append(_distro_constraints)
  200. _distro_constraints = os.path.join('requirements',
  201. 'static',
  202. pydir,
  203. 'darwin-crypto.txt')
  204. if os.path.exists(_distro_constraints):
  205. distro_constraints.append(_distro_constraints)
  206. else:
  207. _install_system_packages(session)
  208. distro = _get_distro_info(session)
  209. distro_keys = [
  210. 'linux',
  211. '{id}'.format(**distro),
  212. '{id}-{version}'.format(**distro),
  213. '{id}-{version_parts[major]}'.format(**distro)
  214. ]
  215. for distro_key in distro_keys:
  216. _distro_constraints = os.path.join('requirements',
  217. 'static',
  218. pydir,
  219. '{}.txt'.format(distro_key))
  220. if os.path.exists(_distro_constraints):
  221. distro_constraints.append(_distro_constraints)
  222. _distro_constraints = os.path.join('requirements',
  223. 'static',
  224. pydir,
  225. '{}-crypto.txt'.format(distro_key))
  226. if os.path.exists(_distro_constraints):
  227. distro_constraints.append(_distro_constraints)
  228. _distro_constraints = os.path.join('requirements',
  229. 'static',
  230. pydir,
  231. '{}-{}.txt'.format(transport, distro_key))
  232. if os.path.exists(_distro_constraints):
  233. distro_constraints.append(_distro_constraints)
  234. distro_constraints.append(_distro_constraints)
  235. _distro_constraints = os.path.join('requirements',
  236. 'static',
  237. pydir,
  238. '{}-{}-crypto.txt'.format(transport, distro_key))
  239. if os.path.exists(_distro_constraints):
  240. distro_constraints.append(_distro_constraints)
  241. return distro_constraints
  242. def _install_requirements(session, transport, *extra_requirements):
  243. # Install requirements
  244. distro_constraints = _get_distro_pip_constraints(session, transport)
  245. _requirements_files = [
  246. os.path.join('requirements', 'base.txt'),
  247. os.path.join('requirements', 'zeromq.txt'),
  248. os.path.join('requirements', 'pytest.txt')
  249. ]
  250. if sys.platform.startswith('linux'):
  251. requirements_files = [
  252. os.path.join('requirements', 'static', 'linux.in')
  253. ]
  254. elif sys.platform.startswith('win'):
  255. requirements_files = [
  256. os.path.join('pkg', 'windows', 'req.txt'),
  257. os.path.join('requirements', 'static', 'windows.in')
  258. ]
  259. elif sys.platform.startswith('darwin'):
  260. requirements_files = [
  261. os.path.join('pkg', 'osx', 'req.txt'),
  262. os.path.join('pkg', 'osx', 'req_ext.txt'),
  263. os.path.join('requirements', 'static', 'darwin.in')
  264. ]
  265. while True:
  266. if not requirements_files:
  267. break
  268. requirements_file = requirements_files.pop(0)
  269. if requirements_file not in _requirements_files:
  270. _requirements_files.append(requirements_file)
  271. session.log('Processing {}'.format(requirements_file))
  272. with open(requirements_file) as rfh: # pylint: disable=resource-leakage
  273. for line in rfh:
  274. line = line.strip()
  275. if not line:
  276. continue
  277. if line.startswith('-r'):
  278. reqfile = os.path.join(os.path.dirname(requirements_file), line.strip().split()[-1])
  279. if reqfile in _requirements_files:
  280. continue
  281. _requirements_files.append(reqfile)
  282. continue
  283. for requirements_file in _requirements_files:
  284. install_command = [
  285. '--progress-bar=off', '-r', requirements_file
  286. ]
  287. for distro_constraint in distro_constraints:
  288. install_command.extend([
  289. '--constraint', distro_constraint
  290. ])
  291. session.install(*install_command, silent=PIP_INSTALL_SILENT)
  292. if extra_requirements:
  293. install_command = [
  294. '--progress-bar=off',
  295. ]
  296. for distro_constraint in distro_constraints:
  297. install_command.extend([
  298. '--constraint', distro_constraint
  299. ])
  300. install_command += list(extra_requirements)
  301. session.install(*install_command, silent=PIP_INSTALL_SILENT)
  302. def _run_with_coverage(session, *test_cmd):
  303. session.install('--progress-bar=off', 'coverage==4.5.3', silent=PIP_INSTALL_SILENT)
  304. session.run('coverage', 'erase')
  305. python_path_env_var = os.environ.get('PYTHONPATH') or None
  306. if python_path_env_var is None:
  307. python_path_env_var = SITECUSTOMIZE_DIR
  308. else:
  309. python_path_entries = python_path_env_var.split(os.pathsep)
  310. if SITECUSTOMIZE_DIR in python_path_entries:
  311. python_path_entries.remove(SITECUSTOMIZE_DIR)
  312. python_path_entries.insert(0, SITECUSTOMIZE_DIR)
  313. python_path_env_var = os.pathsep.join(python_path_entries)
  314. env = {
  315. # The updated python path so that sitecustomize is importable
  316. 'PYTHONPATH': python_path_env_var,
  317. # The full path to the .coverage data file. Makes sure we always write
  318. # them to the same directory
  319. 'COVERAGE_FILE': os.path.abspath(os.path.join(REPO_ROOT, '.coverage')),
  320. # Instruct sub processes to also run under coverage
  321. 'COVERAGE_PROCESS_START': os.path.join(REPO_ROOT, '.coveragerc')
  322. }
  323. if IS_DARWIN:
  324. # Don't nuke our multiprocessing efforts objc!
  325. # https://stackoverflow.com/questions/50168647/multiprocessing-causes-python-to-crash-and-gives-an-error-may-have-been-in-progr
  326. env['OBJC_DISABLE_INITIALIZE_FORK_SAFETY'] = 'YES'
  327. try:
  328. session.run(*test_cmd, env=env)
  329. finally:
  330. # Always combine and generate the XML coverage report
  331. try:
  332. session.run('coverage', 'combine')
  333. except CommandFailed:
  334. # Sometimes some of the coverage files are corrupt which would trigger a CommandFailed
  335. # exception
  336. pass
  337. # Generate report for salt code coverage
  338. session.run(
  339. 'coverage', 'xml',
  340. '-o', os.path.join('artifacts', 'coverage', 'salt.xml'),
  341. '--omit=tests/*',
  342. '--include=salt/*'
  343. )
  344. # Generate report for tests code coverage
  345. session.run(
  346. 'coverage', 'xml',
  347. '-o', os.path.join('artifacts', 'coverage', 'tests.xml'),
  348. '--omit=salt/*',
  349. '--include=tests/*'
  350. )
  351. @nox.session(python=_PYTHON_VERSIONS, name='pytest-parametrized')
  352. @nox.parametrize('coverage', [False, True])
  353. @nox.parametrize('transport', ['zeromq', 'tcp'])
  354. @nox.parametrize('crypto', [None, 'm2crypto', 'pycryptodomex'])
  355. def pytest_parametrized(session, coverage, transport, crypto):
  356. # Install requirements
  357. _install_requirements(session, transport)
  358. if crypto:
  359. if crypto == 'm2crypto':
  360. session.run('pip', 'uninstall', '-y', 'pycrypto', 'pycryptodome', 'pycryptodomex', silent=True)
  361. else:
  362. session.run('pip', 'uninstall', '-y', 'm2crypto', silent=True)
  363. distro_constraints = _get_distro_pip_constraints(session, transport)
  364. install_command = [
  365. '--progress-bar=off',
  366. ]
  367. for distro_constraint in distro_constraints:
  368. install_command.extend([
  369. '--constraint', distro_constraint
  370. ])
  371. install_command.append(crypto)
  372. session.install(*install_command, silent=PIP_INSTALL_SILENT)
  373. cmd_args = [
  374. '--rootdir', REPO_ROOT,
  375. '--log-file={}'.format(RUNTESTS_LOGFILE),
  376. '--log-file-level=debug',
  377. '--no-print-logs',
  378. '-ra',
  379. '-s',
  380. '--transport={}'.format(transport)
  381. ] + session.posargs
  382. _pytest(session, coverage, cmd_args)
  383. @nox.session(python=_PYTHON_VERSIONS)
  384. @nox.parametrize('coverage', [False, True])
  385. def pytest(session, coverage):
  386. '''
  387. pytest session with zeromq transport and default crypto
  388. '''
  389. session.notify(
  390. 'pytest-parametrized-{}(coverage={}, crypto=None, transport=\'zeromq\')'.format(
  391. session.python,
  392. coverage
  393. )
  394. )
  395. @nox.session(python=_PYTHON_VERSIONS, name='pytest-tcp')
  396. @nox.parametrize('coverage', [False, True])
  397. def pytest_tcp(session, coverage):
  398. '''
  399. pytest session with TCP transport and default crypto
  400. '''
  401. session.notify(
  402. 'pytest-parametrized-{}(coverage={}, crypto=None, transport=\'tcp\')'.format(
  403. session.python,
  404. coverage
  405. )
  406. )
  407. @nox.session(python=_PYTHON_VERSIONS, name='pytest-zeromq')
  408. @nox.parametrize('coverage', [False, True])
  409. def pytest_zeromq(session, coverage):
  410. '''
  411. pytest session with zeromq transport and default crypto
  412. '''
  413. session.notify(
  414. 'pytest-parametrized-{}(coverage={}, crypto=None, transport=\'zeromq\')'.format(
  415. session.python,
  416. coverage
  417. )
  418. )
  419. @nox.session(python=_PYTHON_VERSIONS, name='pytest-m2crypto')
  420. @nox.parametrize('coverage', [False, True])
  421. def pytest_m2crypto(session, coverage):
  422. '''
  423. pytest session with zeromq transport and m2crypto
  424. '''
  425. session.notify(
  426. 'pytest-parametrized-{}(coverage={}, crypto=\'m2crypto\', transport=\'zeromq\')'.format(
  427. session.python,
  428. coverage
  429. )
  430. )
  431. @nox.session(python=_PYTHON_VERSIONS, name='pytest-tcp-m2crypto')
  432. @nox.parametrize('coverage', [False, True])
  433. def pytest_tcp_m2crypto(session, coverage):
  434. '''
  435. pytest session with TCP transport and m2crypto
  436. '''
  437. session.notify(
  438. 'pytest-parametrized-{}(coverage={}, crypto=\'m2crypto\', transport=\'tcp\')'.format(
  439. session.python,
  440. coverage
  441. )
  442. )
  443. @nox.session(python=_PYTHON_VERSIONS, name='pytest-zeromq-m2crypto')
  444. @nox.parametrize('coverage', [False, True])
  445. def pytest_zeromq_m2crypto(session, coverage):
  446. '''
  447. pytest session with zeromq transport and m2crypto
  448. '''
  449. session.notify(
  450. 'pytest-parametrized-{}(coverage={}, crypto=\'m2crypto\', transport=\'zeromq\')'.format(
  451. session.python,
  452. coverage
  453. )
  454. )
  455. @nox.session(python=_PYTHON_VERSIONS, name='pytest-pycryptodomex')
  456. @nox.parametrize('coverage', [False, True])
  457. def pytest_pycryptodomex(session, coverage):
  458. '''
  459. pytest session with zeromq transport and pycryptodomex
  460. '''
  461. session.notify(
  462. 'pytest-parametrized-{}(coverage={}, crypto=\'pycryptodomex\', transport=\'zeromq\')'.format(
  463. session.python,
  464. coverage
  465. )
  466. )
  467. @nox.session(python=_PYTHON_VERSIONS, name='pytest-tcp-pycryptodomex')
  468. @nox.parametrize('coverage', [False, True])
  469. def pytest_tcp_pycryptodomex(session, coverage):
  470. '''
  471. pytest session with TCP transport and pycryptodomex
  472. '''
  473. session.notify(
  474. 'pytest-parametrized-{}(coverage={}, crypto=\'pycryptodomex\', transport=\'tcp\')'.format(
  475. session.python,
  476. coverage
  477. )
  478. )
  479. @nox.session(python=_PYTHON_VERSIONS, name='pytest-zeromq-pycryptodomex')
  480. @nox.parametrize('coverage', [False, True])
  481. def pytest_zeromq_pycryptodomex(session, coverage):
  482. '''
  483. pytest session with zeromq transport and pycryptodomex
  484. '''
  485. session.notify(
  486. 'pytest-parametrized-{}(coverage={}, crypto=\'pycryptodomex\', transport=\'zeromq\')'.format(
  487. session.python,
  488. coverage
  489. )
  490. )
  491. @nox.session(python=_PYTHON_VERSIONS, name='pytest-cloud')
  492. @nox.parametrize('coverage', [False, True])
  493. def pytest_cloud(session, coverage):
  494. # Install requirements
  495. _install_requirements(session, 'zeromq')
  496. pydir = _get_pydir(session)
  497. cloud_requirements = os.path.join('requirements', 'static', pydir, 'cloud.txt')
  498. session.install('--progress-bar=off', '-r', cloud_requirements, silent=PIP_INSTALL_SILENT)
  499. cmd_args = [
  500. '--rootdir', REPO_ROOT,
  501. '--log-file={}'.format(RUNTESTS_LOGFILE),
  502. '--log-file-level=debug',
  503. '--no-print-logs',
  504. '-ra',
  505. '-s',
  506. os.path.join('tests', 'integration', 'cloud', 'providers')
  507. ] + session.posargs
  508. _pytest(session, coverage, cmd_args)
  509. @nox.session(python=_PYTHON_VERSIONS, name='pytest-tornado')
  510. @nox.parametrize('coverage', [False, True])
  511. def pytest_tornado(session, coverage):
  512. # Install requirements
  513. _install_requirements(session, 'zeromq')
  514. session.install('--progress-bar=off', 'tornado==5.0.2', silent=PIP_INSTALL_SILENT)
  515. session.install('--progress-bar=off', 'pyzmq==17.0.0', silent=PIP_INSTALL_SILENT)
  516. cmd_args = [
  517. '--rootdir', REPO_ROOT,
  518. '--log-file={}'.format(RUNTESTS_LOGFILE),
  519. '--log-file-level=debug',
  520. '--no-print-logs',
  521. '-ra',
  522. '-s',
  523. ] + session.posargs
  524. _pytest(session, coverage, cmd_args)
  525. def _pytest(session, coverage, cmd_args):
  526. # Create required artifacts directories
  527. _create_ci_directories()
  528. env = None
  529. if IS_DARWIN:
  530. # Don't nuke our multiprocessing efforts objc!
  531. # https://stackoverflow.com/questions/50168647/multiprocessing-causes-python-to-crash-and-gives-an-error-may-have-been-in-progr
  532. env = {'OBJC_DISABLE_INITIALIZE_FORK_SAFETY': 'YES'}
  533. try:
  534. if coverage is True:
  535. _run_with_coverage(session, 'coverage', 'run', '-m', 'py.test', *cmd_args)
  536. else:
  537. session.run('py.test', *cmd_args, env=env)
  538. except CommandFailed:
  539. # Not rerunning failed tests for now
  540. raise
  541. # Re-run failed tests
  542. session.log('Re-running failed tests')
  543. for idx, parg in enumerate(cmd_args):
  544. if parg.startswith('--junitxml='):
  545. cmd_args[idx] = parg.replace('.xml', '-rerun-failed.xml')
  546. cmd_args.append('--lf')
  547. if coverage is True:
  548. _run_with_coverage(session, 'coverage', 'run', '-m', 'py.test', *cmd_args)
  549. else:
  550. session.run('py.test', *cmd_args, env=env)
  551. def _lint(session, rcfile, flags, paths):
  552. _install_requirements(session, 'zeromq')
  553. requirements_file = 'requirements/static/lint.in'
  554. distro_constraints = [
  555. 'requirements/static/{}/lint.txt'.format(_get_pydir(session))
  556. ]
  557. install_command = [
  558. '--progress-bar=off', '-r', requirements_file
  559. ]
  560. for distro_constraint in distro_constraints:
  561. install_command.extend([
  562. '--constraint', distro_constraint
  563. ])
  564. session.install(*install_command, silent=PIP_INSTALL_SILENT)
  565. session.run('pylint', '--version')
  566. pylint_report_path = os.environ.get('PYLINT_REPORT')
  567. cmd_args = [
  568. 'pylint',
  569. '--rcfile={}'.format(rcfile)
  570. ] + list(flags) + list(paths)
  571. stdout = tempfile.TemporaryFile(mode='w+b')
  572. lint_failed = False
  573. try:
  574. session.run(*cmd_args, stdout=stdout)
  575. except CommandFailed:
  576. lint_failed = True
  577. raise
  578. finally:
  579. stdout.seek(0)
  580. contents = stdout.read()
  581. if contents:
  582. if IS_PY3:
  583. contents = contents.decode('utf-8')
  584. else:
  585. contents = contents.encode('utf-8')
  586. sys.stdout.write(contents)
  587. sys.stdout.flush()
  588. if pylint_report_path:
  589. # Write report
  590. with open(pylint_report_path, 'w') as wfh:
  591. wfh.write(contents)
  592. session.log('Report file written to %r', pylint_report_path)
  593. stdout.close()
  594. @nox.session(python='2.7')
  595. def lint(session):
  596. '''
  597. Run PyLint against Salt and it's test suite. Set PYLINT_REPORT to a path to capture output.
  598. '''
  599. session.notify('lint-salt-{}'.format(session.python))
  600. session.notify('lint-tests-{}'.format(session.python))
  601. @nox.session(python='2.7', name='lint-salt')
  602. def lint_salt(session):
  603. '''
  604. Run PyLint against Salt. Set PYLINT_REPORT to a path to capture output.
  605. '''
  606. flags = [
  607. '--disable=I,W1307,C0411,C0413,W8410,str-format-in-logging'
  608. ]
  609. if session.posargs:
  610. paths = session.posargs
  611. else:
  612. paths = ['setup.py', 'salt/']
  613. _lint(session, '.testing.pylintrc', flags, paths)
  614. @nox.session(python='2.7', name='lint-tests')
  615. def lint_tests(session):
  616. '''
  617. Run PyLint against Salt and it's test suite. Set PYLINT_REPORT to a path to capture output.
  618. '''
  619. flags = [
  620. '--disable=I,W0232,E1002,W1307,C0411,C0413,W8410,str-format-in-logging'
  621. ]
  622. if session.posargs:
  623. paths = session.posargs
  624. else:
  625. paths = ['tests/']
  626. _lint(session, '.testing.pylintrc', flags, paths)
  627. @nox.session(python='3')
  628. @nox.parametrize('update', [False, True])
  629. @nox.parametrize('compress', [False, True])
  630. def docs(session, compress, update):
  631. '''
  632. Build Salt's Documentation
  633. '''
  634. session.notify('docs-html(compress={})'.format(compress))
  635. session.notify('docs-man(compress={}, update={})'.format(compress, update))
  636. @nox.session(name='docs-html', python='3')
  637. @nox.parametrize('compress', [False, True])
  638. def docs_html(session, compress):
  639. '''
  640. Build Salt's HTML Documentation
  641. '''
  642. pydir = _get_pydir(session)
  643. if pydir == 'py3.4':
  644. session.error('Sphinx only runs on Python >= 3.5')
  645. requirements_file = 'requirements/static/docs.in'
  646. distro_constraints = [
  647. 'requirements/static/{}/docs.txt'.format(_get_pydir(session))
  648. ]
  649. install_command = [
  650. '--progress-bar=off', '-r', requirements_file
  651. ]
  652. for distro_constraint in distro_constraints:
  653. install_command.extend([
  654. '--constraint', distro_constraint
  655. ])
  656. session.install(*install_command, silent=PIP_INSTALL_SILENT)
  657. os.chdir('doc/')
  658. session.run('make', 'clean', external=True)
  659. session.run('make', 'html', 'SPHINXOPTS=-W', external=True)
  660. if compress:
  661. session.run('tar', '-czvf', 'html-archive.tar.gz', '_build/html', external=True)
  662. os.chdir('..')
  663. @nox.session(name='docs-man', python='3')
  664. @nox.parametrize('update', [False, True])
  665. @nox.parametrize('compress', [False, True])
  666. def docs_man(session, compress, update):
  667. '''
  668. Build Salt's Manpages Documentation
  669. '''
  670. pydir = _get_pydir(session)
  671. if pydir == 'py3.4':
  672. session.error('Sphinx only runs on Python >= 3.5')
  673. requirements_file = 'requirements/static/docs.in'
  674. distro_constraints = [
  675. 'requirements/static/{}/docs.txt'.format(_get_pydir(session))
  676. ]
  677. install_command = [
  678. '--progress-bar=off', '-r', requirements_file
  679. ]
  680. for distro_constraint in distro_constraints:
  681. install_command.extend([
  682. '--constraint', distro_constraint
  683. ])
  684. session.install(*install_command, silent=PIP_INSTALL_SILENT)
  685. os.chdir('doc/')
  686. session.run('make', 'clean', external=True)
  687. session.run('make', 'man', 'SPHINXOPTS=-W', external=True)
  688. if update:
  689. session.run('rm', '-rf', 'man/', external=True)
  690. session.run('cp', '-Rp', '_build/man', 'man/', external=True)
  691. if compress:
  692. session.run('tar', '-czvf', 'man-archive.tar.gz', '_build/man', external=True)
  693. os.chdir('..')