[clang-tools-extra] a2c33b0 - [clang-tidy] Refactor: Extract Class CheckRunner on check_clang_tidy.py
via cfe-commits
cfe-commits at lists.llvm.org
Mon Jan 10 14:53:52 PST 2022
Author: Richard
Date: 2022-01-10T15:44:12-07:00
New Revision: a2c33b0ec976c894dc45558d0b50c9c2fe87e1da
URL: https://github.com/llvm/llvm-project/commit/a2c33b0ec976c894dc45558d0b50c9c2fe87e1da
DIFF: https://github.com/llvm/llvm-project/commit/a2c33b0ec976c894dc45558d0b50c9c2fe87e1da.diff
LOG: [clang-tidy] Refactor: Extract Class CheckRunner on check_clang_tidy.py
Break up the huge function by extracting a class, storing intermediate
state as class members and breaking up the big function into a group
of class methods all at the same level of abstraction.
Differential Revision: https://reviews.llvm.org/D56343
Added:
Modified:
clang-tools-extra/test/clang-tidy/check_clang_tidy.py
Removed:
################################################################################
diff --git a/clang-tools-extra/test/clang-tidy/check_clang_tidy.py b/clang-tools-extra/test/clang-tidy/check_clang_tidy.py
index 4edae67f7076..1a234029b60b 100755
--- a/clang-tools-extra/test/clang-tidy/check_clang_tidy.py
+++ b/clang-tools-extra/test/clang-tidy/check_clang_tidy.py
@@ -39,170 +39,169 @@ def write_file(file_name, text):
f.truncate()
-def run_test_once(args, extra_args):
- resource_dir = args.resource_dir
- assume_file_name = args.assume_filename
- input_file_name = args.input_file_name
- check_name = args.check_name
- temp_file_name = args.temp_file_name
- expect_clang_tidy_error = args.expect_clang_tidy_error
- std = args.std
-
- file_name_with_extension = assume_file_name or input_file_name
- _, extension = os.path.splitext(file_name_with_extension)
- if extension not in ['.c', '.hpp', '.m', '.mm']:
- extension = '.cpp'
- temp_file_name = temp_file_name + extension
-
- clang_tidy_extra_args = extra_args
- clang_extra_args = []
- if '--' in extra_args:
- i = clang_tidy_extra_args.index('--')
- clang_extra_args = clang_tidy_extra_args[i + 1:]
- clang_tidy_extra_args = clang_tidy_extra_args[:i]
-
- # If the test does not specify a config style, force an empty one; otherwise
- # autodetection logic can discover a ".clang-tidy" file that is not related to
- # the test.
- if not any(
- [arg.startswith('-config=') for arg in clang_tidy_extra_args]):
- clang_tidy_extra_args.append('-config={}')
-
- if extension in ['.m', '.mm']:
- clang_extra_args = ['-fobjc-abi-version=2', '-fobjc-arc', '-fblocks'] + \
- clang_extra_args
-
- if extension in ['.cpp', '.hpp', '.mm']:
- clang_extra_args.append('-std=' + std)
-
- # Tests should not rely on STL being available, and instead provide mock
- # implementations of relevant APIs.
- clang_extra_args.append('-nostdinc++')
-
- if resource_dir is not None:
- clang_extra_args.append('-resource-dir=%s' % resource_dir)
-
- with open(input_file_name, 'r', encoding='utf-8') as input_file:
- input_text = input_file.read()
-
- check_fixes_prefixes = []
- check_messages_prefixes = []
- check_notes_prefixes = []
-
- has_check_fixes = False
- has_check_messages = False
- has_check_notes = False
-
- for check in args.check_suffix:
- if check and not re.match('^[A-Z0-9\-]+$', check):
- sys.exit('Only A..Z, 0..9 and "-" are ' +
- 'allowed in check suffixes list, but "%s" was given' % (check))
-
- file_check_suffix = ('-' + check) if check else ''
- check_fixes_prefix = 'CHECK-FIXES' + file_check_suffix
- check_messages_prefix = 'CHECK-MESSAGES' + file_check_suffix
- check_notes_prefix = 'CHECK-NOTES' + file_check_suffix
-
- has_check_fix = check_fixes_prefix in input_text
- has_check_message = check_messages_prefix in input_text
- has_check_note = check_notes_prefix in input_text
-
- if has_check_note and has_check_message:
- sys.exit('Please use either %s or %s but not both' %
- (check_notes_prefix, check_messages_prefix))
-
- if not has_check_fix and not has_check_message and not has_check_note:
- sys.exit('%s, %s or %s not found in the input' %
- (check_fixes_prefix, check_messages_prefix, check_notes_prefix))
-
- has_check_fixes = has_check_fixes or has_check_fix
- has_check_messages = has_check_messages or has_check_message
- has_check_notes = has_check_notes or has_check_note
-
- if has_check_fix:
- check_fixes_prefixes.append(check_fixes_prefix)
- if has_check_message:
- check_messages_prefixes.append(check_messages_prefix)
- if has_check_note:
- check_notes_prefixes.append(check_notes_prefix)
-
- assert has_check_fixes or has_check_messages or has_check_notes
- # Remove the contents of the CHECK lines to avoid CHECKs matching on
- # themselves. We need to keep the comments to preserve line numbers while
- # avoiding empty lines which could potentially trigger formatting-related
- # checks.
- cleaned_test = re.sub('// *CHECK-[A-Z0-9\-]*:[^\r\n]*', '//', input_text)
-
- write_file(temp_file_name, cleaned_test)
-
- original_file_name = temp_file_name + ".orig"
- write_file(original_file_name, cleaned_test)
-
- args = ['clang-tidy', temp_file_name, '-fix', '--checks=-*,' + check_name] + \
- clang_tidy_extra_args + ['--'] + clang_extra_args
- if expect_clang_tidy_error:
- args.insert(0, 'not')
- print('Running ' + repr(args) + '...')
+def try_run(args, raise_error=True):
try:
- clang_tidy_output = \
- subprocess.check_output(args, stderr=subprocess.STDOUT).decode()
+ process_output = \
+ subprocess.check_output(args, stderr=subprocess.STDOUT).decode(errors='ignore')
except subprocess.CalledProcessError as e:
- print('clang-tidy failed:\n' + e.output.decode())
- raise
-
- print('------------------------ clang-tidy output -----------------------')
- print(clang_tidy_output.encode())
- print('\n------------------------------------------------------------------')
-
- try:
-
diff _output = subprocess.check_output(
- ['
diff ', '-u', original_file_name, temp_file_name],
- stderr=subprocess.STDOUT)
- except subprocess.CalledProcessError as e:
-
diff _output = e.output
-
- print('------------------------------ Fixes -----------------------------\n' +
-
diff _output.decode(errors='ignore') +
- '\n------------------------------------------------------------------')
-
- if has_check_fixes:
- try:
- subprocess.check_output(
- ['FileCheck', '-input-file=' + temp_file_name, input_file_name,
- '-check-prefixes=' + ','.join(check_fixes_prefixes),
- '-strict-whitespace'],
- stderr=subprocess.STDOUT)
- except subprocess.CalledProcessError as e:
- print('FileCheck failed:\n' + e.output.decode())
- raise
-
- if has_check_messages:
- messages_file = temp_file_name + '.msg'
- write_file(messages_file, clang_tidy_output)
- try:
- subprocess.check_output(
- ['FileCheck', '-input-file=' + messages_file, input_file_name,
- '-check-prefixes=' + ','.join(check_messages_prefixes),
- '-implicit-check-not={{warning|error}}:'],
- stderr=subprocess.STDOUT)
- except subprocess.CalledProcessError as e:
- print('FileCheck failed:\n' + e.output.decode())
- raise
-
- if has_check_notes:
- notes_file = temp_file_name + '.notes'
- filtered_output = [line for line in clang_tidy_output.splitlines()
- if not "note: FIX-IT applied" in line]
- write_file(notes_file, '\n'.join(filtered_output))
- try:
- subprocess.check_output(
- ['FileCheck', '-input-file=' + notes_file, input_file_name,
- '-check-prefixes=' + ','.join(check_notes_prefixes),
- '-implicit-check-not={{note|warning|error}}:'],
- stderr=subprocess.STDOUT)
- except subprocess.CalledProcessError as e:
- print('FileCheck failed:\n' + e.output.decode())
+ process_output = e.output.decode(errors='ignore')
+ print('%s failed:\n%s' % (' '.join(args), process_output))
+ if raise_error:
raise
+ return process_output
+
+
+class CheckRunner:
+ def __init__(self, args, extra_args):
+ self.resource_dir = args.resource_dir
+ self.assume_file_name = args.assume_filename
+ self.input_file_name = args.input_file_name
+ self.check_name = args.check_name
+ self.temp_file_name = args.temp_file_name
+ self.original_file_name = self.temp_file_name + ".orig"
+ self.expect_clang_tidy_error = args.expect_clang_tidy_error
+ self.std = args.std
+ self.check_suffix = args.check_suffix
+ self.input_text = ''
+ self.check_fixes_prefixes = []
+ self.check_messages_prefixes = []
+ self.check_notes_prefixes = []
+ self.has_check_fixes = False
+ self.has_check_messages = False
+ self.has_check_notes = False
+
+ file_name_with_extension = self.assume_file_name or self.input_file_name
+ _, extension = os.path.splitext(file_name_with_extension)
+ if extension not in ['.c', '.hpp', '.m', '.mm']:
+ extension = '.cpp'
+ self.temp_file_name = self.temp_file_name + extension
+
+ self.clang_extra_args = []
+ self.clang_tidy_extra_args = extra_args
+ if '--' in extra_args:
+ i = self.clang_tidy_extra_args.index('--')
+ self.clang_extra_args = self.clang_tidy_extra_args[i + 1:]
+ self.clang_tidy_extra_args = self.clang_tidy_extra_args[:i]
+
+ # If the test does not specify a config style, force an empty one; otherwise
+ # auto-detection logic can discover a ".clang-tidy" file that is not related to
+ # the test.
+ if not any(
+ [arg.startswith('-config=') for arg in self.clang_tidy_extra_args]):
+ self.clang_tidy_extra_args.append('-config={}')
+
+ if extension in ['.m', '.mm']:
+ self.clang_extra_args = ['-fobjc-abi-version=2', '-fobjc-arc', '-fblocks'] + \
+ self.clang_extra_args
+
+ if extension in ['.cpp', '.hpp', '.mm']:
+ self.clang_extra_args.append('-std=' + self.std)
+
+ # Tests should not rely on STL being available, and instead provide mock
+ # implementations of relevant APIs.
+ self.clang_extra_args.append('-nostdinc++')
+
+ if self.resource_dir is not None:
+ self.clang_extra_args.append('-resource-dir=%s' % self.resource_dir)
+
+ def read_input(self):
+ with open(self.input_file_name, 'r', encoding='utf-8') as input_file:
+ self.input_text = input_file.read()
+
+ def get_prefixes(self):
+ for check in self.check_suffix:
+ if check and not re.match('^[A-Z0-9\\-]+$', check):
+ sys.exit('Only A..Z, 0..9 and "-" are allowed in check suffixes list,'
+ + ' but "%s" was given' % check)
+
+ file_check_suffix = ('-' + check) if check else ''
+ check_fixes_prefix = 'CHECK-FIXES' + file_check_suffix
+ check_messages_prefix = 'CHECK-MESSAGES' + file_check_suffix
+ check_notes_prefix = 'CHECK-NOTES' + file_check_suffix
+
+ has_check_fix = check_fixes_prefix in self.input_text
+ has_check_message = check_messages_prefix in self.input_text
+ has_check_note = check_notes_prefix in self.input_text
+
+ if has_check_note and has_check_message:
+ sys.exit('Please use either %s or %s but not both' %
+ (check_notes_prefix, check_messages_prefix))
+
+ if not has_check_fix and not has_check_message and not has_check_note:
+ sys.exit('%s, %s or %s not found in the input' %
+ (check_fixes_prefix, check_messages_prefix, check_notes_prefix))
+
+ self.has_check_fixes = self.has_check_fixes or has_check_fix
+ self.has_check_messages = self.has_check_messages or has_check_message
+ self.has_check_notes = self.has_check_notes or has_check_note
+
+ if has_check_fix:
+ self.check_fixes_prefixes.append(check_fixes_prefix)
+ if has_check_message:
+ self.check_messages_prefixes.append(check_messages_prefix)
+ if has_check_note:
+ self.check_notes_prefixes.append(check_notes_prefix)
+
+ assert self.has_check_fixes or self.has_check_messages or self.has_check_notes
+
+ def prepare_test_inputs(self):
+ # Remove the contents of the CHECK lines to avoid CHECKs matching on
+ # themselves. We need to keep the comments to preserve line numbers while
+ # avoiding empty lines which could potentially trigger formatting-related
+ # checks.
+ cleaned_test = re.sub('// *CHECK-[A-Z0-9\\-]*:[^\r\n]*', '//', self.input_text)
+ write_file(self.temp_file_name, cleaned_test)
+ write_file(self.original_file_name, cleaned_test)
+
+ def run_clang_tidy(self):
+ args = ['clang-tidy', self.temp_file_name, '-fix', '--checks=-*,' + self.check_name] + \
+ self.clang_tidy_extra_args + ['--'] + self.clang_extra_args
+ if self.expect_clang_tidy_error:
+ args.insert(0, 'not')
+ print('Running ' + repr(args) + '...')
+ clang_tidy_output = try_run(args)
+ print('------------------------ clang-tidy output -----------------------')
+ print(clang_tidy_output.encode())
+ print('\n------------------------------------------------------------------')
+
+
diff _output = try_run(['
diff ', '-u', self.original_file_name, self.temp_file_name], False)
+ print('------------------------------ Fixes -----------------------------\n' +
+
diff _output +
+ '\n------------------------------------------------------------------')
+ return clang_tidy_output
+
+ def check_fixes(self):
+ if self.has_check_fixes:
+ try_run(['FileCheck', '-input-file=' + self.temp_file_name, self.input_file_name,
+ '-check-prefixes=' + ','.join(self.check_fixes_prefixes),
+ '-strict-whitespace'])
+
+ def check_messages(self, clang_tidy_output):
+ if self.has_check_messages:
+ messages_file = self.temp_file_name + '.msg'
+ write_file(messages_file, clang_tidy_output)
+ try_run(['FileCheck', '-input-file=' + messages_file, self.input_file_name,
+ '-check-prefixes=' + ','.join(self.check_messages_prefixes),
+ '-implicit-check-not={{warning|error}}:'])
+
+ def check_notes(self, clang_tidy_output):
+ if self.has_check_notes:
+ notes_file = self.temp_file_name + '.notes'
+ filtered_output = [line for line in clang_tidy_output.splitlines()
+ if not ("note: FIX-IT applied" in line)]
+ write_file(notes_file, '\n'.join(filtered_output))
+ try_run(['FileCheck', '-input-file=' + notes_file, self.input_file_name,
+ '-check-prefixes=' + ','.join(self.check_notes_prefixes),
+ '-implicit-check-not={{note|warning|error}}:'])
+
+ def run(self):
+ self.read_input()
+ self.get_prefixes()
+ self.prepare_test_inputs()
+ clang_tidy_output = self.run_clang_tidy()
+ self.check_fixes()
+ self.check_messages(clang_tidy_output)
+ self.check_notes(clang_tidy_output)
def expand_std(std):
@@ -223,7 +222,7 @@ def csv(string):
return string.split(',')
-def main():
+def parse_arguments():
parser = argparse.ArgumentParser()
parser.add_argument('-expect-clang-tidy-error', action='store_true')
parser.add_argument('-resource-dir')
@@ -232,20 +231,23 @@ def main():
parser.add_argument('check_name')
parser.add_argument('temp_file_name')
parser.add_argument(
- '-check-suffix',
- '-check-suffixes',
- default=[''],
- type=csv,
- help='comma-separated list of FileCheck suffixes')
+ '-check-suffix',
+ '-check-suffixes',
+ default=[''],
+ type=csv,
+ help='comma-separated list of FileCheck suffixes')
parser.add_argument('-std', type=csv, default=['c++11-or-later'])
+ return parser.parse_known_args()
- args, extra_args = parser.parse_known_args()
+
+def main():
+ args, extra_args = parse_arguments()
abbreviated_stds = args.std
for abbreviated_std in abbreviated_stds:
for std in expand_std(abbreviated_std):
args.std = std
- run_test_once(args, extra_args)
+ CheckRunner(args, extra_args).run()
if __name__ == '__main__':
More information about the cfe-commits
mailing list