|
|
|
"""Utility code for facilitating collection of code coverage when running tests."""
|
|
|
|
from __future__ import (absolute_import, division, print_function)
|
|
|
|
__metaclass__ = type
|
|
|
|
|
|
|
|
import contextlib
|
|
|
|
import os
|
|
|
|
import tempfile
|
|
|
|
|
|
|
|
from .config import (
|
|
|
|
IntegrationConfig,
|
|
|
|
SanityConfig,
|
|
|
|
TestConfig,
|
|
|
|
)
|
|
|
|
|
|
|
|
from .util import (
|
|
|
|
COVERAGE_CONFIG_NAME,
|
|
|
|
remove_tree,
|
|
|
|
)
|
|
|
|
|
|
|
|
from .util_common import (
|
|
|
|
write_text_file,
|
|
|
|
)
|
|
|
|
|
|
|
|
from .data import (
|
|
|
|
data_context,
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
@contextlib.contextmanager
|
|
|
|
def coverage_context(args): # type: (TestConfig) -> None
|
|
|
|
"""Content to set up and clean up code coverage configuration for tests."""
|
|
|
|
coverage_setup(args)
|
|
|
|
|
|
|
|
try:
|
|
|
|
yield
|
|
|
|
finally:
|
|
|
|
coverage_cleanup(args)
|
|
|
|
|
|
|
|
|
|
|
|
def coverage_setup(args): # type: (TestConfig) -> None
|
|
|
|
"""Set up code coverage configuration before running tests."""
|
|
|
|
if not args.coverage:
|
|
|
|
return
|
|
|
|
|
|
|
|
coverage_config = generate_coverage_config(args)
|
|
|
|
|
|
|
|
if args.explain:
|
|
|
|
args.coverage_config_base_path = '/tmp/coverage-temp-dir'
|
|
|
|
else:
|
|
|
|
args.coverage_config_base_path = tempfile.mkdtemp()
|
|
|
|
|
|
|
|
write_text_file(os.path.join(args.coverage_config_base_path, COVERAGE_CONFIG_NAME), coverage_config)
|
|
|
|
|
|
|
|
|
|
|
|
def coverage_cleanup(args): # type: (TestConfig) -> None
|
|
|
|
"""Clean up code coverage configuration after tests have finished."""
|
|
|
|
if args.coverage_config_base_path and not args.explain:
|
|
|
|
remove_tree(args.coverage_config_base_path)
|
|
|
|
args.coverage_config_base_path = None
|
|
|
|
|
|
|
|
|
|
|
|
def generate_coverage_config(args): # type: (TestConfig) -> str
|
|
|
|
"""Generate code coverage configuration for tests."""
|
|
|
|
if data_context().content.collection:
|
|
|
|
coverage_config = generate_collection_coverage_config(args)
|
|
|
|
else:
|
|
|
|
coverage_config = generate_ansible_coverage_config()
|
|
|
|
|
|
|
|
return coverage_config
|
|
|
|
|
|
|
|
|
|
|
|
def generate_ansible_coverage_config(): # type: () -> str
|
|
|
|
"""Generate code coverage configuration for Ansible tests."""
|
|
|
|
coverage_config = '''
|
|
|
|
[run]
|
|
|
|
branch = True
|
|
|
|
concurrency = multiprocessing
|
|
|
|
parallel = True
|
|
|
|
|
|
|
|
omit =
|
|
|
|
*/python*/dist-packages/*
|
|
|
|
*/python*/site-packages/*
|
|
|
|
*/python*/distutils/*
|
|
|
|
*/pyshared/*
|
|
|
|
*/pytest
|
|
|
|
*/AnsiballZ_*.py
|
|
|
|
*/test/results/*
|
|
|
|
'''
|
|
|
|
|
|
|
|
return coverage_config
|
|
|
|
|
|
|
|
|
|
|
|
def generate_collection_coverage_config(args): # type: (TestConfig) -> str
|
|
|
|
"""Generate code coverage configuration for Ansible Collection tests."""
|
|
|
|
coverage_config = '''
|
|
|
|
[run]
|
|
|
|
branch = True
|
|
|
|
concurrency = multiprocessing
|
|
|
|
parallel = True
|
|
|
|
disable_warnings =
|
|
|
|
no-data-collected
|
|
|
|
'''
|
|
|
|
|
|
|
|
if isinstance(args, IntegrationConfig):
|
|
|
|
coverage_config += '''
|
|
|
|
include =
|
|
|
|
%s/*
|
|
|
|
*/%s/*
|
|
|
|
''' % (data_context().content.root, data_context().content.collection.directory)
|
|
|
|
elif isinstance(args, SanityConfig):
|
|
|
|
# temporary work-around for import sanity test
|
|
|
|
coverage_config += '''
|
|
|
|
include =
|
|
|
|
%s/*
|
|
|
|
|
|
|
|
omit =
|
|
|
|
*/test/results/*
|
|
|
|
''' % data_context().content.root
|
|
|
|
else:
|
|
|
|
coverage_config += '''
|
|
|
|
include =
|
|
|
|
%s/*
|
|
|
|
''' % data_context().content.root
|
|
|
|
|
|
|
|
return coverage_config
|