File indexing completed on 2024-04-28 16:57:58
0001 #!/usr/bin/env python3 0002 0003 import sys 0004 import os 0005 import subprocess 0006 import string 0007 import re 0008 import json 0009 import threading 0010 import multiprocessing 0011 import argparse 0012 import io 0013 import shutil 0014 from threading import Thread 0015 from sys import platform as _platform 0016 import platform 0017 0018 # cd into the folder containing this script 0019 os.chdir(os.path.realpath(os.path.dirname(sys.argv[0]))) 0020 0021 _verbose = False 0022 _hasStdFileSystem = True 0023 0024 0025 def isWindows(): 0026 return _platform == 'win32' 0027 0028 def isMacOS(): 0029 return _platform == 'darwin' 0030 0031 def isLinux(): 0032 return _platform.startswith('linux') 0033 0034 class QtInstallation: 0035 def __init__(self): 0036 self.int_version = 000 0037 self.qmake_header_path = "/usr/include/qt/" 0038 self.qmake_lib_path = "/usr/lib" 0039 0040 def compiler_flags(self): 0041 0042 extra_includes = '' 0043 if isMacOS(): 0044 extra_includes = " -I%s/QtCore.framework/Headers" % self.qmake_lib_path 0045 extra_includes += " -iframework %s" % self.qmake_lib_path 0046 0047 return "-isystem " + self.qmake_header_path + ("" if isWindows() else " -fPIC") + " -L " + self.qmake_lib_path + extra_includes 0048 0049 0050 class Test: 0051 def __init__(self, check): 0052 self.filenames = [] 0053 self.minimum_qt_version = 500 0054 self.maximum_qt_version = 59999 0055 self.minimum_clang_version = 380 0056 self.minimum_clang_version_for_fixits = 380 0057 self.compare_everything = False 0058 self.link = False # If true we also call the linker 0059 self.check = check 0060 self.expects_failure = False 0061 self.qt_major_version = 5 # Tests use Qt 5 by default 0062 self.env = os.environ 0063 self.checks = [] 0064 self.flags = "" 0065 self.must_fail = False 0066 self.blacklist_platforms = [] 0067 self.qt4compat = False 0068 self.only_qt = False 0069 self.qt_developer = False 0070 self.header_filter = "" 0071 self.ignore_dirs = "" 0072 self.has_fixits = False 0073 self.should_run_fixits_test = False 0074 self.should_run_on_32bit = True 0075 self.cppStandard = "c++14" 0076 self.requires_std_filesystem = False 0077 0078 def filename(self): 0079 if len(self.filenames) == 1: 0080 return self.filenames[0] 0081 return "" 0082 0083 def relativeFilename(self): 0084 # example: "auto-unexpected-qstringbuilder/main.cpp" 0085 return self.check.name + "/" + self.filename() 0086 0087 def yamlFilename(self, is_standalone): 0088 # The name of the yaml file with fixits 0089 # example: "auto-unexpected-qstringbuilder/main.cpp.clazy.yaml" 0090 if is_standalone: 0091 return self.relativeFilename() + ".clazy-standalone.yaml" 0092 else: 0093 return self.relativeFilename() + ".clazy.yaml" 0094 0095 def fixedFilename(self, is_standalone): 0096 if is_standalone: 0097 return self.relativeFilename() + ".clazy-standalone.fixed" 0098 else: 0099 return self.relativeFilename() + ".clazy.fixed" 0100 0101 def expectedFixedFilename(self): 0102 return self.relativeFilename() + ".fixed.expected" 0103 0104 def isScript(self): 0105 return self.filename().endswith(".sh") 0106 0107 def dir(self): 0108 return self.check.name 0109 0110 def setQtMajorVersion(self, major_version): 0111 if major_version == 4: 0112 self.qt_major_version = 4 0113 if self.minimum_qt_version >= 500: 0114 self.minimum_qt_version = 400 0115 0116 def envString(self): 0117 result = "" 0118 for key in self.env: 0119 result += key + '="' + self.env[key] + '" ' 0120 return result 0121 0122 def setEnv(self, e): 0123 self.env = os.environ.copy() 0124 for key in e: 0125 if type(key) is bytes: 0126 key = key.decode('utf-8') 0127 0128 self.env[key] = e[key] 0129 0130 def printableName(self, is_standalone, is_fixits): 0131 name = self.check.name 0132 if len(self.check.tests) > 1: 0133 name += "/" + self.filename() 0134 if is_fixits and is_standalone: 0135 name += " (standalone, fixits)" 0136 elif is_standalone: 0137 name += " (standalone)" 0138 elif is_fixits: 0139 name += " (plugin, fixits)" 0140 else: 0141 name += " (plugin)" 0142 return name 0143 0144 def removeYamlFiles(self): 0145 for f in [self.yamlFilename(False), self.yamlFilename(True)]: 0146 if os.path.exists(f): 0147 os.remove(f) 0148 0149 0150 class Check: 0151 def __init__(self, name): 0152 self.name = name 0153 self.minimum_clang_version = 380 # clang 3.8.0 0154 self.minimum_qt_version = 500 0155 self.maximum_qt_version = 59999 0156 self.enabled = True 0157 self.clazy_standalone_only = False 0158 self.tests = [] 0159 # ------------------------------------------------------------------------------- 0160 # utility functions #1 0161 0162 0163 def get_command_output(cmd, test_env=os.environ, cwd=None): 0164 success = True 0165 0166 try: 0167 if _verbose: 0168 print(cmd) 0169 0170 # Polish up the env to fix "TypeError: environment can only contain strings" exception 0171 str_env = {} 0172 for key in test_env.keys(): 0173 str_env[str(key)] = str(test_env[key]) 0174 0175 output = subprocess.check_output( 0176 cmd, stderr=subprocess.STDOUT, shell=True, env=str_env, cwd=cwd) 0177 except subprocess.CalledProcessError as e: 0178 output = e.output 0179 success = False 0180 0181 if type(output) is bytes: 0182 output = output.decode('utf-8') 0183 0184 return output, success 0185 0186 0187 def load_json(check_name): 0188 check = Check(check_name) 0189 filename = check_name + "/config.json" 0190 if not os.path.exists(filename): 0191 # Ignore this directory 0192 return check 0193 0194 f = open(filename, 'r') 0195 contents = f.read() 0196 f.close() 0197 decoded = json.loads(contents) 0198 check_blacklist_platforms = [] 0199 0200 if 'minimum_clang_version' in decoded: 0201 check.minimum_clang_version = decoded['minimum_clang_version'] 0202 0203 if 'minimum_qt_version' in decoded: 0204 check.minimum_qt_version = decoded['minimum_qt_version'] 0205 0206 if 'maximum_qt_version' in decoded: 0207 check.maximum_qt_version = decoded['maximum_qt_version'] 0208 0209 if 'enabled' in decoded: 0210 check.enabled = decoded['enabled'] 0211 0212 if 'clazy_standalone_only' in decoded: 0213 check.clazy_standalone_only = decoded['clazy_standalone_only'] 0214 0215 if 'blacklist_platforms' in decoded: 0216 check_blacklist_platforms = decoded['blacklist_platforms'] 0217 0218 if 'tests' in decoded: 0219 for t in decoded['tests']: 0220 test = Test(check) 0221 test.blacklist_platforms = check_blacklist_platforms 0222 0223 if 'filename' in t: 0224 test.filenames.append(t['filename']) 0225 0226 if 'filenames' in t: 0227 test.filenames += t['filenames'] 0228 0229 if 'minimum_qt_version' in t: 0230 test.minimum_qt_version = t['minimum_qt_version'] 0231 else: 0232 test.minimum_qt_version = check.minimum_qt_version 0233 0234 if 'maximum_qt_version' in t: 0235 test.maximum_qt_version = t['maximum_qt_version'] 0236 else: 0237 test.maximum_qt_version = check.maximum_qt_version 0238 0239 if 'minimum_clang_version' in t: 0240 test.minimum_clang_version = t['minimum_clang_version'] 0241 else: 0242 test.minimum_clang_version = check.minimum_clang_version 0243 0244 if 'minimum_clang_version_for_fixits' in t: 0245 test.minimum_clang_version_for_fixits = t['minimum_clang_version_for_fixits'] 0246 0247 if 'blacklist_platforms' in t: 0248 test.blacklist_platforms = t['blacklist_platforms'] 0249 if 'compare_everything' in t: 0250 test.compare_everything = t['compare_everything'] 0251 if 'link' in t: 0252 test.link = t['link'] 0253 if 'qt_major_version' in t: 0254 test.setQtMajorVersion(t['qt_major_version']) 0255 if 'env' in t: 0256 test.setEnv(t['env']) 0257 if 'checks' in t: 0258 test.checks = t['checks'] 0259 if 'flags' in t: 0260 test.flags = t['flags'] 0261 if 'must_fail' in t: 0262 test.must_fail = t['must_fail'] 0263 if 'has_fixits' in t: 0264 test.has_fixits = t['has_fixits'] and test.minimum_clang_version_for_fixits <= CLANG_VERSION 0265 if 'expects_failure' in t: 0266 test.expects_failure = t['expects_failure'] 0267 if 'qt4compat' in t: 0268 test.qt4compat = t['qt4compat'] 0269 if 'only_qt' in t: 0270 test.only_qt = t['only_qt'] 0271 if 'cppStandard' in t: 0272 test.cppStandard = t['cppStandard'] 0273 if 'qt_developer' in t: 0274 test.qt_developer = t['qt_developer'] 0275 if 'header_filter' in t: 0276 test.header_filter = t['header_filter'] 0277 if 'ignore_dirs' in t: 0278 test.ignore_dirs = t['ignore_dirs'] 0279 if 'should_run_on_32bit' in t: 0280 test.should_run_on_32bit = t['should_run_on_32bit'] 0281 if 'requires_std_filesystem' in t: 0282 test.requires_std_filesystem = t['requires_std_filesystem'] 0283 0284 if not test.checks: 0285 test.checks.append(test.check.name) 0286 0287 check.tests.append(test) 0288 0289 return check 0290 0291 0292 def find_qt_installation(major_version, qmakes): 0293 installation = QtInstallation() 0294 0295 for qmake in qmakes: 0296 qmake_version_str, success = get_command_output( 0297 qmake + " -query QT_VERSION") 0298 if success and qmake_version_str.startswith(str(major_version) + "."): 0299 qmake_header_path = get_command_output( 0300 qmake + " -query QT_INSTALL_HEADERS")[0].strip() 0301 qmake_lib_path = get_command_output( 0302 qmake + " -query QT_INSTALL_LIBS")[0].strip() 0303 if qmake_header_path: 0304 installation.qmake_header_path = qmake_header_path 0305 if qmake_lib_path: 0306 installation.qmake_lib_path = qmake_lib_path 0307 ver = qmake_version_str.split('.') 0308 installation.int_version = int( 0309 ver[0]) * 10000 + int(ver[1]) * 100 + int(ver[2]) 0310 if _verbose: 0311 print("Found Qt " + str(installation.int_version) + 0312 " using qmake " + qmake) 0313 break 0314 0315 if installation.int_version == 0 and major_version >= 5: # Don't warn for missing Qt4 headers 0316 print("Error: Couldn't find a Qt" + 0317 str(major_version) + " installation") 0318 return installation 0319 0320 0321 def libraryName(): 0322 if _platform == 'win32': 0323 return 'ClazyPlugin.dll' 0324 elif _platform == 'darwin': 0325 return 'ClazyPlugin.dylib' 0326 else: 0327 return 'ClazyPlugin.so' 0328 0329 0330 def link_flags(): 0331 flags = "-lQt5Core -lQt5Gui -lQt5Widgets" 0332 if _platform.startswith('linux'): 0333 flags += " -lstdc++" 0334 return flags 0335 0336 0337 def clazy_cpp_args(cppStandard): 0338 return '-Wno-unused-value -Qunused-arguments -std=' + cppStandard + ' ' 0339 0340 0341 def more_clazy_args(cppStandard): 0342 return " " + clazy_cpp_args(cppStandard) 0343 0344 0345 def clazy_standalone_binary(): 0346 if 'CLAZYSTANDALONE_CXX' in os.environ: # in case we want to use "clazy.AppImage --standalone" instead 0347 return os.environ['CLAZYSTANDALONE_CXX'] 0348 return 'clazy-standalone' 0349 0350 def more_clazy_standalone_args(): 0351 if 'CLANG_BUILTIN_INCLUDE_DIR' in os.environ: 0352 return ' -I ' + os.environ['CLANG_BUILTIN_INCLUDE_DIR'] 0353 return '' 0354 0355 def clazy_standalone_command(test, qt): 0356 result = " -- " + clazy_cpp_args(test.cppStandard) + \ 0357 qt.compiler_flags() + " " + test.flags + more_clazy_standalone_args() 0358 result = " -checks=" + ','.join(test.checks) + " " + result 0359 0360 if test.has_fixits: 0361 result = " -export-fixes=" + \ 0362 test.yamlFilename(is_standalone=True) + result 0363 0364 if test.qt4compat: 0365 result = " -qt4-compat " + result 0366 0367 if test.only_qt: 0368 result = " -only-qt " + result 0369 0370 if test.qt_developer: 0371 result = " -qt-developer " + result 0372 0373 if test.header_filter: 0374 result = " -header-filter " + test.header_filter + " " + result 0375 0376 if test.ignore_dirs: 0377 result = " -ignore-dirs " + test.ignore_dirs + " " + result 0378 0379 return result 0380 0381 def clang_name(): 0382 return os.getenv('CLANGXX', 'clang') 0383 0384 def clazy_command(qt, test, filename): 0385 if test.isScript(): 0386 return "./" + filename 0387 0388 if 'CLAZY_CXX' in os.environ: # In case we want to use clazy.bat 0389 result = os.environ['CLAZY_CXX'] + \ 0390 more_clazy_args(test.cppStandard) + qt.compiler_flags() 0391 else: 0392 clang = clang_name() 0393 result = clang + " -Xclang -load -Xclang " + libraryName() + \ 0394 " -Xclang -add-plugin -Xclang clazy " + \ 0395 more_clazy_args(test.cppStandard) + qt.compiler_flags() 0396 0397 if test.qt4compat: 0398 result = result + " -Xclang -plugin-arg-clazy -Xclang qt4-compat " 0399 0400 if test.only_qt: 0401 result = result + " -Xclang -plugin-arg-clazy -Xclang only-qt " 0402 0403 if test.qt_developer: 0404 result = result + " -Xclang -plugin-arg-clazy -Xclang qt-developer " 0405 0406 # Linking on one platform is enough. Won't waste time on macOS and Windows. 0407 if test.link and _platform.startswith('linux'): 0408 result = result + " " + link_flags() 0409 else: 0410 result = result + " -c " 0411 0412 result = result + test.flags + \ 0413 " -Xclang -plugin-arg-clazy -Xclang " + ','.join(test.checks) + " " 0414 if test.has_fixits: 0415 result += _export_fixes_argument + " " 0416 result += filename 0417 0418 return result 0419 0420 0421 def dump_ast_command(test): 0422 return "clang -std=c++14 -fsyntax-only -Xclang -ast-dump -fno-color-diagnostics -c " + qt_installation(test.qt_major_version).compiler_flags() + " " + test.flags + " " + test.filename() 0423 0424 0425 def compiler_name(): 0426 if 'CLAZY_CXX' in os.environ: 0427 return os.environ['CLAZY_CXX'] # so we can set clazy.bat instead 0428 return os.getenv('CLANGXX', 'clang') 0429 0430 # ------------------------------------------------------------------------------- 0431 # Setup argparse 0432 0433 0434 parser = argparse.ArgumentParser() 0435 parser.add_argument("-v", "--verbose", action='store_true') 0436 parser.add_argument("--no-standalone", action='store_true', 0437 help="Don\'t run clazy-standalone") 0438 parser.add_argument("--no-fixits", action='store_true', 0439 help='Don\'t run fixits') 0440 parser.add_argument("--only-standalone", action='store_true', 0441 help='Only run clazy-standalone') 0442 parser.add_argument("--dump-ast", action='store_true', 0443 help='Dump a unit-test AST to file') 0444 parser.add_argument( 0445 "--exclude", help='Comma separated list of checks to ignore') 0446 parser.add_argument("check_names", nargs='*', 0447 help="The name of the check whose unit-tests will be run. Defaults to running all checks.") 0448 args = parser.parse_args() 0449 0450 if args.only_standalone and args.no_standalone: 0451 print("Error: --only-standalone is incompatible with --no-standalone") 0452 sys.exit(1) 0453 0454 # ------------------------------------------------------------------------------- 0455 # Global variables 0456 0457 _export_fixes_argument = "-Xclang -plugin-arg-clazy -Xclang export-fixes" 0458 _dump_ast = args.dump_ast 0459 _verbose = args.verbose 0460 _no_standalone = args.no_standalone 0461 _no_fixits = args.no_fixits 0462 _only_standalone = args.only_standalone 0463 _num_threads = multiprocessing.cpu_count() 0464 _lock = threading.Lock() 0465 _was_successful = True 0466 _qt5_installation = find_qt_installation( 0467 5, ["QT_SELECT=5 qmake", "qmake-qt5", "qmake"]) 0468 _qt4_installation = find_qt_installation( 0469 4, ["QT_SELECT=4 qmake", "qmake-qt4", "qmake"]) 0470 _excluded_checks = args.exclude.split(',') if args.exclude is not None else [] 0471 0472 # ------------------------------------------------------------------------------- 0473 # utility functions #2 0474 0475 version, success = get_command_output(compiler_name() + ' --version') 0476 match = re.search('clang version ([^\s-]+)', version) 0477 try: 0478 version = match.group(1) 0479 except: 0480 # Now try the Clazy.AppImage way 0481 match = re.search('clang version: (.*)', version) 0482 0483 try: 0484 version = match.group(1) 0485 except: 0486 splitted = version.split() 0487 if len(splitted) > 2: 0488 version = splitted[2] 0489 else: 0490 print("Could not determine clang version, is it in PATH?") 0491 sys.exit(-1) 0492 0493 if _verbose: 0494 print('Found clang version: ' + str(version)) 0495 0496 CLANG_VERSION = int(version.replace('.', '')) 0497 0498 0499 def qt_installation(major_version): 0500 if major_version == 5: 0501 return _qt5_installation 0502 elif major_version == 4: 0503 return _qt4_installation 0504 0505 return None 0506 0507 0508 def run_command(cmd, output_file="", test_env=os.environ, cwd=None): 0509 lines, success = get_command_output(cmd, test_env, cwd=cwd) 0510 # Hack for Windows, we have std::_Vector_base in the expected data 0511 lines = lines.replace("std::_Container_base0", "std::_Vector_base") 0512 lines = lines.replace("std::__1::__vector_base_common", 0513 "std::_Vector_base") # Hack for macOS 0514 lines = lines.replace("std::_Vector_alloc", "std::_Vector_base") 0515 if not success and not output_file: 0516 print(lines) 0517 return False 0518 0519 if _verbose: 0520 print("Running: " + cmd) 0521 print("output_file=" + output_file) 0522 0523 lines = lines.replace('\r\n', '\n') 0524 if output_file: 0525 f = io.open(output_file, 'w', encoding='utf8') 0526 f.writelines(lines) 0527 f.close() 0528 else: 0529 print(lines) 0530 0531 return success 0532 0533 0534 def files_are_equal(file1, file2): 0535 try: 0536 f = io.open(file1, 'r', encoding='utf-8') 0537 lines1 = f.readlines() 0538 f.close() 0539 0540 f = io.open(file2, 'r', encoding='utf-8') 0541 lines2 = f.readlines() 0542 f.close() 0543 0544 return lines1 == lines2 0545 except Exception as ex: 0546 print("Error comparing files:" + str(ex)) 0547 return False 0548 0549 0550 def compare_files(expects_failure, expected_file, result_file, message): 0551 success = files_are_equal(expected_file, result_file) 0552 0553 if expects_failure: 0554 if success: 0555 print("[XOK] " + message) 0556 return False 0557 else: 0558 print("[XFAIL] " + message) 0559 print_differences(expected_file, result_file) 0560 return True 0561 else: 0562 if success: 0563 print("[OK] " + message) 0564 return True 0565 else: 0566 print("[FAIL] " + message) 0567 print_differences(expected_file, result_file) 0568 return False 0569 0570 0571 def get_check_names(): 0572 return list(filter(lambda entry: os.path.isdir(entry), os.listdir("."))) 0573 0574 # The yaml file references the test file in our git repo, but we don't want 0575 # to rewrite that one, as we would need to discard git changes afterwards, 0576 # so patch the yaml file and add a ".fixed" suffix to those files 0577 0578 0579 def patch_fixit_yaml_file(test, is_standalone): 0580 0581 yamlfilename = test.yamlFilename(is_standalone) 0582 fixedfilename = test.fixedFilename(is_standalone) 0583 0584 f = open(yamlfilename, 'r') 0585 lines = f.readlines() 0586 f.close() 0587 f = open(yamlfilename, 'w') 0588 0589 possible_headerfile = test.relativeFilename().replace(".cpp", ".h") 0590 0591 for line in lines: 0592 stripped = line.strip() 0593 if stripped.startswith('MainSourceFile') or stripped.startswith("FilePath") or stripped.startswith("- FilePath"): 0594 line = line.replace(test.relativeFilename(), fixedfilename) 0595 0596 # For Windows: 0597 line = line.replace(test.relativeFilename().replace( 0598 '/', '\\'), fixedfilename.replace('/', '\\')) 0599 0600 # Some tests also apply fix their to their headers: 0601 if not test.relativeFilename().endswith(".hh"): 0602 line = line.replace(possible_headerfile, 0603 fixedfilename.replace(".cpp", ".h")) 0604 f.write(line) 0605 f.close() 0606 0607 shutil.copyfile(test.relativeFilename(), fixedfilename) 0608 0609 if os.path.exists(possible_headerfile): 0610 shutil.copyfile(possible_headerfile, 0611 fixedfilename.replace(".cpp", ".h")) 0612 0613 return True 0614 0615 0616 def run_clang_apply_replacements(check): 0617 command = os.getenv('CLAZY_CLANG_APPLY_REPLACEMENTS', 0618 'clang-apply-replacements') 0619 return run_command(command + ' ' + check.name) 0620 0621 def cleanup_fixit_files(checks): 0622 for check in checks: 0623 filestodelete = list(filter(lambda entry: entry.endswith( 0624 '.fixed') or entry.endswith('.yaml'), os.listdir(check.name))) 0625 for f in filestodelete: 0626 os.remove(check.name + '/' + f) 0627 0628 def print_differences(file1, file2): 0629 # Returns true if the the files are equal 0630 return run_command("diff -Naur --strip-trailing-cr {} {}".format(file1, file2)) 0631 0632 0633 def normalizedCwd(): 0634 if _platform.startswith('linux'): 0635 return subprocess.check_output("pwd -L", shell=True, universal_newlines=True).rstrip('\n') 0636 else: 0637 return os.getcwd().replace('\\', '/') 0638 0639 0640 def extract_word(word, in_file, out_file): 0641 in_f = io.open(in_file, 'r', encoding='utf-8') 0642 out_f = io.open(out_file, 'w', encoding='utf-8') 0643 for line in in_f: 0644 if '[-Wdeprecated-declarations]' in line: 0645 continue 0646 0647 if word in line: 0648 line = line.replace('\\', '/') 0649 # clazy-standalone prints the complete cpp file path for some reason. Normalize it so it compares OK with the expected output. 0650 line = line.replace(f"{normalizedCwd()}/", "") 0651 out_f.write(line) 0652 in_f.close() 0653 out_f.close() 0654 0655 0656 def print_file(filename): 0657 f = open(filename, 'r') 0658 print(f.read()) 0659 f.close() 0660 0661 0662 def file_contains(filename, text): 0663 f = io.open(filename, 'r', encoding='utf-8') 0664 contents = f.read() 0665 f.close() 0666 return text in contents 0667 0668 0669 def is32Bit(): 0670 return platform.architecture()[0] == '32bit' 0671 0672 0673 def run_unit_test(test, is_standalone): 0674 if test.check.clazy_standalone_only and not is_standalone: 0675 return True 0676 0677 qt = qt_installation(test.qt_major_version) 0678 0679 if _verbose: 0680 print 0681 print("Qt version: " + str(qt.int_version)) 0682 print("Qt headers: " + qt.qmake_header_path) 0683 0684 if qt.int_version < test.minimum_qt_version or qt.int_version > test.maximum_qt_version or CLANG_VERSION < test.minimum_clang_version: 0685 if (_verbose): 0686 print("Skipping " + test.check.name + 0687 " because required version is not available") 0688 return True 0689 0690 if test.requires_std_filesystem and not _hasStdFileSystem: 0691 if (_verbose): 0692 print("Skipping " + test.check.name + 0693 " because it requires std::filesystem") 0694 return True 0695 0696 if _platform in test.blacklist_platforms: 0697 if (_verbose): 0698 print("Skipping " + test.check.name + 0699 " because it is blacklisted for this platform") 0700 return True 0701 0702 if not test.should_run_on_32bit and is32Bit(): 0703 if (_verbose): 0704 print("Skipping " + test.check.name + 0705 " because it is blacklisted on 32bit") 0706 return True 0707 0708 checkname = test.check.name 0709 filename = checkname + "/" + test.filename() 0710 0711 output_file = filename + ".out" 0712 result_file = filename + ".result" 0713 expected_file = filename + ".expected" 0714 0715 # Some tests have different output on 32 bit 0716 if is32Bit() and os.path.exists(expected_file + '.x86'): 0717 expected_file = expected_file + '.x86' 0718 0719 if is_standalone and test.isScript(): 0720 return True 0721 0722 if is_standalone: 0723 cmd_to_run = clazy_standalone_binary() + " " + filename + " " + \ 0724 clazy_standalone_command(test, qt) 0725 else: 0726 cmd_to_run = clazy_command(qt, test, filename) 0727 0728 if test.compare_everything: 0729 result_file = output_file 0730 0731 must_fail = test.must_fail 0732 0733 cmd_success = run_command(cmd_to_run, output_file, test.env) 0734 0735 if file_contains(output_file, 'Invalid check: '): 0736 return True 0737 0738 if (not cmd_success and not must_fail) or (cmd_success and must_fail): 0739 print("[FAIL] " + checkname + 0740 " (Failed to build test. Check " + output_file + " for details)") 0741 print("-------------------") 0742 print("Contents of %s:" % output_file) 0743 print_file(output_file) 0744 print("-------------------") 0745 print 0746 return False 0747 0748 if not test.compare_everything: 0749 word_to_grep = "warning:" if not must_fail else "error:" 0750 extract_word(word_to_grep, output_file, result_file) 0751 0752 # Check that it printed the expected warnings 0753 if not compare_files(test.expects_failure, expected_file, result_file, test.printableName(is_standalone, False)): 0754 return False 0755 0756 if test.has_fixits: 0757 # The normal tests succeeded, we can run the respective fixits then 0758 test.should_run_fixits_test = True 0759 0760 return True 0761 0762 0763 def run_unit_tests(tests): 0764 result = True 0765 for test in tests: 0766 test_result = True 0767 if not _only_standalone: 0768 test_result = run_unit_test(test, False) 0769 result = result and test_result 0770 0771 if not _no_standalone: 0772 test_result = test_result and run_unit_test(test, True) 0773 result = result and test_result 0774 0775 if not test_result: 0776 test.removeYamlFiles() 0777 0778 global _was_successful, _lock 0779 with _lock: 0780 _was_successful = _was_successful and result 0781 0782 0783 def patch_yaml_files(requested_checks, is_standalone): 0784 if (is_standalone and _no_standalone) or (not is_standalone and _only_standalone): 0785 # Nothing to do 0786 return True 0787 0788 success = True 0789 for check in requested_checks: 0790 for test in check.tests: 0791 if test.should_run_fixits_test: 0792 yamlfilename = test.yamlFilename(is_standalone) 0793 if not os.path.exists(yamlfilename): 0794 print("[FAIL] " + yamlfilename + " is missing!!") 0795 success = False 0796 continue 0797 if not patch_fixit_yaml_file(test, is_standalone): 0798 print("[FAIL] Could not patch " + yamlfilename) 0799 success = False 0800 continue 0801 return success 0802 0803 0804 def compare_fixit_results(test, is_standalone): 0805 0806 if (is_standalone and _no_standalone) or (not is_standalone and _only_standalone): 0807 # Nothing to do 0808 return True 0809 0810 # Check that the rewritten file is identical to the expected one 0811 if not compare_files(False, test.expectedFixedFilename(), test.fixedFilename(is_standalone), test.printableName(is_standalone, True)): 0812 return False 0813 0814 # Some fixed cpp files have an header that was also fixed. Compare it here too. 0815 possible_headerfile_expected = test.expectedFixedFilename().replace('.cpp', '.h') 0816 if os.path.exists(possible_headerfile_expected): 0817 possible_headerfile = test.fixedFilename( 0818 is_standalone).replace('.cpp', '.h') 0819 if not compare_files(False, possible_headerfile_expected, possible_headerfile, test.printableName(is_standalone, True).replace('.cpp', '.h')): 0820 return False 0821 0822 return True 0823 0824 # This is run sequentially, due to races. As clang-apply-replacements just applies all .yaml files it can find. 0825 # We run a single clang-apply-replacements invocation, which changes all files in the tests/ directory. 0826 0827 0828 def run_fixit_tests(requested_checks): 0829 0830 success = patch_yaml_files(requested_checks, is_standalone=False) 0831 success = patch_yaml_files( 0832 requested_checks, is_standalone=True) and success 0833 0834 for check in requested_checks: 0835 # Call clazy-apply-replacements[.exe] 0836 if not run_clang_apply_replacements(check): 0837 return False 0838 0839 # Now compare all the *.fixed files with the *.fixed.expected counterparts 0840 for test in check.tests: 0841 if test.should_run_fixits_test: 0842 # Check that the rewritten file is identical to the expected one 0843 if not compare_fixit_results(test, is_standalone=False): 0844 success = False 0845 continue 0846 0847 if not compare_fixit_results(test, is_standalone=True): 0848 success = False 0849 continue 0850 0851 return success 0852 0853 0854 def dump_ast(check): 0855 for test in check.tests: 0856 ast_filename = test.filename() + ".ast" 0857 run_command(dump_ast_command(test) + " > " + ast_filename) 0858 print("Dumped AST to " + os.getcwd() + "/" + ast_filename) 0859 # ------------------------------------------------------------------------------- 0860 def load_checks(all_check_names): 0861 checks = [] 0862 for name in all_check_names: 0863 try: 0864 check = load_json(name) 0865 if check.enabled: 0866 checks.append(check) 0867 except: 0868 print("Error while loading " + name) 0869 raise 0870 sys.exit(-1) 0871 return checks 0872 # ------------------------------------------------------------------------------- 0873 def try_compile(filename): 0874 return run_command("%s --std=c++17 -c %s" % (clang_name(), filename)) 0875 0876 # ------------------------------------------------------------------------------- 0877 # main 0878 0879 if isLinux(): 0880 # On Windows and macOS we have recent enough toolchains 0881 _hasStdFileSystem = try_compile('../.cmake_has_filesystem_test.cpp') 0882 0883 if 'CLAZY_NO_WERROR' in os.environ: 0884 del os.environ['CLAZY_NO_WERROR'] 0885 0886 os.environ['CLAZY_CHECKS'] = '' 0887 0888 all_check_names = get_check_names() 0889 all_checks = load_checks(all_check_names) 0890 requested_check_names = args.check_names 0891 requested_check_names = list( 0892 map(lambda x: x.strip("/\\"), requested_check_names)) 0893 0894 for check_name in requested_check_names: 0895 if check_name not in all_check_names: 0896 print("Unknown check: " + check_name) 0897 print 0898 sys.exit(-1) 0899 0900 if not requested_check_names: 0901 requested_check_names = all_check_names 0902 0903 requested_checks = list(filter( 0904 lambda check: check.name in requested_check_names and check.name not in _excluded_checks, all_checks)) 0905 requested_checks = list(filter( 0906 lambda check: check.minimum_clang_version <= CLANG_VERSION, requested_checks)) 0907 0908 threads = [] 0909 0910 if _dump_ast: 0911 for check in requested_checks: 0912 os.chdir(check.name) 0913 dump_ast(check) 0914 os.chdir("..") 0915 else: 0916 cleanup_fixit_files(requested_checks) 0917 # Each list is a list of Test to be worked on by a thread 0918 list_of_chunks = [[] for x in range(_num_threads)] 0919 i = _num_threads 0920 for check in requested_checks: 0921 for test in check.tests: 0922 i = (i + 1) % _num_threads 0923 list_of_chunks[i].append(test) 0924 0925 for tests in list_of_chunks: 0926 if not tests: 0927 continue 0928 0929 t = Thread(target=run_unit_tests, args=(tests,)) 0930 t.start() 0931 threads.append(t) 0932 0933 for thread in threads: 0934 thread.join() 0935 0936 if not _no_fixits and not run_fixit_tests(requested_checks): 0937 _was_successful = False 0938 0939 if _was_successful: 0940 print("SUCCESS") 0941 sys.exit(0) 0942 else: 0943 print("FAIL") 0944 sys.exit(-1)