[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 fa2be89cd4)

Co-authored-by: Matt Clay <matt@mystile.com>
This commit is contained in:
Matt Clay 2020-11-05 15:00:54 -08:00
parent 8f767f7180
commit cfa8075537
4 changed files with 38 additions and 3 deletions

View file

@ -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.

View file

@ -993,6 +993,9 @@ def add_extra_coverage_options(parser):
action='store_true', action='store_true',
help='generate empty report of all python/powershell source files') 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): def add_httptester_options(parser, argparse):
""" """

View file

@ -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.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.all = args.all if 'all' in args else False # type: bool
self.stub = args.stub if 'stub' 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 self.coverage = False # temporary work-around to support intercept_command in cover.py
@ -210,6 +211,14 @@ def enumerate_powershell_lines(
if not filename: if not filename:
continue 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 # PowerShell unpacks arrays if there's only a single entry so this is a defensive check on that
if not isinstance(hits, list): if not isinstance(hits, list):
hits = [hits] hits = [hits]

View file

@ -19,6 +19,7 @@ from ..util import (
from ..util_common import ( from ..util_common import (
ResultType, ResultType,
write_json_file,
write_json_test_results, write_json_test_results,
) )
@ -90,7 +91,12 @@ def _command_coverage_combine_python(args):
output_files = [] output_files = []
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) coverage_file = os.path.join(ResultType.COVERAGE.path, COVERAGE_OUTPUT_FILE_NAME)
suffix = ''
path_checker = PathChecker(args, collection_search_re) 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)) updated.add_arcs(dict((source[0], []) for source in sources))
if not args.explain: 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 updated.write_file(output_file) # always write files to make sure stale files do not exist
if updated: if updated:
@ -180,9 +186,15 @@ def _command_coverage_combine_powershell(args):
coverage_data[source] = _default_stub_value(source_line_count) coverage_data[source] = _default_stub_value(source_line_count)
if not args.explain: 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' 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)) 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], version=parts[3],
) )
export_names = dict(
version=parts[3],
)
group = '' group = ''
for part in COVERAGE_GROUPS: for part in COVERAGE_GROUPS:
if part in args.group_by: if part in args.group_by:
group += '=%s' % names[part] group += '=%s' % names[part]
elif args.export:
group += '=%s' % export_names.get(part, 'various')
if args.export:
group = group.lstrip('=')
return group return group