Merge ~jslarraz/review-tools:move-report into review-tools:master

Proposed by Jorge Sancho Larraz
Status: Merged
Merged at revision: 140737139019d0bd725a3a0d3847221f52c1a884
Proposed branch: ~jslarraz/review-tools:move-report
Merge into: review-tools:master
Diff against target: 540 lines (+196/-203)
5 files modified
bin/snap-review (+3/-104)
bin/snap-verify-declaration (+9/-2)
reviewtools/common.py (+41/-95)
reviewtools/report.py (+141/-0)
tests/test.sh.expected (+2/-2)
Reviewer Review Type Date Requested Status
Alex Murray Approve
Review via email: mp+466806@code.launchpad.net

Commit message

many: move Report class away from bin/snap-review, and use it also on individual checks

To post a comment you must log in.
Revision history for this message
Alex Murray (alexmurray) wrote :

LGTM - yay removal of more global variables!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/bin/snap-review b/bin/snap-review
index 2c26158..777f418 100755
--- a/bin/snap-review
+++ b/bin/snap-review
@@ -19,107 +19,7 @@ from reviewtools.common import (
19)19)
2020
21from reviewtools.sr_declaration import validate_json21from reviewtools.sr_declaration import validate_json
2222from reviewtools.report import Report
23
24class Report(object):
25 results = {}
26 errors = {}
27 warnings = {}
28 info = {}
29
30 def __init__(self, filename, verbose: bool = False):
31 self.filename = filename
32 self.verbose = verbose
33
34 # review_report[<section>][<result_type>][<review_name>] = <result>
35 # section: section of the report (one per review module)
36 # result_type: info, warn, error
37 # review_name: name of the check (prefixed with self.review_type)
38 # result: contents of the review
39 # link: url for more information
40 # manual_review: force manual review
41 # override_result_type: prefix results with [<result_type>] and set
42 # result_type to override_result_type
43 def add_result(
44 self,
45 section,
46 result_type,
47 review_name,
48 result,
49 link=None,
50 manual_review=False,
51 ):
52 """Add result to report"""
53 if section not in self.results:
54 self.results[section] = {
55 "info": {},
56 "warn": {},
57 "error": {},
58 }
59
60 if result_type not in self.results[section]:
61 raise Exception("Invalid result type '%s'" % result_type)
62
63 if review_name not in self.results[section][result_type]:
64 self.results[section][result_type][review_name] = dict()
65
66 self.results[section][result_type][review_name].update(
67 {"text": "%s" % result, "manual_review": manual_review}
68 )
69 if link is not None:
70 self.results[section][result_type][review_name]["link"] = link
71
72 def _summarise_results(self):
73 for module in self.results:
74 for key in self.results[module]["error"]:
75 self.errors[key] = self.results[module]["error"][key]
76 for key in self.results[module]["warn"]:
77 self.warnings[key] = self.results[module]["warn"][key]
78 if self.verbose:
79 for key in self.results[module]["info"]:
80 self.info[key] = self.results[module]["info"][key]
81
82 def print_findings(self, results, description):
83 """
84 Print a summary of the issues found.
85 """
86 if not description or not results:
87 return ""
88 print(description)
89 print("".center(len(description), "-"))
90 for key in sorted(results.keys()):
91 print(" - %s" % key)
92 print("\t%s" % results[key]["text"])
93 if "link" in results[key]:
94 print("\t%s" % results[key]["link"])
95
96 def show_results(self, report_type="console"):
97 self._summarise_results()
98
99 if report_type == "json":
100 print(
101 json.dumps(
102 self.results, sort_keys=True, indent=2, separators=(",", ": ")
103 )
104 )
105 elif report_type == "sdk":
106 for section in sorted(self.results.keys()):
107 output = self.results[section]
108 print("= %s =" % section)
109 print(
110 json.dumps(output, sort_keys=True, indent=2, separators=(",", ": "))
111 )
112 else:
113 self.print_findings(self.errors, "Errors")
114 self.print_findings(self.warnings, "Warnings")
115 if self.verbose:
116 self.print_findings(self.info, "Info")
117 if "runtime-errors" in self.results:
118 print("%s: RUNTIME ERROR" % self.filename)
119 elif self.warnings or self.errors:
120 print("%s: FAIL" % self.filename)
121 else:
122 print("%s: pass" % self.filename)
12323
12424
125def main():25def main():
@@ -244,13 +144,12 @@ def main():
244 rc = 0144 rc = 0
245 report = Report(args.filename, args.verbose)145 report = Report(args.filename, args.verbose)
246 for module in modules:146 for module in modules:
247 section = module.replace("sr_", "snap.v2_")
248 try:147 try:
249 review_class = modules_mod.find_main_class(module)148 review_class = modules_mod.find_main_class(module)
250 review = review_class(args.filename, overrides=overrides)149 review = review_class(args.filename, overrides=overrides)
251150
252 review.do_checks()151 review_report = review.do_checks()
253 report.results[section] = review.review_report152 report.update(review_report)
254 except Exception:153 except Exception:
255 print("Caught exception (setting rc=1 and continuing):")154 print("Caught exception (setting rc=1 and continuing):")
256 traceback.print_exc(file=sys.stdout)155 traceback.print_exc(file=sys.stdout)
diff --git a/bin/snap-verify-declaration b/bin/snap-verify-declaration
index 152c6c7..7d7ee40 100755
--- a/bin/snap-verify-declaration
+++ b/bin/snap-verify-declaration
@@ -81,9 +81,16 @@ def main():
81 "verify_snap_declaration() raised exception for snap decl: %s" % e,81 "verify_snap_declaration() raised exception for snap decl: %s" % e,
82 output_type=error_output_type,82 output_type=error_output_type,
83 )83 )
84 review.set_report_type(error_output_type)
8584
86 return review.do_report()85 report = review.review_report
86 report.show_summary(error_output_type)
87
88 rc = 0
89 if report.errors:
90 rc = 2
91 elif report.warnings:
92 rc = 1
93 return rc
8794
8895
89if __name__ == "__main__":96if __name__ == "__main__":
diff --git a/reviewtools/common.py b/reviewtools/common.py
index d2c15b8..d85ecc0 100644
--- a/reviewtools/common.py
+++ b/reviewtools/common.py
@@ -35,6 +35,7 @@ import types
35import yaml35import yaml
3636
37from reviewtools.overrides import common_external_symlink_override37from reviewtools.overrides import common_external_symlink_override
38from reviewtools.report import Report
3839
39REPORT_OUTPUT = "json"40REPORT_OUTPUT = "json"
40RESULT_TYPES = ["info", "warn", "error"]41RESULT_TYPES = ["info", "warn", "error"]
@@ -156,19 +157,19 @@ class ReviewBase(object):
156157
157 def __init__(self, review_type, overrides=None):158 def __init__(self, review_type, overrides=None):
158 self.review_type = review_type159 self.review_type = review_type
159 # TODO: rename as pkg_report
160 self.review_report = dict()
161
162 global RESULT_TYPES
163 for r in RESULT_TYPES:
164 self.review_report[r] = dict()
165
166 self.overrides = overrides if overrides is not None else {}160 self.overrides = overrides if overrides is not None else {}
161 self.review_report = Report()
162
163 # TODO: this hack is used to generate tests/test.sh output. We may want to revisit it.
164 self.section = "snap.v2_" + self.review_type.split("-")[0]
167165
168 def set_report_type(self, t):166 # TODO: initialization here is only needed to ensure consistent output with tests/test.sh.expected
169 global REPORT_OUTPUT167 # It can be removed if it is ok to not include a section in the report if that section is empty
170 if t is not None and t in ["console", "json"]:168 self.review_report.results[self.section] = {
171 REPORT_OUTPUT = t169 "error": {},
170 "warn": {},
171 "info": {},
172 }
172173
173 def _get_check_name(self, name, app="", extra=""):174 def _get_check_name(self, name, app="", extra=""):
174 name = ":".join([self.review_type, name])175 name = ":".join([self.review_type, name])
@@ -195,13 +196,11 @@ class ReviewBase(object):
195 manual_review=False,196 manual_review=False,
196 ):197 ):
197 """Add result to report"""198 """Add result to report"""
198 global RESULT_TYPES199 self.review_report.add_result(
199 report = self.review_report200 self.section, result_type, review_name, result, link, manual_review
200201 )
201 if result_type not in RESULT_TYPES:
202 error("Invalid result type '%s'" % result_type)
203202
204 if review_name not in report[result_type]:203 if review_name not in self.review_report.results[self.section][result_type]:
205 # log info about check so it can be collected into the204 # log info about check so it can be collected into the
206 # check-names.list file205 # check-names.list file
207 # format should be206 # format should be
@@ -210,32 +209,6 @@ class ReviewBase(object):
210 name = ":".join(review_name.split(":")[:2])209 name = ":".join(review_name.split(":")[:2])
211 link_text = link if link is not None else ""210 link_text = link if link is not None else ""
212 logging.debug(msg.format(name, link_text))211 logging.debug(msg.format(name, link_text))
213 report[result_type][review_name] = dict()
214
215 report[result_type][review_name].update(
216 {"text": "%s" % result, "manual_review": manual_review}
217 )
218 if link is not None:
219 report[result_type][review_name]["link"] = link
220
221 # Only called by ./bin/* individually, not 'snap-review'
222 def do_report(self):
223 """Print report"""
224 global REPORT_OUTPUT
225
226 if REPORT_OUTPUT == "json":
227 jsonmsg(self.review_report)
228 else:
229 import pprint
230
231 pprint.pprint(self.review_report)
232
233 rc = 0
234 if len(self.review_report["error"]):
235 rc = 2
236 elif len(self.review_report["warn"]):
237 rc = 1
238 return rc
239212
240 def do_checks(self):213 def do_checks(self):
241 """Run all methods that start with check_"""214 """Run all methods that start with check_"""
@@ -250,9 +223,7 @@ class ReviewBase(object):
250 func = getattr(self, methodname)223 func = getattr(self, methodname)
251 func()224 func()
252225
253 def set_review_type(self, name):226 return self.review_report
254 """Set review name"""
255 self.review_type = name
256227
257228
258#229#
@@ -263,42 +234,15 @@ class ReviewBase(object):
263def error(out, exit_code=1, do_exit=True, output_type=None):234def error(out, exit_code=1, do_exit=True, output_type=None):
264 """Print error message and exit"""235 """Print error message and exit"""
265 global REPORT_OUTPUT236 global REPORT_OUTPUT
266 global RESULT_TYPES237 if output_type is None:
267238 output_type = REPORT_OUTPUT
268 if output_type is not None:
269 ReviewBase.set_report_type(None, output_type)
270239
271 try:240 if output_type == "json":
272 if REPORT_OUTPUT == "json":241 report = Report()
273 # mock up expected json format:242 report.add_result("runtime-errors", "error", "msg", out, manual_review=True)
274 # {243 report.show_results(output_type)
275 # "test-family": {244 else:
276 # "error": {245 print("ERROR: %s" % (out), file=sys.stderr)
277 # "test-name": {
278 # "manual_review": ...,
279 # "text": ...
280 # }
281 # },
282 # "info": {},
283 # "warn": {}
284 # }
285 # }
286 family = "runtime-errors"
287 name = "msg"
288
289 report = dict()
290 report[family] = dict()
291 for r in RESULT_TYPES:
292 report[family][r] = dict()
293 report[family]["error"][name] = dict()
294 report[family]["error"][name]["text"] = out
295 report[family]["error"][name]["manual_review"] = True
296
297 jsonmsg(report)
298 else:
299 print("ERROR: %s" % (out), file=sys.stderr)
300 except IOError:
301 pass
302246
303 if do_exit:247 if do_exit:
304 sys.exit(exit_code)248 sys.exit(exit_code)
@@ -329,11 +273,6 @@ def debug(out):
329 pass273 pass
330274
331275
332def jsonmsg(out):
333 """Format out as json"""
334 msg(json.dumps(out, sort_keys=True, indent=2, separators=(",", ": ")))
335
336
337def cmd(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT):276def cmd(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT):
338 """Try to execute the given command."""277 """Try to execute the given command."""
339 debug(" ".join(command))278 debug(" ".join(command))
@@ -782,8 +721,15 @@ def run_check(cls):
782 overrides = None721 overrides = None
783722
784 review = cls(fn, overrides=overrides)723 review = cls(fn, overrides=overrides)
785 review.do_checks()724 report = review.do_checks()
786 rc = review.do_report()725 report.show_summary()
726
727 rc = 0
728 if report.errors:
729 rc = 2
730 elif report.warnings:
731 rc = 1
732
787 sys.exit(rc)733 sys.exit(rc)
788734
789735
@@ -950,25 +896,25 @@ def check_results(
950 for t in expected.keys():896 for t in expected.keys():
951 for r in expected[t]:897 for r in expected[t]:
952 testobj.assertTrue(898 testobj.assertTrue(
953 r in report[t],899 r in report.summary[t],
954 "Could not find '%s' (%s) in:\n%s"900 "Could not find '%s' (%s) in:\n%s"
955 % (r, t, json.dumps(report, indent=2)),901 % (r, t, json.dumps(report.summary, indent=2)),
956 )902 )
957 for k in expected[t][r]:903 for k in expected[t][r]:
958 testobj.assertTrue(904 testobj.assertTrue(
959 k in report[t][r],905 k in report.summary[t][r],
960 "Could not find '%s' (%s) in:\n%s"906 "Could not find '%s' (%s) in:\n%s"
961 % (k, r, json.dumps(report, indent=2)),907 % (k, r, json.dumps(report.summary, indent=2)),
962 )908 )
963 testobj.assertEqual(expected[t][r][k], report[t][r][k])909 testobj.assertEqual(expected[t][r][k], report.summary[t][r][k])
964 else:910 else:
965 for k in expected_counts.keys():911 for k in expected_counts.keys():
966 if expected_counts[k] is None:912 if expected_counts[k] is None:
967 continue913 continue
968 testobj.assertEqual(914 testobj.assertEqual(
969 len(report[k]),915 len(report.summary[k]),
970 expected_counts[k],916 expected_counts[k],
971 "(%s not equal)\n%s" % (k, json.dumps(report, indent=2)),917 "(%s not equal)\n%s" % (k, json.dumps(report.summary, indent=2)),
972 )918 )
973919
974920
diff --git a/reviewtools/report.py b/reviewtools/report.py
975new file mode 100644921new file mode 100644
index 0000000..0364f7c
--- /dev/null
+++ b/reviewtools/report.py
@@ -0,0 +1,141 @@
1import json
2import pprint
3
4
5class Report(object):
6 def __init__(self, snap_name=None, verbose: bool = False):
7 self.snap_name = snap_name if snap_name is not None else ""
8 self.verbose = verbose
9
10 self.results = {}
11 self._summary = {
12 "info": {},
13 "warn": {},
14 "error": {},
15 }
16
17 @property
18 def summary(self) -> dict:
19 self._summarise_results()
20 return self._summary
21
22 @property
23 def errors(self) -> dict:
24 return self.summary["error"]
25
26 @property
27 def warnings(self) -> dict:
28 return self.summary["warn"]
29
30 @property
31 def info(self) -> dict:
32 return self.summary["info"]
33
34 def __iter__(self):
35 # Required for compatibility with test_sr_*
36 return self.summary.__iter__()
37
38 def __getitem__(self, item):
39 # Required for compatibility with test_sr_*
40 return self.summary[item]
41
42 # review_report[<section>][<result_type>][<review_name>] = <result>
43 # section: section of the report (one per review module)
44 # result_type: info, warn, error
45 # review_name: name of the check (prefixed with self.review_type)
46 # result: contents of the review
47 # link: url for more information
48 # manual_review: force manual review
49 # override_result_type: prefix results with [<result_type>] and set
50 # result_type to override_result_type
51 def add_result(
52 self,
53 section,
54 result_type,
55 review_name,
56 result,
57 link=None,
58 manual_review=False,
59 ):
60 """Add result to report"""
61 if section not in self.results:
62 self.results[section] = {
63 "info": {},
64 "warn": {},
65 "error": {},
66 }
67
68 if result_type not in self.results[section]:
69 raise Exception("Invalid result type '%s'" % result_type)
70
71 if review_name not in self.results[section][result_type]:
72 self.results[section][result_type][review_name] = dict()
73
74 self.results[section][result_type][review_name].update(
75 {"text": "%s" % result, "manual_review": manual_review}
76 )
77 if link is not None:
78 self.results[section][result_type][review_name]["link"] = link
79
80 def update(self, report):
81 self.results.update(report.results)
82
83 def _summarise_results(self):
84 for module in self.results:
85 for key in self.results[module]["error"]:
86 self._summary["error"][key] = self.results[module]["error"][key]
87 for key in self.results[module]["warn"]:
88 self._summary["warn"][key] = self.results[module]["warn"][key]
89 for key in self.results[module]["info"]:
90 self._summary["info"][key] = self.results[module]["info"][key]
91
92 def print_findings(self, results, description):
93 """
94 Print a summary of the issues found.
95 """
96 if not description or not results:
97 return ""
98 print(description)
99 print("".center(len(description), "-"))
100 for key in sorted(results.keys()):
101 print(" - %s" % key)
102 print("\t%s" % results[key]["text"])
103 if "link" in results[key]:
104 print("\t%s" % results[key]["link"])
105
106 def show_results(self, report_type="console"):
107 if report_type == "json":
108 print(
109 json.dumps(
110 self.results, sort_keys=True, indent=2, separators=(",", ": ")
111 )
112 )
113 elif report_type == "sdk":
114 for section in sorted(self.results.keys()):
115 output = self.results[section]
116 print("= %s =" % section)
117 print(
118 json.dumps(output, sort_keys=True, indent=2, separators=(",", ": "))
119 )
120 else:
121 self.print_findings(self.summary["error"], "Errors")
122 self.print_findings(self.summary["warn"], "Warnings")
123 if self.verbose:
124 self.print_findings(self.summary["info"], "Info")
125 if "runtime-errors" in self.results:
126 print("%s: RUNTIME ERROR" % self.snap_name)
127 elif self.summary["warn"] or self.summary["error"]:
128 print("%s: FAIL" % self.snap_name)
129 else:
130 print("%s: pass" % self.snap_name)
131
132 def show_summary(self, report_type="json"):
133 if report_type == "json":
134 print(
135 json.dumps(
136 self.summary, sort_keys=True, indent=2, separators=(",", ": ")
137 )
138 )
139
140 else:
141 pprint.pprint(self.summary)
diff --git a/tests/test.sh.expected b/tests/test.sh.expected
index 1c726c3..24fbaef 100644
--- a/tests/test.sh.expected
+++ b/tests/test.sh.expected
@@ -119215,7 +119215,7 @@ hello-world_25.snap: FAIL
119215 "info": {},119215 "info": {},
119216 "warn": {}119216 "warn": {}
119217}119217}
119218= snap.v2_extra_tests =119218= snap.v2_extras =
119219{119219{
119220 "error": {119220 "error": {
119221 "extras-tests-v2:error": {119221 "extras-tests-v2:error": {
@@ -119501,7 +119501,7 @@ hello-world_25.snap: FAIL
119501 "info": {},119501 "info": {},
119502 "warn": {}119502 "warn": {}
119503 },119503 },
119504 "snap.v2_extra_tests": {119504 "snap.v2_extras": {
119505 "error": {119505 "error": {
119506 "extras-tests-v2:error": {119506 "extras-tests-v2:error": {
119507 "manual_review": false,119507 "manual_review": false,

Subscribers

People subscribed via source and target branches