Skip to content

Commit 6a2a7a2

Browse files
committed
Enable codecov.io and add coverage grouping.
1 parent a23f503 commit 6a2a7a2

File tree

6 files changed

+131
-22
lines changed

6 files changed

+131
-22
lines changed

.coveragerc

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,5 @@ data_file = test/results/coverage/coverage
2020
omit =
2121
*/python*/dist-packages/*
2222
*/python*/site-packages/*
23-
*/python*/distutils
23+
*/python*/distutils/*
2424
*/pytest

.gitignore

+2-2
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,8 @@ packaging/release/ansible_release
6868
/.cache/
6969
/test/results/coverage/*=coverage.*
7070
/test/results/coverage/coverage*
71-
/test/results/reports/coverage.xml
72-
/test/results/reports/coverage/
71+
/test/results/reports/coverage*.xml
72+
/test/results/reports/coverage*/
7373
/test/results/bot/*.json
7474
/test/results/junit/*.xml
7575
/test/results/logs/*.log

test/runner/lib/cover.py

+84-16
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
ApplicationError,
1515
EnvironmentConfig,
1616
run_command,
17+
common_environment,
1718
)
1819

1920
from lib.executor import (
@@ -23,31 +24,38 @@
2324

2425
COVERAGE_DIR = 'test/results/coverage'
2526
COVERAGE_FILE = os.path.join(COVERAGE_DIR, 'coverage')
27+
COVERAGE_GROUPS = ('command', 'target', 'environment', 'version')
2628

2729

2830
def command_coverage_combine(args):
2931
"""Patch paths in coverage files and merge into a single file.
3032
:type args: CoverageConfig
33+
:rtype: list[str]
3134
"""
3235
coverage = initialize_coverage(args)
3336

3437
modules = dict((t.module, t.path) for t in list(walk_module_targets()))
3538

3639
coverage_files = [os.path.join(COVERAGE_DIR, f) for f in os.listdir(COVERAGE_DIR) if '=coverage.' in f]
3740

38-
arc_data = {}
39-
4041
ansible_path = os.path.abspath('lib/ansible/') + '/'
4142
root_path = os.getcwd() + '/'
4243

4344
counter = 0
45+
groups = {}
4446

4547
for coverage_file in coverage_files:
4648
counter += 1
4749
display.info('[%4d/%4d] %s' % (counter, len(coverage_files), coverage_file), verbosity=2)
4850

4951
original = coverage.CoverageData()
5052

53+
group = get_coverage_group(args, coverage_file)
54+
55+
if group is None:
56+
display.warning('Unexpected name for coverage file: %s' % coverage_file)
57+
continue
58+
5159
if os.path.getsize(coverage_file) == 0:
5260
display.warning('Empty coverage file: %s' % coverage_file)
5361
continue
@@ -83,46 +91,77 @@ def command_coverage_combine(args):
8391
display.info('%s -> %s' % (filename, new_name), verbosity=3)
8492
filename = new_name
8593

94+
if group not in groups:
95+
groups[group] = {}
96+
97+
arc_data = groups[group]
98+
8699
if filename not in arc_data:
87100
arc_data[filename] = set()
88101

89102
arc_data[filename].update(arcs)
90103

91-
updated = coverage.CoverageData()
104+
output_files = []
92105

93-
for filename in arc_data:
94-
if not os.path.isfile(filename):
95-
display.warning('Invalid coverage path: %s' % filename)
96-
continue
106+
for group in sorted(groups):
107+
arc_data = groups[group]
97108

98-
updated.add_arcs({filename: list(arc_data[filename])})
109+
updated = coverage.CoverageData()
99110

100-
if not args.explain:
101-
updated.write_file(COVERAGE_FILE)
111+
for filename in arc_data:
112+
if not os.path.isfile(filename):
113+
display.warning('Invalid coverage path: %s' % filename)
114+
continue
115+
116+
updated.add_arcs({filename: list(arc_data[filename])})
117+
118+
if not args.explain:
119+
output_file = COVERAGE_FILE + group
120+
updated.write_file(output_file)
121+
output_files.append(output_file)
122+
123+
return sorted(output_files)
102124

103125

104126
def command_coverage_report(args):
105127
"""
106128
:type args: CoverageConfig
107129
"""
108-
command_coverage_combine(args)
109-
run_command(args, ['coverage', 'report'])
130+
output_files = command_coverage_combine(args)
131+
132+
for output_file in output_files:
133+
if args.group_by:
134+
display.info('>>> Coverage Group: %s' % ' '.join(os.path.basename(output_file).split('=')[1:]))
135+
136+
env = common_environment()
137+
env.update(dict(COVERAGE_FILE=output_file))
138+
run_command(args, env=env, cmd=['coverage', 'report'])
110139

111140

112141
def command_coverage_html(args):
113142
"""
114143
:type args: CoverageConfig
115144
"""
116-
command_coverage_combine(args)
117-
run_command(args, ['coverage', 'html', '-d', 'test/results/reports/coverage'])
145+
output_files = command_coverage_combine(args)
146+
147+
for output_file in output_files:
148+
dir_name = 'test/results/reports/%s' % os.path.basename(output_file)
149+
env = common_environment()
150+
env.update(dict(COVERAGE_FILE=output_file))
151+
run_command(args, env=env, cmd=['coverage', 'html', '-d', dir_name])
118152

119153

120154
def command_coverage_xml(args):
121155
"""
122156
:type args: CoverageConfig
123157
"""
124-
command_coverage_combine(args)
125-
run_command(args, ['coverage', 'xml', '-o', 'test/results/reports/coverage.xml'])
158+
output_files = command_coverage_combine(args)
159+
160+
for output_file in output_files:
161+
xml_name = 'test/results/reports/%s.xml' % os.path.basename(output_file)
162+
env = common_environment()
163+
env.update(dict(COVERAGE_FILE=output_file))
164+
run_command(args, env=env, cmd=['coverage', 'xml', '-o', xml_name])
126165

127166

128167
def command_coverage_erase(args):
@@ -163,10 +202,39 @@ def initialize_coverage(args):
163202
return coverage
164203

165204

205+
def get_coverage_group(args, coverage_file):
206+
"""
207+
:type args: CoverageConfig
208+
:type coverage_file: str
209+
:rtype: str
210+
"""
211+
parts = os.path.basename(coverage_file).split('=', 4)
212+
213+
if len(parts) != 5 or not parts[4].startswith('coverage.'):
214+
return None
215+
216+
names = dict(
217+
command=parts[0],
218+
target=parts[1],
219+
environment=parts[2],
220+
version=parts[3],
221+
)
222+
223+
group = ''
224+
225+
for part in COVERAGE_GROUPS:
226+
if part in args.group_by:
227+
group += '=%s' % names[part]
228+
229+
return group
230+
231+
166232
class CoverageConfig(EnvironmentConfig):
167233
"""Configuration for the coverage command."""
168234
def __init__(self, args):
169235
"""
170236
:type args: any
171237
"""
172238
super(CoverageConfig, self).__init__(args, 'coverage')
239+
240+
self.group_by = frozenset(args.group_by) if 'group_by' in args and args.group_by else set() # type: frozenset[str]

test/runner/lib/executor.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -858,7 +858,7 @@ def intercept_command(args, cmd, target_name, capture=False, env=None, data=None
858858
version = python_version or args.python_version
859859
interpreter = find_executable('python%s' % version)
860860
coverage_file = os.path.abspath(os.path.join(inject_path, '..', 'output', '%s=%s=%s=%s=coverage' % (
861-
args.command, target_name, args.coverage_label or 'local-%s' % version, version)))
861+
args.command, target_name, args.coverage_label or 'local-%s' % version, 'python-%s' % version)))
862862

863863
env['PATH'] = inject_path + os.pathsep + env['PATH']
864864
env['ANSIBLE_TEST_PYTHON_VERSION'] = version

test/runner/test.py

+19
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,8 @@ def parse_args():
337337
coverage_combine.set_defaults(func=lib.cover.command_coverage_combine,
338338
config=lib.cover.CoverageConfig)
339339

340+
add_extra_coverage_options(coverage_combine)
341+
340342
coverage_erase = coverage_subparsers.add_parser('erase',
341343
parents=[coverage_common],
342344
help='erase coverage data files')
@@ -351,20 +353,26 @@ def parse_args():
351353
coverage_report.set_defaults(func=lib.cover.command_coverage_report,
352354
config=lib.cover.CoverageConfig)
353355

356+
add_extra_coverage_options(coverage_report)
357+
354358
coverage_html = coverage_subparsers.add_parser('html',
355359
parents=[coverage_common],
356360
help='generate html coverage report')
357361

358362
coverage_html.set_defaults(func=lib.cover.command_coverage_html,
359363
config=lib.cover.CoverageConfig)
360364

365+
add_extra_coverage_options(coverage_html)
366+
361367
coverage_xml = coverage_subparsers.add_parser('xml',
362368
parents=[coverage_common],
363369
help='generate xml coverage report')
364370

365371
coverage_xml.set_defaults(func=lib.cover.command_coverage_xml,
366372
config=lib.cover.CoverageConfig)
367373

374+
add_extra_coverage_options(coverage_xml)
375+
368376
if argcomplete:
369377
argcomplete.autocomplete(parser, always_complete_options=False, validator=lambda i, k: True)
370378

@@ -498,6 +506,17 @@ def add_environments(parser, tox_version=False, tox_only=False):
498506
default='never')
499507

500508

509+
def add_extra_coverage_options(parser):
510+
"""
511+
:type parser: argparse.ArgumentParser
512+
"""
513+
parser.add_argument('--group-by',
514+
metavar='GROUP',
515+
action='append',
516+
choices=lib.cover.COVERAGE_GROUPS,
517+
help='group output by: %s' % ', '.join(lib.cover.COVERAGE_GROUPS))
518+
519+
501520
def add_extra_docker_options(parser, integration=True):
502521
"""
503522
:type parser: argparse.ArgumentParser

test/utils/shippable/shippable.sh

+24-2
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,30 @@ find lib/ansible/modules -type d -empty -print -delete
5252
function cleanup
5353
{
5454
if find test/results/coverage/ -mindepth 1 -name '.*' -prune -o -print -quit | grep -q .; then
55-
ansible-test coverage xml --color -v --requirements
56-
cp -a test/results/reports/coverage.xml shippable/codecoverage/coverage.xml
55+
ansible-test coverage xml --color -v --requirements --group-by command --group-by version
56+
cp -a test/results/reports/coverage=*.xml shippable/codecoverage/
57+
58+
# upload coverage report to codecov.io only when using complete on-demand coverage
59+
if [ "${COVERAGE}" ] && [ "${CHANGED}" == "" ]; then
60+
for file in test/results/reports/coverage=*.xml; do
61+
flags="${file##*/coverage=}"
62+
flags="${flags%.xml}"
63+
flags="${flags//=/,}"
64+
flags="${flags//[^a-zA-Z0-9_,]/_}"
65+
66+
bash <(curl -s https://codecov.io/bash) \
67+
-f "${file}" \
68+
-F "${flags}" \
69+
-n "${TEST}" \
70+
-t 83cd8957-dc76-488c-9ada-210dcea51633 \
71+
-X coveragepy \
72+
-X gcov \
73+
-X fix \
74+
-X search \
75+
-X xcode \
76+
|| echo "Failed to upload code coverage report to codecov.io: ${file}"
77+
done
78+
fi
5779
fi
5880

5981
rmdir shippable/testresults/

0 commit comments

Comments
 (0)