diff --git a/.azure-pipelines/scripts/aggregate-coverage.sh b/.azure-pipelines/scripts/aggregate-coverage.sh index f3113dd0a9..1ccfcf2073 100755 --- a/.azure-pipelines/scripts/aggregate-coverage.sh +++ b/.azure-pipelines/scripts/aggregate-coverage.sh @@ -11,7 +11,7 @@ mkdir "${agent_temp_directory}/coverage/" options=(--venv --venv-system-site-packages --color -v) -ansible-test coverage combine --export "${agent_temp_directory}/coverage/" "${options[@]}" +ansible-test coverage combine --group-by command --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. diff --git a/.azure-pipelines/scripts/publish-codecov.py b/.azure-pipelines/scripts/publish-codecov.py new file mode 100755 index 0000000000..ab947f9810 --- /dev/null +++ b/.azure-pipelines/scripts/publish-codecov.py @@ -0,0 +1,101 @@ +#!/usr/bin/env python +""" +Upload code coverage reports to codecov.io. +Multiple coverage files from multiple languages are accepted and aggregated after upload. +Python coverage, as well as PowerShell and Python stubs can all be uploaded. +""" + +import argparse +import dataclasses +import pathlib +import shutil +import subprocess +import tempfile +import typing as t +import urllib.request + + +@dataclasses.dataclass(frozen=True) +class CoverageFile: + name: str + path: pathlib.Path + flags: t.List[str] + + +@dataclasses.dataclass(frozen=True) +class Args: + dry_run: bool + path: pathlib.Path + + +def parse_args() -> Args: + parser = argparse.ArgumentParser() + parser.add_argument('-n', '--dry-run', action='store_true') + parser.add_argument('path', type=pathlib.Path) + + args = parser.parse_args() + + # Store arguments in a typed dataclass + fields = dataclasses.fields(Args) + kwargs = {field.name: getattr(args, field.name) for field in fields} + + return Args(**kwargs) + + +def process_files(directory: pathlib.Path) -> t.Tuple[CoverageFile, ...]: + processed = [] + for file in directory.joinpath('reports').glob('coverage*.xml'): + name = file.stem.replace('coverage=', '') + + # Get flags from name + flags = name.replace('-powershell', '').split('=') # Drop '-powershell' suffix + flags = [flag if not flag.startswith('stub') else flag.split('-')[0] for flag in flags] # Remove "-01" from stub files + + processed.append(CoverageFile(name, file, flags)) + + return tuple(processed) + + +def upload_files(codecov_bin: pathlib.Path, files: t.Tuple[CoverageFile, ...], dry_run: bool = False) -> None: + for file in files: + cmd = [ + str(codecov_bin), + '--name', file.name, + '--file', str(file.path), + ] + for flag in file.flags: + cmd.extend(['--flags', flag]) + + if dry_run: + print(f'DRY-RUN: Would run command: {cmd}') + continue + + subprocess.run(cmd, check=True) + + +def download_file(url: str, dest: pathlib.Path, flags: int, dry_run: bool = False) -> None: + if dry_run: + print(f'DRY-RUN: Would download {url} to {dest} and set mode to {flags:o}') + return + + with urllib.request.urlopen(url) as resp: + with dest.open('w+b') as f: + # Read data in chunks rather than all at once + shutil.copyfileobj(resp, f, 64 * 1024) + + dest.chmod(flags) + + +def main(): + args = parse_args() + url = 'https://ansible-ci-files.s3.amazonaws.com/codecov/linux/codecov' + with tempfile.TemporaryDirectory(prefix='codecov-') as tmpdir: + codecov_bin = pathlib.Path(tmpdir) / 'codecov' + download_file(url, codecov_bin, 0o755, args.dry_run) + + files = process_files(args.path) + upload_files(codecov_bin, files, args.dry_run) + + +if __name__ == '__main__': + main() diff --git a/.azure-pipelines/scripts/publish-codecov.sh b/.azure-pipelines/scripts/publish-codecov.sh deleted file mode 100755 index 6d184f0b8d..0000000000 --- a/.azure-pipelines/scripts/publish-codecov.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env bash -# Upload code coverage reports to codecov.io. -# Multiple coverage files from multiple languages are accepted and aggregated after upload. -# Python coverage, as well as PowerShell and Python stubs can all be uploaded. - -set -o pipefail -eu - -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 - - 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 1bd91bdc99..c039f7dcbd 100755 --- a/.azure-pipelines/scripts/report-coverage.sh +++ b/.azure-pipelines/scripts/report-coverage.sh @@ -12,4 +12,4 @@ if ! ansible-test --help >/dev/null 2>&1; then 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 +ansible-test coverage xml --group-by command --stub --venv --venv-system-site-packages --color -v diff --git a/.azure-pipelines/templates/coverage.yml b/.azure-pipelines/templates/coverage.yml index 1864e44410..1b36ea45a4 100644 --- a/.azure-pipelines/templates/coverage.yml +++ b/.azure-pipelines/templates/coverage.yml @@ -33,7 +33,7 @@ jobs: summaryFileLocation: "$(outputPath)/reports/$(pipelinesCoverage).xml" displayName: Publish to Azure Pipelines condition: gt(variables.coverageFileCount, 0) - - bash: .azure-pipelines/scripts/publish-codecov.sh "$(outputPath)" + - bash: .azure-pipelines/scripts/publish-codecov.py "$(outputPath)" displayName: Publish to codecov.io condition: gt(variables.coverageFileCount, 0) continueOnError: true diff --git a/tests/sanity/ignore-2.10.txt b/tests/sanity/ignore-2.10.txt index 5ec74ad14d..0f006e2723 100644 --- a/tests/sanity/ignore-2.10.txt +++ b/tests/sanity/ignore-2.10.txt @@ -1,3 +1,9 @@ +.azure-pipelines/scripts/publish-codecov.py replace-urlopen +.azure-pipelines/scripts/publish-codecov.py compile-2.6!skip # Uses Python 3.6+ syntax +.azure-pipelines/scripts/publish-codecov.py compile-2.7!skip # Uses Python 3.6+ syntax +.azure-pipelines/scripts/publish-codecov.py compile-3.5!skip # Uses Python 3.6+ syntax +.azure-pipelines/scripts/publish-codecov.py future-import-boilerplate +.azure-pipelines/scripts/publish-codecov.py metaclass-boilerplate plugins/module_utils/cloud.py pylint:bad-option-value # a pylint test that is disabled was modified over time plugins/modules/cloud/lxc/lxc_container.py use-argspec-type-path plugins/modules/cloud/lxc/lxc_container.py validate-modules:use-run-command-not-popen diff --git a/tests/sanity/ignore-2.11.txt b/tests/sanity/ignore-2.11.txt index 58122f5dfa..45d343f785 100644 --- a/tests/sanity/ignore-2.11.txt +++ b/tests/sanity/ignore-2.11.txt @@ -1,3 +1,9 @@ +.azure-pipelines/scripts/publish-codecov.py replace-urlopen +.azure-pipelines/scripts/publish-codecov.py compile-2.6!skip # Uses Python 3.6+ syntax +.azure-pipelines/scripts/publish-codecov.py compile-2.7!skip # Uses Python 3.6+ syntax +.azure-pipelines/scripts/publish-codecov.py compile-3.5!skip # Uses Python 3.6+ syntax +.azure-pipelines/scripts/publish-codecov.py future-import-boilerplate +.azure-pipelines/scripts/publish-codecov.py metaclass-boilerplate plugins/modules/cloud/lxc/lxc_container.py use-argspec-type-path plugins/modules/cloud/lxc/lxc_container.py validate-modules:use-run-command-not-popen plugins/modules/cloud/misc/rhevm.py validate-modules:parameter-state-invalid-choice diff --git a/tests/sanity/ignore-2.12.txt b/tests/sanity/ignore-2.12.txt index 2c3bb66d8c..1654a4aa15 100644 --- a/tests/sanity/ignore-2.12.txt +++ b/tests/sanity/ignore-2.12.txt @@ -1,3 +1,4 @@ +.azure-pipelines/scripts/publish-codecov.py replace-urlopen plugins/modules/cloud/lxc/lxc_container.py use-argspec-type-path plugins/modules/cloud/lxc/lxc_container.py validate-modules:use-run-command-not-popen plugins/modules/cloud/misc/rhevm.py validate-modules:parameter-state-invalid-choice diff --git a/tests/sanity/ignore-2.13.txt b/tests/sanity/ignore-2.13.txt index 2c3bb66d8c..1654a4aa15 100644 --- a/tests/sanity/ignore-2.13.txt +++ b/tests/sanity/ignore-2.13.txt @@ -1,3 +1,4 @@ +.azure-pipelines/scripts/publish-codecov.py replace-urlopen plugins/modules/cloud/lxc/lxc_container.py use-argspec-type-path plugins/modules/cloud/lxc/lxc_container.py validate-modules:use-run-command-not-popen plugins/modules/cloud/misc/rhevm.py validate-modules:parameter-state-invalid-choice diff --git a/tests/sanity/ignore-2.9.txt b/tests/sanity/ignore-2.9.txt index 9b352f2f72..f67aa96d78 100644 --- a/tests/sanity/ignore-2.9.txt +++ b/tests/sanity/ignore-2.9.txt @@ -1,3 +1,9 @@ +.azure-pipelines/scripts/publish-codecov.py replace-urlopen +.azure-pipelines/scripts/publish-codecov.py compile-2.6!skip # Uses Python 3.6+ syntax +.azure-pipelines/scripts/publish-codecov.py compile-2.7!skip # Uses Python 3.6+ syntax +.azure-pipelines/scripts/publish-codecov.py compile-3.5!skip # Uses Python 3.6+ syntax +.azure-pipelines/scripts/publish-codecov.py future-import-boilerplate +.azure-pipelines/scripts/publish-codecov.py metaclass-boilerplate plugins/module_utils/cloud.py pylint:bad-option-value # a pylint test that is disabled was modified over time plugins/modules/cloud/lxc/lxc_container.py use-argspec-type-path plugins/modules/cloud/lxc/lxc_container.py validate-modules:use-run-command-not-popen