Skip to content

Commit 90a8ffc

Browse files
committed
rewritten coverage report generation script
now in python :-) also adding coverage for examples
1 parent 9986b16 commit 90a8ffc

File tree

2 files changed

+196
-98
lines changed

2 files changed

+196
-98
lines changed

generate_coverage.py

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
# coding=utf-8
2+
"""
3+
.. moduleauthor:: Torbjörn Klatt <[email protected]>
4+
"""
5+
6+
from sys import version_info
7+
# require at least Python 3.3
8+
assert(version_info[0] >= 3 and version_info[1] >= 3)
9+
10+
11+
import logging
12+
from logging.config import dictConfig
13+
dictConfig(
14+
{
15+
'version': 1,
16+
'disable_existing_loggers': False,
17+
'formatters': {
18+
'default': {
19+
'style': '{',
20+
'format': '[{levelname!s:<8s}] {message!s}'
21+
}
22+
},
23+
'handlers': {
24+
'console': {
25+
'class': 'logging.StreamHandler',
26+
'formatter': 'default'
27+
}
28+
},
29+
'root': {
30+
'handlers': ['console'],
31+
'level': 'INFO'
32+
}
33+
}
34+
)
35+
36+
37+
import subprocess as sp
38+
try:
39+
sp.check_call('lcov --version', shell=True, stdout=sp.DEVNULL, stderr=sp.DEVNULL)
40+
except sp.CalledProcessError as err:
41+
logging.critical("lcov command not found. It is required.")
42+
raise err
43+
44+
try:
45+
sp.check_call('genhtml --version', shell=True, stdout=sp.DEVNULL, stderr=sp.DEVNULL)
46+
except sp.CalledProcessError as err:
47+
logging.critical("genhtml command not found. It is required.")
48+
raise err
49+
50+
51+
import argparse
52+
import os
53+
import os.path
54+
import shutil
55+
import re
56+
57+
58+
class Options(object):
59+
coverage_dir = ""
60+
build_dir = ""
61+
base_dir = ""
62+
tests = []
63+
tracefiles = []
64+
final_tracefile = ""
65+
66+
67+
options = Options()
68+
options.base_dir = os.path.abspath(os.path.curdir)
69+
70+
71+
def setup_and_init_options():
72+
help_string = "Note:\n" \
73+
"This only works for builds made with GCC and the following CMake variables:\n" \
74+
" -Dpfasst_WITH_GCC_PROF=ON -Dpfasst_BUILD_TESTS=ON"
75+
76+
parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter, description=help_string)
77+
parser.add_argument('-d', '--build_dir', required=True,
78+
help="name of build directory containing a debug build with GCC and enabled profiling")
79+
parser.add_argument('-o', '--output', default='coverage',
80+
help="output directory for generated coverage report")
81+
82+
_args = parser.parse_args()
83+
if not os.access(_args.build_dir, os.W_OK):
84+
logging.critical("Given build path could not be found: %s" % _args.build_dir)
85+
raise ValueError("Given build path could not be found: %s" % _args.build_dir)
86+
options.build_dir = os.path.abspath(_args.build_dir)
87+
88+
if not os.access(_args.output, os.W_OK):
89+
logging.info("output directory not found. creating: %s" % _args.output)
90+
os.mkdir(_args.output)
91+
else:
92+
logging.warning("clearing out output directory: %s" % _args.output)
93+
shutil.rmtree(_args.output)
94+
os.mkdir(_args.output)
95+
options.coverage_dir = os.path.abspath(_args.output)
96+
97+
98+
def get_test_directories():
99+
logging.info("looking for tests ...")
100+
for root, dirs, files in os.walk(options.build_dir + '/tests'):
101+
match_name = re.search('^.*/(?P<test_name>test_[a-zA-Z\-_]+)\.dir$', root)
102+
match_is_example = re.search('^.*/tests/examples/.*$', root)
103+
is_example = match_is_example is not None
104+
if match_name is not None:
105+
testname = match_name.groupdict()['test_name']
106+
options.tests.append({'path': root, 'name': testname, 'is_example': is_example})
107+
logging.info("%d tests found" % len(options.tests))
108+
109+
110+
def run_test(path, name, is_example):
111+
logging.info("running %s" % name)
112+
logging.debug("in %s" % path)
113+
output_file = open('%s/%s.log' % (options.coverage_dir, name), mode='a')
114+
115+
os.chdir(os.path.abspath(path))
116+
logging.debug("deleting old tracing data ...")
117+
print('### deleting old tracing data ...', file=output_file, flush=True)
118+
sp.check_call('lcov --zerocounters --directory .', shell=True, stdout=output_file, stderr=output_file)
119+
print('### done.', file=output_file, flush=True)
120+
121+
os.chdir(options.build_dir)
122+
logging.debug("running test ...")
123+
print('### running test ...', file=output_file, flush=True)
124+
sp.check_call('ctest -R %s' % name, shell=True, stdout=output_file, stderr=output_file)
125+
print('### done.', file=output_file, flush=True)
126+
127+
os.chdir(os.path.abspath(path))
128+
logging.debug("capturing all tracing data ...")
129+
print('### capturing all tracing data ...', file=output_file, flush=True)
130+
sp.check_call('lcov --capture --directory . --output-file "%s.info.complete"' % name,
131+
shell=True, stdout=output_file, stderr=output_file)
132+
print('### done.', file=output_file, flush=True)
133+
134+
logging.debug("removing unnecessary data ...")
135+
print('### removing unnecessary data ...', file=output_file, flush=True)
136+
sp.check_call('lcov --remove "%s.info.complete" "*%s/include/pfasst/easylogging++.h" '
137+
'--output-file %s.info.prelim'
138+
% (name, options.base_dir, name),
139+
shell=True, stdout=output_file, stderr=output_file)
140+
print('### done.', file=output_file, flush=True)
141+
142+
logging.debug("extracting interesting tracing data ...")
143+
print('### extracting interesting tracing data ...', file=output_file, flush=True)
144+
sp.check_call('lcov --extract "%s.info.prelim" "*%s/include/**/*" --output-file %s.info'
145+
% (name, options.base_dir, name),
146+
shell=True, stdout=output_file, stderr=output_file)
147+
options.tracefiles.append("%s/%s.info" % (os.path.abspath(path), name))
148+
if is_example:
149+
logging.debug("this test belongs to an example, thus also covering examples code")
150+
sp.check_call('lcov --extract "%s.info.prelim" "*%s/examples/**/*" --output-file %s.info.example'
151+
% (name, options.base_dir, name),
152+
shell=True, stdout=output_file, stderr=output_file)
153+
options.tracefiles.append("%s/%s.info.example" % (os.path.abspath(path), name))
154+
print('### done.', file=output_file, flush=True)
155+
156+
os.chdir(options.base_dir)
157+
output_file.close()
158+
159+
160+
def aggregate_tracefiles():
161+
logging.info("aggregating %d tracefiles ..." % len(options.tracefiles))
162+
output_file = open('%s/aggegrating.log' % (options.coverage_dir,), mode='a')
163+
options.final_tracefile = "%s/all_tests.info" % options.coverage_dir
164+
for tracefile in options.tracefiles:
165+
logging.debug("adding tracefile: %s" % (tracefile))
166+
print("### adding tracefile: %s" % (tracefile,), file=output_file, flush=True)
167+
if os.access(options.final_tracefile, os.W_OK):
168+
sp.check_call('lcov --add-tracefile "%s" --add-tracefile "%s" --output-file "%s"'
169+
% (options.final_tracefile, tracefile, options.final_tracefile),
170+
shell=True, stdout=output_file, stderr=output_file)
171+
else:
172+
sp.check_call('lcov --add-tracefile "%s" --output-file "%s"'
173+
% (tracefile, options.final_tracefile),
174+
shell=True, stdout=output_file, stderr=output_file)
175+
print("### done.", file=output_file, flush=True)
176+
output_file.close()
177+
178+
179+
def generate_html():
180+
logging.info("generating HTML report ...")
181+
output_file = open('%s/generate_html.log' % (options.coverage_dir,), mode='a')
182+
sp.check_call('genhtml --output-directory %s --demangle-cpp --num-spaces 2 --sort '
183+
'--title "PFASST++ Test Coverage" --prefix "%s" --function-coverage --legend '
184+
'"%s"' % (options.coverage_dir, options.base_dir, options.final_tracefile),
185+
shell=True, stdout=output_file, stderr=output_file)
186+
output_file.close()
187+
logging.info("coverage report can be found in: %s" % options.coverage_dir)
188+
189+
190+
if __name__ == "__main__":
191+
setup_and_init_options()
192+
get_test_directories()
193+
for test in options.tests:
194+
run_test(**test)
195+
aggregate_tracefiles()
196+
generate_html()

generate_coverage.sh

Lines changed: 0 additions & 98 deletions
This file was deleted.

0 commit comments

Comments
 (0)