[tox]
envlist =
    py{36,37,38,39,310}-{local,integ,examples},
    mypy-py{3},
    bandit, doc8, readme, docs,
    flake8{,-tests,-examples}, pylint{,-tests,-examples},
    isort-check, black-check

# Additional test environments:
# vulture :: Runs vulture. Prone to false-positives.
# linters :: Runs all linters over all source code.
# linters-tests :: Runs all linters over all tests.

# Autoformatter helper environments:
# autoformat : Apply autoformatting
# black-check : Check for "black" issues
# blacken : Fix all "black" issues
# isort-seed : Generate a known_third_party list for isort.
#   NOTE: generates in .isort.cfg; move to isort section in setup.cfg
# isort-check : Check for isort issues
# isort : Fix isort issues

# Operational helper environments:
# docs :: Builds Sphinx documentation.
# serve-docs :: Starts local webserver to serve built documentation.
# park :: Builds name-parking packages using pypi-parker.
# build :: Builds source and wheel dist files.
# test-release :: Builds dist files and uploads to testpypi pypirc profile.
# release :: Builds dist files and uploads to pypi pypirc profile.

# Reporting environments:
#
# coverage :: Runs code coverage, failing the build if coverage is below the configured threshold

[testenv]
passenv =
    # Identifies AWS KMS key id to use in integration tests
    AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID \
    AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID_2 \
    # Identifies AWS MRK KMS key id to use in integration tests
    AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_MRK_KEY_ID_1 \
    # Pass through AWS credentials
    AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN \
    # AWS Role access in CodeBuild is via the contaner URI
    AWS_CONTAINER_CREDENTIALS_RELATIVE_URI \
    # Pass through AWS profile name (useful for local testing)
    AWS_PROFILE \
    # The region for the MRK aware components
    AWS_REGION \
    AWS_DEFAULT_REGION \
     # Pass through custom pip config file settings
    PIP_CONFIG_FILE
setenv =
  PATH = {env:PATH}{:}examples/bin
sitepackages = False
deps =
    -rdev_requirements/test-requirements.txt
    examples: ./examples
commands =
    local: pytest -m local -l test/ {posargs}
    integ: pytest -m integ -l test/ {posargs}
    examples: pytest -m examples -l examples/test {posargs}
    all: pytest --cov aws_encryption_sdk_cli -l test/ {posargs}

# Run code coverage on the unit tests
[testenv:coverage]
commands = pytest --cov aws_encryption_sdk test/

# mypy
[testenv:mypy-coverage]
commands =
    # Make mypy linecoverage report readable by coverage
    python -c \
        "t = open('.coverage', 'w');\
        c = open('build/coverage.json').read();\
        t.write('!coverage.py: This is a private format, don\'t read it directly!\n');\
        t.write(c);\
        t.close()"
    coverage report -m

[testenv:mypy-common]
basepython = python3
deps = -rdev_requirements/linter-requirements.txt

[testenv:mypy-py3]
basepython = {[testenv:mypy-common]basepython}
deps = -rdev_requirements/coverage-requirements.txt
commands =
    python -m mypy \
        --show-error-codes \
        --linecoverage-report build \
        src/aws_encryption_sdk_cli/ \
        {posargs}
    {[testenv:mypy-coverage]commands}

# Linters
[testenv:flake8]
basepython = python3
deps = -rdev_requirements/linter-requirements.txt
commands =
    flake8 \
        src/aws_encryption_sdk_cli/ \
        setup.py \
        doc/conf.py \
        {posargs}

[testenv:flake8-tests]
basepython = {[testenv:flake8]basepython}
deps = {[testenv:flake8]deps}
commands =
    flake8 \
        # Ignore F811 redefinition errors in tests (breaks with pytest-mock use)
        # Ignore D103 docstring requirements for tests
        # Ignore D401 imperative mood for module docstrings (was hanging up on integration_test_utils)
        # E203 is not PEP8 compliant https://github.com/ambv/black#slices
        # W503 is not PEP8 compliant https://github.com/ambv/black#line-breaks--binary-operators
        --ignore F811,D103,D401,E203,W503 \
        test/ \
        {posargs}

[testenv:flake8-examples]
basepython = {[testenv:flake8]basepython}
deps = {[testenv:flake8]deps}
commands =
    flake8 \
        # Ignore C901 complexity requirements (examples optimize for straightforward readability)
        --ignore C901 \
        examples/src/aws_encryption_sdk_cli_examples/
    flake8 \
        # Ignore F811 redefinition errors in tests (breaks with fixture use)
        # Ignore D103 docstring requirements for tests
        --ignore F811,D103 \
        # Our path munging confuses isort, so disable flake8-isort checks on that file
        # --per-file-ignores="examples/test/examples_test_utils.py:I003,I004,I005,examples/test/test_aws_kms_encrypted_examples.py:I005" \
        examples/test/

[testenv:blacken-src]
basepython = python3
deps = -rdev_requirements/linter-requirements.txt
commands =
    black --line-length 120 \
        src/aws_encryption_sdk_cli/ \
        setup.py \
        doc/conf.py \
        test/ \
        {posargs}


[testenv:blacken]
basepython = python3
deps =
    {[testenv:blacken-src]deps}
commands =
    {[testenv:blacken-src]commands}

[testenv:black-check]
basepython = python3
deps =
    {[testenv:blacken]deps}
commands =
    {[testenv:blacken-src]commands} --diff

[testenv:isort-seed]
basepython = python3
deps = -rdev_requirements/linter-requirements.txt
commands = seed-isort-config

[testenv:isort]
basepython = python3
# We need >=5.0.0 because
# several configuration settings changed with 5.0.0
deps = -rdev_requirements/linter-requirements.txt
commands = isort \
    src \
    test \
    doc \
    setup.py \
    {posargs}

[testenv:isort-check]
basepython = python3
deps = {[testenv:isort]deps}
commands = {[testenv:isort]commands} -c

[testenv:autoformat]
basepython = python3
deps =
    {[testenv:blacken]deps}
    {[testenv:isort]deps}
commands =
    {[testenv:blacken]commands}
    {[testenv:isort]commands}

[testenv:pylint]
basepython = python3
deps =
    {[testenv]deps}
    -rdev_requirements/linter-requirements.txt
commands =
    pylint \
        --rcfile=src/pylintrc \
        src/aws_encryption_sdk_cli/  \
        setup.py \
        doc/conf.py \
        {posargs}

[testenv:pylint-examples]
basepython = {[testenv:pylint]basepython}
deps = {[testenv:pylint]deps}
commands =
    pylint --rcfile=examples/src/pylintrc examples/src/aws_encryption_sdk_cli_examples/
    pylint --rcfile=examples/test/pylintrc --disable R0801 examples/test/

[testenv:pylint-tests]
basepython = {[testenv:pylint]basepython}
deps = {[testenv:pylint]deps}
commands =
    pylint \
        --rcfile=test/pylintrc \
        test/unit/ \
        test/integration/ \
        {posargs}

[testenv:doc8]
basepython = python3
deps = -rdev_requirements/linter-requirements.txt
commands = doc8 doc/index.rst test/integration/README.rst README.rst CHANGELOG.rst

[testenv:readme]
basepython = python3
deps = -rdev_requirements/linter-requirements.txt
commands = python setup.py check -r -s

[testenv:bandit]
basepython = python3
deps = -rdev_requirements/linter-requirements.txt
commands = bandit -r src/aws_encryption_sdk_cli/

# Prone to false positives: only run independently
[testenv:vulture]
basepython = python3
deps = -rdev_requirements/linter-requirements.txt
commands = vulture src/aws_encryption_sdk_cli/

[testenv:linters]
basepython = python3
deps =
    {[testenv:flake8]deps}
    {[testenv:pylint]deps}
    {[testenv:doc8]deps}
    {[testenv:readme]deps}
    {[testenv:bandit]deps}
commands =
    {[testenv:flake8]commands}
    {[testenv:pylint]commands}
    {[testenv:doc8]commands}
    {[testenv:readme]commands}
    {[testenv:bandit]commands}

[testenv:linters-tests]
basepython = python3
deps =
    {[testenv:flake8-tests]deps}
    {[testenv:pylint-tests]deps}
commands =
    {[testenv:flake8-tests]commands}
    {[testenv:pylint-tests]commands}

[testenv:linters-examples]
basepython = python3
deps =
    {[testenv:flake8-examples]deps}
    {[testenv:pylint-examples]deps}
commands =
    {[testenv:flake8-examples]commands}
    {[testenv:pylint-examples]commands}

# Documentation
[testenv:docs]
basepython = python3
deps = -rdev_requirements/doc-requirements.txt
commands =
    sphinx-build -E -c doc/ -b html doc/ doc/build/html

[testenv:serve-docs]
basepython = python3
skip_install = true
changedir = doc/build/html
deps =
commands =
    python -m http.server {posargs}

# Release tooling
[testenv:park]
basepython = python3
skip_install = true
deps = -rdev_requirements/release-requirements.txt
commands = python setup.py park

[testenv:build]
basepython = python3
skip_install = true
deps =
    {[testenv:docs]deps}
    -rdev_requirements/release-requirements.txt
commands =
    {[testenv:docs]commands}
    python setup.py sdist bdist_wheel

[testenv:release-base]
basepython = python3
skip_install = true
deps =
    {[testenv:build]deps}
    -rdev_requirements/release-requirements.txt
passenv =
    # Intentionally omit TWINE_REPOSITORY_URL from the passenv list,
    # as this overrides other ways of setting the repository and could
    # unexpectedly result in releasing to the wrong repo
    {[testenv]passenv} \
    TWINE_USERNAME \
    TWINE_PASSWORD
commands =
    {[testenv:build]commands}

[testenv:release-private]
basepython = python3
skip_install = true
deps = {[testenv:release-base]deps}
passenv =
    {[testenv:release-base]passenv} \
    TWINE_REPOSITORY_URL
setenv =
    # Explicitly set the URL as the env variable value, which will cause us to
    # throw an error if the variable is not set. Otherwise, omission of the
    # env variable could cause us to unintentionally upload to the wrong repo
    TWINE_REPOSITORY_URL = {env:TWINE_REPOSITORY_URL}
commands =
    {[testenv:release-base]commands}
    # Omitting an explicit repository will cause twine to use the repository
    # specified in the environment variable
    twine upload --skip-existing {toxinidir}/dist/*

[testenv:test-release]
basepython = python3
skip_install = true
deps = {[testenv:release-base]deps}
passenv =
    {[testenv:release-base]passenv}
commands =
    {[testenv:release-base]commands}
    twine upload --skip-existing --repository testpypi {toxinidir}/dist/*

[testenv:release]
basepython = python3
skip_install = true
deps = {[testenv:release-base]deps}
passenv =
    {[testenv:release-base]passenv}
whitelist_externals = unset
commands =
    {[testenv:release-base]commands}
    twine upload --skip-existing --repository pypi {toxinidir}/dist/*