From cfa8075537f5fa83ca5bfe3170373aaef9ce932f Mon Sep 17 00:00:00 2001 From: Matt Clay Date: Thu, 5 Nov 2020 15:00:54 -0800 Subject: [PATCH] [stable-2.10] Add coverage exporting to ansible-test A new `--export` option for `ansible-test coverage combine` allows multi-step aggregation of code coverage for CI pipelines. (cherry picked from commit fa2be89cd44f0c867f24351c3ba73d5e849cb507) Co-authored-by: Matt Clay --- .../ansible-test-coverage-combine-export.yml | 2 ++ test/lib/ansible_test/_internal/cli.py | 3 +++ .../_internal/coverage/__init__.py | 9 +++++++ .../_internal/coverage/combine.py | 27 ++++++++++++++++--- 4 files changed, 38 insertions(+), 3 deletions(-) create mode 100644 changelogs/fragments/ansible-test-coverage-combine-export.yml diff --git a/changelogs/fragments/ansible-test-coverage-combine-export.yml b/changelogs/fragments/ansible-test-coverage-combine-export.yml new file mode 100644 index 00000000000..ff3f57fc30d --- /dev/null +++ b/changelogs/fragments/ansible-test-coverage-combine-export.yml @@ -0,0 +1,2 @@ +minor_changes: + - ansible-test - Added a ``--export`` option to the ``ansible-test coverage combine`` command to facilitate multi-stage aggregation of coverage in CI pipelines. diff --git a/test/lib/ansible_test/_internal/cli.py b/test/lib/ansible_test/_internal/cli.py index 0c2061dc90b..c12b6488050 100644 --- a/test/lib/ansible_test/_internal/cli.py +++ b/test/lib/ansible_test/_internal/cli.py @@ -993,6 +993,9 @@ def add_extra_coverage_options(parser): action='store_true', help='generate empty report of all python/powershell source files') + parser.add_argument('--export', + help='directory to export combined coverage files to') + def add_httptester_options(parser, argparse): """ diff --git a/test/lib/ansible_test/_internal/coverage/__init__.py b/test/lib/ansible_test/_internal/coverage/__init__.py index 88bb9843130..462d672e158 100644 --- a/test/lib/ansible_test/_internal/coverage/__init__.py +++ b/test/lib/ansible_test/_internal/coverage/__init__.py @@ -61,6 +61,7 @@ class CoverageConfig(EnvironmentConfig): self.group_by = frozenset(args.group_by) if 'group_by' in args and args.group_by else set() # type: t.FrozenSet[str] self.all = args.all if 'all' in args else False # type: bool self.stub = args.stub if 'stub' in args else False # type: bool + self.export = args.export if 'export' in args else None # type: str self.coverage = False # temporary work-around to support intercept_command in cover.py @@ -210,6 +211,14 @@ def enumerate_powershell_lines( if not filename: continue + if isinstance(hits, dict) and not hits.get('Line'): + # Input data was previously aggregated and thus uses the standard ansible-test output format for PowerShell coverage. + # This format differs from the more verbose format of raw coverage data from the remote Windows hosts. + hits = dict((int(key), value) for key, value in hits.items()) + + yield filename, hits + continue + # PowerShell unpacks arrays if there's only a single entry so this is a defensive check on that if not isinstance(hits, list): hits = [hits] diff --git a/test/lib/ansible_test/_internal/coverage/combine.py b/test/lib/ansible_test/_internal/coverage/combine.py index 28986b2354a..fa0b8f7d397 100644 --- a/test/lib/ansible_test/_internal/coverage/combine.py +++ b/test/lib/ansible_test/_internal/coverage/combine.py @@ -19,6 +19,7 @@ from ..util import ( from ..util_common import ( ResultType, + write_json_file, write_json_test_results, ) @@ -90,7 +91,12 @@ def _command_coverage_combine_python(args): output_files = [] - coverage_file = os.path.join(ResultType.COVERAGE.path, COVERAGE_OUTPUT_FILE_NAME) + if args.export: + coverage_file = os.path.join(args.export, '') + suffix = '=coverage.combined' + else: + coverage_file = os.path.join(ResultType.COVERAGE.path, COVERAGE_OUTPUT_FILE_NAME) + suffix = '' path_checker = PathChecker(args, collection_search_re) @@ -109,7 +115,7 @@ def _command_coverage_combine_python(args): updated.add_arcs(dict((source[0], []) for source in sources)) if not args.explain: - output_file = coverage_file + group + output_file = coverage_file + group + suffix updated.write_file(output_file) # always write files to make sure stale files do not exist if updated: @@ -180,9 +186,15 @@ def _command_coverage_combine_powershell(args): coverage_data[source] = _default_stub_value(source_line_count) if not args.explain: + if args.export: + output_file = os.path.join(args.export, group + '=coverage.combined') + write_json_file(output_file, coverage_data, formatted=False) + output_files.append(output_file) + continue + output_file = COVERAGE_OUTPUT_FILE_NAME + group + '-powershell' - write_json_test_results(ResultType.COVERAGE, output_file, coverage_data) + write_json_test_results(ResultType.COVERAGE, output_file, coverage_data, formatted=False) output_files.append(os.path.join(ResultType.COVERAGE.path, output_file)) @@ -267,10 +279,19 @@ def get_coverage_group(args, coverage_file): version=parts[3], ) + export_names = dict( + version=parts[3], + ) + group = '' for part in COVERAGE_GROUPS: if part in args.group_by: group += '=%s' % names[part] + elif args.export: + group += '=%s' % export_names.get(part, 'various') + + if args.export: + group = group.lstrip('=') return group