Skip to content

Commit 74c3544

Browse files
authored
Merge pull request #4781 from lplarson/specific-coverage
[coverage] Add scripts supporting specific coverage
2 parents 888a32b + 573a58c commit 74c3544

File tree

7 files changed

+658
-1
lines changed

7 files changed

+658
-1
lines changed

.pep8

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
[flake8]
2-
filename = *.py,80+-check,Benchmark_Driver,Benchmark_DTrace.in,Benchmark_GuardMalloc.in,Benchmark_RuntimeLeaksRunner.in,build-script,check-incremental,gyb,line-directive,mock-distcc,ns-html2rst,recursive-lipo,rth,run-test,submit-benchmark-results,update-checkout,viewcfg,backtrace-check
2+
filename = *.py,80+-check,Benchmark_Driver,Benchmark_DTrace.in,Benchmark_GuardMalloc.in,Benchmark_RuntimeLeaksRunner.in,build-script,check-incremental,gyb,line-directive,mock-distcc,ns-html2rst,recursive-lipo,rth,run-test,submit-benchmark-results,update-checkout,viewcfg,backtrace-check,coverage-build-db,coverage-generate-data,coverage-touch-tests

test/CMakeLists.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,12 @@ if(PYTHONINTERP_FOUND)
144144
only_long
145145
)
146146

147+
if(NOT "${COVERAGE_DB}" STREQUAL "")
148+
add_custom_target("touch-covering-tests"
149+
COMMAND "${SWIFT_SOURCE_DIR}/utils/coverage-touch-tests" "--swift-dir" "${SWIFT_SOURCE_DIR}" "--coverage-db" "${COVERAGE_DB}"
150+
COMMENT "Touching covering tests")
151+
endif()
152+
147153
foreach(SDK ${SWIFT_SDKS})
148154
foreach(ARCH ${SWIFT_SDK_${SDK}_ARCHITECTURES})
149155
# Configure variables for this subdirectory.
@@ -188,6 +194,10 @@ if(PYTHONINTERP_FOUND)
188194
"swift-reflection-test${VARIANT_SUFFIX}")
189195
endif()
190196

197+
if(NOT "${COVERAGE_DB}" STREQUAL "")
198+
list(APPEND test_dependencies "touch-covering-tests")
199+
endif()
200+
191201
set(validation_test_dependencies
192202
"swiftStdlibCollectionUnittest-${SWIFT_SDK_${SDK}_LIB_SUBDIR}"
193203
"swiftStdlibUnicodeUnittest-${SWIFT_SDK_${SDK}_LIB_SUBDIR}")

utils/build-script

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -811,6 +811,9 @@ class BuildScriptInvocation(object):
811811
if args.lit_args:
812812
impl_args += ["--llvm-lit-args=%s" % args.lit_args]
813813

814+
if args.coverage_db:
815+
impl_args += ["--coverage-db=%s" % args.coverage_db]
816+
814817
# Compute the set of host-specific variables, which we pass through to
815818
# the build script via environment variables.
816819
host_specific_variables = self.compute_host_specific_variables()
@@ -2009,6 +2012,11 @@ details of the setups of other systems or automated environments.""")
20092012
metavar="LITARGS",
20102013
default="-sv")
20112014

2015+
parser.add_argument(
2016+
"--coverage-db",
2017+
help="coverage database to use when prioritizing testing",
2018+
metavar="PATH")
2019+
20122020
args = migration.parse_args(parser, sys.argv[1:])
20132021

20142022
if args.build_script_impl_args:

utils/build-script-impl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,7 @@ KNOWN_SETTINGS=(
237237
user-config-args "" "**Renamed to --extra-cmake-options**: User-supplied arguments to cmake when used to do configuration."
238238
only-execute "all" "Only execute the named action (see implementation)"
239239
llvm-lit-args "" "If set, override the lit args passed to LLVM"
240+
coverage-db "" "If set, coverage database to use when prioritizing testing"
240241
build-toolchain-only "" "If set, only build the necessary tools to build an external toolchain"
241242
)
242243

@@ -727,6 +728,10 @@ function set_build_options_for_host() {
727728
-DLLVM_LIT_ARGS="${LLVM_LIT_ARGS}"
728729
)
729730
fi
731+
732+
swift_cmake_options+=(
733+
-DCOVERAGE_DB="${COVERAGE_DB}"
734+
)
730735
}
731736

732737
function configure_default_options() {

utils/coverage-build-db

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
#!/usr/bin/env python
2+
# utils/coverage-build-db - Build sqlite3 database from profdata
3+
#
4+
# This source file is part of the Swift.org open source project
5+
#
6+
# Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors
7+
# Licensed under Apache License v2.0 with Runtime Library Exception
8+
#
9+
# See http://swift.org/LICENSE.txt for license information
10+
# See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
11+
12+
import Queue
13+
import argparse
14+
import logging
15+
import multiprocessing
16+
import os
17+
import re
18+
import sqlite3
19+
import sys
20+
21+
logging_format = '%(asctime)s %(levelname)s %(message)s'
22+
logging.basicConfig(level=logging.DEBUG,
23+
format=logging_format,
24+
filename='/tmp/%s.log' % os.path.basename(__file__),
25+
filemode='w')
26+
console = logging.StreamHandler()
27+
console.setLevel(logging.INFO)
28+
formatter = logging.Formatter(logging_format)
29+
console.setFormatter(formatter)
30+
logging.getLogger().addHandler(console)
31+
32+
33+
def parse_coverage_log(filepath):
34+
"""Return parsed coverage intervals from `llvm-profdata show` output at
35+
`filepath`"""
36+
logging.info('Parsing: %s', filepath)
37+
test = os.path.basename(os.path.dirname(filepath)).rsplit('.', 1)[0]
38+
with open(filepath) as f:
39+
# Initial parse
40+
parsed = []
41+
for section in [section.split("\n")
42+
for section in f.read().split("\n\n")]:
43+
if ".cpp:" in section[0] or ".h:" in section[0]:
44+
parsed_section = [section[0].split(":", 1) + [test]]
45+
for line in section[1:]:
46+
linedata = linedata_re.match(line).group(1, 2)
47+
parsed_section.append(linedata)
48+
parsed.append(parsed_section)
49+
# Build intervals
50+
parsed_interval = []
51+
for section in parsed:
52+
new_section = [section[0]]
53+
i = 1
54+
if len(section) > 2:
55+
while i < len(section)-1:
56+
while i < len(section)-1 and section[i][0] in ['0', None]:
57+
i += 1
58+
if i == len(section)-1:
59+
if section[i][0] not in ['0', None]:
60+
istart = int(section[i][1])
61+
iend = istart
62+
new_section.append((istart, iend))
63+
break
64+
istart = int(section[i][1])
65+
iend = istart # single line interval starting
66+
while i < len(section)-1 and \
67+
int(section[i+1][1]) == iend + 1 and \
68+
section[i+1][0] not in ['0', None]:
69+
iend += 1
70+
i += 1
71+
new_section.append((istart, iend))
72+
i += 1
73+
else:
74+
if section[i][0] not in ['0', None]:
75+
istart = int(section[i][1])
76+
iend = istart
77+
new_section.append((istart, iend))
78+
parsed_interval.append(new_section)
79+
return parsed_interval
80+
81+
82+
def worker(args):
83+
"""Parse coverage log at path `args[0]` and place in queue `args[1]`"""
84+
args[1].put(parse_coverage_log(args[0]))
85+
86+
manager = multiprocessing.Manager()
87+
result_queue = manager.Queue()
88+
worker_queue = []
89+
90+
linedata_re = re.compile(r"^\s*([^\s]+?)?\|\s*(\d+)\|")
91+
strings = dict()
92+
93+
pool = multiprocessing.Pool(3)
94+
95+
96+
def insert_coverage_section_into_db(db_cursor, section):
97+
"""Insert parsed intervals in `section` into database at `db_cursor`"""
98+
filename = section[0][0]
99+
function = section[0][1]
100+
test = section[0][2]
101+
logging.debug('Inserting section into database for file: %s', filename)
102+
if filename not in strings:
103+
db_cursor.execute("INSERT INTO strings (string) VALUES (?)",
104+
(filename,))
105+
strings[filename] = db_cursor.lastrowid
106+
if test not in strings:
107+
db_cursor.execute("INSERT INTO strings (string) VALUES (?)", (test,))
108+
strings[test] = db_cursor.lastrowid
109+
if function not in strings:
110+
db_cursor.execute("INSERT INTO strings (string) VALUES (?)",
111+
(function,))
112+
strings[function] = db_cursor.lastrowid
113+
for istart, iend in section[1:]:
114+
logging.debug('%s, %s, %s, %s, %s', function, filename, test, istart,
115+
iend)
116+
db_cursor.execute("INSERT INTO coverage VALUES (?,?,?,?,?)",
117+
(strings[function], strings[filename],
118+
strings[test], istart, iend))
119+
120+
121+
def main():
122+
parser = argparse.ArgumentParser(
123+
description='Build sqlite3 database from profdata')
124+
parser.add_argument('root_directory',
125+
metavar='root-directory',
126+
help='a root directory to recursively search for '
127+
'coverage logs')
128+
parser.add_argument('--coverage-filename',
129+
help='a coverage log file name to search for '
130+
'(default: coverage.log.demangled)',
131+
metavar='NAME',
132+
default='coverage.log.demangled')
133+
parser.add_argument('--db-filepath',
134+
help='the filepath of the coverage db to build '
135+
'(default: coverage.db)',
136+
metavar='PATH',
137+
default='coverage.db')
138+
parser.add_argument('--log',
139+
help='the level of information to log (default: info)',
140+
metavar='LEVEL',
141+
default='info',
142+
choices=['info', 'debug', 'warning', 'error',
143+
'critical'])
144+
args = parser.parse_args()
145+
146+
console.setLevel(level=args.log.upper())
147+
logging.debug(args)
148+
149+
if not os.path.exists(args.root_directory):
150+
logging.critical('Directory not found: %s', args.root_directory)
151+
return 1
152+
153+
if os.path.exists(args.db_filepath):
154+
logging.info('Deleting existing database: %s', args.db_filepath)
155+
os.unlink(args.db_filepath)
156+
157+
logging.info('Creating database: %s', args.db_filepath)
158+
conn = sqlite3.connect(args.db_filepath)
159+
160+
try:
161+
logging.debug('Creating coverage table')
162+
c = conn.cursor()
163+
c.execute('''CREATE TABLE strings
164+
(id integer primary key autoincrement, string text)''')
165+
c.execute('''CREATE TABLE coverage
166+
(function references strings(id),
167+
src references strings(id),
168+
test references strings(id),
169+
istart integer, iend integer)''')
170+
conn.commit()
171+
172+
for root, folders, files in os.walk(args.root_directory):
173+
for f in files:
174+
if f == args.coverage_filename:
175+
filepath = os.path.join(root, f)
176+
logging.debug('Adding file to queue: %s', filepath)
177+
worker_queue.append((filepath, result_queue))
178+
179+
logging.info('Finished adding %s paths to queue', len(worker_queue))
180+
pool.map_async(worker, worker_queue)
181+
182+
while True:
183+
parsed = result_queue.get(timeout=60)
184+
logging.info('Inserting coverage data into db for %s function(s)',
185+
len(parsed))
186+
for section in parsed:
187+
insert_coverage_section_into_db(c, section)
188+
conn.commit()
189+
result_queue.task_done()
190+
except Queue.Empty:
191+
logging.info('Queue exhausted. Shutting down...')
192+
except KeyboardInterrupt:
193+
logging.debug('Caught KeyboardInterrupt. Shutting down...')
194+
finally:
195+
logging.debug('Closing database')
196+
conn.commit()
197+
conn.close()
198+
logging.debug('Terminating pool')
199+
pool.terminate()
200+
201+
return 0
202+
203+
if __name__ == '__main__':
204+
sys.exit(main())

0 commit comments

Comments
 (0)