[tox]
envlist =
    py3,
    black,
    ruff,
    isort,
    mypy,
    pylint

[doc8]
ignore-path-errors=doc/rtd/topics/faq.rst;D001

[testenv]
package = skip
basepython = python3
setenv =
    LC_ALL = en_US.utf-8
passenv =
    PYTEST_ADDOPTS
    HYPOTHESIS_PROFILE
deps =
    -r{toxinidir}/requirements-all.txt
    -r{toxinidir}/requirements.txt
    -r{toxinidir}/test-requirements.txt

[types]
deps =
    # each release of type stubs relates to a specific version of a library
    # so leave these unpinned
    types-jsonschema
    types-Jinja2
    types-oauthlib
    types-passlib
    types-PyYAML
    types-requests
    types-setuptools
    typing-extensions

[pinned_versions]
deps =
    {[types]deps}
    black==24.8.0
    hypothesis==6.111.0
    hypothesis_jsonschema==0.23.1
    isort==5.13.2
    mypy==1.11.1
    pylint==3.2.6
    ruff==0.6.4

[latest_versions]
deps =
    {[types]deps}
    black
    hypothesis
    hypothesis_jsonschema
    isort
    mypy
    pylint
    ruff

[files]
schema = cloudinit/config/schemas/schema-cloud-config-v1.json
version = cloudinit/config/schemas/versions.schema.cloud-config.json
network_v1 = cloudinit/config/schemas/schema-network-config-v1.json
network_v2 = cloudinit/config/schemas/schema-network-config-v2.json

[testenv:ruff]
deps = {[pinned_versions]deps}
commands = {envpython} -m ruff check {posargs:.}

[testenv:pylint]
deps = {[pinned_versions]deps}
commands = {envpython} -m pylint {posargs:.}

[testenv:black]
deps = {[pinned_versions]deps}
commands = {envpython} -m black --check {posargs:.}

[testenv:isort]
deps = {[pinned_versions]deps}
commands = {envpython} -m isort --check-only --diff {posargs:.}

[testenv:mypy]
deps =
    -r{toxinidir}/integration-requirements.txt
    {[testenv]deps}
    {[pinned_versions]deps}
commands = {envpython} -m mypy {posargs:cloudinit/ tests/ tools/}

[testenv:check_format]
deps =
    -r{toxinidir}/integration-requirements.txt
    {[testenv]deps}
    {[pinned_versions]deps}
commands =
    {envpython} -m ruff check {posargs:.}
    {envpython} -m pylint {posargs:.}
    {envpython} -m black --check {posargs:.}
    {envpython} -m isort --check-only --diff {posargs:.}
    {envpython} -m mypy {posargs:cloudinit/ tests/ tools/}

[testenv:check_format_tip]
deps =
    -r{toxinidir}/integration-requirements.txt
    {[testenv]deps}
    {[latest_versions]deps}
commands =
    {envpython} -m ruff check {posargs:.}
    {envpython} -m pylint {posargs:.}
    {envpython} -m black --check {posargs:.}
    {envpython} -m isort --check-only --diff {posargs:.}
    {envpython} -m mypy {posargs:cloudinit/ tests/ tools/}

[testenv:do_format]
deps = {[pinned_versions]deps}
commands =
    {envpython} -m isort .
    {envpython} -m black .
    {envpython} -m json.tool --indent 2 {[files]schema} {[files]schema}
    {envpython} -m json.tool --indent 2 {[files]version} {[files]version}
    {envpython} -m json.tool --indent 2 {[files]network_v1} {[files]network_v1}
    {envpython} -m json.tool --indent 2 {[files]network_v2} {[files]network_v2}

[testenv:do_format_tip]
deps = {[latest_versions]deps}
commands =
    {envpython} -m isort .
    {envpython} -m black .
    {envpython} -m json.tool --indent 2 {[files]schema} {[files]schema}
    {envpython} -m json.tool --indent 2 {[files]version} {[files]version}
    {envpython} -m json.tool --indent 2 {[files]network_v1} {[files]network_v1}
    {envpython} -m json.tool --indent 2 {[files]network_v2} {[files]network_v2}

[testenv:py3]
commands = {envpython} -m pytest -vv -m "not hypothesis_slow" --cov=cloudinit --cov-branch {posargs:tests/unittests}

[testenv:py3-fast]
deps =
    {[testenv]deps}
    pytest-xdist
commands = {envpython} -m pytest -n auto -m "not hypothesis_slow" -m "not serial" {posargs:tests/unittests}

[testenv:hypothesis-slow]
deps = {[pinned_versions]deps}
    {[testenv]deps}
commands = {envpython} -m pytest \
            -m hypothesis_slow \
            --hypothesis-show-statistics \
            {posargs:tests/unittests}

#commands = {envpython} -X tracemalloc=40 -Werror::ResourceWarning:cloudinit -m pytest \
[testenv:py3-leak]
commands = {envpython} -X tracemalloc=40 -Wall -m pytest {posargs:tests/unittests}

# generates html coverage report from the most recent pytest run
[testenv:coverage-html]
deps = {[testenv]deps}
commands = coverage html -i

# prints out the coverage report "table" from the most recent pytest run
[testenv:coverage-report]
deps = {[testenv]deps}
commands = coverage report -i

[testenv:lowest-supported]
# Tox is going to install requirements from pip. This is fine for
# testing python version compatibility, but when we build cloud-init, we are
# building against the dependencies in the OS repo, not pip. The OS
# dependencies will generally be older than what is found in pip.

# To obtain these versions, check the versions of these libraries
# in the oldest support Ubuntu distro. Theses versions are from bionic.
deps =
    jinja2==2.10.1
    oauthlib==3.1.0
    pyserial==3.4
    configobj==5.0.6
    pyyaml==5.3.1
    requests==2.22.0
    jsonpatch==1.23
    jsonschema==3.2.0
    # test-requirements
    pytest==4.6.9
    pytest-cov==2.8.1
    pytest-mock==1.10.4
    setuptools==45.2.0
    responses==0.9.0
    passlib
    # required for this version of jinja2
    markupsafe==2.0.1
commands = {envpython} -m pytest -m "not hypothesis_slow" --cov=cloud-init --cov-branch {posargs:tests/unittests}

[testenv:doc]
deps = -r{toxinidir}/doc-requirements.txt
commands =
    {envpython} -m sphinx {posargs:-W doc/rtd doc/rtd_html}
    {envpython} -m doc8 doc/rtd
passenv =
    CLOUD_INIT_*

[testenv:doc-spelling]
deps = -r{toxinidir}/doc-requirements.txt
commands = {envpython} -m sphinx -b spelling {posargs:-W doc/rtd doc/rtd_html}

# linkcheck shows false positives and has noisy output.
# Despite these limitations, it is better than a manual search of the docs.
# suggested workflow is:
#
#     tox -e linkcheck | grep broken  # takes some time
#
# followed by manual verification of the links reported
[testenv:linkcheck]
deps = -r{toxinidir}/doc-requirements.txt
commands =
    {envpython} -m sphinx {posargs:-b linkcheck doc/rtd doc/rtd_html}

[testenv:tip-ruff]
deps = {[latest_versions]deps}
commands = {envpython} -m ruff check {posargs:.}

[testenv:tip-mypy]
deps =
    -r{toxinidir}/integration-requirements.txt
    {[testenv]deps}
    {[latest_versions]deps}
commands = {envpython} -m mypy {posargs:cloudinit/ tests/ tools/}

[testenv:tip-pylint]
deps = {[latest_versions]deps}
    {[testenv]deps}
commands = {envpython} -m pylint {posargs:.}

[testenv:tip-black]
deps = {[latest_versions]deps}
commands = {envpython} -m black --check {posargs:.}

[testenv:tip-isort]
deps = {[latest_versions]deps}
commands = {envpython} -m isort --check-only --diff {posargs:.}

[testenv:integration-tests]
deps = -r{toxinidir}/integration-requirements.txt
commands = {envpython} -m pytest --log-cli-level=INFO {posargs:tests/integration_tests}
passenv =
    CLOUD_INIT_*
    PYCLOUDLIB_*
    SSH_AUTH_SOCK
    OS_*

[testenv:integration-tests-fast]
deps = 
    -r{toxinidir}/integration-requirements.txt
    -r{toxinidir}/test-requirements.txt
commands = {envpython} -m pytest --log-cli-level=INFO -n auto -m "not hypothesis_slow" -m "not serial" {posargs:tests/integration_tests}
passenv =
    CLOUD_INIT_*
    PYCLOUDLIB_*
    SSH_AUTH_SOCK
    OS_*

[testenv:integration-tests-ci]
deps = -r{toxinidir}/integration-requirements.txt
commands = {envpython} -m pytest --log-cli-level=INFO {posargs:tests/integration_tests}
passenv =
    CLOUD_INIT_*
    SSH_AUTH_SOCK
    OS_*
    GITHUB_*
setenv =
    PYTEST_ADDOPTS="-m ci and not adhoc"

[testenv:integration-tests-jenkins]
# Pytest's RC=1 means "Tests were collected and run but some of the tests failed".
# Do not fail in this case, but let Jenkins handle it using the junit report.
deps = -r{toxinidir}/integration-requirements.txt
allowlist_externals = sh
commands = sh -c "{envpython} -m pytest --log-cli-level=INFO {posargs:tests/integration_tests/none} || [ $? -eq 1 ]"
passenv =
    *_proxy
    CLOUD_INIT_*
    PYCLOUDLIB_*
    SSH_AUTH_SOCK
    OS_*
    GOOGLE_*
    GCP_*
setenv =
    PYTEST_ADDOPTS="-m not adhoc"
    ON_JENKINS="1"

[pytest]
# TODO: s/--strict/--strict-markers/ once pytest version is high enough
testpaths = tools tests/unittests
addopts = --strict
log_format = %(asctime)s %(levelname)-9s %(name)s:%(filename)s:%(lineno)d %(message)s
log_date_format = %Y-%m-%d %H:%M:%S
markers =
    adhoc: only run on adhoc basis, not in any CI environment (travis or jenkins)
    allow_all_subp: allow all subp usage (disable_subp_usage)
    allow_subp_for: allow subp usage for the given commands (disable_subp_usage)
    ci: run this integration test as part of CI test runs
    ds_sys_cfg: a sys_cfg dict to be used by datasource fixtures
    hypothesis_slow: hypothesis test too slow to run as unit test
    instance_name: the name to be used for the test instance
    integration_cloud_args: args for IntegrationCloud customization
    is_iscsi: whether is an instance has iscsi net cfg or not
    lxd_config_dict: set the config_dict passed on LXD instance creation
    lxd_setup: specify callable to be called between init and start
    lxd_use_exec: `execute` will use `lxc exec` instead of SSH
    serial: tests that do not work in parallel, skipped with py3-fast
    unstable: skip this test because it is flakey
    user_data: the user data to be passed to the test instance
    allow_dns_lookup: disable autochecking for host network configuration

[coverage:paths]
source =
    cloudinit/
    /usr/lib/python3/dist-packages/cloudinit/
