Index: presubmit_support.py |
diff --git a/presubmit_support.py b/presubmit_support.py |
index fe1c2878c5c27b64e75dfd36e2b456ac84aa43af..6f0d9c98ff73e2a1a0564dccb7772c447ac1dd4d 100755 |
--- a/presubmit_support.py |
+++ b/presubmit_support.py |
@@ -15,6 +15,7 @@ __version__ = '1.6.2' |
import cpplint |
import cPickle # Exposed through the API. |
import cStringIO # Exposed through the API. |
+import collections |
import contextlib |
import fnmatch |
import glob |
@@ -22,6 +23,7 @@ import inspect |
import json # Exposed through the API. |
import logging |
import marshal # Exposed through the API. |
+import multiprocessing |
import optparse |
import os # Somewhat exposed through the API. |
import pickle # Exposed through the API. |
@@ -54,6 +56,9 @@ class PresubmitFailure(Exception): |
pass |
+CommandData = collections.namedtuple('CommandData', |
+ ['name', 'cmd', 'kwargs', 'message']) |
+ |
def normpath(path): |
'''Version of os.path.normpath that also changes backward slashes to |
forward slashes when not running on Windows. |
@@ -104,68 +109,99 @@ class PresubmitOutput(object): |
return ''.join(self.written_output) |
-class OutputApi(object): |
- """An instance of OutputApi gets passed to presubmit scripts so that they |
- can output various types of results. |
- """ |
- def __init__(self, is_committing): |
- self.is_committing = is_committing |
- |
- class PresubmitResult(object): |
- """Base class for result objects.""" |
- fatal = False |
- should_prompt = False |
- |
- def __init__(self, message, items=None, long_text=''): |
- """ |
- message: A short one-line message to indicate errors. |
- items: A list of short strings to indicate where errors occurred. |
- long_text: multi-line text output, e.g. from another tool |
- """ |
- self._message = message |
- self._items = [] |
- if items: |
- self._items = items |
- self._long_text = long_text.rstrip() |
+# Top level object so multiprocessing can pickle |
+# Public access through OutputApi object. |
+class _PresubmitResult(object): |
+ """Base class for result objects.""" |
+ fatal = False |
+ should_prompt = False |
- def handle(self, output): |
- output.write(self._message) |
+ def __init__(self, message, items=None, long_text=''): |
+ """ |
+ message: A short one-line message to indicate errors. |
+ items: A list of short strings to indicate where errors occurred. |
+ long_text: multi-line text output, e.g. from another tool |
+ """ |
+ self._message = message |
+ self._items = items or [] |
+ if items: |
+ self._items = items |
+ self._long_text = long_text.rstrip() |
+ |
+ def handle(self, output): |
+ output.write(self._message) |
+ output.write('\n') |
+ for index, item in enumerate(self._items): |
+ output.write(' ') |
+ # Write separately in case it's unicode. |
+ output.write(str(item)) |
+ if index < len(self._items) - 1: |
+ output.write(' \\') |
output.write('\n') |
- for index, item in enumerate(self._items): |
- output.write(' ') |
- # Write separately in case it's unicode. |
- output.write(str(item)) |
- if index < len(self._items) - 1: |
- output.write(' \\') |
- output.write('\n') |
- if self._long_text: |
- output.write('\n***************\n') |
- # Write separately in case it's unicode. |
- output.write(self._long_text) |
- output.write('\n***************\n') |
- if self.fatal: |
- output.fail() |
+ if self._long_text: |
+ output.write('\n***************\n') |
+ # Write separately in case it's unicode. |
+ output.write(self._long_text) |
+ output.write('\n***************\n') |
+ if self.fatal: |
+ output.fail() |
+ |
+ |
+# Top level object so multiprocessing can pickle |
+# Public access through OutputApi object. |
+class _PresubmitAddReviewers(_PresubmitResult): |
+ """Add some suggested reviewers to the change.""" |
+ def __init__(self, reviewers): |
+ super(_PresubmitAddReviewers, self).__init__('') |
+ self.reviewers = reviewers |
- class PresubmitAddReviewers(PresubmitResult): |
- """Add some suggested reviewers to the change.""" |
- def __init__(self, reviewers): |
- super(OutputApi.PresubmitAddReviewers, self).__init__('') |
- self.reviewers = reviewers |
+ def handle(self, output): |
+ output.reviewers.extend(self.reviewers) |
- def handle(self, output): |
- output.reviewers.extend(self.reviewers) |
- class PresubmitError(PresubmitResult): |
- """A hard presubmit error.""" |
- fatal = True |
+# Top level object so multiprocessing can pickle |
+# Public access through OutputApi object. |
+class _PresubmitError(_PresubmitResult): |
+ """A hard presubmit error.""" |
+ fatal = True |
- class PresubmitPromptWarning(PresubmitResult): |
- """An warning that prompts the user if they want to continue.""" |
- should_prompt = True |
- class PresubmitNotifyResult(PresubmitResult): |
- """Just print something to the screen -- but it's not even a warning.""" |
- pass |
+# Top level object so multiprocessing can pickle |
+# Public access through OutputApi object. |
+class _PresubmitPromptWarning(_PresubmitResult): |
+ """An warning that prompts the user if they want to continue.""" |
+ should_prompt = True |
+ |
+ |
+# Top level object so multiprocessing can pickle |
+# Public access through OutputApi object. |
+class _PresubmitNotifyResult(_PresubmitResult): |
+ """Just print something to the screen -- but it's not even a warning.""" |
+ pass |
+ |
+ |
+# Top level object so multiprocessing can pickle |
+# Public access through OutputApi object. |
+class _MailTextResult(_PresubmitResult): |
+ """A warning that should be included in the review request email.""" |
+ def __init__(self, *args, **kwargs): |
+ super(_MailTextResult, self).__init__() |
+ raise NotImplementedError() |
+ |
+ |
+class OutputApi(object): |
+ """An instance of OutputApi gets passed to presubmit scripts so that they |
+ can output various types of results. |
+ """ |
+ PresubmitResult = _PresubmitResult |
+ PresubmitAddReviewers = _PresubmitAddReviewers |
+ PresubmitError = _PresubmitError |
+ PresubmitPromptWarning = _PresubmitPromptWarning |
+ PresubmitNotifyResult = _PresubmitNotifyResult |
+ MailTextResult = _MailTextResult |
+ |
+ def __init__(self, is_committing): |
+ self.is_committing = is_committing |
def PresubmitPromptOrNotify(self, *args, **kwargs): |
"""Warn the user when uploading, but only notify if committing.""" |
@@ -173,12 +209,6 @@ class OutputApi(object): |
return self.PresubmitNotifyResult(*args, **kwargs) |
return self.PresubmitPromptWarning(*args, **kwargs) |
- class MailTextResult(PresubmitResult): |
- """A warning that should be included in the review request email.""" |
- def __init__(self, *args, **kwargs): |
- super(OutputApi.MailTextResult, self).__init__() |
- raise NotImplementedError() |
- |
class InputApi(object): |
"""An instance of this object is passed to presubmit scripts so they can |
@@ -284,6 +314,7 @@ class InputApi(object): |
self.owners_db = owners.Database(change.RepositoryRoot(), |
fopen=file, os_path=self.os_path, glob=self.glob) |
self.verbose = verbose |
+ self.Command = CommandData |
# Replace <hash_map> and <hash_set> as headers that need to be included |
# with "base/hash_tables.h" instead. |
@@ -437,6 +468,26 @@ class InputApi(object): |
"""Returns if a change is TBR'ed.""" |
return 'TBR' in self.change.tags |
+ @staticmethod |
+ def RunTests(tests_mix, parallel=True): |
+ tests = [] |
+ msgs = [] |
+ for t in tests_mix: |
+ if isinstance(t, OutputApi.PresubmitResult): |
+ msgs.append(t) |
+ else: |
+ assert issubclass(t.message, _PresubmitResult) |
+ tests.append(t) |
+ if parallel: |
+ pool = multiprocessing.Pool() |
+ # async recipe works around multiprocessing bug handling Ctrl-C |
+ msgs.extend(pool.map_async(CallCommand, tests).get(99999)) |
+ pool.close() |
+ pool.join() |
+ else: |
+ msgs.extend(map(CallCommand, tests)) |
+ return [m for m in msgs if m] |
+ |
class AffectedFile(object): |
"""Representation of a file in a change.""" |
@@ -1238,6 +1289,18 @@ def canned_check_filter(method_names): |
for name, method in filtered.iteritems(): |
setattr(presubmit_canned_checks, name, method) |
+def CallCommand(cmd_data): |
+ # multiprocessing needs a top level function with a single argument. |
+ cmd_data.kwargs['stdout'] = subprocess.PIPE |
+ cmd_data.kwargs['stderr'] = subprocess.STDOUT |
+ try: |
+ (out, _), code = subprocess.communicate(cmd_data.cmd, **cmd_data.kwargs) |
+ if code != 0: |
+ return cmd_data.message('%s failed\n%s' % (cmd_data.name, out)) |
+ except OSError as e: |
+ return cmd_data.message( |
+ '%s exec failure\n %s\n%s' % (cmd_data.name, e, out)) |
+ |
def Main(argv): |
parser = optparse.OptionParser(usage="%prog [options] <files...>", |