Message ID | 20230504142523.2989306-2-luca.fancellu@arm.com (mailing list archive) |
---|---|
State | Superseded |
Headers | show |
Series | diff-report.py tool | expand |
> On 4 May 2023, at 15:25, Luca Fancellu <Luca.Fancellu@arm.com> wrote: > > Add a new tool, diff-report.py that can be used to make diff between > reports generated by xen-analysis.py tool. > Currently this tool supports the Xen cppcheck text report format in > its operations. > > The tool prints every finding that is in the report passed with -r > (check report) which is not in the report passed with -b (baseline). > > Signed-off-by: Luca Fancellu <luca.fancellu@arm.com> > --- > xen/scripts/diff-report.py | 76 ++++++++++++ > .../xen_analysis/diff_tool/__init__.py | 0 > .../xen_analysis/diff_tool/cppcheck_report.py | 41 +++++++ > xen/scripts/xen_analysis/diff_tool/debug.py | 36 ++++++ > xen/scripts/xen_analysis/diff_tool/report.py | 114 ++++++++++++++++++ > 5 files changed, 267 insertions(+) > create mode 100755 xen/scripts/diff-report.py > create mode 100644 xen/scripts/xen_analysis/diff_tool/__init__.py > create mode 100644 xen/scripts/xen_analysis/diff_tool/cppcheck_report.py > create mode 100644 xen/scripts/xen_analysis/diff_tool/debug.py > create mode 100644 xen/scripts/xen_analysis/diff_tool/report.py > > diff --git a/xen/scripts/diff-report.py b/xen/scripts/diff-report.py > new file mode 100755 > index 000000000000..4913fb43a8f9 > --- /dev/null > +++ b/xen/scripts/diff-report.py > @@ -0,0 +1,76 @@ > +#!/usr/bin/env python3 > + > +import os, sys > +from argparse import ArgumentParser > +from xen_analysis.diff_tool.debug import Debug > +from xen_analysis.diff_tool.report import ReportError > +from xen_analysis.diff_tool.cppcheck_report import CppcheckReport > + > + > +def log_info(text, end='\n'): > + global args > + global file_out > + > + if (args.verbose): > + print(text, end=end, file=file_out) > + > + > +def main(argv): > + global args > + global file_out > + > + parser = ArgumentParser(prog="diff-report.py") > + parser.add_argument("-b", "--baseline", required=True, type=str, > + help="Path to the baseline report.") > + parser.add_argument("--debug", action='store_true', > + help="Produce intermediate reports during operations.") > + parser.add_argument("-o", "--out", default="stdout", type=str, > + help="Where to print the tool output. Default is " > + "stdout") > + parser.add_argument("-r", "--report", required=True, type=str, > + help="Path to the 'check report', the one checked " > + "against the baseline.") > + parser.add_argument("-v", "--verbose", action='store_true', > + help="Print more informations during the run.") > + > + args = parser.parse_args() > + > + if args.out == "stdout": > + file_out = sys.stdout > + else: > + try: > + file_out = open(args.out, "wt") > + except OSError as e: > + print("ERROR: Issue opening file {}: {}".format(args.out, e)) > + sys.exit(1) > + > + debug = Debug(args) > + > + try: > + baseline_path = os.path.realpath(args.baseline) > + log_info("Loading baseline report {}".format(baseline_path), "") > + baseline = CppcheckReport(baseline_path) > + baseline.parse() > + debug.debug_print_parsed_report(baseline) > + log_info(" [OK]") > + new_rep_path = os.path.realpath(args.report) > + log_info("Loading check report {}".format(new_rep_path), "") > + new_rep = CppcheckReport(new_rep_path) > + new_rep.parse() > + debug.debug_print_parsed_report(new_rep) > + log_info(" [OK]") > + except ReportError as e: > + print("ERROR: {}".format(e)) > + sys.exit(1) > + > + output = new_rep - baseline > + print(output, end="", file=file_out) > + > + if len(output) > 0: > + sys.exit(1) > + > + sys.exit(0) > + > + > +if __name__ == "__main__": > + main(sys.argv[1:]) > diff --git a/xen/scripts/xen_analysis/diff_tool/__init__.py b/xen/scripts/xen_analysis/diff_tool/__init__.py > new file mode 100644 > index 000000000000..e69de29bb2d1 > diff --git a/xen/scripts/xen_analysis/diff_tool/cppcheck_report.py b/xen/scripts/xen_analysis/diff_tool/cppcheck_report.py > new file mode 100644 > index 000000000000..787a51aca583 > --- /dev/null > +++ b/xen/scripts/xen_analysis/diff_tool/cppcheck_report.py > @@ -0,0 +1,41 @@ > +#!/usr/bin/env python3 > + > +import re > +from .report import Report, ReportError > + > + > +class CppcheckReport(Report): > + def __init__(self, report_path: str) -> None: > + super().__init__(report_path) > + # This matches a string like: > + # path/to/file.c(<line number>,<digits>):<whatever> > + # and captures file name path and line number > + # the last capture group is used for text substitution in __str__ > + self.__report_entry_regex = re.compile(r'^(.*)\((\d+)(,\d+\):.*)$') > + > + def parse(self) -> None: > + report_path = self.get_report_path() > + try: > + with open(report_path, "rt") as infile: > + report_lines = infile.readlines() > + except OSError as e: > + raise ReportError("Issue with reading file {}: {}" > + .format(report_path, e)) > + for line in report_lines: > + entry = self.__report_entry_regex.match(line) > + if entry and entry.group(1) and entry.group(2): > + file_path = entry.group(1) > + line_number = int(entry.group(2)) > + self.add_entry(file_path, line_number, line) > + else: > + raise ReportError("Malformed report entry in file {}:\n{}" > + .format(report_path, line)) > + > + def __str__(self) -> str: > + ret = "" > + for entry in self.to_list(): > + ret += re.sub(self.__report_entry_regex, > + r'{}({}\3'.format(entry.file_path, > + entry.line_number), > + entry.text) > + return ret > diff --git a/xen/scripts/xen_analysis/diff_tool/debug.py b/xen/scripts/xen_analysis/diff_tool/debug.py > new file mode 100644 > index 000000000000..d46df3300d21 > --- /dev/null > +++ b/xen/scripts/xen_analysis/diff_tool/debug.py > @@ -0,0 +1,36 @@ > +#!/usr/bin/env python3 > + > +import os > +from .report import Report > + > + > +class Debug: > + def __init__(self, args): > + self.args = args > + > + def __get_debug_out_filename(self, path: str, type: str) -> str: > + # Take basename > + file_name = os.path.basename(path) > + # Split in name and extension > + file_name = os.path.splitext(file_name) > + if self.args.out != "stdout": > + out_folder = os.path.dirname(self.args.out) > + else: > + out_folder = "./" > + dbg_report_path = out_folder + file_name[0] + type + file_name[1] > + > + return dbg_report_path > + > + def __debug_print_report(self, report: Report, type: str) -> None: > + report_name = self.__get_debug_out_filename(report.get_report_path(), > + type) > + try: > + with open(report_name, "wt") as outfile: > + print(report, end="", file=outfile) > + except OSError as e: > + print("ERROR: Issue opening file {}: {}".format(report_name, e)) > + > + def debug_print_parsed_report(self, report: Report) -> None: > + if not self.args.debug: > + return > + self.__debug_print_report(report, ".parsed") > diff --git a/xen/scripts/xen_analysis/diff_tool/report.py b/xen/scripts/xen_analysis/diff_tool/report.py > new file mode 100644 > index 000000000000..d958d1816eb4 > --- /dev/null > +++ b/xen/scripts/xen_analysis/diff_tool/report.py > @@ -0,0 +1,114 @@ > +#!/usr/bin/env python3 > + > +import os > + > + > +class ReportError(Exception): > + pass > + > + > +class Report: > + class ReportEntry: > + def __init__(self, file_path: str, line_number: int, > + entry_text: list, line_id: int) -> None: > + if not isinstance(line_number, int) or \ > + not isinstance(line_id, int): > + raise ReportError("ReportEntry constructor wrong type args") > + self.file_path = file_path > + self.line_number = line_number > + self.text = entry_text > + self.line_id = line_id > + I realise now that I had a rebase mistake here, the class ReportEntry should not have these two functions, it was part of another branch, I will fix this in the next version, in the mean time I’ll wait for other possible findings for this patch. <delete from here> > + def __str__(self) -> str: > + ret = '' > + header = 'File path:Count\n' > + > + for path in self.stats: > + ret += f'{path}: {len(self.stats[path])}\n' > + > + if ret == '': > + ret += 'No new issues introduced\n' > + > + ret = header + ret > + > + return ret > + > + def __len__(self) -> int: > + ret = 0 > + > + for ln_list in self.stats.values(): > + ret += len(ln_list) > + > + return ret <to here> > + > + def __init__(self, report_path: str) -> None: > + self.__entries = {} > + self.__path = report_path > + self.__last_line_order = 0 > + > + def parse(self) -> None: > + raise ReportError("Please create a specialised class from 'Report'.") > + > + def get_report_path(self) -> str: > + return self.__path > + > + def get_report_entries(self) -> dict: > + return self.__entries > + > + def add_entry(self, entry_path: str, entry_line_number: int, > + entry_text: list) -> None: > + entry = Report.ReportEntry(entry_path, entry_line_number, entry_text, > + self.__last_line_order) > + if entry_path in self.__entries.keys(): > + self.__entries[entry_path].append(entry) > + else: > + self.__entries[entry_path] = [entry] > + self.__last_line_order += 1 > + > + def to_list(self) -> list: > + report_list = [] > + for _, entries in self.__entries.items(): > + for entry in entries: > + report_list.append(entry) > + > + report_list.sort(key=lambda x: x.line_id) > + return report_list > + > + def __str__(self) -> str: > + ret = "" > + for entry in self.to_list(): > + ret += entry.file_path + ":" + entry.line_number + ":" + entry.text > + > + return ret > + > + def __len__(self) -> int: > + return len(self.to_list()) > + > + def __sub__(self, report_b: 'Report') -> 'Report': > + if self.__class__ != report_b.__class__: > + raise ReportError("Diff of different type of report!") > + > + filename, file_extension = os.path.splitext(self.__path) > + diff_report = self.__class__(filename + ".diff" + file_extension) > + # Put in the diff report only records of this report that are not > + # present in the report_b. > + for file_path, entries in self.__entries.items(): > + rep_b_entries = report_b.get_report_entries() > + if file_path in rep_b_entries.keys(): > + # File path exists in report_b, so check what entries of that > + # file path doesn't exist in report_b and add them to the diff > + rep_b_entries_num = [ > + x.line_number for x in rep_b_entries[file_path] > + ] > + for entry in entries: > + if entry.line_number not in rep_b_entries_num: > + diff_report.add_entry(file_path, entry.line_number, > + entry.text) > + else: > + # File path doesn't exist in report_b, so add every entry > + # of that file path to the diff > + for entry in entries: > + diff_report.add_entry(file_path, entry.line_number, > + entry.text) > + > + return diff_report > -- > 2.34.1 > >
On Thu, 4 May 2023, Luca Fancellu wrote: > Add a new tool, diff-report.py that can be used to make diff between > reports generated by xen-analysis.py tool. > Currently this tool supports the Xen cppcheck text report format in > its operations. > > The tool prints every finding that is in the report passed with -r > (check report) which is not in the report passed with -b (baseline). > > Signed-off-by: Luca Fancellu <luca.fancellu@arm.com> Acked-by: Stefano Stabellini <sstabellini@kernel.org> Tested-by: Stefano Stabellini <sstabellini@kernel.org> > --- > xen/scripts/diff-report.py | 76 ++++++++++++ > .../xen_analysis/diff_tool/__init__.py | 0 > .../xen_analysis/diff_tool/cppcheck_report.py | 41 +++++++ > xen/scripts/xen_analysis/diff_tool/debug.py | 36 ++++++ > xen/scripts/xen_analysis/diff_tool/report.py | 114 ++++++++++++++++++ > 5 files changed, 267 insertions(+) > create mode 100755 xen/scripts/diff-report.py > create mode 100644 xen/scripts/xen_analysis/diff_tool/__init__.py > create mode 100644 xen/scripts/xen_analysis/diff_tool/cppcheck_report.py > create mode 100644 xen/scripts/xen_analysis/diff_tool/debug.py > create mode 100644 xen/scripts/xen_analysis/diff_tool/report.py > > diff --git a/xen/scripts/diff-report.py b/xen/scripts/diff-report.py > new file mode 100755 > index 000000000000..4913fb43a8f9 > --- /dev/null > +++ b/xen/scripts/diff-report.py > @@ -0,0 +1,76 @@ > +#!/usr/bin/env python3 > + > +import os, sys > +from argparse import ArgumentParser > +from xen_analysis.diff_tool.debug import Debug > +from xen_analysis.diff_tool.report import ReportError > +from xen_analysis.diff_tool.cppcheck_report import CppcheckReport > + > + > +def log_info(text, end='\n'): > + global args > + global file_out > + > + if (args.verbose): > + print(text, end=end, file=file_out) > + > + > +def main(argv): > + global args > + global file_out > + > + parser = ArgumentParser(prog="diff-report.py") > + parser.add_argument("-b", "--baseline", required=True, type=str, > + help="Path to the baseline report.") > + parser.add_argument("--debug", action='store_true', > + help="Produce intermediate reports during operations.") > + parser.add_argument("-o", "--out", default="stdout", type=str, > + help="Where to print the tool output. Default is " > + "stdout") > + parser.add_argument("-r", "--report", required=True, type=str, > + help="Path to the 'check report', the one checked " > + "against the baseline.") > + parser.add_argument("-v", "--verbose", action='store_true', > + help="Print more informations during the run.") > + > + args = parser.parse_args() > + > + if args.out == "stdout": > + file_out = sys.stdout > + else: > + try: > + file_out = open(args.out, "wt") > + except OSError as e: > + print("ERROR: Issue opening file {}: {}".format(args.out, e)) > + sys.exit(1) > + > + debug = Debug(args) > + > + try: > + baseline_path = os.path.realpath(args.baseline) > + log_info("Loading baseline report {}".format(baseline_path), "") > + baseline = CppcheckReport(baseline_path) > + baseline.parse() > + debug.debug_print_parsed_report(baseline) > + log_info(" [OK]") > + new_rep_path = os.path.realpath(args.report) > + log_info("Loading check report {}".format(new_rep_path), "") > + new_rep = CppcheckReport(new_rep_path) > + new_rep.parse() > + debug.debug_print_parsed_report(new_rep) > + log_info(" [OK]") > + except ReportError as e: > + print("ERROR: {}".format(e)) > + sys.exit(1) > + > + output = new_rep - baseline > + print(output, end="", file=file_out) > + > + if len(output) > 0: > + sys.exit(1) > + > + sys.exit(0) > + > + > +if __name__ == "__main__": > + main(sys.argv[1:]) > diff --git a/xen/scripts/xen_analysis/diff_tool/__init__.py b/xen/scripts/xen_analysis/diff_tool/__init__.py > new file mode 100644 > index 000000000000..e69de29bb2d1 > diff --git a/xen/scripts/xen_analysis/diff_tool/cppcheck_report.py b/xen/scripts/xen_analysis/diff_tool/cppcheck_report.py > new file mode 100644 > index 000000000000..787a51aca583 > --- /dev/null > +++ b/xen/scripts/xen_analysis/diff_tool/cppcheck_report.py > @@ -0,0 +1,41 @@ > +#!/usr/bin/env python3 > + > +import re > +from .report import Report, ReportError > + > + > +class CppcheckReport(Report): > + def __init__(self, report_path: str) -> None: > + super().__init__(report_path) > + # This matches a string like: > + # path/to/file.c(<line number>,<digits>):<whatever> > + # and captures file name path and line number > + # the last capture group is used for text substitution in __str__ > + self.__report_entry_regex = re.compile(r'^(.*)\((\d+)(,\d+\):.*)$') > + > + def parse(self) -> None: > + report_path = self.get_report_path() > + try: > + with open(report_path, "rt") as infile: > + report_lines = infile.readlines() > + except OSError as e: > + raise ReportError("Issue with reading file {}: {}" > + .format(report_path, e)) > + for line in report_lines: > + entry = self.__report_entry_regex.match(line) > + if entry and entry.group(1) and entry.group(2): > + file_path = entry.group(1) > + line_number = int(entry.group(2)) > + self.add_entry(file_path, line_number, line) > + else: > + raise ReportError("Malformed report entry in file {}:\n{}" > + .format(report_path, line)) > + > + def __str__(self) -> str: > + ret = "" > + for entry in self.to_list(): > + ret += re.sub(self.__report_entry_regex, > + r'{}({}\3'.format(entry.file_path, > + entry.line_number), > + entry.text) > + return ret > diff --git a/xen/scripts/xen_analysis/diff_tool/debug.py b/xen/scripts/xen_analysis/diff_tool/debug.py > new file mode 100644 > index 000000000000..d46df3300d21 > --- /dev/null > +++ b/xen/scripts/xen_analysis/diff_tool/debug.py > @@ -0,0 +1,36 @@ > +#!/usr/bin/env python3 > + > +import os > +from .report import Report > + > + > +class Debug: > + def __init__(self, args): > + self.args = args > + > + def __get_debug_out_filename(self, path: str, type: str) -> str: > + # Take basename > + file_name = os.path.basename(path) > + # Split in name and extension > + file_name = os.path.splitext(file_name) > + if self.args.out != "stdout": > + out_folder = os.path.dirname(self.args.out) > + else: > + out_folder = "./" > + dbg_report_path = out_folder + file_name[0] + type + file_name[1] > + > + return dbg_report_path > + > + def __debug_print_report(self, report: Report, type: str) -> None: > + report_name = self.__get_debug_out_filename(report.get_report_path(), > + type) > + try: > + with open(report_name, "wt") as outfile: > + print(report, end="", file=outfile) > + except OSError as e: > + print("ERROR: Issue opening file {}: {}".format(report_name, e)) > + > + def debug_print_parsed_report(self, report: Report) -> None: > + if not self.args.debug: > + return > + self.__debug_print_report(report, ".parsed") > diff --git a/xen/scripts/xen_analysis/diff_tool/report.py b/xen/scripts/xen_analysis/diff_tool/report.py > new file mode 100644 > index 000000000000..d958d1816eb4 > --- /dev/null > +++ b/xen/scripts/xen_analysis/diff_tool/report.py > @@ -0,0 +1,114 @@ > +#!/usr/bin/env python3 > + > +import os > + > + > +class ReportError(Exception): > + pass > + > + > +class Report: > + class ReportEntry: > + def __init__(self, file_path: str, line_number: int, > + entry_text: list, line_id: int) -> None: > + if not isinstance(line_number, int) or \ > + not isinstance(line_id, int): > + raise ReportError("ReportEntry constructor wrong type args") > + self.file_path = file_path > + self.line_number = line_number > + self.text = entry_text > + self.line_id = line_id > + > + def __str__(self) -> str: > + ret = '' > + header = 'File path:Count\n' > + > + for path in self.stats: > + ret += f'{path}: {len(self.stats[path])}\n' > + > + if ret == '': > + ret += 'No new issues introduced\n' > + > + ret = header + ret > + > + return ret > + > + def __len__(self) -> int: > + ret = 0 > + > + for ln_list in self.stats.values(): > + ret += len(ln_list) > + > + return ret > + > + def __init__(self, report_path: str) -> None: > + self.__entries = {} > + self.__path = report_path > + self.__last_line_order = 0 > + > + def parse(self) -> None: > + raise ReportError("Please create a specialised class from 'Report'.") > + > + def get_report_path(self) -> str: > + return self.__path > + > + def get_report_entries(self) -> dict: > + return self.__entries > + > + def add_entry(self, entry_path: str, entry_line_number: int, > + entry_text: list) -> None: > + entry = Report.ReportEntry(entry_path, entry_line_number, entry_text, > + self.__last_line_order) > + if entry_path in self.__entries.keys(): > + self.__entries[entry_path].append(entry) > + else: > + self.__entries[entry_path] = [entry] > + self.__last_line_order += 1 > + > + def to_list(self) -> list: > + report_list = [] > + for _, entries in self.__entries.items(): > + for entry in entries: > + report_list.append(entry) > + > + report_list.sort(key=lambda x: x.line_id) > + return report_list > + > + def __str__(self) -> str: > + ret = "" > + for entry in self.to_list(): > + ret += entry.file_path + ":" + entry.line_number + ":" + entry.text > + > + return ret > + > + def __len__(self) -> int: > + return len(self.to_list()) > + > + def __sub__(self, report_b: 'Report') -> 'Report': > + if self.__class__ != report_b.__class__: > + raise ReportError("Diff of different type of report!") > + > + filename, file_extension = os.path.splitext(self.__path) > + diff_report = self.__class__(filename + ".diff" + file_extension) > + # Put in the diff report only records of this report that are not > + # present in the report_b. > + for file_path, entries in self.__entries.items(): > + rep_b_entries = report_b.get_report_entries() > + if file_path in rep_b_entries.keys(): > + # File path exists in report_b, so check what entries of that > + # file path doesn't exist in report_b and add them to the diff > + rep_b_entries_num = [ > + x.line_number for x in rep_b_entries[file_path] > + ] > + for entry in entries: > + if entry.line_number not in rep_b_entries_num: > + diff_report.add_entry(file_path, entry.line_number, > + entry.text) > + else: > + # File path doesn't exist in report_b, so add every entry > + # of that file path to the diff > + for entry in entries: > + diff_report.add_entry(file_path, entry.line_number, > + entry.text) > + > + return diff_report > -- > 2.34.1 >
> On 17 May 2023, at 02:26, Stefano Stabellini <sstabellini@kernel.org> wrote: > > On Thu, 4 May 2023, Luca Fancellu wrote: >> Add a new tool, diff-report.py that can be used to make diff between >> reports generated by xen-analysis.py tool. >> Currently this tool supports the Xen cppcheck text report format in >> its operations. >> >> The tool prints every finding that is in the report passed with -r >> (check report) which is not in the report passed with -b (baseline). >> >> Signed-off-by: Luca Fancellu <luca.fancellu@arm.com> > > Acked-by: Stefano Stabellini <sstabellini@kernel.org> > Tested-by: Stefano Stabellini <sstabellini@kernel.org> Thank you Stefano for taking the time to review and test it, I will push the new version of the serie with the stale functions removed and I will add your A-by and T-by. Cheers, Luca
diff --git a/xen/scripts/diff-report.py b/xen/scripts/diff-report.py new file mode 100755 index 000000000000..4913fb43a8f9 --- /dev/null +++ b/xen/scripts/diff-report.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python3 + +import os, sys +from argparse import ArgumentParser +from xen_analysis.diff_tool.debug import Debug +from xen_analysis.diff_tool.report import ReportError +from xen_analysis.diff_tool.cppcheck_report import CppcheckReport + + +def log_info(text, end='\n'): + global args + global file_out + + if (args.verbose): + print(text, end=end, file=file_out) + + +def main(argv): + global args + global file_out + + parser = ArgumentParser(prog="diff-report.py") + parser.add_argument("-b", "--baseline", required=True, type=str, + help="Path to the baseline report.") + parser.add_argument("--debug", action='store_true', + help="Produce intermediate reports during operations.") + parser.add_argument("-o", "--out", default="stdout", type=str, + help="Where to print the tool output. Default is " + "stdout") + parser.add_argument("-r", "--report", required=True, type=str, + help="Path to the 'check report', the one checked " + "against the baseline.") + parser.add_argument("-v", "--verbose", action='store_true', + help="Print more informations during the run.") + + args = parser.parse_args() + + if args.out == "stdout": + file_out = sys.stdout + else: + try: + file_out = open(args.out, "wt") + except OSError as e: + print("ERROR: Issue opening file {}: {}".format(args.out, e)) + sys.exit(1) + + debug = Debug(args) + + try: + baseline_path = os.path.realpath(args.baseline) + log_info("Loading baseline report {}".format(baseline_path), "") + baseline = CppcheckReport(baseline_path) + baseline.parse() + debug.debug_print_parsed_report(baseline) + log_info(" [OK]") + new_rep_path = os.path.realpath(args.report) + log_info("Loading check report {}".format(new_rep_path), "") + new_rep = CppcheckReport(new_rep_path) + new_rep.parse() + debug.debug_print_parsed_report(new_rep) + log_info(" [OK]") + except ReportError as e: + print("ERROR: {}".format(e)) + sys.exit(1) + + output = new_rep - baseline + print(output, end="", file=file_out) + + if len(output) > 0: + sys.exit(1) + + sys.exit(0) + + +if __name__ == "__main__": + main(sys.argv[1:]) diff --git a/xen/scripts/xen_analysis/diff_tool/__init__.py b/xen/scripts/xen_analysis/diff_tool/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/xen/scripts/xen_analysis/diff_tool/cppcheck_report.py b/xen/scripts/xen_analysis/diff_tool/cppcheck_report.py new file mode 100644 index 000000000000..787a51aca583 --- /dev/null +++ b/xen/scripts/xen_analysis/diff_tool/cppcheck_report.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python3 + +import re +from .report import Report, ReportError + + +class CppcheckReport(Report): + def __init__(self, report_path: str) -> None: + super().__init__(report_path) + # This matches a string like: + # path/to/file.c(<line number>,<digits>):<whatever> + # and captures file name path and line number + # the last capture group is used for text substitution in __str__ + self.__report_entry_regex = re.compile(r'^(.*)\((\d+)(,\d+\):.*)$') + + def parse(self) -> None: + report_path = self.get_report_path() + try: + with open(report_path, "rt") as infile: + report_lines = infile.readlines() + except OSError as e: + raise ReportError("Issue with reading file {}: {}" + .format(report_path, e)) + for line in report_lines: + entry = self.__report_entry_regex.match(line) + if entry and entry.group(1) and entry.group(2): + file_path = entry.group(1) + line_number = int(entry.group(2)) + self.add_entry(file_path, line_number, line) + else: + raise ReportError("Malformed report entry in file {}:\n{}" + .format(report_path, line)) + + def __str__(self) -> str: + ret = "" + for entry in self.to_list(): + ret += re.sub(self.__report_entry_regex, + r'{}({}\3'.format(entry.file_path, + entry.line_number), + entry.text) + return ret diff --git a/xen/scripts/xen_analysis/diff_tool/debug.py b/xen/scripts/xen_analysis/diff_tool/debug.py new file mode 100644 index 000000000000..d46df3300d21 --- /dev/null +++ b/xen/scripts/xen_analysis/diff_tool/debug.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 + +import os +from .report import Report + + +class Debug: + def __init__(self, args): + self.args = args + + def __get_debug_out_filename(self, path: str, type: str) -> str: + # Take basename + file_name = os.path.basename(path) + # Split in name and extension + file_name = os.path.splitext(file_name) + if self.args.out != "stdout": + out_folder = os.path.dirname(self.args.out) + else: + out_folder = "./" + dbg_report_path = out_folder + file_name[0] + type + file_name[1] + + return dbg_report_path + + def __debug_print_report(self, report: Report, type: str) -> None: + report_name = self.__get_debug_out_filename(report.get_report_path(), + type) + try: + with open(report_name, "wt") as outfile: + print(report, end="", file=outfile) + except OSError as e: + print("ERROR: Issue opening file {}: {}".format(report_name, e)) + + def debug_print_parsed_report(self, report: Report) -> None: + if not self.args.debug: + return + self.__debug_print_report(report, ".parsed") diff --git a/xen/scripts/xen_analysis/diff_tool/report.py b/xen/scripts/xen_analysis/diff_tool/report.py new file mode 100644 index 000000000000..d958d1816eb4 --- /dev/null +++ b/xen/scripts/xen_analysis/diff_tool/report.py @@ -0,0 +1,114 @@ +#!/usr/bin/env python3 + +import os + + +class ReportError(Exception): + pass + + +class Report: + class ReportEntry: + def __init__(self, file_path: str, line_number: int, + entry_text: list, line_id: int) -> None: + if not isinstance(line_number, int) or \ + not isinstance(line_id, int): + raise ReportError("ReportEntry constructor wrong type args") + self.file_path = file_path + self.line_number = line_number + self.text = entry_text + self.line_id = line_id + + def __str__(self) -> str: + ret = '' + header = 'File path:Count\n' + + for path in self.stats: + ret += f'{path}: {len(self.stats[path])}\n' + + if ret == '': + ret += 'No new issues introduced\n' + + ret = header + ret + + return ret + + def __len__(self) -> int: + ret = 0 + + for ln_list in self.stats.values(): + ret += len(ln_list) + + return ret + + def __init__(self, report_path: str) -> None: + self.__entries = {} + self.__path = report_path + self.__last_line_order = 0 + + def parse(self) -> None: + raise ReportError("Please create a specialised class from 'Report'.") + + def get_report_path(self) -> str: + return self.__path + + def get_report_entries(self) -> dict: + return self.__entries + + def add_entry(self, entry_path: str, entry_line_number: int, + entry_text: list) -> None: + entry = Report.ReportEntry(entry_path, entry_line_number, entry_text, + self.__last_line_order) + if entry_path in self.__entries.keys(): + self.__entries[entry_path].append(entry) + else: + self.__entries[entry_path] = [entry] + self.__last_line_order += 1 + + def to_list(self) -> list: + report_list = [] + for _, entries in self.__entries.items(): + for entry in entries: + report_list.append(entry) + + report_list.sort(key=lambda x: x.line_id) + return report_list + + def __str__(self) -> str: + ret = "" + for entry in self.to_list(): + ret += entry.file_path + ":" + entry.line_number + ":" + entry.text + + return ret + + def __len__(self) -> int: + return len(self.to_list()) + + def __sub__(self, report_b: 'Report') -> 'Report': + if self.__class__ != report_b.__class__: + raise ReportError("Diff of different type of report!") + + filename, file_extension = os.path.splitext(self.__path) + diff_report = self.__class__(filename + ".diff" + file_extension) + # Put in the diff report only records of this report that are not + # present in the report_b. + for file_path, entries in self.__entries.items(): + rep_b_entries = report_b.get_report_entries() + if file_path in rep_b_entries.keys(): + # File path exists in report_b, so check what entries of that + # file path doesn't exist in report_b and add them to the diff + rep_b_entries_num = [ + x.line_number for x in rep_b_entries[file_path] + ] + for entry in entries: + if entry.line_number not in rep_b_entries_num: + diff_report.add_entry(file_path, entry.line_number, + entry.text) + else: + # File path doesn't exist in report_b, so add every entry + # of that file path to the diff + for entry in entries: + diff_report.add_entry(file_path, entry.line_number, + entry.text) + + return diff_report
Add a new tool, diff-report.py that can be used to make diff between reports generated by xen-analysis.py tool. Currently this tool supports the Xen cppcheck text report format in its operations. The tool prints every finding that is in the report passed with -r (check report) which is not in the report passed with -b (baseline). Signed-off-by: Luca Fancellu <luca.fancellu@arm.com> --- xen/scripts/diff-report.py | 76 ++++++++++++ .../xen_analysis/diff_tool/__init__.py | 0 .../xen_analysis/diff_tool/cppcheck_report.py | 41 +++++++ xen/scripts/xen_analysis/diff_tool/debug.py | 36 ++++++ xen/scripts/xen_analysis/diff_tool/report.py | 114 ++++++++++++++++++ 5 files changed, 267 insertions(+) create mode 100755 xen/scripts/diff-report.py create mode 100644 xen/scripts/xen_analysis/diff_tool/__init__.py create mode 100644 xen/scripts/xen_analysis/diff_tool/cppcheck_report.py create mode 100644 xen/scripts/xen_analysis/diff_tool/debug.py create mode 100644 xen/scripts/xen_analysis/diff_tool/report.py