daca: Add compare-normal-exhaustive.py script to compare normal and exhaustive check level (#5860)

This commit is contained in:
Daniel Marjamäki 2024-01-09 11:45:44 +01:00 committed by GitHub
parent 08d754d536
commit 8903f0e4b1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 224 additions and 5 deletions

View File

@ -0,0 +1,213 @@
#!/usr/bin/env python3
# Compare "normal" check level and "exhaustive" check level
import donate_cpu_lib as lib
import argparse
import glob
import os
import sys
import random
import subprocess
def format_float(a, b=1):
if a > 0 and b > 0:
return '{:.2f}'.format(a / b)
return 'N/A'
if __name__ == "__main__":
__my_script_name = os.path.splitext(os.path.basename(sys.argv[0]))[0]
__work_path = os.path.expanduser(os.path.join('~', 'cppcheck-' + __my_script_name + '-workfolder'))
parser = argparse.ArgumentParser(description='Compare --check-level=normal and --check-level=exhaustive output')
parser.add_argument('-j', default=1, type=int, help='Concurency execution threads')
parser.add_argument('--cppcheck-path', default=None, type=str, help='Path to Cppcheck binary, if not given then clone and compile')
package_group = parser.add_mutually_exclusive_group()
package_group.add_argument('-p', default=256, type=int, help='Count of packages to check')
package_group.add_argument('--packages', nargs='+', help='Check specific packages and then stop.')
package_group.add_argument('--packages-path', default=None, type=str, help='Check packages in path.')
parser.add_argument('-o', default='my_check_diff.log', help='Filename of result inside a working path dir')
language_group = parser.add_mutually_exclusive_group()
language_group.add_argument('--c-only', dest='c_only', help='Only process c packages', action='store_true')
language_group.add_argument('--cpp-only', dest='cpp_only', help='Only process c++ packages', action='store_true')
parser.add_argument('--work-path', '--work-path=', default=__work_path, type=str, help='Working directory for reference repo')
args = parser.parse_args()
print(args)
if not lib.check_requirements():
print("Error: Check requirements")
sys.exit(1)
work_path = os.path.abspath(args.work_path)
if not os.path.exists(work_path):
os.makedirs(work_path)
lib.set_jobs('-j' + str(args.j))
result_file = os.path.join(work_path, args.o)
(f, ext) = os.path.splitext(result_file)
timing_file = f + '_timing' + ext
normal_results = f + '_normal' + ext
exhaustive_results = f + '_exhaustive' + ext
if os.path.exists(result_file):
os.remove(result_file)
if os.path.exists(timing_file):
os.remove(timing_file)
if os.path.exists(normal_results):
os.remove(normal_results)
if os.path.exists(exhaustive_results):
os.remove(exhaustive_results)
cppcheck_path = args.cppcheck_path
if cppcheck_path is None:
cppcheck_path = os.path.join(work_path, 'cppcheck')
try:
lib.clone_cppcheck(cppcheck_path, '')
pass
except Exception as e:
print('Failed to clone Cppcheck repository ({}), retry later'.format(e))
sys.exit(1)
if not lib.compile_cppcheck(cppcheck_path):
print('Failed to compile Cppcheck')
sys.exit(1)
if args.packages_path:
# You can download packages using daca2-download.py
args.packages = glob.glob(os.path.join(args.packages_path, '*.tar.xz'))
args.p = len(args.packages)
packages_idxs = list(range(args.p))
random.shuffle(packages_idxs)
elif args.packages:
args.p = len(args.packages)
packages_idxs = []
else:
packages_count = lib.get_packages_count()
if not packages_count:
print("network or server might be temporarily down..")
sys.exit(1)
packages_idxs = list(range(packages_count))
random.shuffle(packages_idxs)
packages_processed = 0
crashes = []
timeouts = []
while (packages_processed < args.p and len(packages_idxs) > 0) or args.packages:
if args.packages:
package = args.packages.pop()
else:
package = lib.get_package(packages_idxs.pop())
if package.startswith('ftp://') or package.startswith('http://'):
tgz = lib.download_package(work_path, package, None)
if tgz is None:
print("No package downloaded")
continue
else:
print('Package: ' + package)
tgz = package
source_path, source_found = lib.unpack_package(work_path, tgz, c_only=args.c_only, cpp_only=args.cpp_only)
if not source_found:
print("No files to process")
continue
results_to_diff = list()
normal_crashed = False
exhaustive_crashed = False
normal_timeout = False
exhaustive_timeout = False
enable = 'style'
debug_warnings = False
libraries = lib.library_includes.get_libraries(source_path)
c, errout, info, time_normal, cppcheck_options, timing_info = lib.scan_package(cppcheck_path, source_path, libraries, enable=enable, debug_warnings=debug_warnings, check_level='normal')
if c < 0:
if c == -101 and 'error: could not find or open any of the paths given.' in errout:
# No sourcefile found (for example only headers present)
print('Error: 101')
elif c == lib.RETURN_CODE_TIMEOUT:
print('Normal check level timed out!')
normal_timeout = True
continue # we don't want to compare timeouts
else:
print('Normal check level crashed!')
normal_crashed = True
results_to_diff.append(errout)
c, errout, info, time_exhaustive, cppcheck_options, timing_info = lib.scan_package(cppcheck_path, source_path, libraries, enable=enable, debug_warnings=debug_warnings, check_level='exhaustive')
if c < 0:
if c == -101 and 'error: could not find or open any of the paths given.' in errout:
# No sourcefile found (for example only headers present)
print('Error: 101')
elif c == lib.RETURN_CODE_TIMEOUT:
print('Exhaustive check level timed out!')
exhaustive_timeout = True
continue # we don't want to compare timeouts
else:
print('Exhaustive check level crashed!')
exhaustive_crashed = True
results_to_diff.append(errout)
if normal_crashed or exhaustive_crashed:
who = None
if normal_crashed and exhaustive_crashed:
who = 'Both'
elif normal_crashed:
who = 'Normal'
else:
who = 'Exhaustive'
crashes.append(package + ' ' + who)
if normal_timeout or exhaustive_timeout:
who = None
if normal_timeout and exhaustive_timeout:
who = 'Both'
elif normal_timeout:
who = 'Normal'
else:
who = 'Exhaustive'
timeouts.append(package + ' ' + who)
with open(result_file, 'a') as myfile:
myfile.write(package + '\n')
diff = lib.diff_results('normal', results_to_diff[0], 'exhaustive', results_to_diff[1])
if not normal_crashed and not exhaustive_crashed and diff != '':
myfile.write('libraries:' + ','.join(libraries) +'\n')
myfile.write('diff:\n' + diff + '\n')
if not normal_crashed and not exhaustive_crashed:
with open(timing_file, 'a') as myfile:
package_width = '140'
timing_width = '>7'
myfile.write('{:{package_width}} {:{timing_width}} {:{timing_width}} {:{timing_width}}\n'.format(
package, format_float(time_normal),
format_float(time_exhaustive), format_float(time_normal, time_exhaustive),
package_width=package_width, timing_width=timing_width))
with open(normal_results, 'a') as myfile:
myfile.write(results_to_diff[0])
with open(exhaustive_results, 'a') as myfile:
myfile.write(results_to_diff[1])
packages_processed += 1
print(str(packages_processed) + ' of ' + str(args.p) + ' packages processed\n')
with open(result_file, 'a') as myfile:
myfile.write('\n\ncrashes\n')
myfile.write('\n'.join(crashes))
with open(result_file, 'a') as myfile:
myfile.write('\n\ntimeouts\n')
myfile.write('\n'.join(timeouts) + '\n')
print('Result saved to: ' + result_file)

View File

@ -16,7 +16,7 @@ import copy
# Version scheme (MAJOR.MINOR.PATCH) should orientate on "Semantic Versioning" https://semver.org/
# Every change in this script should result in increasing the version number accordingly (exceptions may be cosmetic
# changes)
CLIENT_VERSION = "1.3.53"
CLIENT_VERSION = "1.3.54"
# Timeout for analysis with Cppcheck in seconds
CPPCHECK_TIMEOUT = 30 * 60
@ -431,7 +431,7 @@ def __run_command(cmd, print_cmd=True):
return return_code, stdout, stderr, elapsed_time
def scan_package(cppcheck_path, source_path, libraries, capture_callstack=True):
def scan_package(cppcheck_path, source_path, libraries, capture_callstack=True, enable='style,information', debug_warnings=True, check_level=None):
print('Analyze..')
libs = ''
for library in libraries:
@ -441,10 +441,16 @@ def scan_package(cppcheck_path, source_path, libraries, capture_callstack=True):
dir_to_scan = source_path
# TODO: temporarily disabled timing information - use --showtime=top5_summary when next version is released
# TODO: remove missingInclude disabling when it no longer is implied by --enable=information
# Reference for GNU C: https://gcc.gnu.org/onlinedocs/cpp/Common-Predefined-Macros.html
options = libs + ' --check-library --inconclusive --enable=style,information --inline-suppr --disable=missingInclude --suppress=unmatchedSuppression --template=daca2'
options += ' --debug-warnings --suppress=autoNoType --suppress=valueFlowBailout --suppress=bailoutUninitVar --suppress=symbolDatabaseWarning'
options = '{} --inconclusive --enable={} --inline-suppr --template=daca2'.format(libs, enable)
if 'information' in enable:
# TODO: remove missingInclude disabling when it no longer is implied by --enable=information
options += ' --disable=missingInclude --suppress=unmatchedSuppression'
if check_level:
options += ' --check-level=' + check_level
if debug_warnings:
options += ' --check-library --debug-warnings --suppress=autoNoType --suppress=valueFlowBailout' \
' --suppress=bailoutUninitVar --suppress=symbolDatabaseWarning'
options += ' -D__GNUC__ --platform=unix64'
options_rp = options + ' -rp={}'.format(dir_to_scan)
if __make_cmd == 'msbuild.exe':