Fix ansible-test coverage analysis option usage. (#68406)

* Fix ansible-test coverage analysis option usage.

The `--input-dir` option for `coverage analyze targets generate` was being ignored.

No changelog entry since this feature has not yet been released.

* Move coverage config to fix type annotations.

Declaring the types before referencing them makes sure they're recognized by tools such as PyCharm.
This commit is contained in:
Matt Clay 2020-03-23 13:05:29 -07:00 committed by GitHub
parent d049888a92
commit 9765a80bd4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 87 additions and 85 deletions

View file

@ -52,6 +52,17 @@ COVERAGE_CONFIG_PATH = os.path.join(ANSIBLE_TEST_DATA_ROOT, 'coveragerc')
COVERAGE_OUTPUT_FILE_NAME = 'coverage'
class CoverageConfig(EnvironmentConfig):
"""Configuration for the coverage command."""
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.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."""
if args.delegate:
@ -81,19 +92,19 @@ 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_python_coverage_files(): # type: () -> 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 get_coverage_files('python')
return get_coverage_files('python', path)
def get_powershell_coverage_files(): # type: () -> t.List[str]
def get_powershell_coverage_files(path=None): # type: (t.Optional[str]) -> t.List[str]
"""Return the list of PowerShell coverage file paths."""
return get_coverage_files('powershell')
return get_coverage_files('powershell', path)
def get_coverage_files(language): # type: (str) -> t.List[str]
def get_coverage_files(language, path=None): # type: (str, t.Optional[str]) -> t.List[str]
"""Return the list of coverage file paths for the given language."""
coverage_dir = ResultType.COVERAGE.path
coverage_dir = path or ResultType.COVERAGE.path
coverage_files = [os.path.join(coverage_dir, f) for f in os.listdir(coverage_dir)
if '=coverage.' in f and '=%s' % language in f]
@ -247,17 +258,6 @@ def sanitize_filename(
return filename
class CoverageConfig(EnvironmentConfig):
"""Configuration for the coverage command."""
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.coverage = False # temporary work-around to support intercept_command in cover.py
class PathChecker:
"""Checks code coverage paths to verify they are valid and reports on the findings."""
def __init__(self, args, collection_search_re=None): # type: (CoverageConfig, t.Optional[t.Pattern]) -> None

View file

@ -30,6 +30,14 @@ if t.TYPE_CHECKING:
TargetSetIndexes = t.Dict[t.FrozenSet[int], int]
class CoverageAnalyzeTargetsConfig(CoverageAnalyzeConfig):
"""Configuration for the `coverage analyze targets` command."""
def __init__(self, args): # type: (t.Any) -> None
super(CoverageAnalyzeTargetsConfig, self).__init__(args)
self.info_stderr = True
def make_report(target_indexes, arcs, lines): # type: (TargetIndexes, Arcs, Lines) -> t.Dict[str, t.Any]
"""Condense target indexes, arcs and lines into a compact report."""
set_indexes = {}
@ -144,11 +152,3 @@ def generate_indexes(target_indexes, data): # type: (TargetIndexes, NamedPoints
result_point.add(get_target_index(target_name, target_indexes))
return results
class CoverageAnalyzeTargetsConfig(CoverageAnalyzeConfig):
"""Configuration for the `coverage analyze targets` command."""
def __init__(self, args): # type: (t.Any) -> None
super(CoverageAnalyzeTargetsConfig, self).__init__(args)
self.info_stderr = True

View file

@ -21,6 +21,15 @@ if t.TYPE_CHECKING:
)
class CoverageAnalyzeTargetsCombineConfig(CoverageAnalyzeTargetsConfig):
"""Configuration for the `coverage analyze targets combine` command."""
def __init__(self, args): # type: (t.Any) -> None
super(CoverageAnalyzeTargetsCombineConfig, self).__init__(args)
self.input_files = args.input_file # type: t.List[str]
self.output_file = args.output_file # type: str
def command_coverage_analyze_targets_combine(args): # type: (CoverageAnalyzeTargetsCombineConfig) -> None
"""Combine integration test target code coverage reports."""
combined_target_indexes = {} # type: TargetIndexes
@ -53,12 +62,3 @@ def merge_indexes(
for covered_target_index in covered_target_indexes:
combined_point.add(get_target_index(source_index[covered_target_index], combined_index))
class CoverageAnalyzeTargetsCombineConfig(CoverageAnalyzeTargetsConfig):
"""Configuration for the `coverage analyze targets combine` command."""
def __init__(self, args): # type: (t.Any) -> None
super(CoverageAnalyzeTargetsCombineConfig, self).__init__(args)
self.input_files = args.input_file # type: t.List[str]
self.output_file = args.output_file # type: str

View file

@ -17,6 +17,15 @@ from . import (
)
class CoverageAnalyzeTargetsExpandConfig(CoverageAnalyzeTargetsConfig):
"""Configuration for the `coverage analyze targets expand` command."""
def __init__(self, args): # type: (t.Any) -> None
super(CoverageAnalyzeTargetsExpandConfig, self).__init__(args)
self.input_file = args.input_file # type: str
self.output_file = args.output_file # type: str
def command_coverage_analyze_targets_expand(args): # type: (CoverageAnalyzeTargetsExpandConfig) -> None
"""Expand target names in an aggregated coverage file."""
covered_targets, covered_path_arcs, covered_path_lines = read_report(args.input_file)
@ -28,12 +37,3 @@ def command_coverage_analyze_targets_expand(args): # type: (CoverageAnalyzeTarg
if not args.explain:
write_json_file(args.output_file, report, encoder=SortedSetEncoder)
class CoverageAnalyzeTargetsExpandConfig(CoverageAnalyzeTargetsConfig):
"""Configuration for the `coverage analyze targets expand` command."""
def __init__(self, args): # type: (t.Any) -> None
super(CoverageAnalyzeTargetsExpandConfig, self).__init__(args)
self.input_file = args.input_file # type: str
self.output_file = args.output_file # type: str

View file

@ -22,6 +22,19 @@ if t.TYPE_CHECKING:
)
class CoverageAnalyzeTargetsFilterConfig(CoverageAnalyzeTargetsConfig):
"""Configuration for the `coverage analyze targets filter` command."""
def __init__(self, args): # type: (t.Any) -> None
super(CoverageAnalyzeTargetsFilterConfig, self).__init__(args)
self.input_file = args.input_file # type: str
self.output_file = args.output_file # type: str
self.include_targets = args.include_targets # type: t.List[str]
self.exclude_targets = args.exclude_targets # type: t.List[str]
self.include_path = args.include_path # type: t.Optional[str]
self.exclude_path = args.exclude_path # type: t.Optional[str]
def command_coverage_analyze_targets_filter(args): # type: (CoverageAnalyzeTargetsFilterConfig) -> None
"""Filter target names in an aggregated coverage file."""
covered_targets, covered_path_arcs, covered_path_lines = read_report(args.input_file)
@ -89,16 +102,3 @@ def filter_data(
result[src_path] = dst_points
return result
class CoverageAnalyzeTargetsFilterConfig(CoverageAnalyzeTargetsConfig):
"""Configuration for the `coverage analyze targets filter` command."""
def __init__(self, args): # type: (t.Any) -> None
super(CoverageAnalyzeTargetsFilterConfig, self).__init__(args)
self.input_file = args.input_file # type: str
self.output_file = args.output_file # type: str
self.include_targets = args.include_targets # type: t.List[str]
self.exclude_targets = args.exclude_targets # type: t.List[str]
self.include_path = args.include_path # type: t.Optional[str]
self.exclude_path = args.exclude_path # type: t.Optional[str]

View file

@ -44,25 +44,35 @@ if t.TYPE_CHECKING:
)
class CoverageAnalyzeTargetsGenerateConfig(CoverageAnalyzeTargetsConfig):
"""Configuration for the `coverage analyze targets generate` command."""
def __init__(self, args): # type: (t.Any) -> None
super(CoverageAnalyzeTargetsGenerateConfig, self).__init__(args)
self.input_dir = args.input_dir or ResultType.COVERAGE.path # type: str
self.output_file = args.output_file # type: str
def command_coverage_analyze_targets_generate(args): # type: (CoverageAnalyzeTargetsGenerateConfig) -> None
"""Analyze code coverage data to determine which integration test targets provide coverage for each arc or line."""
root = data_context().content.root
target_indexes = {}
arcs = dict((os.path.relpath(path, root), data) for path, data in analyze_python_coverage(args, target_indexes).items())
lines = dict((os.path.relpath(path, root), data) for path, data in analyze_powershell_coverage(args, target_indexes).items())
arcs = dict((os.path.relpath(path, root), data) for path, data in analyze_python_coverage(args, args.input_dir, target_indexes).items())
lines = dict((os.path.relpath(path, root), data) for path, data in analyze_powershell_coverage(args, args.input_dir, target_indexes).items())
report = make_report(target_indexes, arcs, lines)
write_report(args, report, args.output_file)
def analyze_python_coverage(
args, # type: CoverageAnalyzeTargetsConfig
args, # type: CoverageAnalyzeTargetsGenerateConfig
path, # type: str
target_indexes, # type: TargetIndexes
): # type: (...) -> Arcs
"""Analyze Python code coverage."""
results = {} # type: Arcs
collection_search_re, collection_sub_re = get_collection_path_regexes()
modules = get_python_modules()
python_files = get_python_coverage_files()
python_files = get_python_coverage_files(path)
coverage = initialize_coverage(args)
for python_file in python_files:
@ -85,13 +95,14 @@ def analyze_python_coverage(
def analyze_powershell_coverage(
args, # type: CoverageAnalyzeTargetsConfig
args, # type: CoverageAnalyzeTargetsGenerateConfig
path, # type: str
target_indexes, # type: TargetIndexes
): # type: (...) -> Lines
"""Analyze PowerShell code coverage"""
results = {} # type: Lines
collection_search_re, collection_sub_re = get_collection_path_regexes()
powershell_files = get_powershell_coverage_files()
powershell_files = get_powershell_coverage_files(path)
for powershell_file in powershell_files:
if not is_integration_coverage_file(powershell_file):
@ -113,7 +124,7 @@ def analyze_powershell_coverage(
def prune_invalid_filenames(
args, # type: CoverageAnalyzeTargetsConfig
args, # type: CoverageAnalyzeTargetsGenerateConfig
results, # type: t.Dict[str, t.Any]
collection_search_re=None, # type: t.Optional[str]
): # type: (...) -> None
@ -133,12 +144,3 @@ def get_target_name(path): # type: (str) -> str
def is_integration_coverage_file(path): # type: (str) -> bool
"""Returns True if the coverage file came from integration tests, otherwise False."""
return os.path.basename(path).split('=')[0] in ('integration', 'windows-integration', 'network-integration')
class CoverageAnalyzeTargetsGenerateConfig(CoverageAnalyzeTargetsConfig):
"""Configuration for the `coverage analyze targets generate` command."""
def __init__(self, args): # type: (t.Any) -> None
super(CoverageAnalyzeTargetsGenerateConfig, self).__init__(args)
self.input_dir = args.input_dir or ResultType.COVERAGE.path # type: str
self.output_file = args.output_file # type: str

View file

@ -25,6 +25,19 @@ if t.TYPE_CHECKING:
)
class CoverageAnalyzeTargetsMissingConfig(CoverageAnalyzeTargetsConfig):
"""Configuration for the `coverage analyze targets missing` command."""
def __init__(self, args): # type: (t.Any) -> None
super(CoverageAnalyzeTargetsMissingConfig, self).__init__(args)
self.from_file = args.from_file # type: str
self.to_file = args.to_file # type: str
self.output_file = args.output_file # type: str
self.only_gaps = args.only_gaps # type: bool
self.only_exists = args.only_exists # type: bool
def command_coverage_analyze_targets_missing(args): # type: (CoverageAnalyzeTargetsMissingConfig) -> None
"""Identify aggregated coverage in one file missing from another."""
from_targets, from_path_arcs, from_path_lines = read_report(args.from_file)
@ -94,16 +107,3 @@ def find_missing(
target_index.update(get_target_index(name, target_indexes) for name in remaining_targets)
return target_data
class CoverageAnalyzeTargetsMissingConfig(CoverageAnalyzeTargetsConfig):
"""Configuration for the `coverage analyze targets missing` command."""
def __init__(self, args): # type: (t.Any) -> None
super(CoverageAnalyzeTargetsMissingConfig, self).__init__(args)
self.from_file = args.from_file # type: str
self.to_file = args.to_file # type: str
self.output_file = args.output_file # type: str
self.only_gaps = args.only_gaps # type: bool
self.only_exists = args.only_exists # type: bool