1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133
|
"""Runner script for tools used in local development and CI.
Notes:
* 'test' and 'test-<python version>' commands: nox will create separate virtualenvs per python
version, and use `poetry.lock` to determine dependency versions
* 'lint' command: tools and environments are managed by pre-commit
* All other commands: the current environment will be used instead of creating new ones
* Run `nox -l` to see all available commands
"""
import platform
from os import getenv
from os.path import join
from pathlib import Path
from shutil import rmtree
import nox
from nox_poetry import session
nox.options.reuse_existing_virtualenvs = True
nox.options.sessions = ['lint', 'cov']
LIVE_DOCS_PORT = 8181
LIVE_DOCS_IGNORE = ['*.pyc', '*.tmp', join('**', 'modules', '*')]
LIVE_DOCS_WATCH = ['requests_cache', 'examples']
DOCS_DIR = Path('docs')
DOC_BUILD_DIR = DOCS_DIR / '_build' / 'html'
TEST_DIR = Path('tests')
CLEAN_DIRS = ['dist', 'build', DOCS_DIR / '_build', DOCS_DIR / 'modules']
PYTHON_VERSIONS = ['3.8', '3.9', '3.10', '3.11', '3.12', 'pypy3.9', 'pypy3.10']
UNIT_TESTS = TEST_DIR / 'unit'
INTEGRATION_TESTS = TEST_DIR / 'integration'
COMPAT_TESTS = TEST_DIR / 'compat'
ALL_TESTS = [UNIT_TESTS, INTEGRATION_TESTS, COMPAT_TESTS]
STRESS_TEST_MULTIPLIER = 10
DEFAULT_COVERAGE_FORMATS = ['html', 'term']
# Run tests in parallel, grouped by test module
XDIST_ARGS = '--numprocesses=auto --dist=loadfile'
IS_PYPY = platform.python_implementation() == 'PyPy'
@session(python=PYTHON_VERSIONS)
def test(session):
"""Run tests in a separate virtualenv per python version"""
test_paths = session.posargs or ALL_TESTS
session.install('.', 'pytest', 'pytest-xdist', 'requests-mock', 'rich', 'timeout-decorator')
cmd = f'pytest -rsxX {XDIST_ARGS}'
session.run(*cmd.split(' '), *test_paths)
@session(python=False, name='test-current')
def test_current(session):
"""Run tests using the current virtualenv"""
test_paths = session.posargs or ALL_TESTS
cmd = f'pytest -rsxX {XDIST_ARGS}'
session.run(*cmd.split(' '), *test_paths)
@session(python=False)
def clean(session):
"""Clean up temporary build + documentation files"""
for dir in CLEAN_DIRS:
print(f'Removing {dir}')
rmtree(dir, ignore_errors=True)
@session(python=False, name='cov')
def coverage(session):
"""Run tests and generate coverage report"""
cmd = ['pytest', *ALL_TESTS, '-rsxX', '--cov']
if not IS_PYPY:
cmd += XDIST_ARGS.split(' ')
# Add coverage formats
cov_formats = session.posargs or DEFAULT_COVERAGE_FORMATS
cmd += [f'--cov-report={f}' for f in cov_formats]
# Add verbose flag, if set by environment
if getenv('PYTEST_VERBOSE'):
cmd += ['--verbose']
session.run(*cmd)
@session(python=False, name='stress')
def stress_test(session):
"""Run concurrency tests with a higher stress test multiplier"""
cmd = f'pytest {INTEGRATION_TESTS} -rs -k concurrency'
multiplier = session.posargs[0] if session.posargs else STRESS_TEST_MULTIPLIER
session.run(
*cmd.split(' '),
env={'STRESS_TEST_MULTIPLIER': str(multiplier)},
)
@session(python=False)
def docs(session):
"""Build Sphinx documentation"""
session.run('sphinx-build', 'docs', DOC_BUILD_DIR, '-j', 'auto')
@session(python=False)
def linkcheck(session):
"""Check documentation for dead links"""
session.run('sphinx-build', 'docs', DOC_BUILD_DIR, '-b', 'linkcheck')
@session(python=False)
def livedocs(session):
"""Auto-build docs with live reload in browser.
Add `--open` to also open the browser after starting.
"""
args = ['-a']
args += [f'--watch {pattern}' for pattern in LIVE_DOCS_WATCH]
args += [f'--ignore {pattern}' for pattern in LIVE_DOCS_IGNORE]
args += [f'--port {LIVE_DOCS_PORT}', '-j auto']
if session.posargs == ['open']:
args.append('--open-browser')
clean(session)
cmd = 'sphinx-autobuild docs docs/_build/html ' + ' '.join(args)
session.run(*cmd.split(' '))
@session(python=False)
def lint(session):
"""Run linters and code formatters via pre-commit"""
cmd = 'pre-commit run --all-files'
session.run(*cmd.split(' '))
|