new file mode 100755
@@ -0,0 +1,80 @@
+#!/usr/bin/env python3
+
+from __future__ import print_function
+import os
+import sys
+from argparse import ArgumentParser
+from xen_analysis.diff_tool.cppcheck_report import CppcheckReport
+from xen_analysis.diff_tool.debug import Debug
+from xen_analysis.diff_tool.report import ReportError
+
+
+def log_info(text, end='\n'):
+ # type: (str, str) -> None
+ global args
+ global file_out
+
+ if (args.verbose):
+ print(text, end=end, file=file_out)
+
+
+def main(argv):
+ # type: (list) -> None
+ 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:])
new file mode 100644
new file mode 100644
@@ -0,0 +1,44 @@
+#!/usr/bin/env python3
+
+import re
+from .report import Report, ReportError
+
+
+class CppcheckReport(Report):
+ def __init__(self, report_path):
+ # type: (str) -> None
+ super(CppcheckReport, self).__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):
+ # type: () -> 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):
+ # type: () -> 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
new file mode 100644
@@ -0,0 +1,40 @@
+#!/usr/bin/env python3
+
+from __future__ import print_function
+import os
+from .report import Report
+
+
+class Debug:
+ def __init__(self, args):
+ self.args = args
+
+ def __get_debug_out_filename(self, path, type):
+ # type: (str, 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, type):
+ # type: (Report, 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):
+ # type: (Report) -> None
+ if not self.args.debug:
+ return
+ self.__debug_print_report(report, ".parsed")
new file mode 100644
@@ -0,0 +1,100 @@
+#!/usr/bin/env python3
+
+import os
+
+
+class ReportError(Exception):
+ pass
+
+
+class Report(object):
+ class ReportEntry:
+ def __init__(self, file_path, line_number, entry_text, line_id):
+ # type: (str, int, list, 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 __init__(self, report_path):
+ # type: (str) -> None
+ self.__entries = {}
+ self.__path = report_path
+ self.__last_line_order = 0
+
+ def parse(self):
+ # type: () -> None
+ raise ReportError("Please create a specialised class from 'Report'.")
+
+ def get_report_path(self):
+ # type: () -> str
+ return self.__path
+
+ def get_report_entries(self):
+ # type: () -> dict
+ return self.__entries
+
+ def add_entry(self, entry_path, entry_line_number, entry_text):
+ # type: (str, int, str) -> 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):
+ # type: () -> 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):
+ # type: () -> str
+ ret = ""
+ for entry in self.to_list():
+ ret += entry.file_path + ":" + entry.line_number + ":" + entry.text
+
+ return ret
+
+ def __len__(self):
+ # type: () -> int
+ return len(self.to_list())
+
+ def __sub__(self, report_b):
+ # type: (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