Merge ~jslarraz/review-tools:move-report into review-tools:master
- Git
- lp:~jslarraz/review-tools
- move-report
- Merge into 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) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Alex Murray | Approve | ||
Review via email:
|
Commit message
many: move Report class away from bin/snap-review, and use it also on individual checks
Description of the change
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | diff --git a/bin/snap-review b/bin/snap-review |
2 | index 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) |
130 | diff --git a/bin/snap-verify-declaration b/bin/snap-verify-declaration |
131 | index 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__": |
153 | diff --git a/reviewtools/common.py b/reviewtools/common.py |
154 | index 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 | |
372 | diff --git a/reviewtools/report.py b/reviewtools/report.py |
373 | new file mode 100644 |
374 | index 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) |
519 | diff --git a/tests/test.sh.expected b/tests/test.sh.expected |
520 | index 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, |
LGTM - yay removal of more global variables!