Skip to content

Commit cf7b9bf

Browse files
authored
Merge pull request #4974 from danpoe/feature/find-covering-tests
Script to find regression tests that cover given source lines
2 parents 5d2e8b9 + 75fe74b commit cf7b9bf

File tree

1 file changed

+131
-0
lines changed

1 file changed

+131
-0
lines changed

Diff for: scripts/find-covering-tests.py

+131
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
#!/usr/bin/env python3
2+
3+
# Find regression tests that cover given source lines
4+
5+
import argparse
6+
import os
7+
import re
8+
import subprocess
9+
import sys
10+
11+
description =\
12+
'''\
13+
Find cbmc regression tests that cover given source lines
14+
15+
The program for which to analyse coverage needs to be built with gcc/g++/ld and
16+
flag --coverage (or equivalent), and the gcov command needs to be available.
17+
18+
Example (assuming the script is invoked from the cbmc root directory):
19+
20+
find-covering-tests.py \\
21+
--directory regression/cbmc \\
22+
--command '../test.pl -c cbmc' \\
23+
--source-line 'src/cbmc_parse_options.cpp:322' \\
24+
--source-line 'src/goto_symex.cpp:53'
25+
26+
The invocation above determines the regression tests in regression/cbmc which
27+
cover line 322 of file cbmc_parse_options.cpp or line 53 of file goto_symex.cpp.
28+
'''
29+
30+
def remove_existing_coverage_data(source_lines):
31+
source_files = [item[0] for item in source_lines]
32+
33+
for filename in source_files:
34+
pre, ext = os.path.splitext(filename)
35+
gcda_file = pre + '.gcda'
36+
37+
gcov_file = filename + '.gcov'
38+
39+
try:
40+
os.remove(gcda_file)
41+
os.remove(gcov_file)
42+
except:
43+
pass
44+
45+
46+
def parse_source_lines(source_lines):
47+
return [(item[0], int(item[1])) for item in
48+
map(lambda s: s.split(':'), source_lines)]
49+
50+
51+
def is_covered_source_line(filename, line):
52+
if not os.path.isfile(filename):
53+
return False
54+
55+
d = os.path.dirname(filename)
56+
b = os.path.basename(filename)
57+
subprocess.run(['gcov', b],
58+
cwd=d, stdout=subprocess.DEVNULL,
59+
stderr=subprocess.DEVNULL,
60+
check=True)
61+
62+
gcov_file = filename + '.gcov'
63+
if not os.path.isfile(gcov_file):
64+
return False
65+
66+
f = open(gcov_file)
67+
s = f.read()
68+
f.close()
69+
70+
mo = re.search('^\s*[1-9][0-9]*:\s+' + str(line) + ':', s, re.MULTILINE)
71+
return mo is not None
72+
73+
74+
def get_covered_source_lines(source_lines):
75+
covered_source_lines = []
76+
77+
for filename, line in source_lines:
78+
if is_covered_source_line(filename, line):
79+
covered_source_lines.append((filename, line))
80+
81+
return covered_source_lines
82+
83+
84+
def run(config):
85+
source_lines = parse_source_lines(config.source_line)
86+
87+
dirs = filter(
88+
lambda entry: os.path.isdir(os.path.join(config.directory, entry)),
89+
os.listdir(config.directory))
90+
91+
for d in dirs:
92+
remove_existing_coverage_data(source_lines)
93+
print('Running test ' + d)
94+
subprocess.run([config.command + ' ' + d],
95+
cwd=config.directory,
96+
shell=True,
97+
stdout=subprocess.DEVNULL,
98+
stderr=subprocess.DEVNULL,
99+
check=True)
100+
101+
covered_source_lines = get_covered_source_lines(source_lines)
102+
if covered_source_lines:
103+
print(' Covered source lines:')
104+
for filename, line in covered_source_lines:
105+
print(' ' + filename + ':' + str(line))
106+
else:
107+
print(' Does not cover any of the given source lines')
108+
109+
110+
if __name__ == '__main__':
111+
112+
parser = argparse.ArgumentParser(
113+
formatter_class=argparse.RawDescriptionHelpFormatter,
114+
description=description)
115+
116+
parser.add_argument(
117+
'--source-line',
118+
action='append',
119+
metavar='<filename>:<line>',
120+
required=True,
121+
help='source lines for which to determine which tests cover them, can be '
122+
'repeated')
123+
parser.add_argument('--command', required=True,
124+
help='regression test command, typically an invocation of test.pl')
125+
parser.add_argument('--directory', required=True,
126+
help='directory containing regression test directories')
127+
128+
config = parser.parse_args()
129+
130+
run(config)
131+

0 commit comments

Comments
 (0)