diff --git a/.azure-pipelines/scripts/aggregate-coverage.sh b/.azure-pipelines/scripts/aggregate-coverage.sh index f3113dd..c196ab0 100755 --- a/.azure-pipelines/scripts/aggregate-coverage.sh +++ b/.azure-pipelines/scripts/aggregate-coverage.sh @@ -13,8 +13,8 @@ options=(--venv --venv-system-site-packages --color -v) ansible-test coverage combine --export "${agent_temp_directory}/coverage/" "${options[@]}" -if ansible-test coverage analyze targets generate --help >/dev/null 2>&1; then - # Only analyze coverage if the installed version of ansible-test supports it. - # Doing so allows this script to work unmodified for multiple Ansible versions. - ansible-test coverage analyze targets generate "${agent_temp_directory}/coverage/coverage-analyze-targets.json" "${options[@]}" +if ansible-test coverage analyze targets generate --help > /dev/null 2>&1; then + # Only analyze coverage if the installed version of ansible-test supports it. + # Doing so allows this script to work unmodified for multiple Ansible versions. + ansible-test coverage analyze targets generate "${agent_temp_directory}/coverage/coverage-analyze-targets.json" "${options[@]}" fi diff --git a/.azure-pipelines/scripts/publish-codecov.sh b/.azure-pipelines/scripts/publish-codecov.sh index 6d184f0..5dd7cfd 100755 --- a/.azure-pipelines/scripts/publish-codecov.sh +++ b/.azure-pipelines/scripts/publish-codecov.sh @@ -10,18 +10,18 @@ output_path="$1" curl --silent --show-error https://ansible-ci-files.s3.us-east-1.amazonaws.com/codecov/codecov.sh > codecov.sh for file in "${output_path}"/reports/coverage*.xml; do - name="${file}" - name="${name##*/}" # remove path - name="${name##coverage=}" # remove 'coverage=' prefix if present - name="${name%.xml}" # remove '.xml' suffix + name="${file}" + name="${name##*/}" # remove path + name="${name##coverage=}" # remove 'coverage=' prefix if present + name="${name%.xml}" # remove '.xml' suffix - bash codecov.sh \ - -f "${file}" \ - -n "${name}" \ - -X coveragepy \ - -X gcov \ - -X fix \ - -X search \ - -X xcode \ - || echo "Failed to upload code coverage report to codecov.io: ${file}" + bash codecov.sh \ + -f "${file}" \ + -n "${name}" \ + -X coveragepy \ + -X gcov \ + -X fix \ + -X search \ + -X xcode || + echo "Failed to upload code coverage report to codecov.io: ${file}" done diff --git a/.azure-pipelines/scripts/report-coverage.sh b/.azure-pipelines/scripts/report-coverage.sh index 1bd91bd..a397f63 100755 --- a/.azure-pipelines/scripts/report-coverage.sh +++ b/.azure-pipelines/scripts/report-coverage.sh @@ -5,11 +5,11 @@ set -o pipefail -eu PATH="${PWD}/bin:${PATH}" -if ! ansible-test --help >/dev/null 2>&1; then - # Install the devel version of ansible-test for generating code coverage reports. - # This is only used by Ansible Collections, which are typically tested against multiple Ansible versions (in separate jobs). - # Since a version of ansible-test is required that can work the output from multiple older releases, the devel version is used. - pip install https://github.com/ansible/ansible/archive/devel.tar.gz --disable-pip-version-check +if ! ansible-test --help > /dev/null 2>&1; then + # Install the devel version of ansible-test for generating code coverage reports. + # This is only used by Ansible Collections, which are typically tested against multiple Ansible versions (in separate jobs). + # Since a version of ansible-test is required that can work the output from multiple older releases, the devel version is used. + pip install https://github.com/ansible/ansible/archive/devel.tar.gz --disable-pip-version-check fi ansible-test coverage xml --stub --venv --venv-system-site-packages --color -v diff --git a/.azure-pipelines/scripts/run-tests.sh b/.azure-pipelines/scripts/run-tests.sh index a947fdf..99e663b 100755 --- a/.azure-pipelines/scripts/run-tests.sh +++ b/.azure-pipelines/scripts/run-tests.sh @@ -13,22 +13,22 @@ export COVERAGE export IS_PULL_REQUEST if [ "${SYSTEM_PULLREQUEST_TARGETBRANCH:-}" ]; then - IS_PULL_REQUEST=true - COMMIT_MESSAGE=$(git log --format=%B -n 1 HEAD^2) + IS_PULL_REQUEST=true + COMMIT_MESSAGE=$(git log --format=%B -n 1 HEAD^2) else - IS_PULL_REQUEST= - COMMIT_MESSAGE=$(git log --format=%B -n 1 HEAD) + IS_PULL_REQUEST= + COMMIT_MESSAGE=$(git log --format=%B -n 1 HEAD) fi COMPLETE= COVERAGE= if [ "${BUILD_REASON}" = "Schedule" ]; then - COMPLETE=yes + COMPLETE=yes - if printf '%s\n' "${coverage_branches[@]}" | grep -q "^${BUILD_SOURCEBRANCHNAME}$"; then - COVERAGE=yes - fi + if printf '%s\n' "${coverage_branches[@]}" | grep -q "^${BUILD_SOURCEBRANCHNAME}$"; then + COVERAGE=yes + fi fi "${entry_point}" "${test}" 2>&1 | "$(dirname "$0")/time-command.py" diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9e4988e..9172a39 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -45,6 +45,21 @@ repos: - repo: https://github.com/ansible-community/antsibull-changelog rev: 0.22.0 + hooks: + - id: shfmt + name: shfmt + description: Format shell scripts with shfmt + language: golang + additional_dependencies: [mvdan.cc/sh/v3/cmd/shfmt@v3.7.0] + entry: shfmt -i 2 -ci -sr -kp -w + types: [shell] + + - repo: https://github.com/shellcheck-py/shellcheck-py + rev: v0.9.0.5 + hooks: + - id: shellcheck + + - repo: local hooks: - id: antsibull-changelog-lint - id: antsibull-changelog-lint-changelog-yaml diff --git a/tests/utils/gitlab/gitlab.sh b/tests/utils/gitlab/gitlab.sh index 945b84d..b6aaa4b 100755 --- a/tests/utils/gitlab/gitlab.sh +++ b/tests/utils/gitlab/gitlab.sh @@ -11,39 +11,38 @@ ansible_version="${args[0]}" script="${args[1]}" function join { - local IFS="$1"; - shift; - echo "$*"; + local IFS="$1" + shift + echo "$*" } test="$(join / "${args[@]:1}")" command -v python python -V -function retry -{ - # shellcheck disable=SC2034 - for repetition in 1 2 3; do - set +e - "$@" - result=$? - set -e - if [ ${result} == 0 ]; then - return ${result} - fi - echo "@* -> ${result}" - done - echo "Command '@*' failed 3 times!" - exit 1 +function retry { + # shellcheck disable=SC2034 + for repetition in 1 2 3; do + set +e + "$@" + result=$? + set -e + if [ ${result} == 0 ]; then + return ${result} + fi + echo "@* -> ${result}" + done + echo "Command '@*' failed 3 times!" + exit 1 } command -v pip pip --version pip list --disable-pip-version-check if [ "${ansible_version}" == "devel" ]; then - retry pip install https://github.com/ansible/ansible/archive/devel.tar.gz --disable-pip-version-check + retry pip install https://github.com/ansible/ansible/archive/devel.tar.gz --disable-pip-version-check else - retry pip install "https://github.com/ansible/ansible/archive/stable-${ansible_version}.tar.gz" --disable-pip-version-check + retry pip install "https://github.com/ansible/ansible/archive/stable-${ansible_version}.tar.gz" --disable-pip-version-check fi export ANSIBLE_COLLECTIONS_PATHS="${HOME}/.ansible" # shellcheck disable=SC2034 @@ -65,22 +64,20 @@ retry ansible-galaxy -vvv collection install community.internal_test_tools export PYTHONIOENCODING='utf-8' if [ "${JOB_TRIGGERED_BY_NAME:-}" == "nightly-trigger" ]; then - COMPLETE=yes + COMPLETE=yes fi - if [ -n "${COMPLETE:-}" ]; then - # disable change detection triggered by setting the COMPLETE environment variable to a non-empty value - export CHANGED="" + # disable change detection triggered by setting the COMPLETE environment variable to a non-empty value + export CHANGED="" elif [[ "${CI_COMMIT_MESSAGE}" =~ ci_complete ]]; then - # disable change detection triggered by having 'ci_complete' in the latest commit message - export CHANGED="" + # disable change detection triggered by having 'ci_complete' in the latest commit message + export CHANGED="" else - # enable change detection (default behavior) - export CHANGED="" + # enable change detection (default behavior) + export CHANGED="" fi - export UNSTABLE="--allow-unstable-changed" # remove empty core/extras module directories from PRs created prior to the repo-merge diff --git a/tests/utils/gitlab/sanity.sh b/tests/utils/gitlab/sanity.sh index 359fb20..9f6711b 100755 --- a/tests/utils/gitlab/sanity.sh +++ b/tests/utils/gitlab/sanity.sh @@ -8,32 +8,32 @@ IFS='/:' read -ra args <<< "$1" group="${args[1]}" if [ "${BASE_BRANCH:-}" ]; then - base_branch="origin/${BASE_BRANCH}" + base_branch="origin/${BASE_BRANCH}" else - base_branch="" + base_branch="" fi if [ "${group}" == "extra" ]; then - ../internal_test_tools/tools/run.py --color - exit + ../internal_test_tools/tools/run.py --color + exit fi case "${group}" in - 1) options=(--skip-test pylint --skip-test ansible-doc --skip-test validate-modules) ;; - 2) options=( --test ansible-doc --test validate-modules) ;; - 3) options=(--test pylint plugins/modules/) ;; - 4) options=(--test pylint --exclude plugins/modules/) ;; + 1) options=(--skip-test pylint --skip-test ansible-doc --skip-test validate-modules) ;; + 2) options=(--test ansible-doc --test validate-modules) ;; + 3) options=(--test pylint plugins/modules/) ;; + 4) options=(--test pylint --exclude plugins/modules/) ;; esac # allow collection migration sanity tests for groups 3 and 4 to pass without updating this script during migration network_path="lib/ansible/modules/network/" if [ -d "${network_path}" ]; then - if [ "${group}" -eq 3 ]; then - options+=(--exclude "${network_path}") - elif [ "${group}" -eq 4 ]; then - options+=("${network_path}") - fi + if [ "${group}" -eq 3 ]; then + options+=(--exclude "${network_path}") + elif [ "${group}" -eq 4 ]; then + options+=("${network_path}") + fi fi pip install pycodestyle @@ -42,8 +42,8 @@ pip install voluptuous pip install pylint==2.5.3 # shellcheck disable=SC2086 ansible-test sanity --color -v --junit ${COVERAGE:+"$COVERAGE"} ${CHANGED:+"$CHANGED"} \ - --base-branch "${base_branch}" \ - --exclude plugins/module_utils/vendor/ \ - --exclude scripts/ \ - --exclude tests/utils/ \ - "${options[@]}" --allow-disabled + --base-branch "${base_branch}" \ + --exclude plugins/module_utils/vendor/ \ + --exclude scripts/ \ + --exclude tests/utils/ \ + "${options[@]}" --allow-disabled diff --git a/tests/utils/shippable/shippable.sh b/tests/utils/shippable/shippable.sh index 29e7010..655341b 100755 --- a/tests/utils/shippable/shippable.sh +++ b/tests/utils/shippable/shippable.sh @@ -9,9 +9,9 @@ ansible_version="${args[0]}" script="${args[1]}" function join { - local IFS="$1"; - shift; - echo "$*"; + local IFS="$1" + shift + echo "$*" } # Ensure we can write other collections to this dir @@ -24,53 +24,53 @@ docker images quay.io/ansible/* docker ps for container in $(docker ps --format '{{.Image}} {{.ID}}' | grep -v -e '^drydock/' -e '^quay.io/ansible/azure-pipelines-test-container:' | sed 's/^.* //'); do - docker rm -f "${container}" || true # ignore errors + docker rm -f "${container}" || true # ignore errors done docker ps if [ -d /home/shippable/cache/ ]; then - ls -la /home/shippable/cache/ + ls -la /home/shippable/cache/ fi command -v python python -V -function retry -{ - # shellcheck disable=SC2034 - for repetition in 1 2 3; do - set +e - "$@" - result=$? - set -e - if [ ${result} == 0 ]; then - return ${result} - fi - echo "@* -> ${result}" - done - echo "Command '@*' failed 3 times!" - exit 1 +function retry { + # shellcheck disable=SC2034 + for repetition in 1 2 3; do + set +e + "$@" + result=$? + set -e + if [ ${result} == 0 ]; then + return ${result} + fi + echo "@* -> ${result}" + done + echo "Command '@*' failed 3 times!" + exit 1 } command -v pip pip --version pip list --disable-pip-version-check if [ "${ansible_version}" == "devel" ]; then - retry pip install https://github.com/ansible/ansible/archive/devel.tar.gz --disable-pip-version-check + retry pip install https://github.com/ansible/ansible/archive/devel.tar.gz --disable-pip-version-check else - retry pip install "https://github.com/ansible/ansible/archive/stable-${ansible_version}.tar.gz" --disable-pip-version-check + retry pip install "https://github.com/ansible/ansible/archive/stable-${ansible_version}.tar.gz" --disable-pip-version-check fi if [ "${SHIPPABLE_BUILD_ID:-}" ]; then - export ANSIBLE_COLLECTIONS_PATHS="${HOME}/.ansible" - SHIPPABLE_RESULT_DIR="$(pwd)/shippable" - TEST_DIR="${ANSIBLE_COLLECTIONS_PATHS}/ansible_collections/hetzner/hcloud" - mkdir -p "${TEST_DIR}" - cp -aT "${SHIPPABLE_BUILD_DIR}" "${TEST_DIR}" - cd "${TEST_DIR}" + export ANSIBLE_COLLECTIONS_PATHS="${HOME}/.ansible" + SHIPPABLE_RESULT_DIR="$(pwd)/shippable" + TEST_DIR="${ANSIBLE_COLLECTIONS_PATHS}/ansible_collections/hetzner/hcloud" + mkdir -p "${TEST_DIR}" + # shellcheck disable=SC2153 + cp -aT "${SHIPPABLE_BUILD_DIR}" "${TEST_DIR}" + cd "${TEST_DIR}" else - export ANSIBLE_COLLECTIONS_PATHS="${PWD}/../../../" + export ANSIBLE_COLLECTIONS_PATHS="${PWD}/../../../" fi # STAR: HACK install dependencies @@ -84,126 +84,126 @@ retry ansible-galaxy -vvv collection install community.internal_test_tools export PYTHONIOENCODING='utf-8' if [ "${JOB_TRIGGERED_BY_NAME:-}" == "nightly-trigger" ]; then - COVERAGE=yes - COMPLETE=yes + COVERAGE=yes + COMPLETE=yes fi if [ -n "${COVERAGE:-}" ]; then - # on-demand coverage reporting triggered by setting the COVERAGE environment variable to a non-empty value - export COVERAGE="--coverage" + # on-demand coverage reporting triggered by setting the COVERAGE environment variable to a non-empty value + export COVERAGE="--coverage" elif [[ "${COMMIT_MESSAGE}" =~ ci_coverage ]]; then - # on-demand coverage reporting triggered by having 'ci_coverage' in the latest commit message - export COVERAGE="--coverage" + # on-demand coverage reporting triggered by having 'ci_coverage' in the latest commit message + export COVERAGE="--coverage" else - # on-demand coverage reporting disabled (default behavior, always-on coverage reporting remains enabled) - export COVERAGE="--coverage-check" + # on-demand coverage reporting disabled (default behavior, always-on coverage reporting remains enabled) + export COVERAGE="--coverage-check" fi if [ -n "${COMPLETE:-}" ]; then - # disable change detection triggered by setting the COMPLETE environment variable to a non-empty value - export CHANGED="" + # disable change detection triggered by setting the COMPLETE environment variable to a non-empty value + export CHANGED="" elif [[ "${COMMIT_MESSAGE}" =~ ci_complete ]]; then - # disable change detection triggered by having 'ci_complete' in the latest commit message - export CHANGED="" + # disable change detection triggered by having 'ci_complete' in the latest commit message + export CHANGED="" else - # enable change detection (default behavior) - export CHANGED="--changed" + # enable change detection (default behavior) + export CHANGED="--changed" fi if [ "${IS_PULL_REQUEST:-}" == "true" ]; then - # run unstable tests which are targeted by focused changes on PRs - export UNSTABLE="--allow-unstable-changed" + # run unstable tests which are targeted by focused changes on PRs + export UNSTABLE="--allow-unstable-changed" else - # do not run unstable tests outside PRs - export UNSTABLE="" + # do not run unstable tests outside PRs + export UNSTABLE="" fi # remove empty core/extras module directories from PRs created prior to the repo-merge find plugins -type d -empty -print -delete -function cleanup -{ - # for complete on-demand coverage generate a report for all files with no coverage on the "sanity/5" job so we only have one copy - if [ "${COVERAGE}" == "--coverage" ] && [ "${CHANGED}" == "" ] && [ "${test}" == "sanity/5" ]; then - stub="--stub" - # trigger coverage reporting for stubs even if no other coverage data exists - mkdir -p tests/output/coverage/ +function cleanup { + # for complete on-demand coverage generate a report for all files with no coverage on the "sanity/5" job so we only have one copy + if [ "${COVERAGE}" == "--coverage" ] && [ "${CHANGED}" == "" ] && [ "${test}" == "sanity/5" ]; then + stub="--stub" + # trigger coverage reporting for stubs even if no other coverage data exists + mkdir -p tests/output/coverage/ + else + stub="" + fi + + if [ -d tests/output/coverage/ ]; then + if find tests/output/coverage/ -mindepth 1 -name '.*' -prune -o -print -quit | grep -q .; then + process_coverage='yes' # process existing coverage files + elif [ "${stub}" ]; then + process_coverage='yes' # process coverage when stubs are enabled else - stub="" + process_coverage='' fi - if [ -d tests/output/coverage/ ]; then - if find tests/output/coverage/ -mindepth 1 -name '.*' -prune -o -print -quit | grep -q .; then - process_coverage='yes' # process existing coverage files - elif [ "${stub}" ]; then - process_coverage='yes' # process coverage when stubs are enabled - else - process_coverage='' - fi + if [ "${process_coverage}" ]; then + # use python 3.7 for coverage to avoid running out of memory during coverage xml processing + # only use it for coverage to avoid the additional overhead of setting up a virtual environment for a potential no-op job + virtualenv --python /usr/bin/python3.7 ~/ansible-venv + set +ux + # shellcheck disable=SC1090 + . ~/ansible-venv/bin/activate + set -ux - if [ "${process_coverage}" ]; then - # use python 3.7 for coverage to avoid running out of memory during coverage xml processing - # only use it for coverage to avoid the additional overhead of setting up a virtual environment for a potential no-op job - virtualenv --python /usr/bin/python3.7 ~/ansible-venv - set +ux - . ~/ansible-venv/bin/activate - set -ux + # shellcheck disable=SC2086 + ansible-test coverage xml --color -v --requirements --group-by command --group-by version ${stub:+"$stub"} + cp -a tests/output/reports/coverage=*.xml "$SHIPPABLE_RESULT_DIR/codecoverage/" - # shellcheck disable=SC2086 - ansible-test coverage xml --color -v --requirements --group-by command --group-by version ${stub:+"$stub"} - cp -a tests/output/reports/coverage=*.xml "$SHIPPABLE_RESULT_DIR/codecoverage/" + if [ "${ansible_version}" != "2.9" ]; then + # analyze and capture code coverage aggregated by integration test target + ansible-test coverage analyze targets generate -v "$SHIPPABLE_RESULT_DIR/testresults/coverage-analyze-targets.json" + fi - if [ "${ansible_version}" != "2.9" ]; then - # analyze and capture code coverage aggregated by integration test target - ansible-test coverage analyze targets generate -v "$SHIPPABLE_RESULT_DIR/testresults/coverage-analyze-targets.json" - fi + # upload coverage report to codecov.io only when using complete on-demand coverage + if [ "${COVERAGE}" == "--coverage" ] && [ "${CHANGED}" == "" ]; then + for file in tests/output/reports/coverage=*.xml; do + flags="${file##*/coverage=}" + flags="${flags%-powershell.xml}" + flags="${flags%.xml}" + # remove numbered component from stub files when converting to tags + flags="${flags//stub-[0-9]*/stub}" + flags="${flags//=/,}" + flags="${flags//[^a-zA-Z0-9_,]/_}" - # upload coverage report to codecov.io only when using complete on-demand coverage - if [ "${COVERAGE}" == "--coverage" ] && [ "${CHANGED}" == "" ]; then - for file in tests/output/reports/coverage=*.xml; do - flags="${file##*/coverage=}" - flags="${flags%-powershell.xml}" - flags="${flags%.xml}" - # remove numbered component from stub files when converting to tags - flags="${flags//stub-[0-9]*/stub}" - flags="${flags//=/,}" - flags="${flags//[^a-zA-Z0-9_,]/_}" - - bash <(curl -s https://ansible-ci-files.s3.us-east-1.amazonaws.com/codecov/codecov.sh) \ - -f "${file}" \ - -F "${flags}" \ - -n "${test}" \ - -t 8a86e979-f37b-4d5d-95a4-960c280d5eaa \ - -X coveragepy \ - -X gcov \ - -X fix \ - -X search \ - -X xcode \ - || echo "Failed to upload code coverage report to codecov.io: ${file}" - done - fi - fi + bash <(curl -s https://ansible-ci-files.s3.us-east-1.amazonaws.com/codecov/codecov.sh) \ + -f "${file}" \ + -F "${flags}" \ + -n "${test}" \ + -t 8a86e979-f37b-4d5d-95a4-960c280d5eaa \ + -X coveragepy \ + -X gcov \ + -X fix \ + -X search \ + -X xcode || + echo "Failed to upload code coverage report to codecov.io: ${file}" + done + fi fi + fi - if [ -d tests/output/junit/ ]; then - cp -aT tests/output/junit/ "$SHIPPABLE_RESULT_DIR/testresults/" - fi + if [ -d tests/output/junit/ ]; then + cp -aT tests/output/junit/ "$SHIPPABLE_RESULT_DIR/testresults/" + fi - if [ -d tests/output/data/ ]; then - cp -a tests/output/data/ "$SHIPPABLE_RESULT_DIR/testresults/" - fi + if [ -d tests/output/data/ ]; then + cp -a tests/output/data/ "$SHIPPABLE_RESULT_DIR/testresults/" + fi - if [ -d tests/output/bot/ ]; then - cp -aT tests/output/bot/ "$SHIPPABLE_RESULT_DIR/testresults/" - fi + if [ -d tests/output/bot/ ]; then + cp -aT tests/output/bot/ "$SHIPPABLE_RESULT_DIR/testresults/" + fi } if [ "${SHIPPABLE_BUILD_ID:-}" ]; then trap cleanup EXIT; fi if [[ "${COVERAGE:-}" == "--coverage" ]]; then - timeout=60 + timeout=60 else - timeout=45 + timeout=45 fi ansible-test env --dump --show --timeout "${timeout}" --color -v