blob: ef43dbfa94ae30d036c5b7f0b0cca6fe86d29c3c [file] [log] [blame]
#!/usr/bin/env python
# utils/coverage/coverage-touch-tests - Touch tests covering git changes
#
# This source file is part of the Swift.org open source project
#
# Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
# Licensed under Apache License v2.0 with Runtime Library Exception
#
# See https://swift.org/LICENSE.txt for license information
# See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
from __future__ import print_function
import argparse
import logging
import multiprocessing
import os
import re
import subprocess
import sys
logging_format = '%(asctime)s %(levelname)s %(message)s'
logging.basicConfig(level=logging.DEBUG,
format=logging_format,
filename='/tmp/%s.log' % os.path.basename(__file__),
filemode='w')
console = logging.StreamHandler()
console.setLevel(logging.INFO)
formatter = logging.Formatter(logging_format)
console.setFormatter(formatter)
logging.getLogger().addHandler(console)
script_path = os.path.realpath(__file__)
sql_query_covering = '''SELECT DISTINCT test_string.string
FROM strings as src_string, coverage, strings as test_string
WHERE src_string.string = '{0}'
AND ((istart <= {1} AND {1} <= iend) OR
(istart <= {2} AND {2} <= iend) OR
({1} <= istart AND istart <= {2}))
AND src_string.id = coverage.src
AND test_string.id = coverage.test;'''
def worker(args):
i, (coverage_db, (filename, (rstart, rend))) = args
logging.info('[%s] Finding covering tests for: %s %s',
i + 1, filename, (rstart, rend))
tmp_covering_tests = subprocess.check_output(
['sqlite3',
coverage_db,
sql_query_covering.format(filename, rstart, rend)]
).splitlines()
for covering_test in tmp_covering_tests:
logging.debug(' %s', covering_test)
logging.info('Found %s covering tests', len(tmp_covering_tests))
return set(tmp_covering_tests)
NUM_CORES = multiprocessing.cpu_count()
pool = multiprocessing.Pool(NUM_CORES)
def main():
parser = argparse.ArgumentParser(
description='Touch tests covering git changes')
parser.add_argument('--swift-dir',
metavar='PATH',
help='the path to a swift source directory containing '
'changes',
required=True)
parser.add_argument('--coverage-db',
metavar='PATH',
help='the path to a swift coverage database',
required=True)
parser.add_argument('--log',
help='the level of information to log (default: info)',
metavar='LEVEL',
default='info',
choices=['info', 'debug', 'warning', 'error',
'critical'])
args = parser.parse_args()
for path in [args.swift_dir, args.coverage_db]:
assert os.path.exists(path), "Unable to find %s. Try absolute" \
" paths." % path
console.setLevel(level=args.log.upper())
logging.debug(args)
environment = os.environ.copy()
environment['GIT_EXTERNAL_DIFF'] = script_path
logging.info('Getting diff of swift dir: %s', args.swift_dir)
output = subprocess.check_output(['git', '-C', args.swift_dir, 'diff'],
env=environment)
changed_ranges = []
for line in output.splitlines():
logging.debug(line)
if line.startswith('FILENAME'):
filename = line.split()[1]
elif line.startswith('@'):
start_line, num_lines = re.match(
r'\@\@ \-(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? \@\@',
line
).group(1, 2)
if num_lines:
start_line, num_lines = int(start_line), int(num_lines)
changed_range = (start_line, start_line + num_lines - 1)
logging.debug('Found changed range: %s %s',
filename, changed_range)
changed_ranges.append(
(args.coverage_db, (filename, changed_range)))
else:
start_line = int(start_line)
logging.debug(
'Found changed line: %s %s', filename, start_line)
changed_ranges.append(
(args.coverage_db, (filename, (start_line, start_line))))
logging.info('Found %s changed ranges/lines', len(changed_ranges))
relevant_changes = [
cr for cr in changed_ranges
if cr[1][0].endswith('.cpp') or cr[1][0].endswith('.h')
]
relevant_changes_count = len(relevant_changes)
logging.info('Finding covering tests for %s relevant ranges/lines...',
relevant_changes_count)
covering_tests = set().union(*pool.map_async(
worker,
enumerate(relevant_changes)
).get(999999))
logging.info('Combined covering tests:')
for covering_test in covering_tests:
logging.debug(' %s', covering_test)
logging.info('Found %s combined covering tests',
len(covering_tests))
logging.info('Touching covering tests:')
for root, folders, files in os.walk(args.swift_dir):
for filename in files:
if filename in covering_tests:
filepath = os.path.join(root, filename)
logging.info(' %s', filepath)
os.utime(filepath, None)
return 0
def diff():
parser = argparse.ArgumentParser()
parser.add_argument('path')
parser.add_argument('old_file')
parser.add_argument('old_hex')
parser.add_argument('old_mode')
parser.add_argument('new_file')
parser.add_argument('new_hex')
parser.add_argument('new_mode')
args = parser.parse_args()
print('FILENAME', os.path.basename(args.new_file))
sys.stdout.flush()
subprocess.call(['diff', '-U0', args.old_file, args.new_file])
return 0
if __name__ == '__main__':
if 'GIT_EXTERNAL_DIFF' in os.environ:
sys.exit(diff())
else:
sys.exit(main())