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
1diff --git a/bin/snap-review b/bin/snap-review
2index 2c26158..777f418 100755
3--- a/bin/snap-review
4+++ b/bin/snap-review
5@@ -19,107 +19,7 @@ from reviewtools.common import (
6 )
7
8 from reviewtools.sr_declaration import validate_json
9-
10-
11-class Report(object):
12- results = {}
13- errors = {}
14- warnings = {}
15- info = {}
16-
17- def __init__(self, filename, verbose: bool = False):
18- self.filename = filename
19- self.verbose = verbose
20-
21- # review_report[<section>][<result_type>][<review_name>] = <result>
22- # section: section of the report (one per review module)
23- # result_type: info, warn, error
24- # review_name: name of the check (prefixed with self.review_type)
25- # result: contents of the review
26- # link: url for more information
27- # manual_review: force manual review
28- # override_result_type: prefix results with [<result_type>] and set
29- # result_type to override_result_type
30- def add_result(
31- self,
32- section,
33- result_type,
34- review_name,
35- result,
36- link=None,
37- manual_review=False,
38- ):
39- """Add result to report"""
40- if section not in self.results:
41- self.results[section] = {
42- "info": {},
43- "warn": {},
44- "error": {},
45- }
46-
47- if result_type not in self.results[section]:
48- raise Exception("Invalid result type '%s'" % result_type)
49-
50- if review_name not in self.results[section][result_type]:
51- self.results[section][result_type][review_name] = dict()
52-
53- self.results[section][result_type][review_name].update(
54- {"text": "%s" % result, "manual_review": manual_review}
55- )
56- if link is not None:
57- self.results[section][result_type][review_name]["link"] = link
58-
59- def _summarise_results(self):
60- for module in self.results:
61- for key in self.results[module]["error"]:
62- self.errors[key] = self.results[module]["error"][key]
63- for key in self.results[module]["warn"]:
64- self.warnings[key] = self.results[module]["warn"][key]
65- if self.verbose:
66- for key in self.results[module]["info"]:
67- self.info[key] = self.results[module]["info"][key]
68-
69- def print_findings(self, results, description):
70- """
71- Print a summary of the issues found.
72- """
73- if not description or not results:
74- return ""
75- print(description)
76- print("".center(len(description), "-"))
77- for key in sorted(results.keys()):
78- print(" - %s" % key)
79- print("\t%s" % results[key]["text"])
80- if "link" in results[key]:
81- print("\t%s" % results[key]["link"])
82-
83- def show_results(self, report_type="console"):
84- self._summarise_results()
85-
86- if report_type == "json":
87- print(
88- json.dumps(
89- self.results, sort_keys=True, indent=2, separators=(",", ": ")
90- )
91- )
92- elif report_type == "sdk":
93- for section in sorted(self.results.keys()):
94- output = self.results[section]
95- print("= %s =" % section)
96- print(
97- json.dumps(output, sort_keys=True, indent=2, separators=(",", ": "))
98- )
99- else:
100- self.print_findings(self.errors, "Errors")
101- self.print_findings(self.warnings, "Warnings")
102- if self.verbose:
103- self.print_findings(self.info, "Info")
104- if "runtime-errors" in self.results:
105- print("%s: RUNTIME ERROR" % self.filename)
106- elif self.warnings or self.errors:
107- print("%s: FAIL" % self.filename)
108- else:
109- print("%s: pass" % self.filename)
110+from reviewtools.report import Report
111
112
113 def main():
114@@ -244,13 +144,12 @@ def main():
115 rc = 0
116 report = Report(args.filename, args.verbose)
117 for module in modules:
118- section = module.replace("sr_", "snap.v2_")
119 try:
120 review_class = modules_mod.find_main_class(module)
121 review = review_class(args.filename, overrides=overrides)
122
123- review.do_checks()
124- report.results[section] = review.review_report
125+ review_report = review.do_checks()
126+ report.update(review_report)
127 except Exception:
128 print("Caught exception (setting rc=1 and continuing):")
129 traceback.print_exc(file=sys.stdout)
130diff --git a/bin/snap-verify-declaration b/bin/snap-verify-declaration
131index 152c6c7..7d7ee40 100755
132--- a/bin/snap-verify-declaration
133+++ b/bin/snap-verify-declaration
134@@ -81,9 +81,16 @@ def main():
135 "verify_snap_declaration() raised exception for snap decl: %s" % e,
136 output_type=error_output_type,
137 )
138- review.set_report_type(error_output_type)
139
140- return review.do_report()
141+ report = review.review_report
142+ report.show_summary(error_output_type)
143+
144+ rc = 0
145+ if report.errors:
146+ rc = 2
147+ elif report.warnings:
148+ rc = 1
149+ return rc
150
151
152 if __name__ == "__main__":
153diff --git a/reviewtools/common.py b/reviewtools/common.py
154index d2c15b8..d85ecc0 100644
155--- a/reviewtools/common.py
156+++ b/reviewtools/common.py
157@@ -35,6 +35,7 @@ import types
158 import yaml
159
160 from reviewtools.overrides import common_external_symlink_override
161+from reviewtools.report import Report
162
163 REPORT_OUTPUT = "json"
164 RESULT_TYPES = ["info", "warn", "error"]
165@@ -156,19 +157,19 @@ class ReviewBase(object):
166
167 def __init__(self, review_type, overrides=None):
168 self.review_type = review_type
169- # TODO: rename as pkg_report
170- self.review_report = dict()
171-
172- global RESULT_TYPES
173- for r in RESULT_TYPES:
174- self.review_report[r] = dict()
175-
176 self.overrides = overrides if overrides is not None else {}
177+ self.review_report = Report()
178+
179+ # TODO: this hack is used to generate tests/test.sh output. We may want to revisit it.
180+ self.section = "snap.v2_" + self.review_type.split("-")[0]
181
182- def set_report_type(self, t):
183- global REPORT_OUTPUT
184- if t is not None and t in ["console", "json"]:
185- REPORT_OUTPUT = t
186+ # TODO: initialization here is only needed to ensure consistent output with tests/test.sh.expected
187+ # It can be removed if it is ok to not include a section in the report if that section is empty
188+ self.review_report.results[self.section] = {
189+ "error": {},
190+ "warn": {},
191+ "info": {},
192+ }
193
194 def _get_check_name(self, name, app="", extra=""):
195 name = ":".join([self.review_type, name])
196@@ -195,13 +196,11 @@ class ReviewBase(object):
197 manual_review=False,
198 ):
199 """Add result to report"""
200- global RESULT_TYPES
201- report = self.review_report
202-
203- if result_type not in RESULT_TYPES:
204- error("Invalid result type '%s'" % result_type)
205+ self.review_report.add_result(
206+ self.section, result_type, review_name, result, link, manual_review
207+ )
208
209- if review_name not in report[result_type]:
210+ if review_name not in self.review_report.results[self.section][result_type]:
211 # log info about check so it can be collected into the
212 # check-names.list file
213 # format should be
214@@ -210,32 +209,6 @@ class ReviewBase(object):
215 name = ":".join(review_name.split(":")[:2])
216 link_text = link if link is not None else ""
217 logging.debug(msg.format(name, link_text))
218- report[result_type][review_name] = dict()
219-
220- report[result_type][review_name].update(
221- {"text": "%s" % result, "manual_review": manual_review}
222- )
223- if link is not None:
224- report[result_type][review_name]["link"] = link
225-
226- # Only called by ./bin/* individually, not 'snap-review'
227- def do_report(self):
228- """Print report"""
229- global REPORT_OUTPUT
230-
231- if REPORT_OUTPUT == "json":
232- jsonmsg(self.review_report)
233- else:
234- import pprint
235-
236- pprint.pprint(self.review_report)
237-
238- rc = 0
239- if len(self.review_report["error"]):
240- rc = 2
241- elif len(self.review_report["warn"]):
242- rc = 1
243- return rc
244
245 def do_checks(self):
246 """Run all methods that start with check_"""
247@@ -250,9 +223,7 @@ class ReviewBase(object):
248 func = getattr(self, methodname)
249 func()
250
251- def set_review_type(self, name):
252- """Set review name"""
253- self.review_type = name
254+ return self.review_report
255
256
257 #
258@@ -263,42 +234,15 @@ class ReviewBase(object):
259 def error(out, exit_code=1, do_exit=True, output_type=None):
260 """Print error message and exit"""
261 global REPORT_OUTPUT
262- global RESULT_TYPES
263-
264- if output_type is not None:
265- ReviewBase.set_report_type(None, output_type)
266+ if output_type is None:
267+ output_type = REPORT_OUTPUT
268
269- try:
270- if REPORT_OUTPUT == "json":
271- # mock up expected json format:
272- # {
273- # "test-family": {
274- # "error": {
275- # "test-name": {
276- # "manual_review": ...,
277- # "text": ...
278- # }
279- # },
280- # "info": {},
281- # "warn": {}
282- # }
283- # }
284- family = "runtime-errors"
285- name = "msg"
286-
287- report = dict()
288- report[family] = dict()
289- for r in RESULT_TYPES:
290- report[family][r] = dict()
291- report[family]["error"][name] = dict()
292- report[family]["error"][name]["text"] = out
293- report[family]["error"][name]["manual_review"] = True
294-
295- jsonmsg(report)
296- else:
297- print("ERROR: %s" % (out), file=sys.stderr)
298- except IOError:
299- pass
300+ if output_type == "json":
301+ report = Report()
302+ report.add_result("runtime-errors", "error", "msg", out, manual_review=True)
303+ report.show_results(output_type)
304+ else:
305+ print("ERROR: %s" % (out), file=sys.stderr)
306
307 if do_exit:
308 sys.exit(exit_code)
309@@ -329,11 +273,6 @@ def debug(out):
310 pass
311
312
313-def jsonmsg(out):
314- """Format out as json"""
315- msg(json.dumps(out, sort_keys=True, indent=2, separators=(",", ": ")))
316-
317-
318 def cmd(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT):
319 """Try to execute the given command."""
320 debug(" ".join(command))
321@@ -782,8 +721,15 @@ def run_check(cls):
322 overrides = None
323
324 review = cls(fn, overrides=overrides)
325- review.do_checks()
326- rc = review.do_report()
327+ report = review.do_checks()
328+ report.show_summary()
329+
330+ rc = 0
331+ if report.errors:
332+ rc = 2
333+ elif report.warnings:
334+ rc = 1
335+
336 sys.exit(rc)
337
338
339@@ -950,25 +896,25 @@ def check_results(
340 for t in expected.keys():
341 for r in expected[t]:
342 testobj.assertTrue(
343- r in report[t],
344+ r in report.summary[t],
345 "Could not find '%s' (%s) in:\n%s"
346- % (r, t, json.dumps(report, indent=2)),
347+ % (r, t, json.dumps(report.summary, indent=2)),
348 )
349 for k in expected[t][r]:
350 testobj.assertTrue(
351- k in report[t][r],
352+ k in report.summary[t][r],
353 "Could not find '%s' (%s) in:\n%s"
354- % (k, r, json.dumps(report, indent=2)),
355+ % (k, r, json.dumps(report.summary, indent=2)),
356 )
357- testobj.assertEqual(expected[t][r][k], report[t][r][k])
358+ testobj.assertEqual(expected[t][r][k], report.summary[t][r][k])
359 else:
360 for k in expected_counts.keys():
361 if expected_counts[k] is None:
362 continue
363 testobj.assertEqual(
364- len(report[k]),
365+ len(report.summary[k]),
366 expected_counts[k],
367- "(%s not equal)\n%s" % (k, json.dumps(report, indent=2)),
368+ "(%s not equal)\n%s" % (k, json.dumps(report.summary, indent=2)),
369 )
370
371
372diff --git a/reviewtools/report.py b/reviewtools/report.py
373new file mode 100644
374index 0000000..0364f7c
375--- /dev/null
376+++ b/reviewtools/report.py
377@@ -0,0 +1,141 @@
378+import json
379+import pprint
380+
381+
382+class Report(object):
383+ def __init__(self, snap_name=None, verbose: bool = False):
384+ self.snap_name = snap_name if snap_name is not None else ""
385+ self.verbose = verbose
386+
387+ self.results = {}
388+ self._summary = {
389+ "info": {},
390+ "warn": {},
391+ "error": {},
392+ }
393+
394+ @property
395+ def summary(self) -> dict:
396+ self._summarise_results()
397+ return self._summary
398+
399+ @property
400+ def errors(self) -> dict:
401+ return self.summary["error"]
402+
403+ @property
404+ def warnings(self) -> dict:
405+ return self.summary["warn"]
406+
407+ @property
408+ def info(self) -> dict:
409+ return self.summary["info"]
410+
411+ def __iter__(self):
412+ # Required for compatibility with test_sr_*
413+ return self.summary.__iter__()
414+
415+ def __getitem__(self, item):
416+ # Required for compatibility with test_sr_*
417+ return self.summary[item]
418+
419+ # review_report[<section>][<result_type>][<review_name>] = <result>
420+ # section: section of the report (one per review module)
421+ # result_type: info, warn, error
422+ # review_name: name of the check (prefixed with self.review_type)
423+ # result: contents of the review
424+ # link: url for more information
425+ # manual_review: force manual review
426+ # override_result_type: prefix results with [<result_type>] and set
427+ # result_type to override_result_type
428+ def add_result(
429+ self,
430+ section,
431+ result_type,
432+ review_name,
433+ result,
434+ link=None,
435+ manual_review=False,
436+ ):
437+ """Add result to report"""
438+ if section not in self.results:
439+ self.results[section] = {
440+ "info": {},
441+ "warn": {},
442+ "error": {},
443+ }
444+
445+ if result_type not in self.results[section]:
446+ raise Exception("Invalid result type '%s'" % result_type)
447+
448+ if review_name not in self.results[section][result_type]:
449+ self.results[section][result_type][review_name] = dict()
450+
451+ self.results[section][result_type][review_name].update(
452+ {"text": "%s" % result, "manual_review": manual_review}
453+ )
454+ if link is not None:
455+ self.results[section][result_type][review_name]["link"] = link
456+
457+ def update(self, report):
458+ self.results.update(report.results)
459+
460+ def _summarise_results(self):
461+ for module in self.results:
462+ for key in self.results[module]["error"]:
463+ self._summary["error"][key] = self.results[module]["error"][key]
464+ for key in self.results[module]["warn"]:
465+ self._summary["warn"][key] = self.results[module]["warn"][key]
466+ for key in self.results[module]["info"]:
467+ self._summary["info"][key] = self.results[module]["info"][key]
468+
469+ def print_findings(self, results, description):
470+ """
471+ Print a summary of the issues found.
472+ """
473+ if not description or not results:
474+ return ""
475+ print(description)
476+ print("".center(len(description), "-"))
477+ for key in sorted(results.keys()):
478+ print(" - %s" % key)
479+ print("\t%s" % results[key]["text"])
480+ if "link" in results[key]:
481+ print("\t%s" % results[key]["link"])
482+
483+ def show_results(self, report_type="console"):
484+ if report_type == "json":
485+ print(
486+ json.dumps(
487+ self.results, sort_keys=True, indent=2, separators=(",", ": ")
488+ )
489+ )
490+ elif report_type == "sdk":
491+ for section in sorted(self.results.keys()):
492+ output = self.results[section]
493+ print("= %s =" % section)
494+ print(
495+ json.dumps(output, sort_keys=True, indent=2, separators=(",", ": "))
496+ )
497+ else:
498+ self.print_findings(self.summary["error"], "Errors")
499+ self.print_findings(self.summary["warn"], "Warnings")
500+ if self.verbose:
501+ self.print_findings(self.summary["info"], "Info")
502+ if "runtime-errors" in self.results:
503+ print("%s: RUNTIME ERROR" % self.snap_name)
504+ elif self.summary["warn"] or self.summary["error"]:
505+ print("%s: FAIL" % self.snap_name)
506+ else:
507+ print("%s: pass" % self.snap_name)
508+
509+ def show_summary(self, report_type="json"):
510+ if report_type == "json":
511+ print(
512+ json.dumps(
513+ self.summary, sort_keys=True, indent=2, separators=(",", ": ")
514+ )
515+ )
516+
517+ else:
518+ pprint.pprint(self.summary)
519diff --git a/tests/test.sh.expected b/tests/test.sh.expected
520index 1c726c3..24fbaef 100644
521--- a/tests/test.sh.expected
522+++ b/tests/test.sh.expected
523@@ -119215,7 +119215,7 @@ hello-world_25.snap: FAIL
524 "info": {},
525 "warn": {}
526 }
527-= snap.v2_extra_tests =
528+= snap.v2_extras =
529 {
530 "error": {
531 "extras-tests-v2:error": {
532@@ -119501,7 +119501,7 @@ hello-world_25.snap: FAIL
533 "info": {},
534 "warn": {}
535 },
536- "snap.v2_extra_tests": {
537+ "snap.v2_extras": {
538 "error": {
539 "extras-tests-v2:error": {
540 "manual_review": false,

Subscribers

People subscribed via source and target branches