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)