Merge ~jslarraz/review-tools:rework-snap-review into review-tools:master

Proposed by Jorge Sancho Larraz
Status: Merged
Merged at revision: bac6469d99efc75598fd4cd78605c69ade4eb377
Proposed branch: ~jslarraz/review-tools:rework-snap-review
Merge into: review-tools:master
Diff against target: 372 lines (+117/-139)
2 files modified
bin/snap-review (+117/-119)
reviewtools/modules.py (+0/-20)
Reviewer Review Type Date Requested Status
Alex Murray Approve
Review via email: mp+466751@code.launchpad.net

Commit message

Improve bin/snap-review in preparation for using a common report class shared across all reviews

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

LGTM (was it intentional to include the change to reviewtools/sr_lint etc around desktop files here?)

review: Approve
Revision history for this message
Jorge Sancho Larraz (jslarraz) wrote :

Yes, it was hehe

Originally, we initialized review classes with `modules.init_main_class` function. That configured the review_type (json or other) in the review class. It was never used (as snap-review don't use `Review.do_report`) but when `common.error` (which uses report_type) is used to exit the review.

Thus, this update to snap-review requires of updating tests/test.sh.expected and the change to reviewtools/sr_lint etc around desktop files was how I tried to manage this change in a consistent way.

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 37c9fea..2c26158 100755
3--- a/bin/snap-review
4+++ b/bin/snap-review
5@@ -1,6 +1,6 @@
6 #!/usr/bin/python3
7
8-from reviewtools import modules
9+from reviewtools import modules as modules_mod
10 import argparse
11 import copy
12 import json
13@@ -21,33 +21,53 @@ from reviewtools.common import (
14 from reviewtools.sr_declaration import validate_json
15
16
17-def print_findings(results, description):
18- """
19- Print a summary of the issues found.
20- """
21-
22- if not description or not results:
23- return ""
24- print(description)
25- print("".center(len(description), "-"))
26- for key in sorted(results.keys()):
27- print(" - %s" % key)
28- print("\t%s" % results[key]["text"])
29- if "link" in results[key]:
30- print("\t%s" % results[key]["link"])
31-
32-
33-class Results(object):
34+class Report(object):
35 results = {}
36 errors = {}
37 warnings = {}
38 info = {}
39- rc = 0
40
41- def __init__(self, args):
42- self.args = args
43- self.pkg_fn = self.args.filename
44- self.modules = modules.get_modules()
45+ def __init__(self, filename, verbose: bool = False):
46+ self.filename = filename
47+ self.verbose = verbose
48+
49+ # review_report[<section>][<result_type>][<review_name>] = <result>
50+ # section: section of the report (one per review module)
51+ # result_type: info, warn, error
52+ # review_name: name of the check (prefixed with self.review_type)
53+ # result: contents of the review
54+ # link: url for more information
55+ # manual_review: force manual review
56+ # override_result_type: prefix results with [<result_type>] and set
57+ # result_type to override_result_type
58+ def add_result(
59+ self,
60+ section,
61+ result_type,
62+ review_name,
63+ result,
64+ link=None,
65+ manual_review=False,
66+ ):
67+ """Add result to report"""
68+ if section not in self.results:
69+ self.results[section] = {
70+ "info": {},
71+ "warn": {},
72+ "error": {},
73+ }
74+
75+ if result_type not in self.results[section]:
76+ raise Exception("Invalid result type '%s'" % result_type)
77+
78+ if review_name not in self.results[section][result_type]:
79+ self.results[section][result_type][review_name] = dict()
80+
81+ self.results[section][result_type][review_name].update(
82+ {"text": "%s" % result, "manual_review": manual_review}
83+ )
84+ if link is not None:
85+ self.results[section][result_type][review_name]["link"] = link
86
87 def _summarise_results(self):
88 for module in self.results:
89@@ -55,94 +75,51 @@ class Results(object):
90 self.errors[key] = self.results[module]["error"][key]
91 for key in self.results[module]["warn"]:
92 self.warnings[key] = self.results[module]["warn"][key]
93- if self.args.verbose:
94+ if self.verbose:
95 for key in self.results[module]["info"]:
96 self.info[key] = self.results[module]["info"][key]
97
98- def complete_report(self):
99+ def print_findings(self, results, description):
100+ """
101+ Print a summary of the issues found.
102+ """
103+ if not description or not results:
104+ return ""
105+ print(description)
106+ print("".center(len(description), "-"))
107+ for key in sorted(results.keys()):
108+ print(" - %s" % key)
109+ print("\t%s" % results[key]["text"])
110+ if "link" in results[key]:
111+ print("\t%s" % results[key]["link"])
112+
113+ def show_results(self, report_type="console"):
114 self._summarise_results()
115
116- if self.args.json:
117+ if report_type == "json":
118 print(
119 json.dumps(
120 self.results, sort_keys=True, indent=2, separators=(",", ": ")
121 )
122 )
123- elif self.args.sdk:
124+ elif report_type == "sdk":
125 for section in sorted(self.results.keys()):
126 output = self.results[section]
127 print("= %s =" % section)
128 print(
129 json.dumps(output, sort_keys=True, indent=2, separators=(",", ": "))
130 )
131- if output["error"] or output["warn"]:
132- self.rc = 1
133 else:
134- print_findings(self.errors, "Errors")
135- print_findings(self.warnings, "Warnings")
136- if self.args.verbose:
137- print_findings(self.info, "Info")
138- if self.rc == 1:
139- print("%s: RUNTIME ERROR" % self.args.filename)
140+ self.print_findings(self.errors, "Errors")
141+ self.print_findings(self.warnings, "Warnings")
142+ if self.verbose:
143+ self.print_findings(self.info, "Info")
144+ if "runtime-errors" in self.results:
145+ print("%s: RUNTIME ERROR" % self.filename)
146 elif self.warnings or self.errors:
147- print("%s: FAIL" % self.args.filename)
148+ print("%s: FAIL" % self.filename)
149 else:
150- print("%s: pass" % self.args.filename)
151- if self.rc == 1:
152- # always exit(1) if there are errors
153- pass
154- elif self.errors:
155- self.rc = 2
156- elif self.warnings:
157- self.rc = 3
158-
159- def _run_module_checks(self, module, overrides, report_type):
160- # What we are doing here is basically what all the
161- # ./bin/snap-check-* scripts do as well, so for
162- # example something like:
163- #
164- # review = sr_lint.SnapLint(sys.argv[1])
165- # review.do_checks()
166- # rc = review.do_report()
167- #
168- section = module.replace("sr_", "snap.v2_")
169- try:
170- review = modules.init_main_class(
171- module, self.pkg_fn, overrides=overrides, report_type=report_type
172- )
173-
174- if review:
175- review.do_checks()
176- self.results[section] = review.review_report
177- return section
178- except Exception:
179- print("Caught exception (setting rc=1 and continuing):")
180- traceback.print_exc(file=sys.stdout)
181- self.rc = 1
182- return None
183-
184- def run_all_checks(self, overrides):
185- if self.args.json:
186- report_type = "json"
187- elif self.args.sdk:
188- # this should probably be "json", but leave as None since it has
189- # always been that way
190- report_type = None
191- else:
192- report_type = "console"
193-
194- for module in self.modules:
195- self._run_module_checks(module, overrides, report_type)
196-
197- def add_runtime_error(self, args, name, msg):
198- section = "runtime-errors"
199- if section not in self.results:
200- self.results[section] = {}
201- self.results[section] = {
202- "error": {name: {"text": msg, "manual_review": False}},
203- "info": {},
204- "warn": {},
205- }
206+ print("%s: pass" % self.filename)
207
208
209 def main():
210@@ -201,18 +178,13 @@ def main():
211 "file '%s' does not exist." % args.filename, output_type=error_output_type
212 )
213
214- results = Results(args)
215- if not results.modules:
216+ modules = modules_mod.get_modules()
217+ if not modules:
218 print("No 'reviewtools' modules found.")
219 sys.exit(1)
220
221- overrides = None
222- if args.overrides:
223- overrides = json.loads(args.overrides)
224-
225+ overrides = json.loads(args.overrides) if args.overrides else {}
226 if args.plugs:
227- if overrides is None:
228- overrides = {}
229 try:
230 with open(args.plugs, "r") as plugs_file:
231 overrides["snap_decl_plugs"] = json.loads(plugs_file.read())
232@@ -220,8 +192,6 @@ def main():
233 except Exception as e:
234 error("Failed to read plugs: %s" % e, output_type=error_output_type)
235 if args.slots:
236- if overrides is None:
237- overrides = {}
238 try:
239 with open(args.slots, "r") as slots_file:
240 overrides["snap_decl_slots"] = json.loads(slots_file.read())
241@@ -229,16 +199,10 @@ def main():
242 except Exception as e:
243 error("Failed to read slots: %s" % e, output_type=error_output_type)
244 if args.allow_classic:
245- if overrides is None:
246- overrides = {}
247 overrides["snap_allow_classic"] = args.allow_classic
248 if args.on_store:
249- if overrides is None:
250- overrides = {}
251 overrides["snap_on_store"] = args.on_store
252 if args.on_brand:
253- if overrides is None:
254- overrides = {}
255 overrides["snap_on_brand"] = args.on_brand
256
257 # --state-input alone is unsupported
258@@ -249,9 +213,6 @@ def main():
259 )
260
261 if args.state_output:
262- if overrides is None:
263- overrides = {}
264-
265 if args.state_input:
266 # --state-input --state-output indicates we have previous state
267 if not os.path.exists(args.state_input):
268@@ -280,7 +241,20 @@ def main():
269 overrides["state_output"] = copy.deepcopy(overrides["state_input"])
270
271 # Run the tests
272- results.run_all_checks(overrides)
273+ rc = 0
274+ report = Report(args.filename, args.verbose)
275+ for module in modules:
276+ section = module.replace("sr_", "snap.v2_")
277+ try:
278+ review_class = modules_mod.find_main_class(module)
279+ review = review_class(args.filename, overrides=overrides)
280+
281+ review.do_checks()
282+ report.results[section] = review.review_report
283+ except Exception:
284+ print("Caught exception (setting rc=1 and continuing):")
285+ traceback.print_exc(file=sys.stdout)
286+ rc = 1
287
288 # Write out our state
289 if args.state_output:
290@@ -290,8 +264,11 @@ def main():
291 try:
292 verify_override_state(overrides["state_output"])
293 except Exception as e:
294- results.add_runtime_error(
295- args, "state-output-verify", "state-output failed to verify: %s" % e
296+ report.add_result(
297+ "runtime-errors",
298+ "error",
299+ "state-output-verify",
300+ "state-output failed to verify: %s" % e,
301 )
302 overrides["state_output"] = copy.deepcopy(overrides["state_input"])
303
304@@ -312,14 +289,35 @@ def main():
305 out_f.flush()
306 shutil.copyfile(out_f.name, args.state_output)
307 except Exception as e:
308- results.add_runtime_error(
309- args, "state-output-write", "Could not write state output: %s" % e
310+ report.add_result(
311+ "runtime-errors",
312+ "error",
313+ "state-output-write",
314+ "Could not write state output: %s" % e,
315 )
316
317 # Show the results of the report
318- results.complete_report()
319-
320- sys.exit(results.rc)
321+ if args.json:
322+ report_type = "json"
323+ elif args.sdk:
324+ report_type = "sdk"
325+ if report.errors or report.warnings:
326+ # TODO: harmonize return codes
327+ rc = 1
328+ else:
329+ report_type = "console"
330+
331+ # Show results updates of report.errors and report.warnings
332+ report.show_results(report_type)
333+
334+ if rc == 1:
335+ # always exit(1) if there are errors
336+ pass
337+ elif report.errors:
338+ rc = 2
339+ elif report.warnings:
340+ rc = 3
341+ sys.exit(rc)
342
343
344 if __name__ == "__main__":
345diff --git a/reviewtools/modules.py b/reviewtools/modules.py
346index 9b93f32..e0d56d2 100644
347--- a/reviewtools/modules.py
348+++ b/reviewtools/modules.py
349@@ -83,23 +83,3 @@ def find_main_class(module_name):
350 return None
351 init_object = getattr(module, test_class[0][0])
352 return init_object
353-
354-
355-def init_main_class(module_name, pkg_file, overrides=None, report_type=None):
356- """
357- This function will instantiate the main Snap*Review
358- class of a given module and instantiate it with the
359- location of the file we want to inspect.
360- """
361-
362- init_object = find_main_class(module_name)
363- if not init_object:
364- return None
365- try:
366- ob = init_object(pkg_file, overrides)
367- # set the report_type separately since it is in the common class
368- ob.set_report_type(report_type)
369- except TypeError as e:
370- print("Could not init %s: %s" % (init_object, str(e)))
371- raise
372- return ob

Subscribers

People subscribed via source and target branches