1#!/usr/local/bin/bash
2
3set -o pipefail -eux
4
5declare -a args
6IFS='/:' read -ra args <<< "$1"
7
8ansible_version="${args[0]}"
9script="${args[1]}"
10
11function join {
12    local IFS="$1";
13    shift;
14    echo "$*";
15}
16
17# Ensure we can write other collections to this dir
18sudo chown "$(whoami)" "${PWD}/../../"
19
20test="$(join / "${args[@]:1}")"
21
22docker images ansible/ansible
23docker images quay.io/ansible/*
24docker ps
25
26for container in $(docker ps --format '{{.Image}} {{.ID}}' | grep -v -e '^drydock/' -e '^quay.io/ansible/azure-pipelines-test-container:' | sed 's/^.* //'); do
27    docker rm -f "${container}" || true  # ignore errors
28done
29
30docker ps
31
32if [ -d /home/shippable/cache/ ]; then
33    ls -la /home/shippable/cache/
34fi
35
36command -v python
37python -V
38
39function retry
40{
41    # shellcheck disable=SC2034
42    for repetition in 1 2 3; do
43        set +e
44        "$@"
45        result=$?
46        set -e
47        if [ ${result} == 0 ]; then
48            return ${result}
49        fi
50        echo "@* -> ${result}"
51    done
52    echo "Command '@*' failed 3 times!"
53    exit 255
54}
55
56command -v pip
57pip --version
58pip list --disable-pip-version-check
59if [ "${ansible_version}" == "devel" ]; then
60    retry pip install https://github.com/ansible/ansible/archive/devel.tar.gz --disable-pip-version-check
61else
62    retry pip install "https://github.com/ansible/ansible/archive/stable-${ansible_version}.tar.gz" --disable-pip-version-check
63fi
64
65if [ "${SHIPPABLE_BUILD_ID:-}" ]; then
66    export ANSIBLE_COLLECTIONS_PATHS="${HOME}/.ansible"
67    SHIPPABLE_RESULT_DIR="$(pwd)/shippable"
68    TEST_DIR="${ANSIBLE_COLLECTIONS_PATHS}/ansible_collections/community/general"
69    mkdir -p "${TEST_DIR}"
70    cp -aT "${SHIPPABLE_BUILD_DIR}" "${TEST_DIR}"
71    cd "${TEST_DIR}"
72else
73    export ANSIBLE_COLLECTIONS_PATHS="${PWD}/../../../"
74fi
75
76if [ "${test}" == "sanity/extra" ]; then
77    retry pip install junit-xml --disable-pip-version-check
78fi
79
80# START: HACK install dependencies
81if [ "${script}" != "sanity" ] || [ "${test}" == "sanity/extra" ]; then
82    # Nothing further should be added to this list.
83    # This is to prevent modules or plugins in this collection having a runtime dependency on other collections.
84    retry git clone --depth=1 --single-branch https://github.com/ansible-collections/community.internal_test_tools.git "${ANSIBLE_COLLECTIONS_PATHS}/ansible_collections/community/internal_test_tools"
85    # NOTE: we're installing with git to work around Galaxy being a huge PITA (https://github.com/ansible/galaxy/issues/2429)
86    # retry ansible-galaxy -vvv collection install community.internal_test_tools
87fi
88
89if [ "${script}" != "sanity" ] && [ "${script}" != "units" ]; then
90    CRYPTO_BRANCH=main
91    if [ "${script}" == "linux" ] && [[ "${test}" =~ "ubuntu1604/" ]]; then
92        CRYPTO_BRANCH=stable-1
93    fi
94    # To prevent Python dependencies on other collections only install other collections for integration tests
95    retry git clone --depth=1 --single-branch https://github.com/ansible-collections/ansible.posix.git "${ANSIBLE_COLLECTIONS_PATHS}/ansible_collections/ansible/posix"
96    retry git clone --depth=1 --branch "${CRYPTO_BRANCH}" --single-branch https://github.com/ansible-collections/community.crypto.git "${ANSIBLE_COLLECTIONS_PATHS}/ansible_collections/community/crypto"
97    # NOTE: we're installing with git to work around Galaxy being a huge PITA (https://github.com/ansible/galaxy/issues/2429)
98    # retry ansible-galaxy -vvv collection install ansible.posix
99    # retry ansible-galaxy -vvv collection install community.crypto
100fi
101
102# END: HACK
103
104if [ "${script}" != "sanity" ] && [ "${script}" != "units" ]; then
105    # Adds meta/runtime.yml redirects for all modules before running integration tests.
106    # This ensures that ansible-base and ansible-core will use the "real" modules instead of the
107    # symbolic links, which results in coverage to be reported correctly.
108    "${ANSIBLE_COLLECTIONS_PATHS}/ansible_collections/community/internal_test_tools/tools/meta_runtime.py" redirect --target both --flatmap
109fi
110
111export PYTHONIOENCODING='utf-8'
112
113if [ "${JOB_TRIGGERED_BY_NAME:-}" == "nightly-trigger" ]; then
114    COVERAGE=yes
115    COMPLETE=yes
116fi
117
118if [ -n "${COVERAGE:-}" ]; then
119    # on-demand coverage reporting triggered by setting the COVERAGE environment variable to a non-empty value
120    export COVERAGE="--coverage"
121elif [[ "${COMMIT_MESSAGE}" =~ ci_coverage ]]; then
122    # on-demand coverage reporting triggered by having 'ci_coverage' in the latest commit message
123    export COVERAGE="--coverage"
124else
125    # on-demand coverage reporting disabled (default behavior, always-on coverage reporting remains enabled)
126    export COVERAGE="--coverage-check"
127fi
128
129if [ -n "${COMPLETE:-}" ]; then
130    # disable change detection triggered by setting the COMPLETE environment variable to a non-empty value
131    export CHANGED=""
132elif [[ "${COMMIT_MESSAGE}" =~ ci_complete ]]; then
133    # disable change detection triggered by having 'ci_complete' in the latest commit message
134    export CHANGED=""
135else
136    # enable change detection (default behavior)
137    export CHANGED="--changed"
138fi
139
140if [ "${IS_PULL_REQUEST:-}" == "true" ]; then
141    # run unstable tests which are targeted by focused changes on PRs
142    export UNSTABLE="--allow-unstable-changed"
143else
144    # do not run unstable tests outside PRs
145    export UNSTABLE=""
146fi
147
148# remove empty core/extras module directories from PRs created prior to the repo-merge
149find plugins -type d -empty -print -delete
150
151function cleanup
152{
153    # 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
154    if [ "${COVERAGE}" == "--coverage" ] && [ "${CHANGED}" == "" ] && [ "${test}" == "sanity/5" ]; then
155        stub="--stub"
156        # trigger coverage reporting for stubs even if no other coverage data exists
157        mkdir -p tests/output/coverage/
158    else
159        stub=""
160    fi
161
162    if [ -d tests/output/coverage/ ]; then
163        if find tests/output/coverage/ -mindepth 1 -name '.*' -prune -o -print -quit | grep -q .; then
164            process_coverage='yes'  # process existing coverage files
165        elif [ "${stub}" ]; then
166            process_coverage='yes'  # process coverage when stubs are enabled
167        else
168            process_coverage=''
169        fi
170
171        if [ "${process_coverage}" ]; then
172            # use python 3.7 for coverage to avoid running out of memory during coverage xml processing
173            # only use it for coverage to avoid the additional overhead of setting up a virtual environment for a potential no-op job
174            virtualenv --python /usr/bin/python3.7 ~/ansible-venv
175            set +ux
176            . ~/ansible-venv/bin/activate
177            set -ux
178
179            # shellcheck disable=SC2086
180            ansible-test coverage xml --color -v --requirements --group-by command --group-by version ${stub:+"$stub"}
181            cp -a tests/output/reports/coverage=*.xml "$SHIPPABLE_RESULT_DIR/codecoverage/"
182
183            if [ "${ansible_version}" != "2.9" ]; then
184                # analyze and capture code coverage aggregated by integration test target
185                ansible-test coverage analyze targets generate -v "$SHIPPABLE_RESULT_DIR/testresults/coverage-analyze-targets.json"
186            fi
187
188            # upload coverage report to codecov.io only when using complete on-demand coverage
189            if [ "${COVERAGE}" == "--coverage" ] && [ "${CHANGED}" == "" ]; then
190                for file in tests/output/reports/coverage=*.xml; do
191                    flags="${file##*/coverage=}"
192                    flags="${flags%-powershell.xml}"
193                    flags="${flags%.xml}"
194                    # remove numbered component from stub files when converting to tags
195                    flags="${flags//stub-[0-9]*/stub}"
196                    flags="${flags//=/,}"
197                    flags="${flags//[^a-zA-Z0-9_,]/_}"
198
199                    bash <(curl -s https://ansible-ci-files.s3.us-east-1.amazonaws.com/codecov/codecov.sh) \
200                        -f "${file}" \
201                        -F "${flags}" \
202                        -n "${test}" \
203                        -t 20636cf5-4d6a-4b9a-8d2d-6f22ebbaa752 \
204                        -X coveragepy \
205                        -X gcov \
206                        -X fix \
207                        -X search \
208                        -X xcode \
209                    || echo "Failed to upload code coverage report to codecov.io: ${file}"
210                done
211            fi
212        fi
213    fi
214
215    if [ -d  tests/output/junit/ ]; then
216      cp -aT tests/output/junit/ "$SHIPPABLE_RESULT_DIR/testresults/"
217    fi
218
219    if [ -d tests/output/data/ ]; then
220      cp -a tests/output/data/ "$SHIPPABLE_RESULT_DIR/testresults/"
221    fi
222
223    if [ -d  tests/output/bot/ ]; then
224      cp -aT tests/output/bot/ "$SHIPPABLE_RESULT_DIR/testresults/"
225    fi
226}
227
228if [ "${SHIPPABLE_BUILD_ID:-}" ]; then trap cleanup EXIT; fi
229
230if [[ "${COVERAGE:-}" == "--coverage" ]]; then
231    timeout=60
232else
233    timeout=50
234fi
235
236ansible-test env --dump --show --timeout "${timeout}" --color -v
237
238if [ "${SHIPPABLE_BUILD_ID:-}" ]; then "tests/utils/shippable/check_matrix.py"; fi
239"tests/utils/shippable/${script}.sh" "${test}" "${ansible_version}"
240