From 9310258c572807092fcdd42aaebf6bb85ae646b3 Mon Sep 17 00:00:00 2001 From: Patryk Obara Date: Thu, 26 Sep 2019 20:47:04 +0200 Subject: [PATCH] Enforce limit on issues found in static analysis Implements new script (count-bugs.py) for peeking inside clang static analyzer's report and print just a summary. If number of detected bugs goes beyond the limit, script will return with error code 1, thus failing the CI run. The upper limit is set to 113, which is current result of static analysis in our CI environment (local run is likely to indicate different number); upper limit will be updated in time, as issues get fixed or new compiler (detecting more bugs) will be introduced. This commit includes also slight modifictaions to count-warnings.py script, to keep the both scripts outputting in similar format. --- .github/workflows/static.yml | 8 +++- scripts/count-bugs.py | 74 ++++++++++++++++++++++++++++++++++++ scripts/count-warnings.py | 29 ++++++++------ 3 files changed, 99 insertions(+), 12 deletions(-) create mode 100755 scripts/count-bugs.py mode change 100644 => 100755 scripts/count-warnings.py diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml index a59a29f5..ef1cef83 100644 --- a/.github/workflows/static.yml +++ b/.github/workflows/static.yml @@ -12,7 +12,7 @@ jobs: - name: "Install packages" run: sudo apt-get install libsdl1.2-dev libsdl-net1.2-dev libsdl-sound1.2-dev python3-setuptools - name: "Install scan-build (Python version)" - run: sudo pip3 install scan-build + run: sudo pip3 install scan-build beautifulsoup4 html5lib - name: Build run: | # build steps @@ -27,3 +27,9 @@ jobs: with: name: report path: report + - name: Summarize report + run: | + # summary + echo "Full report is included in build Artifacts" + echo + ./scripts/count-bugs.py report/*/index.html diff --git a/scripts/count-bugs.py b/scripts/count-bugs.py new file mode 100755 index 00000000..1b720b54 --- /dev/null +++ b/scripts/count-bugs.py @@ -0,0 +1,74 @@ +#!/usr/bin/python3 + +# Copyright (c) 2019 Patryk Obara +# SPDX-License-Identifier: GPL-2.0-or-later + +# This script prints a summary snippet of information out of reports created +# by scan-build or analyze-build for Clang's static code analysis. +# +# Usage: ./count-warnings.py path/to/report/index.html +# +# This script depends on BeautifulSoup module, if you're distribution is +# missing it, you can use pipenv to install it for virtualenv spanning only +# this repo: pipenv install beautifulsoup4 html5lib + +# pylint: disable=invalid-name +# pylint: disable=missing-docstring + +import sys + +from bs4 import BeautifulSoup + +# Maximum allowed number of issues; if report will include more bugs, +# then script will return with status 1. Simply change this line if you +# want to set a different limit. +# +MAX_ISSUES = 113 + + +def summary_values(summary_table): + if not summary_table: + return + for row in summary_table.find_all('tr'): + description = row.find('td', 'SUMM_DESC') + value = row.find('td', 'Q') + if description is None or value is None: + continue + yield description.get_text(), int(value.get_text()) + + +def read_soup(index_html): + with open(index_html) as index: + soup = BeautifulSoup(index, 'html5lib') + tables = soup.find_all('table') + summary = tables[1] + return {bug: count for bug, count in summary_values(summary)} + + +def find_longest_name_length(names): + return max(len(x) for x in names) + + +def print_summary(issues): + summary = list(issues.items()) + size = find_longest_name_length(issues.keys()) + 1 + for warning, count in sorted(summary, key=lambda x: -x[1]): + print(' {text:{field_size}s}: {count}'.format( + text=warning, count=count, field_size=size)) + print() + + +def main(): + bug_types = read_soup(sys.argv[1]) + total = bug_types.pop('All Bugs') + if bug_types: + print("Bugs grouped by type:\n") + print_summary(bug_types) + print('Total: {} bugs (out of {} allowed)\n'.format(total, MAX_ISSUES)) + if total > MAX_ISSUES: + print('Error: upper limit of allowed bugs is', MAX_ISSUES) + sys.exit(1) + + +if __name__ == '__main__': + main() diff --git a/scripts/count-warnings.py b/scripts/count-warnings.py old mode 100644 new mode 100755 index 54aa06a8..6fa3a712 --- a/scripts/count-warnings.py +++ b/scripts/count-warnings.py @@ -1,8 +1,9 @@ #!/usr/bin/python3 -# Copyright (c) 2019 Patryk Obara +# Copyright (c) 2019 Patryk Obara +# SPDX-License-Identifier: GPL-2.0-or-later -# This script counts and all warnings and prints a summary. +# This script counts all compiler warnings and prints a summary. # # Usage: ./count-warnings.py build.log # Usage: cat "*.log" | ./count-warnings.py - @@ -18,11 +19,11 @@ import os import re import sys -# Maximum allowed number of warnings; if build will include more warnings, +# Maximum allowed number of issues; if build will include more warnings, # then script will return with status 1. Simply change this line if you # want to set a different limit. # -MAX_WARNINGS = 357 +MAX_ISSUES = 357 # For recognizing warnings in GCC format in stderr: # @@ -63,11 +64,16 @@ def get_input_lines(name): return logs.readlines() -def print_summary(warning_types): - print("Warnings grouped by type:\n") - summary = list(warning_types.items()) +def find_longest_name_length(names): + return max(len(x) for x in names) + + +def print_summary(issues): + summary = list(issues.items()) + size = find_longest_name_length(issues.keys()) + 1 for warning, count in sorted(summary, key=lambda x: -x[1]): - print(' {:28s}: {}'.format(warning, count)) + print(' {text:{field_size}s}: {count}'.format( + text=warning, count=count, field_size=size)) print() @@ -77,10 +83,11 @@ def main(): for line in get_input_lines(sys.argv[1]): total += count_warning(line, warning_types) if warning_types: + print("Warnings grouped by type:\n") print_summary(warning_types) - print('Total: {} warnings\n'.format(total)) - if total > MAX_WARNINGS: - print('Error: upper limit of warnings is', MAX_WARNINGS) + print('Total: {} warnings (out of {} allowed)\n'.format(total, MAX_ISSUES)) + if total > MAX_ISSUES: + print('Error: upper limit of allowed warnings is', MAX_ISSUES) sys.exit(1)