mirror of
https://github.com/ansible-collections/community.general.git
synced 2025-07-22 04:40:22 -07:00
Miscellaneous bug fixes for ansible-test.
- Overhauled coverage injector to fix issues with non-local tests. - Updated integration tests to work with the new coverage injector. - Fix concurrency issue by using random temp files for delegation. - Fix handling of coverage files from root user. - Fix handling of coverage files without arcs. - Make sure temp copy of injector is world readable and executable.
This commit is contained in:
parent
548cacdf6a
commit
dfd19a812f
26 changed files with 259 additions and 155 deletions
|
@ -33,8 +33,7 @@ def command_coverage_combine(args):
|
|||
|
||||
modules = dict((t.module, t.path) for t in list(walk_module_targets()))
|
||||
|
||||
coverage_files = [os.path.join(COVERAGE_DIR, f) for f in os.listdir(COVERAGE_DIR)
|
||||
if f.startswith('coverage') and f != 'coverage']
|
||||
coverage_files = [os.path.join(COVERAGE_DIR, f) for f in os.listdir(COVERAGE_DIR) if '=coverage.' in f]
|
||||
|
||||
arc_data = {}
|
||||
|
||||
|
@ -60,7 +59,12 @@ def command_coverage_combine(args):
|
|||
continue
|
||||
|
||||
for filename in original.measured_files():
|
||||
arcs = set(original.arcs(filename))
|
||||
arcs = set(original.arcs(filename) or [])
|
||||
|
||||
if not arcs:
|
||||
# This is most likely due to using an unsupported version of coverage.
|
||||
display.warning('No arcs found for "%s" in coverage file: %s' % (filename, coverage_file))
|
||||
continue
|
||||
|
||||
if '/ansible_modlib.zip/ansible/' in filename:
|
||||
new_name = re.sub('^.*/ansible_modlib.zip/ansible/', ansible_path, filename)
|
||||
|
@ -68,11 +72,14 @@ def command_coverage_combine(args):
|
|||
filename = new_name
|
||||
elif '/ansible_module_' in filename:
|
||||
module = re.sub('^.*/ansible_module_(?P<module>.*).py$', '\\g<module>', filename)
|
||||
if module not in modules:
|
||||
display.warning('Skipping coverage of unknown module: %s' % module)
|
||||
continue
|
||||
new_name = os.path.abspath(modules[module])
|
||||
display.info('%s -> %s' % (filename, new_name), verbosity=3)
|
||||
filename = new_name
|
||||
elif filename.startswith('/root/ansible/'):
|
||||
new_name = re.sub('^/.*?/ansible/', root_path, filename)
|
||||
elif re.search('^(/.*?)?/root/ansible/', filename):
|
||||
new_name = re.sub('^(/.*?)?/root/ansible/', root_path, filename)
|
||||
display.info('%s -> %s' % (filename, new_name), verbosity=3)
|
||||
filename = new_name
|
||||
|
||||
|
@ -125,7 +132,7 @@ def command_coverage_erase(args):
|
|||
initialize_coverage(args)
|
||||
|
||||
for name in os.listdir(COVERAGE_DIR):
|
||||
if not name.startswith('coverage'):
|
||||
if not name.startswith('coverage') and '=coverage.' not in name:
|
||||
continue
|
||||
|
||||
path = os.path.join(COVERAGE_DIR, name)
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
from __future__ import absolute_import, print_function
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
|
@ -124,6 +125,10 @@ def delegate_tox(args, exclude, require):
|
|||
if not args.python:
|
||||
cmd += ['--python', version]
|
||||
|
||||
if isinstance(args, TestConfig):
|
||||
if args.coverage and not args.coverage_label:
|
||||
cmd += ['--coverage-label', 'tox-%s' % version]
|
||||
|
||||
run_command(args, tox + cmd)
|
||||
|
||||
|
||||
|
@ -153,6 +158,12 @@ def delegate_docker(args, exclude, require):
|
|||
|
||||
cmd = generate_command(args, '/root/ansible/test/runner/test.py', options, exclude, require)
|
||||
|
||||
if isinstance(args, TestConfig):
|
||||
if args.coverage and not args.coverage_label:
|
||||
image_label = re.sub('^ansible/ansible:', '', args.docker)
|
||||
image_label = re.sub('[^a-zA-Z0-9]+', '-', image_label)
|
||||
cmd += ['--coverage-label', 'docker-%s' % image_label]
|
||||
|
||||
if isinstance(args, IntegrationConfig):
|
||||
if not args.allow_destructive:
|
||||
cmd.append('--allow-destructive')
|
||||
|
@ -162,75 +173,77 @@ def delegate_docker(args, exclude, require):
|
|||
if isinstance(args, ShellConfig):
|
||||
cmd_options.append('-it')
|
||||
|
||||
if not args.explain:
|
||||
lib.pytar.create_tarfile('/tmp/ansible.tgz', '.', lib.pytar.ignore)
|
||||
with tempfile.NamedTemporaryFile(prefix='ansible-source-', suffix='.tgz') as local_source_fd:
|
||||
try:
|
||||
if not args.explain:
|
||||
lib.pytar.create_tarfile(local_source_fd.name, '.', lib.pytar.ignore)
|
||||
|
||||
try:
|
||||
if util_image:
|
||||
util_options = [
|
||||
if util_image:
|
||||
util_options = [
|
||||
'--detach',
|
||||
]
|
||||
|
||||
util_id, _ = docker_run(args, util_image, options=util_options)
|
||||
|
||||
if args.explain:
|
||||
util_id = 'util_id'
|
||||
else:
|
||||
util_id = util_id.strip()
|
||||
else:
|
||||
util_id = None
|
||||
|
||||
test_options = [
|
||||
'--detach',
|
||||
'--volume', '/sys/fs/cgroup:/sys/fs/cgroup:ro',
|
||||
'--privileged=%s' % str(privileged).lower(),
|
||||
]
|
||||
|
||||
util_id, _ = docker_run(args, util_image, options=util_options)
|
||||
if util_id:
|
||||
test_options += [
|
||||
'--link', '%s:ansible.http.tests' % util_id,
|
||||
'--link', '%s:sni1.ansible.http.tests' % util_id,
|
||||
'--link', '%s:sni2.ansible.http.tests' % util_id,
|
||||
'--link', '%s:fail.ansible.http.tests' % util_id,
|
||||
'--env', 'HTTPTESTER=1',
|
||||
]
|
||||
|
||||
if isinstance(args, TestConfig):
|
||||
cloud_platforms = get_cloud_providers(args)
|
||||
|
||||
for cloud_platform in cloud_platforms:
|
||||
test_options += cloud_platform.get_docker_run_options()
|
||||
|
||||
test_id, _ = docker_run(args, test_image, options=test_options)
|
||||
|
||||
if args.explain:
|
||||
util_id = 'util_id'
|
||||
test_id = 'test_id'
|
||||
else:
|
||||
util_id = util_id.strip()
|
||||
else:
|
||||
util_id = None
|
||||
test_id = test_id.strip()
|
||||
|
||||
test_options = [
|
||||
'--detach',
|
||||
'--volume', '/sys/fs/cgroup:/sys/fs/cgroup:ro',
|
||||
'--privileged=%s' % str(privileged).lower(),
|
||||
]
|
||||
# write temporary files to /root since /tmp isn't ready immediately on container start
|
||||
docker_put(args, test_id, 'test/runner/setup/docker.sh', '/root/docker.sh')
|
||||
docker_exec(args, test_id, ['/bin/bash', '/root/docker.sh'])
|
||||
docker_put(args, test_id, local_source_fd.name, '/root/ansible.tgz')
|
||||
docker_exec(args, test_id, ['mkdir', '/root/ansible'])
|
||||
docker_exec(args, test_id, ['tar', 'oxzf', '/root/ansible.tgz', '-C', '/root/ansible'])
|
||||
|
||||
if util_id:
|
||||
test_options += [
|
||||
'--link', '%s:ansible.http.tests' % util_id,
|
||||
'--link', '%s:sni1.ansible.http.tests' % util_id,
|
||||
'--link', '%s:sni2.ansible.http.tests' % util_id,
|
||||
'--link', '%s:fail.ansible.http.tests' % util_id,
|
||||
'--env', 'HTTPTESTER=1',
|
||||
]
|
||||
# docker images are only expected to have a single python version available
|
||||
if isinstance(args, UnitsConfig) and not args.python:
|
||||
cmd += ['--python', 'default']
|
||||
|
||||
if isinstance(args, TestConfig):
|
||||
cloud_platforms = get_cloud_providers(args)
|
||||
|
||||
for cloud_platform in cloud_platforms:
|
||||
test_options += cloud_platform.get_docker_run_options()
|
||||
|
||||
test_id, _ = docker_run(args, test_image, options=test_options)
|
||||
|
||||
if args.explain:
|
||||
test_id = 'test_id'
|
||||
else:
|
||||
test_id = test_id.strip()
|
||||
|
||||
# write temporary files to /root since /tmp isn't ready immediately on container start
|
||||
docker_put(args, test_id, 'test/runner/setup/docker.sh', '/root/docker.sh')
|
||||
docker_exec(args, test_id, ['/bin/bash', '/root/docker.sh'])
|
||||
docker_put(args, test_id, '/tmp/ansible.tgz', '/root/ansible.tgz')
|
||||
docker_exec(args, test_id, ['mkdir', '/root/ansible'])
|
||||
docker_exec(args, test_id, ['tar', 'oxzf', '/root/ansible.tgz', '-C', '/root/ansible'])
|
||||
|
||||
# docker images are only expected to have a single python version available
|
||||
if isinstance(args, UnitsConfig) and not args.python:
|
||||
cmd += ['--python', 'default']
|
||||
|
||||
try:
|
||||
docker_exec(args, test_id, cmd, options=cmd_options)
|
||||
try:
|
||||
docker_exec(args, test_id, cmd, options=cmd_options)
|
||||
finally:
|
||||
with tempfile.NamedTemporaryFile(prefix='ansible-result-', suffix='.tgz') as local_result_fd:
|
||||
docker_exec(args, test_id, ['tar', 'czf', '/root/results.tgz', '-C', '/root/ansible/test', 'results'])
|
||||
docker_get(args, test_id, '/root/results.tgz', local_result_fd.name)
|
||||
run_command(args, ['tar', 'oxzf', local_result_fd.name, '-C', 'test'])
|
||||
finally:
|
||||
docker_exec(args, test_id, ['tar', 'czf', '/root/results.tgz', '-C', '/root/ansible/test', 'results'])
|
||||
docker_get(args, test_id, '/root/results.tgz', '/tmp/results.tgz')
|
||||
run_command(args, ['tar', 'oxzf', '/tmp/results.tgz', '-C', 'test'])
|
||||
finally:
|
||||
if util_id:
|
||||
docker_rm(args, util_id)
|
||||
if util_id:
|
||||
docker_rm(args, util_id)
|
||||
|
||||
if test_id:
|
||||
docker_rm(args, test_id)
|
||||
if test_id:
|
||||
docker_rm(args, test_id)
|
||||
|
||||
|
||||
def delegate_remote(args, exclude, require):
|
||||
|
@ -257,6 +270,10 @@ def delegate_remote(args, exclude, require):
|
|||
|
||||
cmd = generate_command(args, 'ansible/test/runner/test.py', options, exclude, require)
|
||||
|
||||
if isinstance(args, TestConfig):
|
||||
if args.coverage and not args.coverage_label:
|
||||
cmd += ['--coverage-label', 'remote-%s-%s' % (platform, version)]
|
||||
|
||||
if isinstance(args, IntegrationConfig):
|
||||
if not args.allow_destructive:
|
||||
cmd.append('--allow-destructive')
|
||||
|
|
|
@ -12,7 +12,6 @@ import functools
|
|||
import shutil
|
||||
import stat
|
||||
import random
|
||||
import pipes
|
||||
import string
|
||||
import atexit
|
||||
|
||||
|
@ -607,7 +606,7 @@ def command_integration_script(args, target):
|
|||
env = integration_environment(args, target, cmd)
|
||||
cwd = target.path
|
||||
|
||||
intercept_command(args, cmd, env=env, cwd=cwd)
|
||||
intercept_command(args, cmd, target_name=target.name, env=env, cwd=cwd)
|
||||
|
||||
|
||||
def command_integration_role(args, target, start_at_task):
|
||||
|
@ -668,7 +667,7 @@ def command_integration_role(args, target, start_at_task):
|
|||
|
||||
env['ANSIBLE_ROLES_PATH'] = os.path.abspath('test/integration/targets')
|
||||
|
||||
intercept_command(args, cmd, env=env, cwd=cwd)
|
||||
intercept_command(args, cmd, target_name=target.name, env=env, cwd=cwd)
|
||||
|
||||
|
||||
def command_units(args):
|
||||
|
@ -723,7 +722,7 @@ def command_units(args):
|
|||
display.info('Unit test with Python %s' % version)
|
||||
|
||||
try:
|
||||
intercept_command(args, command, env=env, python_version=version)
|
||||
intercept_command(args, command, target_name='units', env=env, python_version=version)
|
||||
except SubprocessError as ex:
|
||||
# pytest exits with status code 5 when all tests are skipped, which isn't an error for our use case
|
||||
if ex.status != 5:
|
||||
|
@ -838,7 +837,7 @@ def compile_version(args, python_version, include, exclude):
|
|||
return TestSuccess(command, test, python_version=python_version)
|
||||
|
||||
|
||||
def intercept_command(args, cmd, capture=False, env=None, data=None, cwd=None, python_version=None):
|
||||
def intercept_command(args, cmd, target_name, capture=False, env=None, data=None, cwd=None, python_version=None):
|
||||
"""
|
||||
:type args: TestConfig
|
||||
:type cmd: collections.Iterable[str]
|
||||
|
@ -853,13 +852,25 @@ def intercept_command(args, cmd, capture=False, env=None, data=None, cwd=None, p
|
|||
env = common_environment()
|
||||
|
||||
cmd = list(cmd)
|
||||
escaped_cmd = ' '.join(pipes.quote(c) for c in cmd)
|
||||
inject_path = get_coverage_path(args)
|
||||
config_path = os.path.join(inject_path, 'injector.json')
|
||||
version = python_version or args.python_version
|
||||
interpreter = find_executable('python%s' % version)
|
||||
coverage_file = os.path.abspath(os.path.join(inject_path, '..', 'output', '%s=%s=%s=%s=coverage' % (
|
||||
args.command, target_name, args.coverage_label or 'local-%s' % version, version)))
|
||||
|
||||
env['PATH'] = inject_path + os.pathsep + env['PATH']
|
||||
env['ANSIBLE_TEST_COVERAGE'] = 'coverage' if args.coverage else 'version'
|
||||
env['ANSIBLE_TEST_PYTHON_VERSION'] = python_version or args.python_version
|
||||
env['ANSIBLE_TEST_CMD'] = escaped_cmd
|
||||
env['ANSIBLE_TEST_PYTHON_VERSION'] = version
|
||||
env['ANSIBLE_TEST_PYTHON_INTERPRETER'] = interpreter
|
||||
|
||||
config = dict(
|
||||
python_interpreter=interpreter,
|
||||
coverage_file=coverage_file if args.coverage else None,
|
||||
)
|
||||
|
||||
if not args.explain:
|
||||
with open(config_path, 'w') as config_fd:
|
||||
json.dump(config, config_fd, indent=4, sort_keys=True)
|
||||
|
||||
return run_command(args, cmd, capture=capture, env=env, data=data, cwd=cwd)
|
||||
|
||||
|
@ -888,6 +899,10 @@ def get_coverage_path(args):
|
|||
shutil.copytree(src, os.path.join(coverage_path, 'coverage'))
|
||||
shutil.copy('.coveragerc', os.path.join(coverage_path, 'coverage', '.coveragerc'))
|
||||
|
||||
for root, dir_names, file_names in os.walk(coverage_path):
|
||||
for name in dir_names + file_names:
|
||||
os.chmod(os.path.join(root, name), stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH)
|
||||
|
||||
for directory in 'output', 'logs':
|
||||
os.mkdir(os.path.join(coverage_path, directory))
|
||||
os.chmod(os.path.join(coverage_path, directory), stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO)
|
||||
|
@ -1210,7 +1225,7 @@ class EnvironmentDescription(object):
|
|||
:type command: list[str]
|
||||
:rtype: str
|
||||
"""
|
||||
stdout, stderr = raw_command(command, capture=True)
|
||||
stdout, stderr = raw_command(command, capture=True, cmd_verbosity=2)
|
||||
return (stdout or '').strip() + (stderr or '').strip()
|
||||
|
||||
@staticmethod
|
||||
|
|
|
@ -2,7 +2,9 @@
|
|||
|
||||
from __future__ import absolute_import, print_function
|
||||
|
||||
import os
|
||||
import pipes
|
||||
import tempfile
|
||||
|
||||
from time import sleep
|
||||
|
||||
|
@ -135,11 +137,15 @@ class ManagePosixCI(object):
|
|||
|
||||
def upload_source(self):
|
||||
"""Upload and extract source."""
|
||||
if not self.core_ci.args.explain:
|
||||
lib.pytar.create_tarfile('/tmp/ansible.tgz', '.', lib.pytar.ignore)
|
||||
with tempfile.NamedTemporaryFile(prefix='ansible-source-', suffix='.tgz') as local_source_fd:
|
||||
remote_source_dir = '/tmp'
|
||||
remote_source_path = os.path.join(remote_source_dir, os.path.basename(local_source_fd.name))
|
||||
|
||||
self.upload('/tmp/ansible.tgz', '/tmp')
|
||||
self.ssh('rm -rf ~/ansible && mkdir ~/ansible && cd ~/ansible && tar oxzf /tmp/ansible.tgz')
|
||||
if not self.core_ci.args.explain:
|
||||
lib.pytar.create_tarfile(local_source_fd.name, '.', lib.pytar.ignore)
|
||||
|
||||
self.upload(local_source_fd.name, remote_source_dir)
|
||||
self.ssh('rm -rf ~/ansible && mkdir ~/ansible && cd ~/ansible && tar oxzf %s' % remote_source_path)
|
||||
|
||||
def download(self, remote, local):
|
||||
"""
|
||||
|
|
|
@ -644,7 +644,7 @@ def command_sanity_ansible_doc(args, targets, python_version):
|
|||
cmd = ['ansible-doc'] + modules
|
||||
|
||||
try:
|
||||
stdout, stderr = intercept_command(args, cmd, env=env, capture=True, python_version=python_version)
|
||||
stdout, stderr = intercept_command(args, cmd, target_name='ansible-doc', env=env, capture=True, python_version=python_version)
|
||||
status = 0
|
||||
except SubprocessError as ex:
|
||||
stdout = ex.stdout
|
||||
|
|
|
@ -65,6 +65,7 @@ class TestConfig(EnvironmentConfig):
|
|||
super(TestConfig, self).__init__(args, command)
|
||||
|
||||
self.coverage = args.coverage # type: bool
|
||||
self.coverage_label = args.coverage_label # type: str
|
||||
self.include = args.include # type: list [str]
|
||||
self.exclude = args.exclude # type: list [str]
|
||||
self.require = args.require # type: list [str]
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue