Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
| 5 | 5 |
| 6 """Enables directory-specific presubmit checks to run at upload and/or commit. | 6 """Enables directory-specific presubmit checks to run at upload and/or commit. |
| 7 """ | 7 """ |
| 8 | 8 |
| 9 __version__ = '1.7.0' | 9 __version__ = '1.7.0' |
| 10 | 10 |
| 11 # TODO(joi) Add caching where appropriate/needed. The API is designed to allow | 11 # TODO(joi) Add caching where appropriate/needed. The API is designed to allow |
| 12 # caching (between all different invocations of presubmit scripts for a given | 12 # caching (between all different invocations of presubmit scripts for a given |
| 13 # change). We should add it as our presubmit scripts start feeling slow. | 13 # change). We should add it as our presubmit scripts start feeling slow. |
| 14 | 14 |
| 15 import cpplint | 15 import cpplint |
| 16 import cPickle # Exposed through the API. | 16 import cPickle # Exposed through the API. |
| 17 import cStringIO # Exposed through the API. | 17 import cStringIO # Exposed through the API. |
| 18 import collections | |
| 19 import contextlib | 18 import contextlib |
| 20 import fnmatch | 19 import fnmatch |
| 21 import glob | 20 import glob |
| 22 import inspect | 21 import inspect |
| 23 import json # Exposed through the API. | 22 import json # Exposed through the API. |
| 24 import logging | 23 import logging |
| 25 import marshal # Exposed through the API. | 24 import marshal # Exposed through the API. |
| 26 import multiprocessing | 25 import multiprocessing |
| 27 import optparse | 26 import optparse |
| 28 import os # Somewhat exposed through the API. | 27 import os # Somewhat exposed through the API. |
| (...skipping 20 matching lines...) Expand all Loading... | |
| 49 | 48 |
| 50 | 49 |
| 51 # Ask for feedback only once in program lifetime. | 50 # Ask for feedback only once in program lifetime. |
| 52 _ASKED_FOR_FEEDBACK = False | 51 _ASKED_FOR_FEEDBACK = False |
| 53 | 52 |
| 54 | 53 |
| 55 class PresubmitFailure(Exception): | 54 class PresubmitFailure(Exception): |
| 56 pass | 55 pass |
| 57 | 56 |
| 58 | 57 |
| 59 CommandData = collections.namedtuple('CommandData', | 58 class CommandData(object): |
| 60 ['name', 'cmd', 'kwargs', 'message']) | 59 def __init__(self, name, cmd, kwargs, message): |
| 60 self.name = name | |
| 61 self.cmd = cmd | |
| 62 self.kwargs = kwargs | |
| 63 self.message = message | |
| 64 self.info = None | |
| 65 | |
| 61 | 66 |
| 62 def normpath(path): | 67 def normpath(path): |
| 63 '''Version of os.path.normpath that also changes backward slashes to | 68 '''Version of os.path.normpath that also changes backward slashes to |
| 64 forward slashes when not running on Windows. | 69 forward slashes when not running on Windows. |
| 65 ''' | 70 ''' |
| 66 # This is safe to always do because the Windows version of os.path.normpath | 71 # This is safe to always do because the Windows version of os.path.normpath |
| 67 # will replace forward slashes with backward slashes. | 72 # will replace forward slashes with backward slashes. |
| 68 path = path.replace(os.sep, '/') | 73 path = path.replace(os.sep, '/') |
| 69 return os.path.normpath(path) | 74 return os.path.normpath(path) |
| 70 | 75 |
| (...skipping 390 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 461 file_item = file_item.AbsoluteLocalPath() | 466 file_item = file_item.AbsoluteLocalPath() |
| 462 if not file_item.startswith(self.change.RepositoryRoot()): | 467 if not file_item.startswith(self.change.RepositoryRoot()): |
| 463 raise IOError('Access outside the repository root is denied.') | 468 raise IOError('Access outside the repository root is denied.') |
| 464 return gclient_utils.FileRead(file_item, mode) | 469 return gclient_utils.FileRead(file_item, mode) |
| 465 | 470 |
| 466 @property | 471 @property |
| 467 def tbr(self): | 472 def tbr(self): |
| 468 """Returns if a change is TBR'ed.""" | 473 """Returns if a change is TBR'ed.""" |
| 469 return 'TBR' in self.change.tags | 474 return 'TBR' in self.change.tags |
| 470 | 475 |
| 471 @staticmethod | 476 def RunTests(self, tests_mix, parallel=True): |
| 472 def RunTests(tests_mix, parallel=True): | |
| 473 tests = [] | 477 tests = [] |
| 474 msgs = [] | 478 msgs = [] |
| 475 for t in tests_mix: | 479 for t in tests_mix: |
| 476 if isinstance(t, OutputApi.PresubmitResult): | 480 if isinstance(t, OutputApi.PresubmitResult): |
| 477 msgs.append(t) | 481 msgs.append(t) |
| 478 else: | 482 else: |
| 479 assert issubclass(t.message, _PresubmitResult) | 483 assert issubclass(t.message, _PresubmitResult) |
| 480 tests.append(t) | 484 tests.append(t) |
| 485 if self.verbose: | |
| 486 t.info = _PresubmitNotifyResult | |
| 481 if len(tests) > 1 and parallel: | 487 if len(tests) > 1 and parallel: |
| 482 pool = multiprocessing.Pool() | 488 pool = multiprocessing.Pool() |
| 483 # async recipe works around multiprocessing bug handling Ctrl-C | 489 # async recipe works around multiprocessing bug handling Ctrl-C |
| 484 msgs.extend(pool.map_async(CallCommand, tests).get(99999)) | 490 msgs.extend(pool.map_async(CallCommand, tests).get(99999)) |
| 485 pool.close() | 491 pool.close() |
| 486 pool.join() | 492 pool.join() |
| 487 else: | 493 else: |
| 488 msgs.extend(map(CallCommand, tests)) | 494 msgs.extend(map(CallCommand, tests)) |
| 489 return [m for m in msgs if m] | 495 return [m for m in msgs if m] |
| 490 | 496 |
| (...skipping 854 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1345 for method_name in method_names: | 1351 for method_name in method_names: |
| 1346 if not hasattr(presubmit_canned_checks, method_name): | 1352 if not hasattr(presubmit_canned_checks, method_name): |
| 1347 raise NonexistantCannedCheckFilter(method_name) | 1353 raise NonexistantCannedCheckFilter(method_name) |
| 1348 filtered[method_name] = getattr(presubmit_canned_checks, method_name) | 1354 filtered[method_name] = getattr(presubmit_canned_checks, method_name) |
| 1349 setattr(presubmit_canned_checks, method_name, lambda *_a, **_kw: []) | 1355 setattr(presubmit_canned_checks, method_name, lambda *_a, **_kw: []) |
| 1350 yield | 1356 yield |
| 1351 finally: | 1357 finally: |
| 1352 for name, method in filtered.iteritems(): | 1358 for name, method in filtered.iteritems(): |
| 1353 setattr(presubmit_canned_checks, name, method) | 1359 setattr(presubmit_canned_checks, name, method) |
| 1354 | 1360 |
| 1361 | |
| 1355 def CallCommand(cmd_data): | 1362 def CallCommand(cmd_data): |
| 1356 # multiprocessing needs a top level function with a single argument. | 1363 """Runs an external program, potentially from a child process created by the |
| 1364 multiprocessing module. | |
| 1365 | |
| 1366 multiprocessing needs a top level function with a single argument. | |
| 1367 """ | |
| 1357 cmd_data.kwargs['stdout'] = subprocess.PIPE | 1368 cmd_data.kwargs['stdout'] = subprocess.PIPE |
| 1358 cmd_data.kwargs['stderr'] = subprocess.STDOUT | 1369 cmd_data.kwargs['stderr'] = subprocess.STDOUT |
| 1359 try: | 1370 try: |
| 1371 start = time.time() | |
| 1360 (out, _), code = subprocess.communicate(cmd_data.cmd, **cmd_data.kwargs) | 1372 (out, _), code = subprocess.communicate(cmd_data.cmd, **cmd_data.kwargs) |
| 1361 if code != 0: | 1373 duration = time.time() - start |
| 1362 return cmd_data.message('%s failed\n%s' % (cmd_data.name, out)) | |
| 1363 except OSError as e: | 1374 except OSError as e: |
| 1375 duration = time.time() - start | |
|
iannucci
2013/12/02 19:36:38
finally?
M-A Ruel
2013/12/02 21:23:24
You cannot do try/finally/except in python, only t
| |
| 1364 return cmd_data.message( | 1376 return cmd_data.message( |
| 1365 '%s exec failure\n %s' % (cmd_data.name, e)) | 1377 '%s exec failure (%4.2fs)\n %s' % (cmd_data.name, duration, e)) |
| 1378 if code != 0: | |
| 1379 return cmd_data.message( | |
| 1380 '%s (%4.2fs) failed\n%s' % (cmd_data.name, duration, out)) | |
| 1381 if cmd_data.info: | |
| 1382 return cmd_data.info('%s (%4.2fs)' % (cmd_data.name, duration)) | |
| 1366 | 1383 |
| 1367 | 1384 |
| 1368 def Main(argv): | 1385 def Main(argv): |
| 1369 parser = optparse.OptionParser(usage="%prog [options] <files...>", | 1386 parser = optparse.OptionParser(usage="%prog [options] <files...>", |
| 1370 version="%prog " + str(__version__)) | 1387 version="%prog " + str(__version__)) |
| 1371 parser.add_option("-c", "--commit", action="store_true", default=False, | 1388 parser.add_option("-c", "--commit", action="store_true", default=False, |
| 1372 help="Use commit instead of upload checks") | 1389 help="Use commit instead of upload checks") |
| 1373 parser.add_option("-u", "--upload", action="store_false", dest='commit', | 1390 parser.add_option("-u", "--upload", action="store_false", dest='commit', |
| 1374 help="Use upload instead of commit checks") | 1391 help="Use upload instead of commit checks") |
| 1375 parser.add_option("-r", "--recursive", action="store_true", | 1392 parser.add_option("-r", "--recursive", action="store_true", |
| (...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1446 except PresubmitFailure, e: | 1463 except PresubmitFailure, e: |
| 1447 print >> sys.stderr, e | 1464 print >> sys.stderr, e |
| 1448 print >> sys.stderr, 'Maybe your depot_tools is out of date?' | 1465 print >> sys.stderr, 'Maybe your depot_tools is out of date?' |
| 1449 print >> sys.stderr, 'If all fails, contact maruel@' | 1466 print >> sys.stderr, 'If all fails, contact maruel@' |
| 1450 return 2 | 1467 return 2 |
| 1451 | 1468 |
| 1452 | 1469 |
| 1453 if __name__ == '__main__': | 1470 if __name__ == '__main__': |
| 1454 fix_encoding.fix_encoding() | 1471 fix_encoding.fix_encoding() |
| 1455 sys.exit(Main(None)) | 1472 sys.exit(Main(None)) |
| OLD | NEW |