1#!/usr/bin/env python3
2
3from __future__ import print_function
4import os
5import sys
6from argparse import ArgumentParser
7from xen_analysis.diff_tool.cppcheck_report import CppcheckReport
8from xen_analysis.diff_tool.debug import Debug
9from xen_analysis.diff_tool.report import ReportError
10from xen_analysis.diff_tool.unified_format_parser import \
11    (UnifiedFormatParser, UnifiedFormatParseError)
12from xen_analysis.settings import repo_dir
13from xen_analysis.utils import invoke_command
14
15
16class DiffReportError(Exception):
17    pass
18
19
20def log_info(text, end='\n'):
21    # type: (str, str) -> None
22    global args
23    global file_out
24
25    if (args.verbose):
26        print(text, end=end, file=file_out)
27
28
29def main(argv):
30    # type: (list) -> None
31    global args
32    global file_out
33
34    parser = ArgumentParser(prog="diff-report.py")
35    parser.add_argument("-b", "--baseline", required=True, type=str,
36                        help="Path to the baseline report.")
37    parser.add_argument("--debug", action='store_true',
38                        help="Produce intermediate reports during operations.")
39    parser.add_argument("-o", "--out", default="stdout", type=str,
40                        help="Where to print the tool output. Default is "
41                             "stdout")
42    parser.add_argument("-r", "--report", required=True, type=str,
43                        help="Path to the 'check report', the one checked "
44                             "against the baseline.")
45    parser.add_argument("-v", "--verbose", action='store_true',
46                        help="Print more informations during the run.")
47    parser.add_argument("--patch", type=str,
48                        help="The patch file containing the changes to the "
49                             "code, from the baseline analysis result to the "
50                             "'check report' analysis result.\n"
51                             "Do not use with --baseline-rev/--report-rev")
52    parser.add_argument("--baseline-rev", type=str,
53                        help="Revision or SHA of the codebase analysed to "
54                             "create the baseline report.\n"
55                             "Use together with --report-rev")
56    parser.add_argument("--report-rev", type=str,
57                        help="Revision or SHA of the codebase analysed to "
58                             "create the 'check report'.\n"
59                             "Use together with --baseline-rev")
60
61    args = parser.parse_args()
62
63    if args.patch and (args.baseline_rev or args.report_rev):
64        print("ERROR: '--patch' argument can't be used with '--baseline-rev'"
65              " or '--report-rev'.")
66        sys.exit(1)
67
68    if bool(args.baseline_rev) != bool(args.report_rev):
69        print("ERROR: '--baseline-rev' must be used together with "
70              "'--report-rev'.")
71        sys.exit(1)
72
73    if args.out == "stdout":
74        file_out = sys.stdout
75    else:
76        try:
77            file_out = open(args.out, "wt")
78        except OSError as e:
79            print("ERROR: Issue opening file {}: {}".format(args.out, e))
80            sys.exit(1)
81
82    debug = Debug(args)
83
84    try:
85        baseline_path = os.path.realpath(args.baseline)
86        log_info("Loading baseline report {}".format(baseline_path), "")
87        baseline = CppcheckReport(baseline_path)
88        baseline.parse()
89        debug.debug_print_parsed_report(baseline)
90        log_info(" [OK]")
91        new_rep_path = os.path.realpath(args.report)
92        log_info("Loading check report {}".format(new_rep_path), "")
93        new_rep = CppcheckReport(new_rep_path)
94        new_rep.parse()
95        debug.debug_print_parsed_report(new_rep)
96        log_info(" [OK]")
97        diff_source = None
98        if args.patch:
99            diff_source = os.path.realpath(args.patch)
100        elif args.baseline_rev:
101            git_diff = invoke_command(
102                "git --git-dir={}/.git diff -C -C {}..{}"
103                .format(repo_dir, args.baseline_rev, args.report_rev),
104                True, DiffReportError, "Error occured invoking:\n{}\n\n{}"
105            )
106            diff_source = git_diff.splitlines(keepends=True)
107        if diff_source:
108            log_info("Parsing changes...", "")
109            diffs = UnifiedFormatParser(diff_source)
110            debug.debug_print_parsed_diff(diffs)
111            log_info(" [OK]")
112    except (DiffReportError, ReportError, UnifiedFormatParseError) as e:
113        print("ERROR: {}".format(e))
114        sys.exit(1)
115
116    if args.patch or args.baseline_rev:
117        log_info("Patching baseline...", "")
118        baseline_patched = baseline.patch(diffs)
119        debug.debug_print_patched_report(baseline_patched)
120        log_info(" [OK]")
121        output = new_rep - baseline_patched
122    else:
123        output = new_rep - baseline
124
125    print(output, end="", file=file_out)
126
127    if len(output) > 0:
128        sys.exit(1)
129
130    sys.exit(0)
131
132
133if __name__ == "__main__":
134    main(sys.argv[1:])
135