Merge lp:~savoirfairelinux-openerp/lp-community-utils/openerp_review into lp:lp-community-utils

Status: Work in progress
Proposed branch: lp:~savoirfairelinux-openerp/lp-community-utils/openerp_review
Merge into: lp:lp-community-utils
Prerequisite: lp:~savoirfairelinux-openerp/lp-community-utils/nag_refactor
Diff against target: 515 lines (+496/-0) (has conflicts)
2 files modified
lp.py (+38/-0)
openerp_review (+458/-0)
Text conflict in openerp-nag
To merge this branch: bzr merge lp:~savoirfairelinux-openerp/lp-community-utils/openerp_review
Reviewer Review Type Date Requested Status
OpenERP Community Reviewer/Maintainer Pending
Review via email: mp+214447@code.launchpad.net

Description of the change

The intent for this script is to be like Travis CI for github.

When run, this script will use some of openerp-nag's functions to identify OCA addon MPs.

To these, it will comment on the MP providing information about the and review according to http://pad.openerp.com/p/community-review

Here's a description of the behaviour expected

Gather information about an OpenERP addon MP
* Has this MP been reviewed before by an automated script?
  * Does it contain message identifying as automated?
  * Has there been a new commit since last automated message?
* Does it merge cleanly?
* Is this a new module?
  * Add of a directory and an __openerp__.py file
  * AGPL-3 license check
  * Does it include tests?
  * Does it include demo data?
  * Does it include security files?
  * Does the module version have 2 digits (1.0)
  * Has the .pot file been generated
  * Run flake8 on the entire module
  * Check for deprecated code
  * Check for TODO and FIXME comments
  * Check for debugging breakpoints (ipdb, pdb, set_trace())
  * Test install the module on its own, run tests
  * Does it follow all OpenERP conventions
* Is this a modification of an existing module
  * Which modules are involved?
  * What kind of changes are made?
    * Code behaviour
    * Model changes
      * Has the version been bumped?
      * Any migration scripts included?
    * View changes
    * Tests
      * New tests
      * Added tests
      * Removed tests
    * Demo data
    * Data
    * Translation
    * TODO, FIXME comments
      * Added comments
      * Removed comments
    * Check for debugging breakpoints (ipdb, pdb, set_trace())
  * Evaluate if changes create a net improvement
    * Run flake8 before and after merge
    * Check for deprecated code before and after merge
    * Run tests before and after
      * Are they fixed?
      * Are they broken?
      * Are they unchanged?
      * Which tests results have changed? Are they new/removed?
    * Check conventions before and after merge
* Is this a mixture of both?
  * Separate code of new and modified
* Is this a removal?
* Provide link to http://pad.openerp.com/p/community-review
  * Identify points of concern from the pad
* Vote on MP (or don't)

To post a comment you must log in.
31. By Sandy Carter (http://www.savoirfairelinux.com)

[IMP] added stub functions

32. By Sandy Carter (http://www.savoirfairelinux.com)

Function to list contents of local or remote bzr repository

Given a path, return a list of tracked files in bzr using code for `bzr ls`
Give the list of files without checking out. Useful for scanning launchpad
repos.

Example usgae:
    >>>> lp.list_bzr_repo('lp-community-utils')
    ['lp:lp-community-utils/README.rst',
     'lp:lp-community-utils/checkout-flake8.sh',
     'lp:lp-community-utils/clone_mp_to_community.py',
     'lp:lp-community-utils/merge_mp.py',
     'lp:lp-community-utils/openerp-nag',
     'lp:lp-community-utils/projects',
     'lp:lp-community-utils/replay_missing.py']

May be expanded to support revnos and other flags. The cmd_ls.run() function
signature has the following arguments:
    revision
    verbose
    recursive
    from_root
    unknown
    versioned
    ignored
    null
    kind
    show_ids
    path
    directory
Currently, only path is used.

33. By Sandy Carter (http://www.savoirfairelinux.com)

Check target for openerp addons manifest files

Parse options to target project merge proposals

34. By Sandy Carter (http://www.savoirfairelinux.com)

[IMP] Review project and series implemented

Add logging
Add more options to ls_bzr_repo(), notably the missing recursive option
Separate review_project() into review_series()
Implement skip based on non-addon repo, empty MP repo

35. By Sandy Carter (http://www.savoirfairelinux.com)

Implement been_reviewed() check

Check if there exists a comment older than the last change of the MP which
matches the automated message

36. By Sandy Carter (http://www.savoirfairelinux.com)

Implement checking pep8 in review

Unmerged revisions

36. By Sandy Carter (http://www.savoirfairelinux.com)

Implement checking pep8 in review

35. By Sandy Carter (http://www.savoirfairelinux.com)

Implement been_reviewed() check

Check if there exists a comment older than the last change of the MP which
matches the automated message

34. By Sandy Carter (http://www.savoirfairelinux.com)

[IMP] Review project and series implemented

Add logging
Add more options to ls_bzr_repo(), notably the missing recursive option
Separate review_project() into review_series()
Implement skip based on non-addon repo, empty MP repo

33. By Sandy Carter (http://www.savoirfairelinux.com)

Check target for openerp addons manifest files

Parse options to target project merge proposals

32. By Sandy Carter (http://www.savoirfairelinux.com)

Function to list contents of local or remote bzr repository

Given a path, return a list of tracked files in bzr using code for `bzr ls`
Give the list of files without checking out. Useful for scanning launchpad
repos.

Example usgae:
    >>>> lp.list_bzr_repo('lp-community-utils')
    ['lp:lp-community-utils/README.rst',
     'lp:lp-community-utils/checkout-flake8.sh',
     'lp:lp-community-utils/clone_mp_to_community.py',
     'lp:lp-community-utils/merge_mp.py',
     'lp:lp-community-utils/openerp-nag',
     'lp:lp-community-utils/projects',
     'lp:lp-community-utils/replay_missing.py']

May be expanded to support revnos and other flags. The cmd_ls.run() function
signature has the following arguments:
    revision
    verbose
    recursive
    from_root
    unknown
    versioned
    ignored
    null
    kind
    show_ids
    path
    directory
Currently, only path is used.

31. By Sandy Carter (http://www.savoirfairelinux.com)

[IMP] added stub functions

30. By Sandy Carter (http://www.savoirfairelinux.com)

[ADD] stub script for openerp_review along with details in description

29. By Sandy Carter (http://www.savoirfairelinux.com)

Moved common code out of openerp-nag into lp.py so it can be used by other scripts

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lp.py'
--- lp.py 2014-05-07 22:28:20 +0000
+++ lp.py 2014-05-07 22:28:20 +0000
@@ -29,6 +29,8 @@
29 ProgressBar = None29 ProgressBar = None
3030
31from launchpadlib.launchpad import Launchpad31from launchpadlib.launchpad import Launchpad
32from bzrlib import commands as bzr_commands, builtins as bzr_builtins
33from cStringIO import StringIO
3234
3335
34# Nag:36# Nag:
@@ -250,3 +252,39 @@
250 return Launchpad.login_anonymously(consumer_name, service_root)252 return Launchpad.login_anonymously(consumer_name, service_root)
251 else:253 else:
252 return Launchpad.login_with(consumer_name, service_root)254 return Launchpad.login_with(consumer_name, service_root)
255
256
257def list_bzr_repo(repo, recursive=False, *args, **kwargs):
258 """
259 Given a path, return a list of tracked files in bzr using code for `bzr ls`
260 Return the list of files without checking out. Useful for scanning
261 launchpad repos.
262
263 Example usage:
264 >>>> lp.list_bzr_repo('lp-community-utils')
265 ['lp:lp-community-utils/README.rst',
266 'lp:lp-community-utils/checkout-flake8.sh',
267 'lp:lp-community-utils/clone_mp_to_community.py',
268 'lp:lp-community-utils/merge_mp.py',
269 'lp:lp-community-utils/openerp-nag',
270 'lp:lp-community-utils/projects',
271 'lp:lp-community-utils/replay_missing.py']
272 """
273 bzr_commands.load_plugins()
274 cmd_obj = bzr_builtins.cmd_ls()
275 cmd_obj.outf = StringIO()
276 cmd_obj.run(path=repo, recursive=recursive, *args, **kwargs)
277 return cmd_obj.outf.getvalue().splitlines()
278
279
280def branch_bzr_repo(from_location, to_location, *args, **kwargs):
281 """
282 Given a remote location and a local destination, branch a bzr repo using
283 code for `bzr branch`
284
285 Example usage:
286 >>>> lp.list_bzr_repo('lp-community-utils', '/tmp')
287 """
288 bzr_commands.load_plugins()
289 cmd_obj = bzr_builtins.cmd_branch()
290 cmd_obj.run(from_location, to_location, *args, **kwargs)
253291
=== added file 'openerp_review'
--- openerp_review 1970-01-01 00:00:00 +0000
+++ openerp_review 2014-05-07 22:28:20 +0000
@@ -0,0 +1,458 @@
1#!/usr/bin/env python2
2# -*- encoding: utf-8 -*-
3###############################################################################
4#
5# OpenERP, Open Source Management Solution
6# This module copyright (C) 2010 - 2014 Savoir-faire Linux
7# (<http://www.savoirfairelinux.com>).
8#
9# This program is free software: you can redistribute it and/or modify
10# it under the terms of the GNU Affero General Public License as
11# published by the Free Software Foundation, either version 3 of the
12# License, or (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU Affero General Public License for more details.
18#
19# You should have received a copy of the GNU Affero General Public License
20# along with this program. If not, see <http://www.gnu.org/licenses/>.
21#
22###############################################################################
23
24"""
25Gather information about an OpenERP addon MP
26* Has this MP been reviewed before by an automated script?
27 * Does it contain message identifying as automated?
28 * Has there been a new commit since last automated message?
29* Does it merge cleanly?
30* Is this a new module?
31 * Add of a directory and an __openerp__.py file
32 * AGPL-3 license check
33 * Does it include tests?
34 * Does it include demo data?
35 * Does it include security files?
36 * Does the module version have 2 digits (1.0)
37 * Has the .pot file been generated
38 * Run flake8 on the entire module
39 * Check for deprecated code
40 * Check for TODO and FIXME comments
41 * Check for debugging breakpoints (ipdb, pdb, set_trace())
42 * Test install the module on its own, run tests
43 * Does it follow all OpenERP conventions
44* Is this a modification of an existing module
45 * Which modules are involved?
46 * What kind of changes are made?
47 * Code behaviour
48 * Model changes
49 * Has the version been bumped?
50 * Any migration scripts included?
51 * View changes
52 * Tests
53 * New tests
54 * Added tests
55 * Removed tests
56 * Demo data
57 * Data
58 * Translation
59 * TODO, FIXME comments
60 * Added comments
61 * Removed comments
62 * Check for debugging breakpoints (ipdb, pdb, set_trace())
63 * Evaluate if changes create a net improvement
64 * Run flake8 before and after merge
65 * Check for deprecated code before and after merge
66 * Run tests before and after
67 * Are they fixed?
68 * Are they broken?
69 * Are they unchanged?
70 * Which tests results have changed? Are they new/removed?
71 * Check conventions before and after merge
72* Is this a mixture of both?
73 * Separate code of new and modified
74* Is this a removal?
75* Provide link to http://pad.openerp.com/p/community-review
76 * Identify points of concern from the pad
77* Vote on MP (or don't)
78
79"""
80
81
82from __future__ import (
83 unicode_literals,
84 print_function,
85 absolute_import,
86 division,
87)
88
89import logging
90import os
91import re
92import tempfile
93import shutil
94from pep8 import parse_udiff, StandardReport
95from flake8 import engine
96from argparse import ArgumentParser
97from lp import (
98 launchpad_login,
99 list_bzr_repo,
100 branch_bzr_repo,
101)
102
103consumer_name = u'OpenERP Community Automatic Reviewer Scripts'
104MANIF = u'__openerp__.py'
105AUTOMATED_MESSAGE = u"This is an automated provided by the %s" % consumer_name
106ALL_GOOD_MESSAGE = u"All tests came back clean"
107AUTOMATED_MESSAGE_REGEX = re.compile(AUTOMATED_MESSAGE)
108MESSAGE_CONTAINS_CONFLICTS = u"""\
109Merge proposal contains conflicts on the following files
110 * %s
111Please, rebase or merge with target branch.
112"""
113FLAKE8_IGNORE = u"E501"
114FLAKE8_INIT_IGNORE = u"E501,F401"
115FLAKE8_MANIFEST_IGNORE = u"E501"
116
117
118logging.basicConfig(format=u'%(levelname)s:%(message)s', level=logging.INFO)
119
120
121class NoPrintingPep8Report(StandardReport):
122 def __init__(self, options):
123 super(NoPrintingPep8Report, self).__init__(options=options)
124 self.issues = []
125
126 def get_file_results(self):
127 """Return the result instead of printing it."""
128 self._deferred_print.sort()
129 for line_number, offset, code, text, doc in self._deferred_print:
130 self.issues.append(self._fmt % {
131 'path': self.filename,
132 'row': self.line_offset + line_number, 'col': offset + 1,
133 'code': code, 'text': text,
134 })
135 if self._show_source:
136 if line_number > len(self.lines):
137 line = ''
138 else:
139 line = self.lines[line_number - 1]
140 self.issues.append(line.rstrip())
141 self.issues.append(re.sub(r'\S', ' ', line[:offset]) + '^')
142 if self._show_pep8 and doc:
143 self.issues.append(' ' + doc.strip())
144 return self.file_errors
145
146
147def review_series(series, mps):
148 """
149 Given series, conduct review on all open openERP addon MPs
150 Check series if not in format of directory/__openerp__.py or empty
151 Skip MPs which have already been merged
152 """
153 branch = series.branch
154 if not is_openerp_addon_branch(branch):
155 logging.info(u"%s does not seem to be an OpenERP addon repo, "
156 "skipping..." % branch.bzr_identity)
157 return
158 filtered_mps = filter(lambda m: m.target_branch == branch, mps)
159 if not filtered_mps:
160 logging.info(u"%s does not seem to have any active Merge Proposals, "
161 "skipping..." % branch.bzr_identity)
162 return
163 for mp in filtered_mps:
164 if been_reviewed(mp):
165 logging.info(
166 u"%s has already been reviewed by this script, "
167 "skipping..." % branch.bzr_identity)
168 return
169 else:
170 issues = review_mp(mp)
171 review = compose_review(mp, issues)
172 post_message_on_launchpad(mp, review)
173 return
174
175
176def review_project(project):
177 """Given repo, conduct review on all series"""
178 mps = project.getMergeProposals()
179 for s in project.series:
180 review_series(s, mps)
181
182
183def is_openerp_addon_branch(branch):
184 """
185 Check if the branch is for OpenERP addons
186 If not, it probably shouldn't be reviewed with this script
187 Repo should either be empty or be of the format module_name/__openerp__.py
188 """
189 files = list_bzr_repo(branch.bzr_identity, recursive=True)
190 files = map(os.path.split, files)
191 manif_files = filter(lambda x: x[1] == MANIF, files)
192 return not files or bool(manif_files)
193
194
195def review_mp(mp):
196 """Perform series of reviews on launchpad merge proposal"""
197 def perform_checks(checks, mp, branch_dir):
198 for check in checks:
199 res = check(mp, branch_dir)
200 if res:
201 yield res
202 checks = [
203 contains_flake8_errors_on_diff,
204 ]
205 conflicts = contains_conflicts(mp)
206 if conflicts:
207 return MESSAGE_CONTAINS_CONFLICTS % '\n * '.join(conflicts)
208 repo_dir = branch_to_tmp(mp)
209 res = next(perform_checks(checks, mp, repo_dir), ALL_GOOD_MESSAGE)
210 shutil.rmtree(repo_dir)
211 return res
212
213
214def been_reviewed(mp):
215 """
216 Has this MP been reviewed before by an automated script?
217 * Does it contain message identifying as automated?
218 * Has there been a new commit since last automated message?
219 Return boolean of whether or not to skip this MP
220 """
221 last_change = mp.source_branch.date_last_modified
222 return any(c for c in mp.all_comments
223 if c.date_created > last_change
224 and AUTOMATED_MESSAGE_REGEX.match(c.message_body))
225
226
227def branch_to_tmp(mp):
228 """Branch the source branch to a temporary directory"""
229 tmp_dir = os.path.join(tempfile.mkdtemp(prefix='openerp_review'),
230 mp.source_branch.name)
231 branch_bzr_repo(mp.source_branch.bzr_identity, tmp_dir)
232 return tmp_dir
233
234
235def contains_conflicts(mp):
236 """
237 Does this MP merge cleanly?
238 * If it contains conflicts, suggest rebasing or merging with upstream
239 * Show commands needed to do it
240 Return boolean of whether or not to continue reviewing this MP
241 """
242 return mp.preview_diff.conflicts
243
244
245def contains_flake8_errors_on_diff(mp, branch_dir):
246 """
247 Does this MP introduce pep8 violations?
248
249 Run flake8 against diff
250 Separate __init__.py, __openerp__.py and other files as they have different
251 exceptions.
252 """
253 pushd = os.getcwd()
254 os.chdir(branch_dir)
255 diff = mp.preview_diff.diff_text.open().read()
256 selected_lines = parse_udiff(diff)
257 init_files = []
258 manifest_files = []
259 other_files = []
260 for f in filter(lambda k: k.endswith('.py'), selected_lines.keys()):
261 if os.path.basename(f) == '__init__.py':
262 init_files.append(f)
263 elif os.path.basename(f) == '__openerp__.py':
264 manifest_files.append(f)
265 else:
266 other_files.append(f)
267 init_style = engine.get_style_guide(selected_lines=selected_lines,
268 reporter=NoPrintingPep8Report,
269 ignore=FLAKE8_INIT_IGNORE,
270 quiet=True)
271 init_report = {f: init_style.check_files(f).issues
272 for f in init_files}
273 manifest_style = engine.get_style_guide(selected_lines=selected_lines,
274 reporter=NoPrintingPep8Report,
275 ignore=FLAKE8_MANIFEST_IGNORE,
276 quiet=True)
277 manifest_report = {f: manifest_style.check_files(f).issues
278 for f in manifest_files}
279 other_style = engine.get_style_guide(selected_lines=selected_lines,
280 reporter=NoPrintingPep8Report,
281 ignore=FLAKE8_IGNORE,
282 quiet=True)
283 other_report = {f: other_style.check_files(f).issues
284 for f in other_files}
285 issues = dict(
286 filter(
287 lambda (key, val): val,
288 init_report.items() +
289 manifest_report.items() +
290 other_report.items()
291 )
292 )
293 os.chdir(pushd)
294 return issues
295
296
297def find_modified_modules(mp):
298 """
299 From diff, find which directories modification happened in.
300 Of these directories, those containing __openerp__.py are considered to be
301 modules.
302 Return a list of paths to modules which have had modifications
303 """
304 # TODO
305 raise NotImplementedError(find_modified_modules.__doc__)
306
307
308def get_repo_version(mp):
309 """
310 Identify if this test is being run on a 6.0, 6.1, 7.0, 8.0 or other
311 repo/module.
312 Return version, this should change the behaviour of the tests.
313 """
314 # TODO
315 raise NotImplementedError(get_repo_version.__doc__)
316
317
318def get_modification_type(mp, module_path):
319 """
320 From diff and path, find out if a module has been added, modified, renamed
321 or removed.
322 If a directory and an __openerp__.py file have been added, then it is an
323 added module.
324 """
325 # TODO
326 raise NotImplementedError(get_modification_type.__doc__)
327
328
329def review_new_module(mp, version, module_path):
330 """
331 Review the path according to new_module rules for the version in question.
332 All checks from http://pad.openerp.com/p/community-review should be
333 considered and comments on launchpad should be valid since these are new
334 files. Review should be safe too.
335 * AGPL-3 license check
336 * Does it include tests?
337 * Does it include demo data?
338 * Does it include security files?
339 * Does the module version have 2 digits (1.0)
340 * Has the .pot file been generated
341 * Run flake8 on the entire module
342 * Check for deprecated code
343 * Check for TODO and FIXME comments
344 * Check for debugging breakpoints (ipdb, pdb, set_trace())
345 * Test install the module on its own, run tests
346 * Does it follow all OpenERP conventions
347 """
348 # TODO
349 raise NotImplementedError(review_new_module.__doc__)
350
351
352def identify_changes_to_module(mp, module_path):
353 """
354 Try to breakdown changes made to module to allow review to focus on only
355 that and avoid unrelated comments.
356 * Code behaviour
357 * Model changes
358 * View changes
359 * Tests
360 * New tests
361 * Added tests
362 * Removed tests
363 * Demo data
364 * Data
365 * Translation
366 * TODO, FIXME comments
367 * Added comments
368 * Removed comments
369 * Check for debugging breakpoints (ipdb, pdb, set_trace())
370 """
371 # TODO
372 raise NotImplementedError(identify_changes_to_module.__doc__)
373
374
375def review_modified_module(mp, version, module_path):
376 """
377 See if module passes review process before MP.
378 If it does, do full review, comment on each issue and problem with merge.
379 If it didn't, mention it, then try to calculate net improvement. If changes
380 add or remove problems, mention it. Try to avoid unrelated messages in
381 review (i.e. problems which existed before and have not been touched by MP)
382 * What kind of changes are made?
383 * Evaluate if changes create a net improvement
384 * Run flake8 before and after merge
385 * Perform flake8 on diff generated by git's diff on functions
386 * bzr diff --using git --diff-options "diff -W -p" > diff_file
387 * flake8-python2 --diff diff_file
388 * Check for deprecated code before and after merge
389 * Run tests before and after
390 * Are they fixed?
391 * Are they broken?
392 * Are they unchanged?
393 * Which tests results have changed? Are they new/removed?
394 * Check conventions before and after merge
395 """
396 changes = identify_changes_to_module(mp, module_path)
397 # TODO
398 raise NotImplementedError(review_modified_module.__doc__)
399
400
401def review_renamed_module(mp, version, module_path):
402 """
403 Should do the same review as modified module, but keep in mind that files
404 are in a different place.
405 If there are no changes besides the name of the module path, skip modified
406 module review
407 """
408 # TODO
409 raise NotImplementedError(review_renamed_module.__doc__)
410
411
412def review_removed_module(mp, version, module_path):
413 """
414 Evaluate what was lost, see if any other modules in repo depend on it.
415 If module was non-installable prior, there shouldn't be any issue.
416 """
417 # TODO
418 raise NotImplementedError(review_removed_module.__doc__)
419
420
421def compose_review(mp, issues):
422 """
423 From issues raised by review_* functions, stitch together a nice message to
424 be posted on launchpad
425 """
426 message = AUTOMATED_MESSAGE
427 # TODO
428 logging.warning("TODO: " + compose_review.__doc__)
429 return message
430
431
432def post_message_on_launchpad(mp, message):
433 """Given a message to post on launchpad, post it on the MP."""
434 # TODO
435 logging.warning("TODO: " + post_message_on_launchpad.__doc__)
436
437
438def parse_args():
439 """Define and parse command line arguments for script."""
440 # TODO
441 parser = ArgumentParser()
442 group = parser.add_argument_group()
443 group.add_argument(u'-p', u'--project', metavar="project",
444 required=True, help=u'Launchpad repository identifier.')
445 return parser.parse_args()
446
447
448def main():
449 """Main Function"""
450 args = parse_args()
451 lp = launchpad_login(False, consumer_name, 'production')
452 project = lp.projects[args.project]
453 review = review_project(project)
454 # TODO
455 raise NotImplementedError(main.__doc__)
456
457if __name__ == "__main__":
458 exit(main())

Subscribers

People subscribed via source and target branches