Expand delegation options for coverage commands.
This commit is contained in:
parent
e875e91363
commit
ce04056797
8 changed files with 101 additions and 29 deletions
|
@ -0,0 +1,2 @@
|
||||||
|
minor_changes:
|
||||||
|
- ansible-test - The ``ansible-test coverage`` commands ``combine``, ``report``, ``html`` and ``xml`` now support delegation.
|
|
@ -108,14 +108,17 @@ from .util_common import (
|
||||||
|
|
||||||
from .commands.coverage.combine import (
|
from .commands.coverage.combine import (
|
||||||
command_coverage_combine,
|
command_coverage_combine,
|
||||||
|
CoverageCombineConfig,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .commands.coverage.erase import (
|
from .commands.coverage.erase import (
|
||||||
command_coverage_erase,
|
command_coverage_erase,
|
||||||
|
CoverageEraseConfig,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .commands.coverage.html import (
|
from .commands.coverage.html import (
|
||||||
command_coverage_html,
|
command_coverage_html,
|
||||||
|
CoverageHtmlConfig,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .commands.coverage.report import (
|
from .commands.coverage.report import (
|
||||||
|
@ -125,6 +128,7 @@ from .commands.coverage.report import (
|
||||||
|
|
||||||
from .commands.coverage.xml import (
|
from .commands.coverage.xml import (
|
||||||
command_coverage_xml,
|
command_coverage_xml,
|
||||||
|
CoverageXmlConfig,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .commands.coverage.analyze.targets.generate import (
|
from .commands.coverage.analyze.targets.generate import (
|
||||||
|
@ -154,7 +158,6 @@ from .commands.coverage.analyze.targets.missing import (
|
||||||
|
|
||||||
from .commands.coverage import (
|
from .commands.coverage import (
|
||||||
COVERAGE_GROUPS,
|
COVERAGE_GROUPS,
|
||||||
CoverageConfig,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if t.TYPE_CHECKING:
|
if t.TYPE_CHECKING:
|
||||||
|
@ -579,6 +582,10 @@ def parse_args():
|
||||||
|
|
||||||
add_environments(coverage_common, argparse, isolated_delegation=False)
|
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',
|
coverage = subparsers.add_parser('coverage',
|
||||||
help='code coverage management and reporting')
|
help='code coverage management and reporting')
|
||||||
|
|
||||||
|
@ -588,11 +595,11 @@ def parse_args():
|
||||||
add_coverage_analyze(coverage_subparsers, coverage_common)
|
add_coverage_analyze(coverage_subparsers, coverage_common)
|
||||||
|
|
||||||
coverage_combine = coverage_subparsers.add_parser('combine',
|
coverage_combine = coverage_subparsers.add_parser('combine',
|
||||||
parents=[coverage_common],
|
parents=[coverage_common_isolated_delegation],
|
||||||
help='combine coverage data and rewrite remote paths')
|
help='combine coverage data and rewrite remote paths')
|
||||||
|
|
||||||
coverage_combine.set_defaults(func=command_coverage_combine,
|
coverage_combine.set_defaults(func=command_coverage_combine,
|
||||||
config=CoverageConfig)
|
config=CoverageCombineConfig)
|
||||||
|
|
||||||
coverage_combine.add_argument('--export',
|
coverage_combine.add_argument('--export',
|
||||||
help='directory to export combined coverage files to')
|
help='directory to export combined coverage files to')
|
||||||
|
@ -604,10 +611,10 @@ def parse_args():
|
||||||
help='erase coverage data files')
|
help='erase coverage data files')
|
||||||
|
|
||||||
coverage_erase.set_defaults(func=command_coverage_erase,
|
coverage_erase.set_defaults(func=command_coverage_erase,
|
||||||
config=CoverageConfig)
|
config=CoverageEraseConfig)
|
||||||
|
|
||||||
coverage_report = coverage_subparsers.add_parser('report',
|
coverage_report = coverage_subparsers.add_parser('report',
|
||||||
parents=[coverage_common],
|
parents=[coverage_common_isolated_delegation],
|
||||||
help='generate console coverage report')
|
help='generate console coverage report')
|
||||||
|
|
||||||
coverage_report.set_defaults(func=command_coverage_report,
|
coverage_report.set_defaults(func=command_coverage_report,
|
||||||
|
@ -629,20 +636,20 @@ def parse_args():
|
||||||
add_extra_coverage_options(coverage_report)
|
add_extra_coverage_options(coverage_report)
|
||||||
|
|
||||||
coverage_html = coverage_subparsers.add_parser('html',
|
coverage_html = coverage_subparsers.add_parser('html',
|
||||||
parents=[coverage_common],
|
parents=[coverage_common_isolated_delegation],
|
||||||
help='generate html coverage report')
|
help='generate html coverage report')
|
||||||
|
|
||||||
coverage_html.set_defaults(func=command_coverage_html,
|
coverage_html.set_defaults(func=command_coverage_html,
|
||||||
config=CoverageConfig)
|
config=CoverageHtmlConfig)
|
||||||
|
|
||||||
add_extra_coverage_options(coverage_html)
|
add_extra_coverage_options(coverage_html)
|
||||||
|
|
||||||
coverage_xml = coverage_subparsers.add_parser('xml',
|
coverage_xml = coverage_subparsers.add_parser('xml',
|
||||||
parents=[coverage_common],
|
parents=[coverage_common_isolated_delegation],
|
||||||
help='generate xml coverage report')
|
help='generate xml coverage report')
|
||||||
|
|
||||||
coverage_xml.set_defaults(func=command_coverage_xml,
|
coverage_xml.set_defaults(func=command_coverage_xml,
|
||||||
config=CoverageConfig)
|
config=CoverageXmlConfig)
|
||||||
|
|
||||||
add_extra_coverage_options(coverage_xml)
|
add_extra_coverage_options(coverage_xml)
|
||||||
|
|
||||||
|
|
|
@ -59,12 +59,6 @@ class CoverageConfig(EnvironmentConfig):
|
||||||
def __init__(self, args): # type: (t.Any) -> None
|
def __init__(self, args): # type: (t.Any) -> None
|
||||||
super(CoverageConfig, self).__init__(args, 'coverage')
|
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
|
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."""
|
"""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)
|
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]
|
def get_python_coverage_files(path=None): # type: (t.Optional[str]) -> t.List[str]
|
||||||
"""Return the list of Python coverage file paths."""
|
"""Return the list of Python coverage file paths."""
|
||||||
return get_coverage_files('python', path)
|
return get_coverage_files('python', path)
|
||||||
|
|
|
@ -5,6 +5,8 @@ __metaclass__ = type
|
||||||
import os
|
import os
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
from ... import types as t
|
||||||
|
|
||||||
from ...target import (
|
from ...target import (
|
||||||
walk_compile_targets,
|
walk_compile_targets,
|
||||||
walk_powershell_targets,
|
walk_powershell_targets,
|
||||||
|
@ -17,7 +19,7 @@ from ...io import (
|
||||||
from ...util import (
|
from ...util import (
|
||||||
ANSIBLE_TEST_DATA_ROOT,
|
ANSIBLE_TEST_DATA_ROOT,
|
||||||
display,
|
display,
|
||||||
find_executable,
|
ApplicationError,
|
||||||
)
|
)
|
||||||
|
|
||||||
from ...util_common import (
|
from ...util_common import (
|
||||||
|
@ -27,10 +29,19 @@ from ...util_common import (
|
||||||
write_json_test_results,
|
write_json_test_results,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from ...executor import (
|
||||||
|
Delegate,
|
||||||
|
)
|
||||||
|
|
||||||
|
from ...data import (
|
||||||
|
data_context,
|
||||||
|
)
|
||||||
|
|
||||||
from . import (
|
from . import (
|
||||||
enumerate_python_arcs,
|
enumerate_python_arcs,
|
||||||
enumerate_powershell_lines,
|
enumerate_powershell_lines,
|
||||||
get_collection_path_regexes,
|
get_collection_path_regexes,
|
||||||
|
get_all_coverage_files,
|
||||||
get_python_coverage_files,
|
get_python_coverage_files,
|
||||||
get_python_modules,
|
get_python_modules,
|
||||||
get_powershell_coverage_files,
|
get_powershell_coverage_files,
|
||||||
|
@ -44,9 +55,28 @@ from . import (
|
||||||
|
|
||||||
def command_coverage_combine(args):
|
def command_coverage_combine(args):
|
||||||
"""Patch paths in coverage files and merge into a single file.
|
"""Patch paths in coverage files and merge into a single file.
|
||||||
:type args: CoverageConfig
|
:type args: CoverageCombineConfig
|
||||||
:rtype: list[str]
|
: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)
|
paths = _command_coverage_combine_powershell(args) + _command_coverage_combine_python(args)
|
||||||
|
|
||||||
for path in paths:
|
for path in paths:
|
||||||
|
@ -55,9 +85,18 @@ def command_coverage_combine(args):
|
||||||
return paths
|
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):
|
def _command_coverage_combine_python(args):
|
||||||
"""
|
"""
|
||||||
:type args: CoverageConfig
|
:type args: CoverageCombineConfig
|
||||||
:rtype: list[str]
|
:rtype: list[str]
|
||||||
"""
|
"""
|
||||||
coverage = initialize_coverage(args)
|
coverage = initialize_coverage(args)
|
||||||
|
@ -136,7 +175,7 @@ def _command_coverage_combine_python(args):
|
||||||
|
|
||||||
def _command_coverage_combine_powershell(args):
|
def _command_coverage_combine_powershell(args):
|
||||||
"""
|
"""
|
||||||
:type args: CoverageConfig
|
:type args: CoverageCombineConfig
|
||||||
:rtype: list[str]
|
:rtype: list[str]
|
||||||
"""
|
"""
|
||||||
coverage_files = get_powershell_coverage_files()
|
coverage_files = get_powershell_coverage_files()
|
||||||
|
@ -217,7 +256,7 @@ def _command_coverage_combine_powershell(args):
|
||||||
|
|
||||||
def _get_coverage_targets(args, walk_func):
|
def _get_coverage_targets(args, walk_func):
|
||||||
"""
|
"""
|
||||||
:type args: CoverageConfig
|
:type args: CoverageCombineConfig
|
||||||
:type walk_func: Func
|
:type walk_func: Func
|
||||||
:rtype: list[tuple[str, int]]
|
: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):
|
def _build_stub_groups(args, sources, default_stub_value):
|
||||||
"""
|
"""
|
||||||
:type args: CoverageConfig
|
:type args: CoverageCombineConfig
|
||||||
:type sources: List[tuple[str, int]]
|
:type sources: List[tuple[str, int]]
|
||||||
:type default_stub_value: Func[List[str]]
|
:type default_stub_value: Func[List[str]]
|
||||||
:rtype: dict
|
:rtype: dict
|
||||||
|
@ -273,7 +312,7 @@ def _build_stub_groups(args, sources, default_stub_value):
|
||||||
|
|
||||||
def get_coverage_group(args, coverage_file):
|
def get_coverage_group(args, coverage_file):
|
||||||
"""
|
"""
|
||||||
:type args: CoverageConfig
|
:type args: CoverageCombineConfig
|
||||||
:type coverage_file: str
|
:type coverage_file: str
|
||||||
:rtype: str
|
:rtype: str
|
||||||
"""
|
"""
|
||||||
|
@ -306,3 +345,16 @@ def get_coverage_group(args, coverage_file):
|
||||||
group = group.lstrip('=')
|
group = group.lstrip('=')
|
||||||
|
|
||||||
return group
|
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
|
||||||
|
|
|
@ -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."""
|
"""Erase code coverage data files collected during test runs."""
|
||||||
coverage_dir = ResultType.COVERAGE.path
|
coverage_dir = ResultType.COVERAGE.path
|
||||||
|
|
||||||
|
@ -25,3 +25,7 @@ def command_coverage_erase(args): # type: (CoverageConfig) -> None
|
||||||
|
|
||||||
if not args.explain:
|
if not args.explain:
|
||||||
os.remove(path)
|
os.remove(path)
|
||||||
|
|
||||||
|
|
||||||
|
class CoverageEraseConfig(CoverageConfig):
|
||||||
|
"""Configuration for the coverage erase command."""
|
||||||
|
|
|
@ -18,17 +18,17 @@ from ...util_common import (
|
||||||
|
|
||||||
from .combine import (
|
from .combine import (
|
||||||
command_coverage_combine,
|
command_coverage_combine,
|
||||||
|
CoverageCombineConfig,
|
||||||
)
|
)
|
||||||
|
|
||||||
from . import (
|
from . import (
|
||||||
run_coverage,
|
run_coverage,
|
||||||
CoverageConfig,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def command_coverage_html(args):
|
def command_coverage_html(args):
|
||||||
"""
|
"""
|
||||||
:type args: CoverageConfig
|
:type args: CoverageHtmlConfig
|
||||||
"""
|
"""
|
||||||
output_files = command_coverage_combine(args)
|
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])
|
run_coverage(args, output_file, 'html', ['-i', '-d', dir_name])
|
||||||
|
|
||||||
display.info('HTML report generated: file:///%s' % os.path.join(dir_name, 'index.html'))
|
display.info('HTML report generated: file:///%s' % os.path.join(dir_name, 'index.html'))
|
||||||
|
|
||||||
|
|
||||||
|
class CoverageHtmlConfig(CoverageCombineConfig):
|
||||||
|
"""Configuration for the coverage html command."""
|
||||||
|
|
|
@ -18,11 +18,11 @@ from ...data import (
|
||||||
|
|
||||||
from .combine import (
|
from .combine import (
|
||||||
command_coverage_combine,
|
command_coverage_combine,
|
||||||
|
CoverageCombineConfig,
|
||||||
)
|
)
|
||||||
|
|
||||||
from . import (
|
from . import (
|
||||||
run_coverage,
|
run_coverage,
|
||||||
CoverageConfig,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -143,7 +143,7 @@ def _generate_powershell_output_report(args, coverage_file):
|
||||||
return report
|
return report
|
||||||
|
|
||||||
|
|
||||||
class CoverageReportConfig(CoverageConfig):
|
class CoverageReportConfig(CoverageCombineConfig):
|
||||||
"""Configuration for the coverage report command."""
|
"""Configuration for the coverage report command."""
|
||||||
def __init__(self, args):
|
def __init__(self, args):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -36,17 +36,17 @@ from ...data import (
|
||||||
|
|
||||||
from .combine import (
|
from .combine import (
|
||||||
command_coverage_combine,
|
command_coverage_combine,
|
||||||
|
CoverageCombineConfig,
|
||||||
)
|
)
|
||||||
|
|
||||||
from . import (
|
from . import (
|
||||||
run_coverage,
|
run_coverage,
|
||||||
CoverageConfig,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def command_coverage_xml(args):
|
def command_coverage_xml(args):
|
||||||
"""
|
"""
|
||||||
:type args: CoverageConfig
|
:type args: CoverageXmlConfig
|
||||||
"""
|
"""
|
||||||
output_files = command_coverage_combine(args)
|
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
|
return total_lines_hit, total_line_count
|
||||||
|
|
||||||
|
|
||||||
|
class CoverageXmlConfig(CoverageCombineConfig):
|
||||||
|
"""Configuration for the coverage xml command."""
|
||||||
|
|
Loading…
Reference in a new issue