Expand delegation options for coverage commands.

This commit is contained in:
Matt Clay 2021-05-14 17:19:40 -07:00
parent e875e91363
commit ce04056797
8 changed files with 101 additions and 29 deletions

View file

@ -0,0 +1,2 @@
minor_changes:
- ansible-test - The ``ansible-test coverage`` commands ``combine``, ``report``, ``html`` and ``xml`` now support delegation.

View file

@ -108,14 +108,17 @@ from .util_common import (
from .commands.coverage.combine import (
command_coverage_combine,
CoverageCombineConfig,
)
from .commands.coverage.erase import (
command_coverage_erase,
CoverageEraseConfig,
)
from .commands.coverage.html import (
command_coverage_html,
CoverageHtmlConfig,
)
from .commands.coverage.report import (
@ -125,6 +128,7 @@ from .commands.coverage.report import (
from .commands.coverage.xml import (
command_coverage_xml,
CoverageXmlConfig,
)
from .commands.coverage.analyze.targets.generate import (
@ -154,7 +158,6 @@ from .commands.coverage.analyze.targets.missing import (
from .commands.coverage import (
COVERAGE_GROUPS,
CoverageConfig,
)
if t.TYPE_CHECKING:
@ -579,6 +582,10 @@ def parse_args():
add_environments(coverage_common, argparse, isolated_delegation=False)
coverage_common_isolated_delegation = argparse.ArgumentParser(add_help=False, parents=[common])
add_environments(coverage_common_isolated_delegation, argparse)
coverage = subparsers.add_parser('coverage',
help='code coverage management and reporting')
@ -588,11 +595,11 @@ def parse_args():
add_coverage_analyze(coverage_subparsers, coverage_common)
coverage_combine = coverage_subparsers.add_parser('combine',
parents=[coverage_common],
parents=[coverage_common_isolated_delegation],
help='combine coverage data and rewrite remote paths')
coverage_combine.set_defaults(func=command_coverage_combine,
config=CoverageConfig)
config=CoverageCombineConfig)
coverage_combine.add_argument('--export',
help='directory to export combined coverage files to')
@ -604,10 +611,10 @@ def parse_args():
help='erase coverage data files')
coverage_erase.set_defaults(func=command_coverage_erase,
config=CoverageConfig)
config=CoverageEraseConfig)
coverage_report = coverage_subparsers.add_parser('report',
parents=[coverage_common],
parents=[coverage_common_isolated_delegation],
help='generate console coverage report')
coverage_report.set_defaults(func=command_coverage_report,
@ -629,20 +636,20 @@ def parse_args():
add_extra_coverage_options(coverage_report)
coverage_html = coverage_subparsers.add_parser('html',
parents=[coverage_common],
parents=[coverage_common_isolated_delegation],
help='generate html coverage report')
coverage_html.set_defaults(func=command_coverage_html,
config=CoverageConfig)
config=CoverageHtmlConfig)
add_extra_coverage_options(coverage_html)
coverage_xml = coverage_subparsers.add_parser('xml',
parents=[coverage_common],
parents=[coverage_common_isolated_delegation],
help='generate xml coverage report')
coverage_xml.set_defaults(func=command_coverage_xml,
config=CoverageConfig)
config=CoverageXmlConfig)
add_extra_coverage_options(coverage_xml)

View file

@ -59,12 +59,6 @@ class CoverageConfig(EnvironmentConfig):
def __init__(self, args): # type: (t.Any) -> None
super(CoverageConfig, self).__init__(args, 'coverage')
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
def initialize_coverage(args): # type: (CoverageConfig) -> coverage_module
"""Delegate execution if requested, install requirements, then import and return the coverage module. Raises an exception if coverage is not available."""
@ -111,6 +105,11 @@ def run_coverage(args, output_file, command, cmd): # type: (CoverageConfig, str
intercept_command(args, target_name='coverage', env=env, cmd=cmd, disable_coverage=True)
def get_all_coverage_files(): # type: () -> t.List[str]
"""Return a list of all coverage file paths."""
return get_python_coverage_files() + get_powershell_coverage_files()
def get_python_coverage_files(path=None): # type: (t.Optional[str]) -> t.List[str]
"""Return the list of Python coverage file paths."""
return get_coverage_files('python', path)

View file

@ -5,6 +5,8 @@ __metaclass__ = type
import os
import json
from ... import types as t
from ...target import (
walk_compile_targets,
walk_powershell_targets,
@ -17,7 +19,7 @@ from ...io import (
from ...util import (
ANSIBLE_TEST_DATA_ROOT,
display,
find_executable,
ApplicationError,
)
from ...util_common import (
@ -27,10 +29,19 @@ from ...util_common import (
write_json_test_results,
)
from ...executor import (
Delegate,
)
from ...data import (
data_context,
)
from . import (
enumerate_python_arcs,
enumerate_powershell_lines,
get_collection_path_regexes,
get_all_coverage_files,
get_python_coverage_files,
get_python_modules,
get_powershell_coverage_files,
@ -44,9 +55,28 @@ from . import (
def command_coverage_combine(args):
"""Patch paths in coverage files and merge into a single file.
:type args: CoverageConfig
:type args: CoverageCombineConfig
:rtype: list[str]
"""
if args.delegate:
if args.docker or args.remote:
paths = get_all_coverage_files()
exported_paths = [path for path in paths if path.endswith('=coverage.combined')]
if not exported_paths:
raise ExportedCoverageDataNotFound()
pairs = [(path, os.path.relpath(path, data_context().content.root)) for path in exported_paths]
def coverage_callback(files): # type: (t.List[t.Tuple[str, str]]) -> None
"""Add the coverage files to the payload file list."""
display.info('Including %d exported coverage file(s) in payload.' % len(pairs), verbosity=1)
files.extend(pairs)
data_context().register_payload_callback(coverage_callback)
raise Delegate()
paths = _command_coverage_combine_powershell(args) + _command_coverage_combine_python(args)
for path in paths:
@ -55,9 +85,18 @@ def command_coverage_combine(args):
return paths
class ExportedCoverageDataNotFound(ApplicationError):
"""Exception when no combined coverage data is present yet is required."""
def __init__(self):
super(ExportedCoverageDataNotFound, self).__init__(
'Coverage data must be exported before processing with the `--docker` or `--remote` option.\n'
'Export coverage with `ansible-test coverage combine` using the `--export` option.\n'
'The exported files must be in the directory: %s/' % ResultType.COVERAGE.relative_path)
def _command_coverage_combine_python(args):
"""
:type args: CoverageConfig
:type args: CoverageCombineConfig
:rtype: list[str]
"""
coverage = initialize_coverage(args)
@ -136,7 +175,7 @@ def _command_coverage_combine_python(args):
def _command_coverage_combine_powershell(args):
"""
:type args: CoverageConfig
:type args: CoverageCombineConfig
:rtype: list[str]
"""
coverage_files = get_powershell_coverage_files()
@ -217,7 +256,7 @@ def _command_coverage_combine_powershell(args):
def _get_coverage_targets(args, walk_func):
"""
:type args: CoverageConfig
:type args: CoverageCombineConfig
:type walk_func: Func
:rtype: list[tuple[str, int]]
"""
@ -240,7 +279,7 @@ def _get_coverage_targets(args, walk_func):
def _build_stub_groups(args, sources, default_stub_value):
"""
:type args: CoverageConfig
:type args: CoverageCombineConfig
:type sources: List[tuple[str, int]]
:type default_stub_value: Func[List[str]]
:rtype: dict
@ -273,7 +312,7 @@ def _build_stub_groups(args, sources, default_stub_value):
def get_coverage_group(args, coverage_file):
"""
:type args: CoverageConfig
:type args: CoverageCombineConfig
:type coverage_file: str
:rtype: str
"""
@ -306,3 +345,16 @@ def get_coverage_group(args, coverage_file):
group = group.lstrip('=')
return group
class CoverageCombineConfig(CoverageConfig):
"""Configuration for the coverage combine command."""
def __init__(self, args): # type: (t.Any) -> None
super(CoverageCombineConfig, self).__init__(args)
self.group_by = frozenset(args.group_by) if args.group_by else frozenset() # type: t.FrozenSet[str]
self.all = args.all # type: bool
self.stub = args.stub # type: bool
# only available to coverage combine
self.export = args.export if 'export' in args else False # type: str

View file

@ -13,7 +13,7 @@ from . import (
)
def command_coverage_erase(args): # type: (CoverageConfig) -> None
def command_coverage_erase(args): # type: (CoverageEraseConfig) -> None
"""Erase code coverage data files collected during test runs."""
coverage_dir = ResultType.COVERAGE.path
@ -25,3 +25,7 @@ def command_coverage_erase(args): # type: (CoverageConfig) -> None
if not args.explain:
os.remove(path)
class CoverageEraseConfig(CoverageConfig):
"""Configuration for the coverage erase command."""

View file

@ -18,17 +18,17 @@ from ...util_common import (
from .combine import (
command_coverage_combine,
CoverageCombineConfig,
)
from . import (
run_coverage,
CoverageConfig,
)
def command_coverage_html(args):
"""
:type args: CoverageConfig
:type args: CoverageHtmlConfig
"""
output_files = command_coverage_combine(args)
@ -43,3 +43,7 @@ def command_coverage_html(args):
run_coverage(args, output_file, 'html', ['-i', '-d', dir_name])
display.info('HTML report generated: file:///%s' % os.path.join(dir_name, 'index.html'))
class CoverageHtmlConfig(CoverageCombineConfig):
"""Configuration for the coverage html command."""

View file

@ -18,11 +18,11 @@ from ...data import (
from .combine import (
command_coverage_combine,
CoverageCombineConfig,
)
from . import (
run_coverage,
CoverageConfig,
)
@ -143,7 +143,7 @@ def _generate_powershell_output_report(args, coverage_file):
return report
class CoverageReportConfig(CoverageConfig):
class CoverageReportConfig(CoverageCombineConfig):
"""Configuration for the coverage report command."""
def __init__(self, args):
"""

View file

@ -36,17 +36,17 @@ from ...data import (
from .combine import (
command_coverage_combine,
CoverageCombineConfig,
)
from . import (
run_coverage,
CoverageConfig,
)
def command_coverage_xml(args):
"""
:type args: CoverageConfig
:type args: CoverageXmlConfig
"""
output_files = command_coverage_combine(args)
@ -189,3 +189,7 @@ def _add_cobertura_package(packages, package_name, package_data):
})
return total_lines_hit, total_line_count
class CoverageXmlConfig(CoverageCombineConfig):
"""Configuration for the coverage xml command."""