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 |